feat: phases 77–80 — navbar view refresh, static background, API tokens, history suggestion chips
Single consolidated commit for four completed, validated phases (77, 78, 79, 80). The pipeline run left all work uncommitted because the harness commits only with PHASE_COMMIT=1 while child executors are forbidden from committing; the phases themselves all passed validation and moved to .agents/phases/complete/. Phase 77 — navbar view refresh - router.js dispatches bor:view-refresh on re-show / active re-click / popstate (gated on wasMounted; first show and boot exempt) - History / RAG / Sources / Tuning re-fetch on refresh (admin branch); Chat deliberately excluded (stream survival) - History "Refresh" button (admin-only, in-flight disable + status line) - New story suite tests/e2e/test_navbar_refresh.py (7 tests) Phase 78 — static background - Removed the animated glow layers; static 44px grid over the flat --bg canvas; default and reduced-motion renders byte-identical - Updated background/theme E2E suites; removed bg-glow test pins Phase 79 — API tokens - api_tokens model + migration 0012; hash-only token service - Admin tokens API + Tokens admin view; POST /api/token-auth; live-revoking require_user on chat / suggestions / document content - Frontend token gate with localStorage cache; anonymous E2E suites migrated to token login - New story suite tests/e2e/test_api_tokens.py (9 tests) Phase 80 — history suggestion chips - last_questions() endpoint with SEED fallback; startNewChat() refetch - Seed-semantics docs (config.py, .env.example, README) - Integration state matrix + E2E suite rewritten to the 4 chip states Also included: phase-76 report artifacts and the repo restore-test-db skill (previously untracked), scripts/* ruff fixes from phase 77. Final gate state (phase 80 final pass, covers everything above): - uv run pytest --cov=app → 1637 passed, 0 failed, app/ coverage 99% - uv run ruff check . && uv run pyright → clean, 0 errors - Per-phase story E2E suites green in isolation
This commit is contained in:
+372
-73
@@ -67,30 +67,12 @@ body {
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
/* ---------- Animated background (pure CSS, zero JS — phase 08; reworked
|
||||
phase 25: no movement, only fading light) ----------
|
||||
Owner direction (2026-08-25, verbatim): "It should be smooth,
|
||||
fluxuating, dimming and brightening, but not moving. Different bright
|
||||
spots should slowly fade in and out."
|
||||
- NO movement anywhere in the background: no grid drift, no
|
||||
transform/scale, no background-position animation. The 44px/60s grid
|
||||
drift (0.73px/s, diagonally down-right) rasterizes sub-pixel by
|
||||
sub-pixel and reads as a once-per-second jitter; the 14s whole-layer
|
||||
opacity+scale pulse reads as a uniform blink. Both are gone.
|
||||
- Three independent soft glow spots, each fading in and out on its own
|
||||
SLOW opacity-only cycle — 26s / 34s / 42s, ease-in-out, with negative
|
||||
delays (-12s, -23s) so the cycles run out of phase (LCM 4641s: the
|
||||
composite pattern effectively never repeats within a viewing
|
||||
session). The total light fluxuates smoothly and irregularly.
|
||||
- html::before / html::after join body::before / body::after as
|
||||
background layers: <html> is the root stacking context, so their
|
||||
z-index:-1 pseudo-elements paint ABOVE the var(--bg) canvas and
|
||||
BELOW the transparent, non-stacking <body>'s content — the
|
||||
no-occlusion contract (html owns the canvas, body stays
|
||||
transparent) is unchanged.
|
||||
- No filter (phase-08 no-blur perf anchor), no JS, no new assets;
|
||||
opacity-only keyframes stay compositor-friendly.
|
||||
- prefers-reduced-motion: reduce stills all four layers. */
|
||||
/* ---------- Static background (pure CSS, zero JS) ----------
|
||||
Phase 78 (owner direction, TODO.md L4): the animated background was
|
||||
removed as too resource-intensive — the three opacity-fading glow
|
||||
spots (and their keyframes) are deleted. What remains is fully
|
||||
static: the 44px grid texture below — zero animation cost, zero JS,
|
||||
no filter/blur. */
|
||||
|
||||
/* Static grid texture: 44px cells, 1px lines at 60% --line alpha, masked
|
||||
with a widened radial fade (visible across most of the viewport,
|
||||
@@ -112,48 +94,6 @@ body::before {
|
||||
mask-image: radial-gradient(140% 110% at 50% 0%, black 40%, transparent 90%);
|
||||
}
|
||||
|
||||
/* Glow spot A — the phase-08 indigo (top-left): one soft radial spot
|
||||
fading in and out on its own 26s opacity-only cycle. */
|
||||
body::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(circle 56rem at 12% 8%, rgb(244 63 94 / 0.10), transparent 62%);
|
||||
animation: bg-glow-a 26s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Glow spot B — the phase-08 cyan (bottom-right): 34s cycle, -12s delay
|
||||
(out of phase with spot A). */
|
||||
html::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(circle 60rem at 88% 92%, rgb(251 146 60 / 0.08), transparent 62%);
|
||||
animation: bg-glow-b 34s ease-in-out -12s infinite;
|
||||
}
|
||||
|
||||
/* Glow spot C — a third indigo (bottom-left): 42s cycle, -23s delay
|
||||
(out of phase with spots A and B). */
|
||||
html::after {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(circle 52rem at 14% 86%, rgb(239 68 68 / 0.08), transparent 62%);
|
||||
animation: bg-glow-c 42s ease-in-out -23s infinite;
|
||||
}
|
||||
|
||||
/* Opacity-only fades — nothing but opacity may appear in any bg-*
|
||||
keyframe (the no-movement contract, phase 25). */
|
||||
@keyframes bg-glow-a { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
|
||||
@keyframes bg-glow-b { 0%, 100% { opacity: 0.20; } 50% { opacity: 1; } }
|
||||
@keyframes bg-glow-c { 0%, 100% { opacity: 0.15; } 50% { opacity: 1; } }
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 72rem;
|
||||
@@ -1347,13 +1287,6 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.spinner { animation-duration: 2s; }
|
||||
}
|
||||
/* The background layers are the only other motion on the page: under
|
||||
reduced motion they go static (grid + glows remain, just still) — all
|
||||
four layers (phase 25: the html::before / html::after spots join). */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body::before, body::after, html::before, html::after { animation: none; }
|
||||
}
|
||||
|
||||
/* ---------- Banners ---------- */
|
||||
.kb-banner {
|
||||
display: flex;
|
||||
@@ -1695,6 +1628,114 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
}
|
||||
.sources-gate-link:hover { background: #7d88f5; }
|
||||
|
||||
/* Phase 79 (task 05): the in-app token gate — the gate surface of the
|
||||
two token-only pages (the shell + the document viewer, one shared
|
||||
.auth-gate markup each). A body-level FIXED overlay (the
|
||||
body-level doc-modal precedent): while visible it is the ONLY
|
||||
interactive surface — assets/token-gate.js locks the root (#main,
|
||||
inert) so focus and keyboard traversal stay inside the gate
|
||||
(WCAG, the inert-pair contract).
|
||||
Stacking: z-index 500 — above the app content (the sticky header is
|
||||
20, the skip-link 100) but BELOW the doc-modal (1000), which can
|
||||
only be opened from the unlocked app anyway. Solid --bg: the canvas
|
||||
+ grid texture live on <html> (the body stays transparent), so the
|
||||
overlay reads as the app's own surface — no blur (the phase-08
|
||||
perf anchor).
|
||||
The centered card reuses the #sources-gate visual language
|
||||
(surface card + hairline + glyph + heading + sub + action) in a
|
||||
tighter column. */
|
||||
.auth-gate {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 500;
|
||||
display: flex; /* the card is the only in-flow child — margin: auto centers it */
|
||||
overflow-y: auto; /* short viewports: the card scrolls fully into view */
|
||||
background: var(--bg);
|
||||
}
|
||||
/* Explicit (the global [hidden] rule already wins — this is the
|
||||
documented, testable contract for the skeleton). */
|
||||
.auth-gate[hidden] { display: none; }
|
||||
|
||||
.auth-gate-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 0.4rem;
|
||||
/* margin:auto centers AND stays scrollable on overflow (the flex
|
||||
centering pitfall: align/justify center would clip the top of a
|
||||
taller-than-viewport card). */
|
||||
margin: auto;
|
||||
width: min(28rem, calc(100vw - 2rem));
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 2.5rem 1.75rem;
|
||||
}
|
||||
.auth-gate-glyph { color: var(--brand-ink); width: 44px; height: 44px; }
|
||||
.auth-gate-glyph svg { width: 44px; height: 44px; display: block; }
|
||||
.auth-gate h2 { margin: 0.6rem 0 0.3rem; font-size: 1.4rem; }
|
||||
.auth-gate-sub { margin: 0; max-width: 24rem; color: var(--ink-soft); }
|
||||
|
||||
/* The token form: mono input (the token is code — the house mono
|
||||
language), a visible focus ring (WCAG — the 3px :focus-visible
|
||||
outline at offset 0 + the brand border), and the house submit
|
||||
button (the .tune-save language: dark ink on brand, 5.2:1). Both
|
||||
targets are ≥44px (the touch-target contract). */
|
||||
.auth-gate form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
width: 100%;
|
||||
margin-top: 0.9rem;
|
||||
}
|
||||
.auth-gate input {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.95rem;
|
||||
color: var(--ink); /* 16.7:1 on --bg */
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.65rem 0.8rem;
|
||||
min-height: 44px;
|
||||
width: 100%;
|
||||
}
|
||||
.auth-gate input::placeholder { color: var(--ink-soft); }
|
||||
.auth-gate input:focus-visible { outline-offset: 0; border-color: var(--brand); }
|
||||
.auth-gate-submit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
padding: 0.4rem 1.1rem;
|
||||
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;
|
||||
}
|
||||
.auth-gate-submit:hover:not(:disabled) { background: #7d88f5; }
|
||||
.auth-gate-submit:disabled { opacity: 0.6; cursor: wait; }
|
||||
|
||||
/* The one-line error (role=alert) — the rose/danger family the
|
||||
house alerts use (--err-ink on --err-bg, 9.3:1). */
|
||||
.auth-gate-error {
|
||||
margin: 0.75rem 0 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;
|
||||
}
|
||||
|
||||
/* The secondary "Sign in as admin" link reuses the .sources-gate-link
|
||||
pill language (its own margin-top: 0.75rem applies) — the guest's
|
||||
other door: the admin password login (the static ?next=/ is the
|
||||
no-JS fallback). */
|
||||
|
||||
.table-wrap {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
@@ -2179,6 +2220,49 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* ---------- History refresh button (phase 77 task 03, TODO.md L3) ----------
|
||||
The #view-history page-head is a flex row: the title block left, the
|
||||
actions slot right (the row wraps below 640px). Scoped to
|
||||
#view-history — the other four views' page-heads are untouched.
|
||||
The button reuses the .new-chat-btn visual language: solid brand
|
||||
pill, --bg text on --brand (5.2:1, WCAG AA >=4.5:1), borderless,
|
||||
>=44px target, hover lightens the brand fill, focus-visible via the
|
||||
global 3px rule. The phase-46 auth-link convention: the label is
|
||||
visible >=640px, the glyph is the whole control below (the button's
|
||||
aria-label keeps the accessible name in both). */
|
||||
#view-history .page-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem 1.5rem;
|
||||
}
|
||||
#view-history .page-head .page-head-title { min-width: 0; }
|
||||
.history-refresh {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
min-height: 44px;
|
||||
padding: 0.5rem 0.9rem;
|
||||
border-radius: 999px;
|
||||
border: 0;
|
||||
background: var(--brand);
|
||||
color: var(--bg);
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.history-refresh:hover { background: #f55a72; color: var(--bg); }
|
||||
.history-refresh:disabled { opacity: 0.6; cursor: wait; }
|
||||
/* The refresh glyph is hidden on desktop (the label carries the
|
||||
pill); it is the whole control below 640px (the <=640 block
|
||||
mirrors the phase-46 auth-link icon-only convention). */
|
||||
.history-refresh svg { width: 16px; height: 16px; display: none; }
|
||||
|
||||
/* ---------- History page (phase 50) ----------
|
||||
/history.html: the admin-only saved-chats list (task 04). The
|
||||
FULL-WIDTH table in the 72rem frame (AGENTS.md rule 5 — no skinny
|
||||
@@ -2384,6 +2468,203 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ---------- Tokens view (phase 79 task 06) ----------
|
||||
The admin-issued access tokens (generate · list · revoke). The
|
||||
shell's standard frame (the .container): the page-head (h1 + sub),
|
||||
the #tokens-gate (the #history-gate / .sources-gate language), the
|
||||
create row (label + Generate), the shown-once block (the mono
|
||||
field + Copy), and the FULL-WIDTH table (AGENTS.md rule 5 — the
|
||||
.history-table language: --line hairlines, the brand-soft-tinted
|
||||
thead, row hover, the scrollable .table-wrap). Every pair reuses
|
||||
the Phase-08 AA palette: brand-ink on brand-soft 6.9:1, ink-soft
|
||||
>=5.1:1, err 9.3:1. :focus-visible via the global 3px outline
|
||||
rule. No CDN, system fonts. */
|
||||
.tokens-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
/* Action feedback line (role=status): the .history-status shape —
|
||||
the min-height holds the layout so a line never reflows the table. */
|
||||
.tokens-status {
|
||||
display: block;
|
||||
min-height: 1.2em;
|
||||
color: var(--ink-soft);
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
padding-block: 0.25rem;
|
||||
}
|
||||
/* The create row: label input + Generate — flex, wraps below 640px
|
||||
(the button drops under the full-width input). */
|
||||
.token-create {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
#token-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
#token-label:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; }
|
||||
/* Generate: the .history-refresh brand pill language — the solid
|
||||
brand fill (--bg text on --brand 5.2:1, AA), the ≥44px target, the
|
||||
lightened hover fill, the dimmed :disabled (the in-flight state),
|
||||
the global :focus-visible ring. */
|
||||
.token-generate {
|
||||
min-height: 44px;
|
||||
padding: 0.4rem 1.1rem;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: var(--brand);
|
||||
color: var(--bg);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.token-generate:hover:not(:disabled) { background: #f55a72; color: var(--bg); }
|
||||
.token-generate:disabled { opacity: 0.6; cursor: wait; }
|
||||
/* The shown-once block (owner-locked A4): a quiet brand-soft card
|
||||
around the "shown once" line + the mono read-only field + Copy —
|
||||
the token reads as the app's own credential surface (brand-ink on
|
||||
brand-soft 12.4:1; ink on the field 13.8:1). */
|
||||
.token-once {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.9rem 1rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--brand-soft);
|
||||
}
|
||||
.token-once-copy {
|
||||
margin: 0;
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.token-once-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
/* The mono read-only field: the token is DATA — mono, surface fill,
|
||||
--line border; it truncates with an ellipsis at narrow widths (the
|
||||
full value is the text selection — the inline copy fallback). */
|
||||
#token-once-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
padding: 0.35rem 0.55rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
font-family: var(--mono);
|
||||
font-size: 0.78rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#token-once-value:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; }
|
||||
/* Copy: the Tune/Retry-family ghost button (the .history-share-copy
|
||||
language — --line border, transparent fill, ink-soft, ≥44px, the
|
||||
brand hover pair). */
|
||||
#token-once-copy {
|
||||
min-height: 44px;
|
||||
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;
|
||||
}
|
||||
#token-once-copy:hover { background: var(--brand-soft); color: var(--brand-ink); border-color: var(--brand); }
|
||||
/* The table is FULL-WIDTH (AGENTS.md rule 5): width 100% inside the
|
||||
standard .container; the .table-wrap card + its horizontal scroll
|
||||
cover narrow widths (the phase-07 responsive contract). The
|
||||
.history-table language, verbatim. */
|
||||
.tokens-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 640px;
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
.tokens-table th, .tokens-table td {
|
||||
text-align: left;
|
||||
padding: 0.7rem 1rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tokens-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.tokens-table tbody tr:hover { background: var(--bg); }
|
||||
.tokens-table tbody tr:last-child td { border-bottom: 0; }
|
||||
/* Label: the hand-out name (the column ellipsizes, the full label
|
||||
sits in the title attribute — tokens.js). */
|
||||
.tokens-label-cell {
|
||||
max-width: 20rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
}
|
||||
/* Created / Last used: locale date+time (ink-soft), the full ISO in
|
||||
the title attribute; "never" for the not-yet-used stamp. */
|
||||
.tokens-date-cell { color: var(--ink-soft); white-space: nowrap; }
|
||||
/* Status: the Active em-dash rides the cell's ink-soft (5.1:1 on
|
||||
--surface); the Revoked marker reuses the .stale-pill rose family
|
||||
(err-ink on err-bg ≈9.3:1, err-line border — the stale-pill visual
|
||||
language). */
|
||||
.tokens-status-cell { color: var(--ink-soft); white-space: nowrap; }
|
||||
/* Actions: the Revoke ghost button (the .history-delete language —
|
||||
--line border, transparent fill, ink-soft, ≥44px target) + the
|
||||
inline two-step confirm (the .history-confirm-* pair CSS above —
|
||||
the phase-50 pattern). */
|
||||
.tokens-actions { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||
.token-revoke {
|
||||
min-height: 44px;
|
||||
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;
|
||||
}
|
||||
.token-revoke:hover:not(:disabled) { background: var(--err-bg); color: var(--err-ink); border-color: var(--err-line); }
|
||||
.token-revoke:disabled { opacity: 0.5; cursor: wait; }
|
||||
/* Empty-state row: the muted centered message at full table width
|
||||
(the .history-empty-row language, inline in the table). */
|
||||
.tokens-empty-row td {
|
||||
padding: 2.25rem 1rem;
|
||||
text-align: center;
|
||||
color: var(--ink-soft);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ---------- Shared page (phase 51, task 03) ----------
|
||||
/shared/<token>: the anonymous read-only conversation (owner-locked
|
||||
2026-08-29, TODO.md L6). The shell maps to the PLAN §7 centered
|
||||
@@ -3362,6 +3643,17 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
the two-step confirm pair fits the phone width. */
|
||||
.history-actions-cell { white-space: normal; }
|
||||
.history-actions { flex-wrap: wrap; }
|
||||
/* Phase 79 task 06: the Tokens create row stacks — the label input
|
||||
takes the full width, the Generate pill drops under it (full
|
||||
width); the once field's row stacks the same way; the actions
|
||||
cell wraps so the two-step confirm pair fits the phone width
|
||||
(the table's own horizontal scroll covers the columns). */
|
||||
.token-create { flex-direction: column; align-items: stretch; }
|
||||
.token-generate { width: 100%; }
|
||||
.token-once-row { flex-direction: column; align-items: stretch; }
|
||||
#token-once-copy { width: 100%; }
|
||||
.tokens-actions-cell { white-space: normal; }
|
||||
.tokens-actions { flex-wrap: wrap; }
|
||||
/* Phase 51: the shared page squeezes like the chat column — the
|
||||
title and the note step down (the empty-state-title family); the
|
||||
shell keeps its base 46rem column (the >=1500px 92rem override
|
||||
@@ -3376,6 +3668,13 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
state on a touch screen. */
|
||||
.sync-btn { padding: 0.4rem 0.3rem; }
|
||||
.sync-label { display: none; }
|
||||
/* Phase 77 task 03: the History refresh pill goes icon-only like
|
||||
the phase-46 auth pill (the aria-label keeps the accessible
|
||||
name); the page-head row above wraps the pill below the title
|
||||
block when the width runs out. */
|
||||
.history-refresh { padding: 0.4rem 0.3rem; }
|
||||
.history-refresh-label { display: none; }
|
||||
.history-refresh svg { display: block; }
|
||||
.sync-result {
|
||||
position: absolute !important;
|
||||
width: 1px; height: 1px;
|
||||
|
||||
Reference in New Issue
Block a user