From 0a46f07fa8c11737d1932d8d340c1ef4ca6f9719 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Wed, 26 Aug 2026 09:41:32 -0400 Subject: [PATCH] chore(agent): phase roadmap from TODO.md, 3 phases (34-36) --- .../todo/34_consistent_navbar/00_phase.md | 46 +++++++++++++++ .../01_steering_moves_to_module.md | 33 +++++++++++ .../02_sync_and_chat_moves_to_module.md | 37 ++++++++++++ .../03_full_header_all_pages.md | 39 +++++++++++++ .../04_viewer_titlebar_styles.md | 29 ++++++++++ .../05_e2e_and_contract_update.md | 33 +++++++++++ .../todo/35_git_sources_admin/00_phase.md | 47 +++++++++++++++ .../01_model_and_migration.md | 46 +++++++++++++++ .../02_git_sources_api.md | 39 +++++++++++++ .../03_sync_and_importer_use_db.md | 38 +++++++++++++ .../35_git_sources_admin/04_admin_page.md | 41 +++++++++++++ .../todo/35_git_sources_admin/05_nav_link.md | 32 +++++++++++ .../35_git_sources_admin/06_e2e_and_docs.md | 37 ++++++++++++ .../todo/36_summary_in_viewer/00_phase.md | 40 +++++++++++++ .../01_content_api_summary_field.md | 32 +++++++++++ .../02_viewer_summary_panel.md | 44 ++++++++++++++ .../03_e2e_and_regression.md | 31 ++++++++++ .agent/user_stories/git-sources-admin.md | 53 +++++++++++++++++ .agent/user_stories/nav-consistency.md | 57 +++++++++++++++++++ .agent/user_stories/summary-in-viewer.md | 45 +++++++++++++++ 20 files changed, 799 insertions(+) create mode 100644 .agent/phases/todo/34_consistent_navbar/00_phase.md create mode 100644 .agent/phases/todo/34_consistent_navbar/01_steering_moves_to_module.md create mode 100644 .agent/phases/todo/34_consistent_navbar/02_sync_and_chat_moves_to_module.md create mode 100644 .agent/phases/todo/34_consistent_navbar/03_full_header_all_pages.md create mode 100644 .agent/phases/todo/34_consistent_navbar/04_viewer_titlebar_styles.md create mode 100644 .agent/phases/todo/34_consistent_navbar/05_e2e_and_contract_update.md create mode 100644 .agent/phases/todo/35_git_sources_admin/00_phase.md create mode 100644 .agent/phases/todo/35_git_sources_admin/01_model_and_migration.md create mode 100644 .agent/phases/todo/35_git_sources_admin/02_git_sources_api.md create mode 100644 .agent/phases/todo/35_git_sources_admin/03_sync_and_importer_use_db.md create mode 100644 .agent/phases/todo/35_git_sources_admin/04_admin_page.md create mode 100644 .agent/phases/todo/35_git_sources_admin/05_nav_link.md create mode 100644 .agent/phases/todo/35_git_sources_admin/06_e2e_and_docs.md create mode 100644 .agent/phases/todo/36_summary_in_viewer/00_phase.md create mode 100644 .agent/phases/todo/36_summary_in_viewer/01_content_api_summary_field.md create mode 100644 .agent/phases/todo/36_summary_in_viewer/02_viewer_summary_panel.md create mode 100644 .agent/phases/todo/36_summary_in_viewer/03_e2e_and_regression.md create mode 100644 .agent/user_stories/git-sources-admin.md create mode 100644 .agent/user_stories/nav-consistency.md create mode 100644 .agent/user_stories/summary-in-viewer.md diff --git a/.agent/phases/todo/34_consistent_navbar/00_phase.md b/.agent/phases/todo/34_consistent_navbar/00_phase.md new file mode 100644 index 0000000..db07de5 --- /dev/null +++ b/.agent/phases/todo/34_consistent_navbar/00_phase.md @@ -0,0 +1,46 @@ +# Phase 34 — One Navbar on Every Page + +**Source:** `TODO.md` L3 — "I want the navbar to be consistent between every page. I don't want buttons to pop in and out of existance. Just keep all those buttons active across all tabs." +**Story:** `.agent/user_stories/nav-consistency.md` +**Context:** The shared header (phase 19, `frontend/assets/header.js`) + the Tuning nav link (phase 29) already standardize nav + auth on chat / sources / tuning — but `document.html` still uses the separate `.doc-header` variant (back + title + actions, **no nav at all**), `login.html` misses the Tuning link, and two functional controls remain page-scoped: the Tuning steering toggle + panel (chat only, logic in `app.js`) and the Sync sources button (Sources only, logic in `sources.js`). Owner confirmation (2026-08-26): the bar must be identical on **all** pages — nav, Tuning toggle, Sync, New chat, and the auth pair all present everywhere; the locked A10 UI revision stays (admin-only controls hidden for anonymous, active for the admin on every tab). + +## Objective +Make the header bar **identical on all five pages** (chat, sources, document viewer, tuning, login): one shared markup block, one owner of all functional control behavior (`header.js`), the viewer's back link + title preserved in a second titlebar row, and the phase-12/19 height contract (64px desktop / 58px ≤640px) applied to the standard row on every page. + +## Dependencies +- `19_shared_header` (complete) — the `header.js` module, the nav/auth markup + ids, the cached-one-whoami contract, the ship-hidden/reveal-for-admin pattern. +- `29_tuning_nav_link` (complete) — the admin-only `#nav-tuning` reveal pattern this phase completes on the remaining pages. +- `15_steering_notes` + `27_global_tuning` (complete) — the steering toggle/panel logic being moved into the shared module; chat-page behavior must not change. +- `32_admin_sync_button` (complete) — the sync button state machine + `GET/POST /api/sync` being moved into the shared module; Sources-page behavior (result line + error banner) must not change. +- `13_document_back_navigation` (complete) — the `#doc-back` target-resolution behavior the viewer titlebar must preserve. +- `16_admin_auth` (complete) — the whoami gate, the soft-gate pages, the sign-out binding. + +## Tasks +1. `01_steering_moves_to_module.md` — the steering toggle + panel logic moves from `app.js` into `header.js` (exported `refreshSteering()`); the chat per-bubble Tune form keeps working. +2. `02_sync_and_chat_moves_to_module.md` — the sync state machine moves from `sources.js` into `header.js` (`bor:sync-status` event); one module-owned New chat binding; the sign-in `?next=` rewrite. +3. `03_full_header_all_pages.md` — all five pages ship the identical header block; `#steering-panel` exists on every page; the viewer becomes standard row + titlebar row; login gains the full header. +4. `04_viewer_titlebar_styles.md` — the two-row viewer header styles, the sync button's failed state on non-Sources pages, theme/contrast/focus preserved. +5. `05_e2e_and_contract_update.md` — the story E2E suite `test_nav_consistency.py`; `test_header_consistency.py` + `test_shared_header.py` updated to the new viewer contract; regression pass; commit. + +## Testing & Quality +- Unit/integration: frontend-only — no new `app/` logic; the no-CDN integration test (`tests/integration/test_api.py::test_index_html_served_locally`) must still pass (all new markup is same-origin, no new tags). +- Coverage: **>90%** on `app/` — unchanged by this phase (no Python change). +- E2E (mandatory, A16): `tests/e2e/test_nav_consistency.py` — the story gate, run in isolation; plus the two contract suites updated in task 05 and the regression list below. + +## Completion Criteria +- [ ] The same visible header controls appear on **all five pages** in the same order — brand, nav [Chat, Sources, Tuning], Tuning toggle, Sync sources (admin), New chat, exactly one of Sign in / Sign out — verified in `test_nav_consistency.py` for both the admin and the anonymous role. +- [ ] The document viewer shows the standard bar (row 1) + back link and title (row 2); `#doc-back` target resolution (phase 13) unchanged. +- [ ] The login page carries the full header (nav incl. Tuning, Tuning toggle, Sync, New chat, auth pair). +- [ ] Chat page: the steering panel + per-bubble Tune + inline form behave exactly as before; Sources page: the sync button state machine + `#sync-result` line + `#sync-error-banner` behave exactly as before. +- [ ] `uv run pytest` green; `uv run pytest --cov=app --cov-report=term-missing` TOTAL unchanged (>90%). +- [ ] Regressions green in isolation: `test_header_consistency.py`, `test_shared_header.py`, `test_document_back_navigation.py`, `test_document_viewer.py`, `test_steering.py`, `test_global_tuning.py`, `test_sync_button.py`, `test_tuning_nav_link.py`, `test_smoke.py`, `test_chat_rag.py`, `test_admin_auth.py`. +- [ ] `uv run ruff check . && uv run pyright` clean (no Python change, but run the gate). +- [ ] UI Structure Check (AGENTS.md rule 5): landmarks / labels / contrast ≥4.5:1 / focus-visible preserved; no CDN (rule 6). +- [ ] One `--no-gpg-sign` commit staging only this phase's files; `.agent/phases/todo/34_consistent_navbar/` moved to `.agent/phases/complete/`. + +## Locked decisions +- **A10 UI revision preserved** — admin-only controls (Sources / Tuning nav links, Sync button) ship hidden and are revealed only for the signed-in admin; anonymous visitors get the reduced bar, identically on every page (owner confirmation 2026-08-26 — "hidden for anon, visible for admin"). +- **A11 untouched** — vanilla HTML/CSS/JS, no CDN, no new packages. +- **Phase 19 module contract extended, not replaced** — `header.js` keeps the cached one-whoami-per-page promise; it gains ownership of the controls' behavior, not a second whoami. +- **Viewer bar superseded** — the phase-19 single-row viewer bar (PLAN.md §7.1 "the viewer bar = back + title + the same actions") is replaced by the two-row layout at the owner's request (this TODO). `PLAN.md` is not edited (Protocol B); this phase directory records the revision. +- **A16 / A17 honoured** — one new story E2E suite + one atomic `--no-gpg-sign` commit. diff --git a/.agent/phases/todo/34_consistent_navbar/01_steering_moves_to_module.md b/.agent/phases/todo/34_consistent_navbar/01_steering_moves_to_module.md new file mode 100644 index 0000000..7dfaa8c --- /dev/null +++ b/.agent/phases/todo/34_consistent_navbar/01_steering_moves_to_module.md @@ -0,0 +1,33 @@ +# Task 01 — Steering toggle + panel move into header.js + +**Phase:** `34_consistent_navbar` · **Source:** `TODO.md:3 — "I want the navbar to be consistent between every page. I don't want buttons to pop in and out of existance. Just keep all those buttons active across all tabs."` +**Story:** `.agent/user_stories/nav-consistency.md` + +## Objective +Make `frontend/assets/header.js` the owner of the steering toggle + panel behavior (today in `frontend/assets/app.js`), so the toggle can sit in every page's header (task 03) with zero page-script duplication. The chat page's behavior — panel open/close, list, count badge, per-note delete, per-bubble Tune form — must be byte-for-byte the same from the user's perspective. + +## Work +1. `frontend/assets/header.js` — add the steering logic (runs at module import, like the existing sign-out binding): + - `loadSteering()` — `GET /api/steering`; non-2xx (the anonymous 403, unreachable API) → empty list (the current chat-page anonymous state); render via `renderSteeringPanel(notes)`. + - `renderSteeringPanel(notes)` — newest-first `
  • ` rows: the note as `textContent` in a `span.steering-note-text` (XSS contract unchanged — never innerHTML for the note), a per-note Remove `button.steering-delete` with `aria-label="Delete tuning note: …"`; toggle `#steering-empty`'s `hidden` on `notes.length`; set the `#steering-count` badge text. + - `deleteSteeringNote(id, btn)` — disable the row button, `DELETE /api/steering/{id}`, re-load the list, announce through `#steering-announcer` (`role="status"`). + - The `#steering-toggle` click binding — open/close `#steering-panel`, flip `aria-expanded`, move focus into the panel on open (the chat-page a11y contract; read `app.js`'s current implementation first and mirror it exactly, including any close-on-Esc / outside-click behavior it has). + - **Export `refreshSteering()`** (fetch + render) — task 01's `app.js` change wires the per-bubble Tune form's success path to it. + - Update the file's header comment (it now owns the steering controls). +2. `frontend/assets/app.js` — remove the steering **panel** section (the `#steering-toggle` / `#steering-count` / `#steering-panel` / `#steering-list` / `#steering-empty` / `#steering-announcer` refs, `loadSteering`, `renderSteeringPanel`, `deleteSteeringNote`, `announceSteering`, the toggle binding) — **keep** the per-bubble `appendTuneButton` + `openTuneForm` (a chat-specific feature): the inline form's success path calls `refreshSteering()` imported from `./header.js` instead of the removed `loadSteering()`. Keep `TUNE_ICON` and the form's fetch/error handling untouched. +3. Update the comments that describe the panel as chat-page-owned (app.js header comment, index.html steering comments) — the panel now belongs to the shared module; index.html's markup stays for now (task 03 copies it to the other pages). + +Notes: +- All elements are looked up null-safe (`querySelector` + guard) — a page that (still) lacks the panel markup is a no-op, mirroring how `initSharedHeader()` already works. This keeps the app functional between tasks. +- Do not change the steering API (`app/api/steering.py`), the panel markup in `index.html`, or the `#steering-panel` styles. + +## Testing & Quality +- No Python change; the no-CDN integration test is unaffected. +- Coverage: `app/` gate unaffected (no Python change). +- The moved logic is behavior-verified by the regression suites in task 05 (`test_steering.py`, `test_global_tuning.py`); until then `uv run pytest` (unit + integration) must stay green. + +## Completion Criteria +- [ ] `header.js` exports `refreshSteering()` and owns the toggle binding, panel render, per-note delete, count badge, and announcer. +- [ ] `app.js` no longer contains the panel logic; the per-bubble Tune button + inline form remain and call `refreshSteering()` on save. +- [ ] The chat page (`/`) still loads, opens, lists, and deletes steering notes exactly as before (manual smoke via the dev server or the regression suites in task 05). +- [ ] `uv run pytest` green; `uv run ruff check . && uv run pyright` clean. diff --git a/.agent/phases/todo/34_consistent_navbar/02_sync_and_chat_moves_to_module.md b/.agent/phases/todo/34_consistent_navbar/02_sync_and_chat_moves_to_module.md new file mode 100644 index 0000000..0021f27 --- /dev/null +++ b/.agent/phases/todo/34_consistent_navbar/02_sync_and_chat_moves_to_module.md @@ -0,0 +1,37 @@ +# Task 02 — Sync state machine + New chat + sign-in next move into header.js + +**Phase:** `34_consistent_navbar` · **Source:** `TODO.md:3 — "I want the navbar to be consistent between every page. I don't want buttons to pop in and out of existance. Just keep all those buttons active across all tabs."` +**Story:** `.agent/user_stories/nav-consistency.md` + +## Objective +Make `header.js` the owner of the Sync button state machine (today in `sources.js`), the single New chat binding (today duplicated across `app.js` / `sources.js` / `tuning.js` / `document.js`), and the sign-in `?next=` derivation — so the same markup on any page (task 03) behaves identically. + +## Work +1. `frontend/assets/header.js` — add the sync state machine (read `sources.js`'s sync section first and mirror its contract exactly): + - **Boot (admin only):** `await fetchIsAdmin()` on the cached whoami — non-admins never poll (the status endpoint is admin-only). One `GET /api/sync/status`: `running` → enter the running state + start polling (the phase-32 reload-mid-sync re-attach); terminal → render the last result. + - **Click `#sync-btn`:** `POST /api/sync` → 202 enters running; 409 attaches to the running state (one sync at a time). + - **Poll** `GET /api/sync/status` every 2000 ms — one live timer, stopped on a terminal state. **No client-side hard timeout** (phase-32 locked decision — a sync can outlive the page; the state machine simply keeps polling). + - **Button states (§7.4 never-stale):** idle → label "Sync"; running → `disabled` + `aria-busy="true"` + spinner class (`.sync-icon.is-spinning`) + label "Syncing…"; success → label "Synced HH:MM"; failed → error state with the sanitized error string in the button's `title` + `aria-label` (on non-Sources pages that is where the failure is visible — the Sources page's own banner is driven by the event below). + - **On every state change** dispatch `window.dispatchEvent(new CustomEvent("bor:sync-status", { detail: }))` where `detail` is the `GET /api/sync/status` object — step 2 points the Sources page's banner/result line at it. +2. `frontend/assets/header.js` — **one** New chat binding (module scope, null-safe): if `#messages` exists (chat page) → `window.dispatchEvent(new CustomEvent("bor:new-chat"))` and let the page script act; otherwise `clearChatStorage()` + `location.href = "/"` (the existing non-chat behavior — "new chat" means go to the chat, fresh). +3. `frontend/assets/header.js` — **sign-in `?next=` rewrite:** in `initSharedHeader()` (or the module-scope boot), set `#sign-in-link`'s `href` to `/login.html?next=` (default `/`) — the admin lands back on the page they signed in from. + - ASSUMPTION: on the chat page this changes the static fallback `?next=/sources.html` to `/` at runtime — landing on the page you signed in from ("return to where you were"). The page markup keeps its current href as the no-JS fallback. +4. `frontend/assets/sources.js` — remove the sync state machine (the `#sync-btn` click handler, the 2 s poll loop, the button-state helpers, the boot re-attach). **Keep** `#sync-result` + `#sync-error-banner` rendering, now driven by a `window.addEventListener("bor:sync-status", …)` subscription: `running` → clear the result line, hide the banner; `success` → render the last-result counts in `#sync-result` (reuse the existing formatting, "added" always shown); `failed` → show `#sync-error-banner` with the error text; `idle` → hide the banner, clear the result. +5. `frontend/assets/app.js` — replace the direct `#new-chat-btn` click binding with `window.addEventListener("bor:new-chat", startNewChat)` (the `startNewChat` function itself is unchanged). +6. `frontend/assets/sources.js`, `frontend/assets/tuning.js`, `frontend/assets/document.js` — remove their `#new-chat-btn` click bindings (the module owns them). Update the file-header comments (document.js: the module now owns New chat; sources.js: sync is module-owned, the banner is event-driven). + +Notes: +- Null-safe element lookups throughout (a page that doesn't (yet) have `#sync-btn` is a no-op — the app stays functional between tasks). +- The module must keep exactly **one** whoami per page load (the cached promise) — the sync boot may await it but must not add a fetch. +- Do not touch `app/api/sync.py` — the API contract is unchanged. + +## Testing & Quality +- No Python change; the no-CDN integration test is unaffected. +- Coverage: `app/` gate unaffected. +- Behavior parity is verified by the regression suites in task 05 (`test_sync_button.py`, `test_shared_header.py`, `test_chat_rag.py`); until then `uv run pytest` must stay green. + +## Completion Criteria +- [ ] `header.js` owns: the sync state machine (+ `bor:sync-status` event), the single New chat binding (`bor:new-chat` on chat, clear+navigate elsewhere), and the sign-in `next` rewrite. +- [ ] `sources.js` no longer contains the sync state machine — `#sync-result` / `#sync-error-banner` render off the event; no `#new-chat-btn` binding remains in any page script. +- [ ] On the Sources page the full phase-32 cycle (click → polling → success counts / failure banner, reload re-attach) still works — confirmed in task 05 via `test_sync_button.py`. +- [ ] `uv run pytest` green; `uv run ruff check . && uv run pyright` clean. diff --git a/.agent/phases/todo/34_consistent_navbar/03_full_header_all_pages.md b/.agent/phases/todo/34_consistent_navbar/03_full_header_all_pages.md new file mode 100644 index 0000000..36d4b95 --- /dev/null +++ b/.agent/phases/todo/34_consistent_navbar/03_full_header_all_pages.md @@ -0,0 +1,39 @@ +# Task 03 — The identical full header on all five pages + +**Phase:** `34_consistent_navbar` · **Source:** `TODO.md:3 — "I want the navbar to be consistent between every page. I don't want buttons to pop in and out of existance. Just keep all those buttons active across all tabs."` +**Story:** `.agent/user_stories/nav-consistency.md` + +## Objective +Ship the **identical header block** on all five pages — brand, nav [Chat, Sources, Tuning], Tuning toggle, Sync sources, New chat, Sign in / Sign out — and the `#steering-panel` section on every page; the document viewer keeps back + title in a second titlebar row; the login page finally carries the full header. + +## Work +The canonical block is `index.html`'s current header **plus** the `#sync-btn` copied verbatim from `sources.html` (hidden by default, `#sync-label` + `.sync-icon` inside). Place the Sync button **after** the Tuning toggle and **before** the New chat button on every page. + +1. `frontend/index.html` (chat) — add the `#sync-btn` block to the header (the only missing control); everything else already ships. `#steering-panel` stays where it is (after `#kb-banner` in `
    `). +2. `frontend/sources.html` — add the Tuning toggle block (copied from `index.html`: `#steering-toggle` + `#steering-count`) after the nav; add the `#steering-panel` section (copied from `index.html`, incl. the `#steering-announcer` paragraph) as the **first child of `
    `**; keep the existing `#sync-btn` where it is. +3. `frontend/tuning.html` — add the Tuning toggle block + the `#sync-btn` block to the header (same order as chat); add the `#steering-panel` section as the first child of `
    `. +4. `frontend/document.html` — restructure the header: + - **Row 1** becomes the standard `.app-header` / `.header-inner` bar, byte-for-byte the same block as the other pages: brand, `