Files
brain-of-reese/frontend/shared.html
T
ducoterra b34b8ca672
Build and Push Containers / build-and-push-app (push) Successful in 1m46s
Build and Push Containers / build-and-push-db (push) Successful in 12s
phase: 182_verbatim_show_text
**Phase 182 — final verification pass (all 8 tasks already complete; this was verification + docs + one defect fix).**

- Verified: `app/rag/verbatim.py` (locator grammar, `splitlines()` slice, line-boundary cap, `resolve_answer_blocks`), `SHOW_TEXT_TOOL` + `shown_texts`, `SHOW_TEXT_CLAUSE`, `done.answer_text` (9 fields, key dropped when empty) + `ChatMessageRecord.answer_text` (≤50, 600 000-char cap counted in the 8 M budget), `verbatim.js` + 4 `{verbatim:true}` renderers, `Containerfile` minify step, `.env.example` 3 vars.
- `uv run python -c "…answer_text, show_text_max_chars, show_text_max_blocks"` → `True 12000 5`; each negative refuses startup naming its field; `0` accepted as inert ✅
- `bash .agents/validate.sh` → **validation OK**: 4320 unit+integration tests green, `TOTAL 6849 17 → 99%` (gate app/ >90% OK), `ruff check .` "All checks passed!", `pyright` 0 errors (re-run after my edits, 08:28).
- E2E, each in isolation `--no-cov`: `test_verbatim_show_text.py` 13 passed (panel bytes == `documents.content`, server caption, clipboard == server string, `/document.html?source=…&path=…`, honest unresolved state, reload+share, `show_image` regression, flag-off app) ✅; green: `test_answer_images` `test_harness_aligned_tools` `test_agent_document_tools` `test_markdown_tables` `test_markdown_inline_links_images` `test_svg_document_preview` `test_smoke` `test_document_viewer` `test_markdown_blockquotes` `test_chat_persistence` `test_thinking_timeline` `test_chat_rag` `test_settings_mobile_value_width`.
- **Defect found & fixed (pre-existing, not phase 182):** `tests/e2e/test_admin_settings_panel.py::test_admin_sees_groups_and_source_badges` failed — its whole-page `assert "reese" not in page.content()` tripped on the shell's onboarding chips, which follow the newest saved chats in the one shared E2E DB (a leftover `reeselink.json` question from a tool suite). Scoped the strict sweep to `.settings-shell` and kept a whole-page `reese:reese` credential needle → 12 passed with the leftovers still present.
- Docs: README fixed 2 inconsistencies — Quick Start omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the app fail-loud refuses boot) and the broken anchor `#chat--` → `#chat-`; full re-read of README (1816 lines) + AGENTS confirmed the phase-182 feature/settings/log/UI-contract text matches the code, and every `.env.example` var + every `Settings` field is documented.
- Notable: kept `resolve_answer_blocks` synchronous on the SSE terminal path (bounded indexed SELECTs, matching the existing `query_log` precedent and the phase's documented fence-lookup bound); no live-infra changes made.
- Next pending phase: none — `todo/` holds only 182 (its 8 task files are in `complete/`; the harness commits + moves `00_phase.md`).
2026-10-15 08:33:12 -04:00

214 lines
13 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="description" content="A shared Brain of Reese conversation — read-only.">
<title>Shared conversation · Brain of Reese</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/assets/styles.css">
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="app-header">
<div class="container header-inner">
<span class="brand">
<svg class="brand-mark" aria-hidden="true" viewBox="0 0 64 64"><path d="M32 4 55 18v28L32 60 9 46V18Z" stroke-width="4" stroke-linejoin="round"/><circle cx="32" cy="32" r="6.5"/><path d="M32 25.5V16M32 48v-9.5M25.5 32H16M48 32h-9.5" stroke-width="3" stroke-linecap="round"/></svg>
<span class="brand-text">Brain of <strong>Reese</strong></span>
</span>
<!-- Phase 46 (owner permission 2026-08-27, `TODO.md` L9): the
mobile hamburger — visible ≤640px only (CSS); opens the nav as
an animated dropdown. Behavior: assets/header.js. -->
<button type="button" class="nav-toggle" id="nav-toggle"
aria-expanded="false" aria-controls="app-nav" aria-label="Menu">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M4 7h16M4 12h16M4 17h16"/></svg>
</button>
<nav class="app-nav" id="app-nav" aria-label="Primary">
<!-- Phase 51 (owner-locked 2026-08-29, `TODO.md` L6): no nav link
is "current" here — the shared page is a read-only detail
view reachable from a link, not one of the app's pages
(the document.html convention, phase 10/13). -->
<a href="/" class="nav-link">Chat</a>
<!-- Phase 19 (now every page — phase 34, owner confirmation
2026-08-26): the Sources link is admin-only (owner
permission 2026-08-23) — hidden by default, header.js
reveals it once whoami says admin. A guest on this page
never sees it. -->
<a href="/sources.html" class="nav-link" id="nav-sources" hidden>RAG</a>
<!-- Phase 35 (owner permission 2026-08-26): the Git sources
link is admin-only — hidden by default, header.js
reveals it once whoami says admin, exactly like the
Sources link above. -->
<a href="/git-sources.html" class="nav-link" id="nav-git-sources" hidden>Sources</a>
<!-- Phase 29 (now every page — phase 34, owner confirmation
2026-08-26): the Global Tuning link is admin-only (owner
permission 2026-08-25) — hidden by default, header.js
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 79 (task 06): the Tokens link is admin-only —
hidden by default, header.js reveals it once whoami says
admin, exactly like the History link above (the
phase-34 one-bar contract: the SAME nav ships on every
page — test_nav_consistency pins the inventory parity).
Null-safe: header.js is a no-op on a page without it. -->
<a href="/tokens.html" class="nav-link" id="nav-tokens" hidden>Tokens</a>
<!-- Phase 91 (task 04): the Theme link is admin-only — hidden
by default, header.js reveals it once whoami says admin,
exactly like the Tokens link above (the phase-34 one-bar
contract: the SAME nav ships on every page —
test_nav_consistency pins the inventory parity).
Null-safe: header.js is a no-op on a page without it. -->
<a href="/theme.html" class="nav-link" id="nav-theme" hidden>Theme</a>
<!-- Phase 168 (task 05): the Settings link is admin-only —
hidden by default, header.js reveals it once whoami says
admin, exactly like the Theme link above (the phase-34
one-bar contract: the SAME nav ships on every page —
test_nav_consistency pins the inventory parity).
Null-safe: header.js is a no-op on a page without it. -->
<a href="/settings.html" class="nav-link" id="nav-settings" hidden>Settings</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>
<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="M10 4h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-8"/><path d="M4 12h11"/><path d="m12 9 3 3-3 3"/></svg>
<span class="auth-label">Sign in</span>
</a>
<!-- Phase 46 (mobile dropdown copy — desktop bar copy is
outside the nav; see styles.css .sign-out-mobile rules). -->
<button type="button" class="auth-link sign-out-btn sign-out-mobile" id="sign-out-btn-mobile" aria-label="Sign out" 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="M14 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8"/><path d="M9 12h11"/><path d="m17 9 3 3-3 3"/></svg>
<span class="auth-label">Sign out</span>
</button>
</nav>
<!-- Phase 15: the tuning-notes panel (stored in Postgres, read
into every system prompt) — owned by the shared header
module (assets/header.js); the #steering-panel section
ships in every page's <main>. The navbar toggle was
removed at owner request (2026-08-28): note management
lives on /tuning.html. -->
<!-- Phase 16: single-admin auth — exactly one of Sign in / Sign
out is visible; /api/whoami decides at load (the shared
header module). Icon-only below 640px (aria-labels keep the
accessible names). -->
<!-- Phase 51 (owner-locked 2026-08-29, `TODO.md` L6): ?next=/ —
a guest signing in FROM a shared page returns to the APP
ROOT, not the shared URL (the shared link stays valid and
public either way; the app root is where a signed-in
visitor's chat lives). header.js rewrites ?next= to the
current pathname for an admin; the static fallback above is
the guest's. -->
<a href="/login.html?next=/" class="auth-link sign-in-link" id="sign-in-link" 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="M10 4h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-8"/><path d="M4 12h11"/><path d="m12 9 3 3-3 3"/></svg>
<span class="auth-label">Sign in</span>
</a>
<button type="button" class="auth-link sign-out-btn" id="sign-out-btn" aria-label="Sign out" 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="M14 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8"/><path d="M9 12h11"/><path d="m17 9 3 3-3 3"/></svg>
<span class="auth-label">Sign out</span>
</button>
</div>
</header>
<main id="main" class="app-main" tabindex="-1">
<!-- Phase 15 (now every page — phase 34, owner confirmation
2026-08-26): the tuning-notes panel (stored notes, newest
first) — rendered + driven by assets/header.js (shared), not
the page script. First child of <main> on the non-chat pages;
the chat page keeps it after #kb-banner. -->
<section class="steering-panel" id="steering-panel" role="region"
aria-label="Tuning notes" hidden>
<div class="steering-panel-head">
<h2 class="steering-panel-title">Tuning notes</h2>
<p class="steering-panel-sub">Every note below steers all future answers.</p>
</div>
<ul class="steering-list" id="steering-list"></ul>
<p class="steering-empty" id="steering-empty">No tuning notes yet — press “Tune” under any answer to add one.</p>
</section>
<p class="visually-hidden" id="steering-announcer" role="status" aria-live="polite" aria-atomic="true"></p>
<!-- Phase 51 (owner-locked 2026-08-29, `TODO.md` L6): the
anonymous shared conversation — READ-ONLY, ZERO CONTROLS.
The shell maps to the 46rem centered chat column (styles.css
.shared-shell, the PLAN §7 column contract): the conversation
reads exactly like the chat page's, minus the composer, the
New chat / Save / Share pills, and every meta-row action.
shared.js renders the records through the SAME .msg/.bubble/
.thinking/.tool-calls structure the chat page uses, so the
existing CSS applies unchanged. Nothing below is interactive:
no form or button element in the content (the header's own
controls are the shared bar's, not the conversation's), the
"Maybe try" chips are plain <span> text (a guest tapping a
chip has nowhere to go), and the source chips are plain text
too (no href — guests cannot open documents, the documents
API is admin-only, phase 16). -->
<div class="container shared-shell">
<!-- Phase 93 task 03 (TODO.md L3): the head sits on the shared
.page-head surface panel — the grid no longer fights the
heading text (the h1 + lede were direct shell children). -->
<div class="page-head">
<h1 id="shared-title">Shared conversation</h1>
<p class="shared-note">Shared via Brain of Reese — read-only.</p>
</div>
<!-- The invalid / revoked state — ship-hidden; shared.js reveals
it for a malformed token (no fetch of any kind) and for a
404 (wrong or revoked) / network / malformed-body read. The
title keeps its fallback, nothing else renders, and there is
no error banner on this page (zero controls). -->
<div id="shared-invalid" hidden>This share link is invalid or was revoked.</div>
<section class="messages" id="messages" aria-label="Shared conversation">
<!-- shared.js appends one .msg per record here. -->
</section>
</div>
</main>
<footer class="app-footer">
<div class="container footer-inner">
<span class="footer-text">Powered by self-hosted models</span>
<span class="footer-version" id="app-version"></span>
</div>
</footer>
<!-- Phase 39: the brand layer — a CLASSIC script, first on every
page: window.BOR_BRAND is set at parse time (before the module
scripts evaluate) and refreshed from /api/config (a byte-
identical no-op for the default name).
Phase 10: the classic markdown renderer (markdown.js) —
escape-first, XSS-safe; shared.js calls the global
renderMarkdown on the stored raw text.
Phase 19: the shared header module loads through the page
script's own `import "./header.js"` — a hoisted import that is
evaluated before the page script body calls initSharedHeader()
at boot (no direct header.js <script> tag — single-evaluation
design). NO modal overlay, NO composer, NO Save/Share/
Retry/Tune markup anywhere (owner-locked: zero controls).
Phase 182 (task 07) is the ONE owner-directed exception, and it is
narrow: a shared answer's verbatim panel and its ordinary code blocks
carry a Copy button and a plain `/document.html?source=&path=` link,
because a shared page that shows a slice of a file has to let the
reader copy and check it. Both come from the SHARED panel client
(verbatim.js — the same globals the chat page calls, so a shared turn
renders pixel-identically); this page still builds no control of its
own, wires no modal, and keeps every chip a plain span. -->
<!-- ABSOLUTE asset paths on purpose: the page is served from the
NESTED route /shared/<token> (not a root-level .html), so a
relative "assets/…" ref would resolve to /shared/assets/… and
404 (the middleware's ?v= rewrite handles both forms, but it
cannot change the relative-ness). The static /shared.html URL
works with absolute refs too. -->
<script src="/assets/brand.js"></script>
<script src="/assets/markdown.js"></script>
<!-- Phase 182 (task 07): the verbatim panel client, AFTER markdown.js
(absolute path like its neighbours — the nested /shared/<token> route
breaks a relative ref). -->
<script src="/assets/verbatim.js"></script>
<script type="module" src="/assets/shared.js"></script>
</body>
</html>