chore(agent): add phases 20-23 from TODO.md — four owner-confirmed bug fixes; clear TODO.md

This commit is contained in:
2026-08-24 15:23:49 -04:00
parent 2afc77ee56
commit 824914ca3d
13 changed files with 1040 additions and 0 deletions
@@ -0,0 +1,120 @@
# Phase 23 — Containerfile: Build the Whole App Image Again
**Source:** `TODO.md` L6 — *"Fix Containerfile build not working"*
**Story:** `.agent/user_stories/containerfile-build.md` (created by task 02)
**Context:** `Containerfile` (3 stages: node:22-alpine + esbuild
0.25.5 frontend bundle → uv/python deps → slim runtime serving
`/app/static`); `frontend/` (4 pages: `index.html`, `sources.html`,
`document.html`, `login.html`; assets: `styles.css`, `markdown.js`
(classic script), `header.js`/`app.js`/`sources.js`/`document.js`/
`login.js` (ES modules)); `scripts/entrypoint.sh`.
## Verified diagnosis (2026-08-24, this conversion — not a guess)
1. **Root cause of the build failure:** phase 19 switched the page
scripts to `import … from "/assets/header.js"` (an absolute URL).
esbuild resolves that as the *filesystem* path `/assets/header.js`
and the stage-1 bundle dies:
`✘ [ERROR] Could not resolve "/assets/header.js"`
(reproduced with esbuild **0.25.5**, the exact pinned version, on a
copy of `frontend/`).
2. **Secondary gap (image would be broken even if it built):** stage 1
bundles only `app.js` + `sources.js` and copies only `index.html` +
`sources.html`. Missing from the image: `document.html` +
`login.html` (phases 10/16), `document.js` + `login.js`, and
`markdown.js` (classic script loaded by `index.html` +
`document.html`).
3. **Verified fix:** with relative imports (`from "./header.js"`) all
four page scripts bundle cleanly with esbuild 0.25.5.
4. **Latent double-evaluation trap:** all four HTML pages also load
`<script type="module" src="/assets/header.js">` directly while the
page script imports it. In dev the browser dedupes (same module
URL) — but in the image the bundled page script already contains the
header code, so shipping a raw `header.js` too would evaluate the
module **twice** (duplicate sign-out listener, double init). The
direct tags are redundant: the page script's `import` is hoisted and
guarantees `header.js` evaluates before the page script's body calls
`initSharedHeader()`, in dev and in the bundle alike.
## Objective
`podman build -f Containerfile .` succeeds, and the resulting image
serves the **whole app** — all four pages with their bundled, minified,
local-only assets (No CDN rule) — with `header.js` evaluated exactly
once per page.
## Owner-confirmed (2026-08-24, roadmap A4)
1. **Relative imports** (`./header.js`) over an esbuild alias — simpler,
verified working, dev-server behavior unchanged (files are
side-by-side).
2. **Remove the four redundant direct `header.js` script tags** (the
design above) rather than ship a raw `header.js` into the image —
single module evaluation, no duplicate listeners.
3. The image must cover **all four pages + all local assets** they
reference — the integration test (task 02) enforces this coverage so
the gap cannot silently reappear.
## Dependencies
- `19_shared_header` (complete) — introduced the absolute imports (root
cause) and the direct `header.js` tags.
- `10_story_document_viewer` / `16_admin_auth` (complete) — the pages
missing from the image.
- `08_story_dark_tech_theme` (complete) — No CDN rule the image must
honor.
## Tasks
1. `01_fix_containerfile_build.md` — relative imports, tag removal,
stage-1 asset coverage, green `podman build`, image smoke test.
2. `02_integration_test_commit.md` — `tests/integration/
test_containerfile_assets.py` (hermetic coverage pin), regression
suites, story file, final validation, the single atomic commit,
phase move to `complete/`.
## Locked decisions
- **A11 honored** — vanilla JS, no CDN, static serving from FastAPI.
**A16 honored** — integration test for the new build coverage; story
file + report; no Playwright suite required (this phase is
build/infrastructure — the phase gate is the hermetic integration
test + the real `podman build` + image smoke recorded in the report,
plus the dev-server E2E regressions). No anchor changed.
## Testing & Quality
- **Integration (new `tests/integration/test_containerfile_assets.py`,
hermetic — no podman, no network):** every `frontend/*.html` is
copied into stage 1's `/out`; every local `src`/`href` asset
referenced by the four pages is produced by a stage-1 line (esbuild
`--outfile` or `cp`); the four page module scripts are the exact set
esbuild bundles; `markdown.js` is produced; no HTML references
`/assets/header.js` directly (single-evaluation design pin); the
esbuild version stays pinned.
- **Unit:** none (no `app/` changes).
- **Coverage:** the >90% `app/` gate is unaffected, re-run to prove it.
- **Build gate (manual, recorded in the report):** `podman build
-f Containerfile .` green; image smoke (task 01 step 6) results +
log excerpt in `.agent/reports/23_containerfile_build/`.
- **Dev regressions (E2E, isolated):** `test_smoke.py`,
`test_shared_header.py`, `test_chat_persistence.py` (the HTML tag
removal touches dev page load).
- **Lint/types:** `uv run ruff check . && uv run pyright` clean.
## Completion Criteria
- [ ] Local esbuild 0.25.5 bundles all four page scripts cleanly.
- [ ] `podman build -f Containerfile .` green (log excerpt in the
report).
- [ ] Image smoke: container runs (throwaway Postgres 17 + pgvector);
`GET /`, `/sources.html`, `/document.html`, `/login.html` → 200;
`/assets/app.js` minified and contains the header code;
`/assets/markdown.js` 200; no `http(s)://` asset reference in any
served page (No CDN rule).
- [ ] Dev server unchanged in behavior: the three regression E2E suites
green in isolation.
- [ ] `uv run pytest` green; `uv run pytest --cov=app
--cov-report=term-missing` ≥ today's number.
- [ ] `uv run ruff check . && uv run pyright` clean.
- [ ] `.agent/user_stories/containerfile-build.md` exists.
- [ ] One `--no-gpg-sign` commit (below);
`.agent/phases/todo/23_containerfile_build/` moved to
`.agent/phases/complete/`.
## Commit
```bash
git add -A .agent/ Containerfile frontend/ tests/ && git commit --no-gpg-sign -m "fix(build): Containerfile builds again — relative module imports, all four pages and shared assets in the image"
```