feat(rag): feed whole matched documents to the LLM — no context truncation (A7 revised)
This commit is contained in:
@@ -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"
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
# Task 02 — Integration coverage test, story file, validation, commit
|
||||
|
||||
**Phase:** `23_containerfile_build` · **Source:** `TODO.md` L6
|
||||
|
||||
## Objective
|
||||
Pin the stage-1 asset coverage so it can't silently rot again (a new
|
||||
page/script/asset without a matching Containerfile line fails CI), plus
|
||||
regressions, story file, final validation, and the single atomic commit.
|
||||
|
||||
## Work
|
||||
1. `tests/integration/test_containerfile_assets.py` (new — **hermetic**:
|
||||
parses `Containerfile` + `frontend/` as text, no podman, no network).
|
||||
Tests:
|
||||
1. `test_every_html_page_is_copied_into_stage1` — for each
|
||||
`frontend/*.html` in the repo, a stage-1 line copies it into
|
||||
`/out` (regex over the `cp` line; the set must be exactly the
|
||||
four current pages — a new page added to `frontend/` fails this).
|
||||
2. `test_every_local_asset_reference_is_produced` — collect every
|
||||
local `src=`/`href=` under `assets/` or `/assets/` from the four
|
||||
HTML files; each basename must be produced by a stage-1 line
|
||||
(an `esbuild … --outfile=/out/assets/<name>` or a `cp` of it).
|
||||
(This is what catches a missing `markdown.js`-style gap.)
|
||||
3. `test_page_module_scripts_are_bundled` — the set of `type="module"`
|
||||
page scripts referenced by the HTML (basenames) equals the set of
|
||||
scripts esbuild bundles in stage 1 (`app.js`, `sources.js`,
|
||||
`document.js`, `login.js`).
|
||||
4. `test_header_module_is_imported_not_directly_loaded` — no HTML
|
||||
file contains a `<script … src="/assets/header.js">` (or
|
||||
`assets/header.js`) tag (the single-evaluation design pin,
|
||||
owner-confirmed A4-2); and each of the four page scripts imports
|
||||
it relatively (`from "./header.js"`).
|
||||
5. `test_markdown_js_is_a_produced_classic_script` — `markdown.js`
|
||||
has a stage-1 minify line **without** `--bundle` (it is a classic
|
||||
global script) and no `import`/`export` statements at its top
|
||||
level (source pin of that assumption).
|
||||
6. `test_esbuild_stays_pinned` — the frontend stage pins a concrete
|
||||
`esbuild@X.Y.Z` version (no floating version).
|
||||
2. `.agent/user_stories/containerfile-build.md` (new) — story file per
|
||||
the repo format: goal, the bug report verbatim from `TODO.md` L6, the
|
||||
verified diagnosis (root cause + missing-asset gap + double-eval
|
||||
trap), the owner-confirmed A4 decisions, and the test mapping table.
|
||||
3. Regressions, in isolation, one command each (prereq
|
||||
`podman compose up -d db`):
|
||||
- `uv run pytest tests/e2e/test_smoke.py -v --no-cov`
|
||||
- `uv run pytest tests/e2e/test_shared_header.py -v --no-cov`
|
||||
- `uv run pytest tests/e2e/test_chat_persistence.py -v --no-cov`
|
||||
4. Final validation: `uv run pytest` green (includes the new
|
||||
integration test); `uv run pytest --cov=app --cov-report=term-missing`
|
||||
≥ today's number (>90% gate); `uv run ruff check . && uv run pyright`
|
||||
clean.
|
||||
5. Finish the phase report (`.agent/reports/23_containerfile_build/` —
|
||||
build log excerpt, smoke results, regression results).
|
||||
6. Commit (one atomic commit) and move the phase:
|
||||
```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"
|
||||
mv .agent/phases/todo/23_containerfile_build .agent/phases/complete/
|
||||
```
|
||||
|
||||
## Testing & Quality
|
||||
- New integration suite green within `uv run pytest`; the three
|
||||
regression E2E suites green in isolation; full suite green; `app/`
|
||||
coverage at or above today's number (>90%); ruff + pyright clean.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `test_containerfile_assets.py` 6/6 within the full suite.
|
||||
- [ ] Regressions (smoke, shared header, chat persistence) green in
|
||||
isolation.
|
||||
- [ ] Story file + phase report (build log + smoke evidence) exist.
|
||||
- [ ] One `--no-gpg-sign` commit; phase directory in `complete/`.
|
||||
Reference in New Issue
Block a user