chore(agent): phase roadmap from TODO.md, 3 phases (34-36)
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Story: Admin Page to Add / Remove Git Sources
|
||||
|
||||
**Phase:** `35_git_sources_admin` · **E2E:** `tests/e2e/test_git_sources_admin.py`
|
||||
|
||||
## Narrative
|
||||
|
||||
As **the admin (owner)**, today I manage the git repos that feed the
|
||||
knowledge base by editing `BOR_GIT_SOURCES` in `.env` and restarting. I
|
||||
want a page only I can access where I can **add and remove git
|
||||
sources** — no env file, no restart — and have the Sync button
|
||||
(phase 32) and `import_docs` (phase 28) use that stored list.
|
||||
|
||||
- **Given** I am signed in as admin
|
||||
- **When** I open the Git sources page and add a repo URL (or remove
|
||||
one)
|
||||
- **Then** the change is stored in Postgres (`git_sources` table) and
|
||||
the next sync / import uses the stored list — the
|
||||
`BOR_GIT_SOURCES` env var remains only a fallback while the table is
|
||||
empty, and phase 32's fail-loud "no git sources configured" is
|
||||
preserved when both are empty.
|
||||
|
||||
## Acceptance criteria
|
||||
1. Migration 0006: `git_sources` (id UUID PK, url TEXT UNIQUE NOT
|
||||
NULL, added_at TIMESTAMPTZ) — reversible, integration-tested
|
||||
up/down (existing migration-test pattern).
|
||||
2. Admin-only API (phase 16 pattern — 403 `admin only` anonymous):
|
||||
`GET /api/git-sources` (DB rows in added order; while the table is
|
||||
empty, the env list with `from_env: true`), `POST /api/git-sources`
|
||||
{url} (trimmed, 1–500 chars, `https?://` / `ssh://` / `git@`
|
||||
shape, 409 on duplicate with a detail that never echoes the URL),
|
||||
`DELETE /api/git-sources/{id}` (204 / 404).
|
||||
3. `effective_git_sources()` shared by `app/api/sync.py::_run_sync`
|
||||
and `scripts/import_docs.py`: DB rows win, env fallback only while
|
||||
the table is empty, fail-loud unchanged when both are empty; the
|
||||
sync log line records the list origin (`origin=db|env`).
|
||||
4. `/git-sources.html` — soft-gated like the Sources page (anonymous:
|
||||
sign-in gate) with the standard header (phase 34) and an
|
||||
admin-only "Git sources" nav link on all five pages (phase 29
|
||||
pattern): sources list (mono URL + added date + per-row Remove),
|
||||
an add form (labeled input + Add, inline error, never-stale button
|
||||
per §7.4), an env-fallback note while the table is empty, and a
|
||||
hint that the KB itself updates via the Sync button.
|
||||
5. `.env.example` + README: `BOR_GIT_SOURCES` documented as the
|
||||
empty-table fallback; the admin page is the primary management UI.
|
||||
6. Unit + integration green, `app/` coverage >90%, story E2E green in
|
||||
isolation, ruff + pyright clean, one `--no-gpg-sign` commit.
|
||||
|
||||
## Playwright Mapping Rule
|
||||
`tests/e2e/test_git_sources_admin.py` — one story, one file, run in
|
||||
isolation: anonymous gets the sign-in gate (and 403s on the API); the
|
||||
admin adds/removes sources on the page (the list updates live, invalid
|
||||
input gets an inline error) and reaches the page via the "Git
|
||||
sources" nav link on every page.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Story: One Navbar on Every Page
|
||||
|
||||
**Phase:** `34_consistent_navbar` · **E2E:** `tests/e2e/test_nav_consistency.py`
|
||||
|
||||
## Narrative
|
||||
|
||||
As **any user (admin or anonymous)**, I move between the Chat, Sources,
|
||||
Document viewer, Global Tuning, and Sign in pages. Today the header bar
|
||||
is only similar between pages: the document viewer replaces it with a
|
||||
back + title bar (every nav button vanishes), the login page misses the
|
||||
Tuning link, and the functional controls are page-scoped — the Tuning
|
||||
steering toggle lives on chat only, the Sync sources button on Sources
|
||||
only — so buttons pop in and out as I switch tabs. I want ONE identical
|
||||
bar on every page.
|
||||
|
||||
- **Given** any of the app's five pages
|
||||
- **When** I look at the header
|
||||
- **Then** I see the same controls in the same order — brand, nav
|
||||
[Chat, Sources, Tuning], Tuning toggle, Sync sources (admin), New
|
||||
chat, and exactly one of Sign in / Sign out — with none missing
|
||||
because of which page I happen to be on.
|
||||
|
||||
## Acceptance criteria
|
||||
1. `frontend/assets/header.js` is the single owner of the functional
|
||||
header controls: the steering toggle + panel logic (moved from
|
||||
`app.js`, exported `refreshSteering()`), the Sync button state
|
||||
machine (moved from `sources.js`, dispatching `bor:sync-status`),
|
||||
one New chat binding (chat page: `bor:new-chat` event; other pages:
|
||||
clear + navigate), and the sign-in `?next=` rewrite to the current
|
||||
page. No control logic remains in the page scripts.
|
||||
2. All five pages (`index.html`, `sources.html`, `document.html`,
|
||||
`tuning.html`, `login.html`) ship the identical header markup; the
|
||||
`#steering-panel` section exists on every page (chat: after
|
||||
`#kb-banner` as today; other pages: first child of `<main>`).
|
||||
3. The document viewer keeps its back link + title in a second
|
||||
titlebar row below the standard bar (phase 13 back-target behavior
|
||||
unchanged); the pinned 64px / 58px height contract applies to the
|
||||
standard row on every page.
|
||||
4. The locked A10 UI revision is preserved: admin-only controls
|
||||
(Sources / Tuning nav links, Sync button) ship hidden and are
|
||||
revealed only for a signed-in admin — for the admin every button is
|
||||
active on every page; anonymous visitors get the reduced bar,
|
||||
identically on every page (owner confirmation 2026-08-26).
|
||||
5. The chat page's per-bubble Tune button + inline form (phases 15/27)
|
||||
and the Sources page's sync result line + error banner (phase 32)
|
||||
keep working exactly as before.
|
||||
6. `tests/e2e/test_nav_consistency.py` green in isolation;
|
||||
`test_header_consistency.py` / `test_shared_header.py` updated to the
|
||||
new viewer contract; regressions green; `app/` coverage >90%
|
||||
(frontend-only phase — unchanged); ruff + pyright clean; one
|
||||
`--no-gpg-sign` commit.
|
||||
|
||||
## Playwright Mapping Rule
|
||||
`tests/e2e/test_nav_consistency.py` — one story, one file, run in
|
||||
isolation. It asserts, per role (admin, anonymous), that the visible
|
||||
header control inventory is identical on all five pages, and that the
|
||||
viewer's back link + titlebar row survive the change.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Story: Summary + Original Document Together in the Viewer
|
||||
|
||||
**Phase:** `36_summary_in_viewer` · **E2E:** `tests/e2e/test_summary_in_viewer.py`
|
||||
|
||||
## Narrative
|
||||
|
||||
As **any user**, phase 30 gives every non-markdown document a
|
||||
lite-model summary (stored on `documents.summary`, indexed as an
|
||||
`is_summary` chunk, used for retrieval). When I open such a document —
|
||||
from the Sources table, a chat source chip, or a direct URL — I only
|
||||
see the raw original. I want to see **the summary and the original
|
||||
document together**.
|
||||
|
||||
- **Given** a document that has a summary (phase 30: non-markdown A9
|
||||
documents)
|
||||
- **When** I open it in the document viewer (the modal or the full
|
||||
page)
|
||||
- **Then** a labeled Summary panel sits above the original content —
|
||||
both visible at once. Documents without a summary (markdown docs,
|
||||
pre-phase-30 rows, fail-soft rows) render exactly as before.
|
||||
|
||||
## Acceptance criteria
|
||||
1. `DocContent` gains `summary: str | None`; `GET
|
||||
/api/documents/content` returns `documents.summary`. The endpoint
|
||||
stays public + stateless — the phase 16 soft rule (catalog gated,
|
||||
viewer public) is unchanged.
|
||||
2. The shared renderer `renderDocument` (`assets/document.js`)
|
||||
renders the summary panel for **both** surfaces at once (the
|
||||
full-page viewer + the chat/sources modal); null/empty summary →
|
||||
no panel; the summary text is written with `textContent` (XSS
|
||||
contract unchanged).
|
||||
3. `.doc-summary` styling matches the dark tech theme (phase 08
|
||||
palette, contrast ≥4.5:1) and reads as a summary, not as document
|
||||
content.
|
||||
4. Integration tests: a summarized non-markdown doc returns its
|
||||
summary; a markdown doc returns null; anonymous access unchanged.
|
||||
5. Story E2E green in isolation (the `summary_kb` fixture: the
|
||||
deterministic digest panel + the full original with its tail
|
||||
sentinel visible together; the markdown control doc → no panel);
|
||||
regressions green; `app/` coverage >90%; ruff + pyright clean; one
|
||||
`--no-gpg-sign` commit.
|
||||
|
||||
## Playwright Mapping Rule
|
||||
`tests/e2e/test_summary_in_viewer.py` — one story, one file, run in
|
||||
isolation.
|
||||
Reference in New Issue
Block a user