feat(chat): save and view chat history — admin-only saved_chats, History page, open-a-chat return

This commit is contained in:
2026-08-29 21:22:25 -04:00
parent 6832957ab0
commit ece93a7c8f
29 changed files with 3099 additions and 20 deletions
+25
View File
@@ -43,6 +43,11 @@
reveals it once whoami says admin, exactly like the
Sources link above. -->
<a href="/tuning.html" class="nav-link" id="nav-tuning" hidden>Tuning</a>
<!-- Phase 50 (owner permission 2026-08-29, `TODO.md` L5): the
History link is admin-only — hidden by default, header.js
reveals it once whoami says admin, exactly like the
Tuning link above. -->
<a href="/history.html" class="nav-link" id="nav-history" hidden>History</a>
<!-- Phase 46 (mobile dropdown copy: sign-in — desktop bar copy is
outside the nav; see styles.css .sign-in-mobile rules). -->
<a href="/login.html?next=/" class="auth-link sign-in-link sign-in-mobile" id="sign-in-link-mobile" hidden>
@@ -109,6 +114,26 @@
<span class="new-chat-label">New chat</span>
</button>
<!-- Phase 50 (owner-locked 2026-08-29, `TODO.md` L5): "Save" stores
the current conversation in Postgres (saved_chats) — admin-only.
SHIPS HIDDEN: app.js reveals it only when whoami says admin
(phase 16 absent-not-hidden — `hidden` is display:none, so
anonymous visitors see no trace). Handler in app.js, upsert +
?chat=<id> contract: an unlinked Save POSTs /api/chats (the
server auto-titles from the first question, 120-char cap) and
links the conversation to the created row's id; a re-Save PUTs
the SAME row (the same conversation never spawns a second row);
a 404 PUT unlinks and recreates; "New chat" unlinks. Booting at
/?chat=<id> (valid uuid, admin) GETs the row and renders its
messages through the SAME restore path as the phase-14 local
session (pixel-identical), links the conversation, and mirrors
it to localStorage — a later Save updates that row. A deleted /
unknown id degrades to the normal local restore with a banner. -->
<button type="button" class="save-chat-btn" id="save-chat-btn" aria-label="Save chat" hidden>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/></svg>
<span class="save-chat-label">Save</span>
</button>
<!-- Phase 49 (2026-08-29, TODO.md L4): the meta row under a brain
bubble can carry JS-injected actions (app.js) — Tune (admin
only, phase 15) and Retry (every visitor; the LAST brain