feat(sources): admin page to add and remove git sources (TODO.md L4)
This commit is contained in:
+274
-11
@@ -1275,6 +1275,253 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.docs-table tbody tr:hover { background: var(--bg); }
|
||||
.docs-table tbody tr:last-child td { border-bottom: 0; }
|
||||
|
||||
/* ---------- Git sources page (phase 35) ----------
|
||||
/git-sources.html: the admin-only manager for the stored git source
|
||||
list (add / remove, git-sources table). Same full-width table
|
||||
language as the Sources page (no skinny single-column list), the
|
||||
phase-16 gate reused verbatim (.sources-gate classes), and the
|
||||
phase-27 form-card language for the add form. Every pair reuses the
|
||||
Phase-08 AA palette: dark ink on brand 5.2:1 (never white on
|
||||
brand, 3.7:1, fails), brand-ink/brand-soft 6.9:1, err 9.1:1,
|
||||
ink-soft >=6.9:1. Touch targets >=44px; :focus-visible via the
|
||||
global 3px outline rule. No filter: blur, no CDN, system fonts. */
|
||||
.git-sources-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Add form — the tuning form's surface as a single row: visible label
|
||||
+ mono URL input (the credentials case is real, so the input is
|
||||
mono) + the brand "Add source" button; wraps to a column at narrow
|
||||
widths (the <=640px block below). */
|
||||
#git-source-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 0.9rem 1rem 1rem;
|
||||
}
|
||||
#git-source-form:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft), var(--shadow); }
|
||||
#git-source-form > label { color: var(--ink); font-weight: 600; white-space: nowrap; }
|
||||
#git-source-url {
|
||||
flex: 1;
|
||||
min-width: 14rem;
|
||||
min-height: 44px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.88rem;
|
||||
color: var(--ink);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.45rem 0.7rem;
|
||||
}
|
||||
#git-source-url::placeholder { color: var(--ink-soft); }
|
||||
#git-source-url:focus-visible { outline-offset: 0; border-color: var(--brand); }
|
||||
#git-source-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
padding: 0.4rem 1.2rem;
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--brand);
|
||||
color: var(--bg); /* dark ink on brand: 5.2:1 */
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
#git-source-add:hover:not(:disabled) { background: #7d88f5; }
|
||||
#git-source-add:disabled { opacity: 0.6; cursor: wait; }
|
||||
|
||||
/* The add form's inline error (role=alert): the err pair (9.1:1);
|
||||
flex-basis 100% drops it onto its own row under the input. */
|
||||
.git-source-error {
|
||||
flex-basis: 100%;
|
||||
margin: 0;
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.45rem 0.8rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Load failure (role=alert) + the retry action: a failed
|
||||
GET /api/git-sources must never leave a stuck page. The retry
|
||||
button keeps the err pair on the err surface (9.1:1). */
|
||||
.git-source-load-error {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 0.7rem 0.9rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.git-source-load-error > span { flex: 1; min-width: 12rem; }
|
||||
#git-sources-retry {
|
||||
min-height: 44px;
|
||||
padding: 0.4rem 1rem;
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--err-ink);
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
#git-sources-retry:hover { background: rgb(239 68 68 / 0.12); }
|
||||
|
||||
/* Env-fallback note (from_env: true — the table is empty and the list
|
||||
is BOR_GIT_SOURCES): the info chip in the theme palette —
|
||||
brand-soft surface, brand-ink text (6.9:1). */
|
||||
.git-source-env-note {
|
||||
margin: 0;
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
border: 1px solid var(--brand-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.6rem 0.9rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
.git-source-env-note code {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.85em;
|
||||
background: var(--surface);
|
||||
color: var(--brand-ink); /* 8.7:1 on surface */
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Hint box (role=note): the page-sub styling family — ink-soft on
|
||||
surface (6.9:1), a dashed border marks it as guidance, not state.
|
||||
It names the Sync button: the action that clones the listed repos
|
||||
and prunes the removed ones (the phase scope boundary). */
|
||||
.git-source-hint {
|
||||
margin: 0;
|
||||
color: var(--ink-soft);
|
||||
background: var(--surface);
|
||||
border: 1px dashed var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.6rem 0.9rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
/* The list: the Sources page's table pattern — full width in the
|
||||
72rem frame, surface card, horizontally scrollable wrapper (the
|
||||
URL column never wraps or ellipsizes: long URLs, credentials
|
||||
included, scroll the wrapper instead of truncating). */
|
||||
#git-sources-table-wrap {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.git-sources-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 560px;
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
.git-sources-table th, .git-sources-table td {
|
||||
text-align: left;
|
||||
padding: 0.7rem 1rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.git-sources-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
/* URL cell: mono at the Sources-table size; the <code> is plain
|
||||
(no chip background — the cell IS the mono readout). */
|
||||
.git-sources-table td.git-source-url-cell { font-family: var(--mono); font-size: 0.82rem; }
|
||||
.git-sources-table td.git-source-url-cell code { font-family: inherit; }
|
||||
.git-sources-table tbody tr:hover { background: var(--bg); }
|
||||
.git-sources-table tbody tr:last-child td { border-bottom: 0; }
|
||||
|
||||
/* Per-row Remove: the tuning row-action language (icon + label,
|
||||
>=44px) with the Delete hover pair (err 9.1:1). */
|
||||
.git-source-remove {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35rem;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
flex: 0 0 auto;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--ink-soft);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
font-size: 0.82rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.git-source-remove svg { width: 14px; height: 14px; display: block; }
|
||||
.git-source-remove:hover:not(:disabled) { background: var(--err-bg); color: var(--err-ink); border-color: var(--err-line); }
|
||||
.git-source-remove:disabled { opacity: 0.5; cursor: wait; }
|
||||
|
||||
/* Per-row delete failure (role=alert): the err pair, inline after the
|
||||
(re-enabled) button. */
|
||||
.git-source-row-error {
|
||||
margin-left: 0.6rem;
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.3rem 0.6rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Env-fallback rows carry no Remove (nothing is stored to remove) —
|
||||
the tag says where the row comes from (brand pair, 6.9:1). */
|
||||
.git-source-env-tag {
|
||||
color: var(--brand-ink);
|
||||
background: var(--brand-soft);
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.6rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Empty state: the tuning page's quiet centered line at full table
|
||||
width — no stored rows AND no env fallback to show. */
|
||||
.git-sources-empty {
|
||||
margin: 0;
|
||||
padding: 1.4rem 1rem;
|
||||
text-align: center;
|
||||
color: var(--ink-soft);
|
||||
font-style: italic;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* ---------- Document viewer (phase 10; two-row header since phase 34) ---------- */
|
||||
/* Phase 34 (owner confirmation 2026-08-26): the viewer header is TWO
|
||||
rows in one sticky <header> — row 1 reuses the standard .app-header /
|
||||
@@ -1660,10 +1907,14 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
— and let the brand wordmark (base ellipsis) absorb any remainder.
|
||||
The ≤640 block below stays tighter and wins at phone widths. */
|
||||
@media (max-width: 900px) {
|
||||
.header-inner { gap: 0.65rem; }
|
||||
.nav-link { padding: 0.4rem 0.6rem; font-size: 0.9rem; }
|
||||
.app-nav { gap: 0.2rem; }
|
||||
.new-chat-btn, .auth-link, .sync-btn, .steering-toggle { padding: 0.45rem 0.6rem; }
|
||||
.header-inner { gap: 0.6rem; }
|
||||
/* Phase 35: the admin-only "Git sources" link joins the nav — a
|
||||
fourth text pill on the admin bar — so the tablet squeeze tightens
|
||||
once more to hold 768px in the admin state (brand already the
|
||||
designated clip target, pills squeeze next). */
|
||||
.nav-link { padding: 0.4rem 0.5rem; font-size: 0.85rem; }
|
||||
.app-nav { gap: 0.15rem; }
|
||||
.new-chat-btn, .auth-link, .sync-btn, .steering-toggle { padding: 0.45rem 0.5rem; }
|
||||
}
|
||||
|
||||
/* ---------- Responsive (mobile-first adjustments) ---------- */
|
||||
@@ -1687,7 +1938,12 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
18px brand mark) to hold 375px with the brand mark intact and
|
||||
360px with the brand clipped clean (overflow:hidden — the mark
|
||||
never overlaps the nav). */
|
||||
.header-inner { gap: 0.3rem; }
|
||||
/* Phase 35: the admin-only "Git sources" link joins the nav — a
|
||||
fourth text pill on the admin bar — so the mobile squeeze tightens
|
||||
once more (0.25rem inner gap, 0.72rem nav pills, 0.3rem pill
|
||||
padding, 0.05rem nav gap) to hold 375px — and 360px, the brand
|
||||
fully clipped — in the admin state without horizontal overflow. */
|
||||
.header-inner { gap: 0.25rem; }
|
||||
.brand { min-width: 0; overflow: hidden; }
|
||||
.brand-mark { width: 18px; height: 18px; }
|
||||
.brand-text {
|
||||
@@ -1698,20 +1954,20 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.nav-link { padding: 0.35rem 0.3rem; font-size: 0.78rem; }
|
||||
.app-nav { gap: 0.1rem; }
|
||||
.new-chat-btn { padding: 0.4rem 0.35rem; }
|
||||
.nav-link { padding: 0.3rem 0.25rem; font-size: 0.72rem; }
|
||||
.app-nav { gap: 0.05rem; }
|
||||
.new-chat-btn { padding: 0.4rem 0.3rem; }
|
||||
.new-chat-label { display: none; }
|
||||
.new-chat-btn svg { display: block; }
|
||||
/* Phase 16: the auth pill goes icon-only like New chat — brand text
|
||||
ellipsizes as the designated squeeze target, no bar overflow. */
|
||||
.auth-link { padding: 0.4rem 0.35rem; }
|
||||
.auth-link { padding: 0.4rem 0.3rem; }
|
||||
.auth-label { display: none; }
|
||||
.auth-link svg { display: block; }
|
||||
/* Phase 32: the sync pill goes icon-only like the other pills (the
|
||||
aria-label keeps the accessible name); the spinning icon is the
|
||||
visible running state on a touch screen. */
|
||||
.sync-btn { padding: 0.4rem 0.35rem; }
|
||||
.sync-btn { padding: 0.4rem 0.3rem; }
|
||||
.sync-label { display: none; }
|
||||
/* The last-result counts stay ANNOUNCED (aria-live is untouched) but
|
||||
go visually hidden — the 58px bar has no room for the text; the
|
||||
@@ -1725,7 +1981,7 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
.steering-toggle { padding: 0.4rem 0.35rem; }
|
||||
.steering-toggle { padding: 0.4rem 0.3rem; }
|
||||
/* Visually hidden, NOT display:none — the accessible name keeps the
|
||||
word "Tuning" next to the count badge. */
|
||||
.steering-label {
|
||||
@@ -1767,6 +2023,13 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.doc-modal-meta { padding-inline: 0.9rem; }
|
||||
.doc-modal-content { padding: 0.75rem 0.9rem 1.25rem; }
|
||||
.composer { padding: 0.5rem; }
|
||||
/* Phase 35: the git sources add form stacks like the other cards —
|
||||
label, full-width mono input, full-width button; the table
|
||||
wrapper's horizontal scroll already covers long URLs. */
|
||||
#git-source-form { flex-direction: column; align-items: stretch; }
|
||||
#git-source-form > label { white-space: normal; }
|
||||
#git-source-url { min-width: 0; }
|
||||
#git-source-add { width: 100%; }
|
||||
.footer-inner { flex-direction: column; gap: 0.2rem; text-align: center; }
|
||||
main { padding-bottom: env(safe-area-inset-bottom, 0); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user