feat(chat): save and view chat history — admin-only saved_chats, History page, open-a-chat return
This commit is contained in:
+173
-1
@@ -308,6 +308,36 @@ html::after {
|
||||
whole control below 640px. */
|
||||
.new-chat-btn svg { width: 16px; height: 16px; display: none; }
|
||||
|
||||
/* Phase 50 (owner-locked 2026-08-29, TODO.md L5): the "Save" pill — the
|
||||
EXACT visual family of .new-chat-btn (same declarations, so the two
|
||||
chat-shell actions always read as a pair): solid brand pill, --bg text
|
||||
on --brand (5.2:1, WCAG AA), borderless, ≥44px target, hover lightens
|
||||
the brand fill, focus-visible via the global 3px rule. Shipped hidden
|
||||
in index.html — app.js reveals it for admin only (phase 16
|
||||
absent-not-hidden); ≤640px overrides below mirror the New chat ones
|
||||
(label stays visible in .chat-shell, icon hidden). */
|
||||
.save-chat-btn {
|
||||
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;
|
||||
}
|
||||
.save-chat-btn:hover { background: #f55a72; color: var(--bg); }
|
||||
/* The save mark is hidden on desktop (the label carries the pill); it is
|
||||
the whole control below 640px (mirrored in the ≤640 block below). */
|
||||
.save-chat-btn svg { width: 16px; height: 16px; display: none; }
|
||||
|
||||
/* Phase 16: header auth controls (Sign in link / Sign out button) — the
|
||||
same ghost pill as New chat, so the bar keeps one visual language.
|
||||
ink-soft on surface ≈6.9:1; hover pair brand-ink/brand-soft ≈6.9:1.
|
||||
@@ -1746,6 +1776,136 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* ---------- 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
|
||||
single-column list), the same table language as the Sources page
|
||||
(the sources-table family: --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
|
||||
>=6.9:1, err 9.1:1. :focus-visible via the global 3px outline
|
||||
rule. No CDN, system fonts. */
|
||||
.history-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
/* Action feedback line (role=status): the sync-result shape —
|
||||
ink-soft on surface (>=4.5:1), small mono; the min-height holds
|
||||
the layout so a delete's line never reflows the table. */
|
||||
.history-status {
|
||||
display: block;
|
||||
min-height: 1.2em;
|
||||
color: var(--ink-soft);
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
padding-block: 0.25rem;
|
||||
}
|
||||
/* 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). */
|
||||
.history-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 640px;
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
.history-table th, .history-table td {
|
||||
text-align: left;
|
||||
padding: 0.7rem 1rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.history-table th {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.history-table tbody tr:hover { background: var(--bg); }
|
||||
.history-table tbody tr:last-child td { border-bottom: 0; }
|
||||
/* Title cell: the Open link — the accent link (brand-ink on surface
|
||||
6.9:1); the column ellipsizes, the full title sits in the title
|
||||
attribute (on the cell AND the link). */
|
||||
.history-title-cell {
|
||||
max-width: 34rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.history-title-link {
|
||||
color: var(--brand-ink);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
.history-title-link:hover { text-decoration: underline; }
|
||||
.history-title-link:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; }
|
||||
/* Messages: the mono numeric readout (ink-soft >=6.9:1). */
|
||||
.history-count-cell { font-family: var(--mono); color: var(--ink-soft); }
|
||||
/* Updated: locale date+time (ink-soft), the full ISO in the title
|
||||
attribute (history.js). */
|
||||
.history-updated-cell { color: var(--ink-soft); white-space: nowrap; }
|
||||
/* Actions: the Delete ghost button (the tuning row-action language)
|
||||
+ the inline two-step confirm pair (phase 50 task 04). */
|
||||
.history-actions { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||
.history-delete {
|
||||
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;
|
||||
}
|
||||
.history-delete:hover:not(:disabled) { background: var(--err-bg); color: var(--err-ink); border-color: var(--err-line); }
|
||||
.history-delete:disabled { opacity: 0.5; cursor: wait; }
|
||||
.history-confirm-text { color: var(--err-ink); font-size: 0.82rem; font-weight: 700; white-space: nowrap; }
|
||||
/* Yes: the error-rose treatment (err-ink on err-bg 9.1:1, err-line
|
||||
border); No: the ghost (transparent, --line border). */
|
||||
.history-confirm-yes {
|
||||
min-height: 44px;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
font-size: 0.82rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.history-confirm-yes:hover:not(:disabled) { background: rgb(239 68 68 / 0.18); }
|
||||
.history-confirm-yes:disabled { opacity: 0.6; cursor: wait; }
|
||||
.history-confirm-no {
|
||||
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;
|
||||
}
|
||||
.history-confirm-no:hover { background: var(--brand-soft); color: var(--brand-ink); }
|
||||
/* Empty-state row: the muted centered message at full table width
|
||||
(the .git-sources-empty language, inline in the table). */
|
||||
.history-empty-row td {
|
||||
padding: 2.25rem 1rem;
|
||||
text-align: center;
|
||||
color: var(--ink-soft);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ---------- 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 /
|
||||
@@ -2177,7 +2337,7 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
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 { padding: 0.45rem 0.5rem; }
|
||||
.new-chat-btn, .save-chat-btn, .auth-link { padding: 0.45rem 0.5rem; }
|
||||
}
|
||||
|
||||
/* ---------- Responsive (mobile-first adjustments) ---------- */
|
||||
@@ -2280,10 +2440,17 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.new-chat-btn { padding: 0.4rem 0.3rem; }
|
||||
.new-chat-label { display: none; }
|
||||
.new-chat-btn svg { display: block; }
|
||||
/* Phase 50: the Save pill squeezes with New chat (same family, same
|
||||
rules — the chat-shell overrides below keep both labels visible). */
|
||||
.save-chat-btn { padding: 0.4rem 0.3rem; }
|
||||
.save-chat-label { display: none; }
|
||||
.save-chat-btn svg { display: block; }
|
||||
/* But on the chat page there is room — keep the label visible and
|
||||
hide the icon (the button lives inside .chat-shell, not the navbar). */
|
||||
.chat-shell .new-chat-label { display: inline; }
|
||||
.chat-shell .new-chat-btn svg { display: none; }
|
||||
.chat-shell .save-chat-label { display: inline; }
|
||||
.chat-shell .save-chat-btn svg { display: none; }
|
||||
/* 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.3rem; }
|
||||
@@ -2392,6 +2559,11 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
#archive-upload-file { min-width: 0; }
|
||||
#git-source-add,
|
||||
#archive-upload-btn { width: 100%; }
|
||||
/* Phase 50: the History table keeps its full width (the .table-wrap
|
||||
horizontal scroll already covers it); the actions cell wraps so
|
||||
the two-step confirm pair fits the phone width. */
|
||||
.history-actions-cell { white-space: normal; }
|
||||
.history-actions { flex-wrap: wrap; }
|
||||
.footer-inner { flex-direction: column; gap: 0.2rem; text-align: center; }
|
||||
main { padding-bottom: env(safe-area-inset-bottom, 0); }
|
||||
/* Sync button goes icon-only on mobile; the label hides, aria-label
|
||||
|
||||
Reference in New Issue
Block a user