Skip to content

fix(*): a rebuilt page reaches the tab, and a stale one says so - #584

Merged
gloryfromca merged 4 commits into
refactor/ui_web_architecturefrom
fix/page_cache_revalidate
Sep 21, 2026
Merged

gloryfromca merged 4 commits into
refactor/ui_web_architecturefrom
fix/page_cache_revalidate

Conversation

@0xKT

@0xKT 0xKT commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Summary

A tester's report, in two halves: after a rebuild the browser may still show the old page, and after a git pull nothing rebuilds the page or says that it is stale. This PR closes both.

A rebuilt page reaches the tab. raven serve sent dist/index.html from / with an ETag and a Last-Modified date but no Cache-Control. A browser then guesses a freshness lifetime from that date (about a tenth of the file's age) and answers a navigation to / from its cache without asking the server. The sign-in page at /auth ends by navigating the tab to / (location.replace('/') after the nonce exchange), and a browser answers that navigation from the copy it still guesses fresh, so a rebuilt or upgraded page kept opening as the build before it until a hard reload. Three rebuilds in a row went unseen this way on the architecture branch; a released install is exposed the same way after raven upgrade whenever raven web is run again inside that guessed lifetime, which for a weeks-old install is days. The assets already carried Cache-Control: no-cache for exactly this reason (the provider icons, 2026-09-11); the same on_response_prepare hook now covers / too, registers whenever the page is served rather than only when an assets directory exists, and no-cache keeps the 304 path (an unchanged 1.3 MB page still costs no bytes; no-store would re-download it on every navigation). The upgrade watcher's HEAD probe of / already used cache: 'no-store' and was never affected.

A stale page says so. Only a source checkout can serve a page older than the code beside it: ui-web/dist is git-ignored, so a pull brings ui-web/src and the message catalogue but not a rebuild, and the served page silently stays on the old build. resolve_ui_dist now judges that the way make would, by mtime: when the checkout's dist/index.html is older than anything under ui-web/src (tests, the src/test harness layer and snapshots excluded, since they change without changing the page), than the build's own files (build.py, vite.config.ts, package.json, package-lock.json, icon/raven.svg) or than i18n/messages.json, it logs one WARNING naming make build-ui, and both page hosts (standalone raven serve and the gateway's page mount behind raven web) hand that judgement to build_app, which answers / with X-Raven-Page-Behind: sources -- asked per response, so a rebuild takes the header away without a restart. The page's existing 30-second HEAD probe of / reads it and raises the rail-foot notice row with a third wording, "Stale page build / How to rebuild"; the click opens the confirm sheet with the command instead of reloading a page that would come back the same, and the row goes back down when a later look no longer carries the header. It outranks the rebuilt-page notice ("UI updated on disk / Reload"), because the reload that one offers would come back behind as well: the row asks for the rebuild first, and the probe that sees it land hands the row to the reload. A newer release outranks both. The wheel's copy ships beside the code it was built with, so a raven web user of an installed raven never sees the warning or the header.

The terminal alone was not enough for the hint: raven web detaches the gateway and its log goes to web.log, so the page is the one place both launch paths can show it.

Also in this change: showUpNote now writes the row's two texts through one wording(kind) helper (four textContent writes down to two), so the DOM-touch ratchet's pin for app/updates.ts drops from 4 to 2 and the debt row in ui-web/CONTRIBUTING.md follows. The TUI's generated copy of the catalogue (ui-tui/src/i18n/messages.generated.ts) is regenerated for the four new keys, which its lint:i18n gate requires.

Reviewed before opening by read-only panels (HTTP caching semantics, aiohttp mechanics and test quality, page integration, completeness), with adversarial refutation of every blocking claim; what survived is in. One follow-up came out of it and is deliberately not here: /files/download (raven/rpc/transports/deliverables.py) has the same shape as the page had -- ETag and Last-Modified, no Cache-Control, a token URL that stays the same when the deliverable is regenerated, fetched by <img src> and <a download> in default cache mode. Same one-header fix, separate PR.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

Run from the branch checkout, base origin/refactor/ui_web_architecture:

  • uv run --frozen --extra dev pytest tests/test_cli_serve_commands.py tests/test_rpc_transport.py tests/test_cli_gateway_page.py -q -> 133 passed in 5.06s

  • make lint-python -> ruff check: All checks passed!; ruff format --check: 2019 files already formatted

  • make test-python -> 24034 passed, 109 skipped in 254.85s

  • npm run --prefix ui-web gen:check -> generated.ts matches the contract (189 methods); npm run --prefix ui-web type-check -> clean; npm run --prefix ui-web lint -> 0 errors (5 pre-existing react-hooks warnings in files this PR does not touch)

  • npm test --prefix ui-web -> 189 files, 2594 tests passed in 19.68s (includes the new src/app/updates.test.ts and the state-dom-touch, i18n-keys, first-frame-literals gates)

  • npm run --prefix ui-tui lint, lint:rpc, lint:i18n, type-check, test, build -> 0 errors (23 pre-existing warnings), both generated tables in sync, 144 files / 2074 tests passed in 12.19s, bundle built

  • npm run --prefix ui-web build, python3 ui-web/build.py, node ui-web/scripts/check-page.mjs, node ui-web/scripts/check-css.mjs, node ui-web/scripts/check-class-namespace.mjs -> all OK, both boot snapshots match their goldens

  • uv run pre-commit run --from-ref origin/refactor/ui_web_architecture --to-ref HEAD -> every hook Passed or Skipped; commitlint, scripts/check_commit_messages.py, scripts/check_large_files.py, scripts/check_source_language.py -> OK

  • Live, raven serve from this branch with an isolated RAVEN_HOME: curl -sI http://127.0.0.1:<port>/ answers 200 with Cache-Control: no-cache and an ETag, the same request with that ETag in If-None-Match answers 304 also carrying the directive; with dist/index.html backdated below its sources the startup log carries the WARNING and the same request adds X-Raven-Page-Behind: sources, which disappears after touch dist/index.html with no restart; /assets/raven.svg carries no-cache and never the behind header

  • Live, both install shapes for the no-cache half: a wheel built from this branch and installed into a fresh venv (resolve_ui_dist resolves to the venv's raven/ui/dist), and the raven gateway --page-port child that raven web starts from a source checkout -- both answer / with no-cache and 304 with the directive

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed (ui-web/CONTRIBUTING.md debt row; the notice text itself is in the catalogue)

Risk

  • Security impact considered (two response headers on /; the behind header only ever appears on a source checkout and names no path; auth, origin checks and the nonce flow are untouched)
  • Backward compatibility considered (browsers now send If-None-Match on each navigation and get a 304 for an unchanged page; build_app gains an optional keyword; no RPC contract or config change)
  • Rollback path is clear for risky changes (revert the three commits)

A tab that already holds the old copy does not see the new header until it next asks the server: it may open the old build once more, until a hard reload or until its guessed lifetime runs out. Verify from a fresh browser profile or after one hard reload, not from the tab that showed the bug. The mtime judgement is make's: a checkout or pull that rewrites a source file with unchanged content also makes the page read as behind until the next make build-ui, which is a spurious warning rather than a missed one.

Related Issues

N/A

`raven serve` sent dist/index.html from `/` with an ETag and a Last-Modified
date but no Cache-Control, so a browser guessed a freshness lifetime from
that date and answered a navigation to `/` from its cache without asking.
The sign-in page at /auth ends by navigating the tab to `/`, so a rebuilt or
upgraded page kept opening as the build before it until a hard reload.

The assets already carry `Cache-Control: no-cache` for the same reason.
Extend that one hook to the page: it now registers whenever the page is
served, not only when an assets directory exists, and matches `/` as well
as `/assets/`. `no-cache` keeps the copy and revalidates it by ETag, so an
unchanged page still costs a 304 and no bytes.

The asset test no longer says the page is a different question; a page test
pins the directive with no assets directory at all.

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
@0xKT
0xKT requested a review from gloryfromca September 21, 2026 06:14

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the full target diff and the surrounding build_app routes and response hook, the /auth navigation caller, the history that introduced asset revalidation, compatibility with the target branch's newer commit, and the repository's AGENTS.md and Context Map/Web UI architecture rules. The hook now covers the served root page even when there is no assets directory, keeps ETag-based 304 responses, and does not change the no-static placeholder or unrelated routes. I found no weakened tests or backward-compatibility issue.

Verification: uv run pytest tests/test_rpc_transport.py -x (32 passed). git diff --check github/refactor/ui_web_architecture...HEAD passed, and a merge-tree check against the current target completed without conflicts.

A source checkout serves ui-web/dist, which is git-ignored: a pull brings
ui-web/src and the message catalogue but no rebuild, and the page silently
stays on the old build. Only the checkout can be in that state; the wheel's
copy ships beside the code it was built with.

resolve_ui_dist now judges it the way make would, by mtime -- dist/index.html
older than anything under ui-web/src (tests, the harness layer and snapshots
excluded), the build's own files or i18n/messages.json -- and logs one WARNING
naming make build-ui. Both page hosts hand that judgement to build_app, which
answers "/" with X-Raven-Page-Behind, asked per response so a rebuild takes
the header away without a restart.

The page's existing HEAD probe of "/" reads it and raises the rail-foot row
with a third notice: stale page build, how to rebuild. It outranks the
rebuilt-page notice, whose reload would come back behind as well, yields to a
newer release, goes down when a later look no longer carries the header, and
its click explains the command instead of reloading. raven web detaches the
terminal that gets the warning, so the page is the one place both launch
paths can show it.

showUpNote writes the row's two texts through one wording() helper, so the
DOM-touch ratchet's pin for app/updates.ts drops from 4 to 2.

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
@0xKT 0xKT changed the title fix(rpc): serve the page with no-cache like its assets fix(*): a rebuilt page reaches the tab, and a stale one says so Sep 21, 2026

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; suggestions only, and they are marked inline.

The new revision's source-checkout freshness signal, both page-host wiring paths, response-header behavior, Web UI notice arbitration, target-branch compatibility, and the relevant AGENTS.md/CLAUDE.md and Context Map/Web UI architecture rules are sound. I also checked the history and callers, backward compatibility with packaged pages and callers that omit page_behind, and that the tests add coverage rather than weakening existing assertions.

Named nonblocking follow-up (kept here rather than as an open thread): add ui-web/tsconfig.json to _PAGE_BUILD_FILES. Vite reads emit-affecting compiler options from it, so a pull that changes only such an option can currently leave dist/index.html stale without either warning. This is narrow because build-config changes normally accompany a source edit, so it does not hold this improvement.

Verification: uv run pytest tests/test_rpc_transport.py tests/test_cli_serve_commands.py tests/test_cli_gateway_page.py -x passed (133 tests); npm test --prefix ui-web passed (189 files, 2594 tests); and npm run type-check --prefix ui-web passed. The first focused UI attempt reported vitest: not found before the locked dependencies were installed; after npm ci --prefix ui-web, the full suite passed. The canonical source-language script, git diff --check, and a merge-tree check against the current target also passed.

gloryfromca
gloryfromca previously approved these changes Sep 21, 2026
…keys

i18n/messages.json is the one catalogue both front ends read; the TUI keeps a
generated copy that `lint:i18n` holds in step with it, and the four
gui.update.behind* keys the page gained had not been carried over.

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; suggestions only, and they are marked inline.

No new findings. The new commit only regenerates the TUI message table for the four catalog entries already reviewed, and the generated English and Chinese values match i18n/messages.json without unrelated movement. I checked the delta, generator ownership and history, source-language exemption behavior, target compatibility, and that no prior tests were weakened. The previously recorded named follow-up remains the only nonblocking item.

Verification: npm run lint:i18n --prefix ui-tui passed; npm test --prefix ui-tui -- src/__tests__/i18n.test.ts passed (8 tests); the canonical source-language check and git diff --check passed; and a merge-tree check against the current target completed without conflicts. The first generator-check attempt reported missing prettier because this worktree lacked TUI dependencies; after npm ci --prefix ui-tui, both checks passed.

@gloryfromca
gloryfromca merged commit 2eca67b into refactor/ui_web_architecture Sep 21, 2026
17 checks passed
@gloryfromca
gloryfromca deleted the fix/page_cache_revalidate branch September 21, 2026 09:21
gloryfromca added a commit that referenced this pull request Sep 21, 2026
## Summary

Second catch-up of `refactor/ui_web_architecture` with `main`, this time
to
`6ebc2baa` (#592): the 31 commits `main` gained since #529 took it to
`dbe259f6`. Synced main to `6ebc2baa`; the next round starts from that
SHA.

#529 landed as a squash, so git still saw the two branches diverge at
`5a29ea22` and re-raised every conflict #529 had already settled (68
paths
instead of 33). The first commit here merges `dbe259f6` with `-s ours`
--
tree unchanged, ancestry recorded -- so the second commit merges only
what
is new; a third takes #528 and a fifth takes #592 (READMEs only, no
conflict), both of which landed on `main` while the earlier commits were
being verified. The same steps work for every later round.

Thirty-three paths conflicted in the second commit, one in the third;
the
fourth commit is what an independent read of the merge turned up (the
sheet's Connect, `capabilities_wanted`'s fourth case, the dead styles);
the
sixth merges this branch's own tip (#584, #591, #586), where only the
fixture-shape gate's UNSENT list met (both sides kept). How each
conflict
was settled:

- 17 legacy ui-web files (`live/`, `demo/`, `features/xa`,
  `features/knowledge`, `scripts/boot-order.test.mjs`,
  `composer/open-conversation.test.ts`), edited on `main` for #542, #549
  and #554: deleted on this branch, and the deletions stand. `main`'s
  `shell/workdir.ts` and its test, which git relocated into `lib/`, are
  dropped for the same reason -- see the follow-up below.
- `main.tsx`, `page.html`, `state/session/registry.test.ts`: this
branch's
version. `main`'s edits there were the legacy chip wiring; the registry
conflict was a rename git guessed from
`scripts/page-switch-live.test.mjs`.
- `raven/agent/subagent/probe.py`: both imports; Test records a snapshot
  for a preset too (`main`, #554) through `record_capabilities` (this
  branch, #559); a kept record carries `needs_auth`; the boot backfill
  re-measures an outdated model menu (this branch) and a recorded
  credential refusal (`main`).
- `raven/rpc/methods/subagents.py`: `needs_auth` reads the snapshot this
  branch already loads per row.
- `raven/agent/subagent/manager.py` (#528, third commit): both sides
kept
at both hunks -- `main`'s two shutdown refusals beside
`SPAWN_REFUSED_PREFIX`
  with this branch's `_row_pin` after them, and the spawn announce that
types its mark and carries `node_id` (this branch) then returns early
when
  `_inject` was refused (`main`).
- `tests/test_subagent_probe.py`, `test_subagent_acp.py`,
`test_rpc_subagents.py`, `test_rpc_console.py`, `test_config_update.py`:
  both sides' new tests, in order. This branch's two backfill tests pin
  `_unconfigured_acp_preset_rows` the way `main`'s do, and the
`_agent_home` helper both sides defined is one function passing a
`Path`,
  as the config property does.
- `README.md`, `README.zh-CN.md`: `main`'s figure and showcase section.
- `ui-web/src/rpc/generated.ts`, `ui-tui/src/rpc/generated.ts`,
  `ui-tui/src/i18n/messages.generated.ts`: regenerated from the merged
  schema and catalogue.

Carried onto the rebuilt tree, since the backend halves merged cleanly:

- #554: the agent hub's rows, its sheet and the wizard step name a
refused
  agent `Unauthorized` (disabled) instead of offering Connect; the sheet
offers Test beside it, the press that re-measures the handshake and lets
the row return to Connect after a sign-in. `capabilities_wanted` treats
a
recorded refusal as wanting a re-measure, the same fourth case the boot
  backfill gained from `main`. Four tests.
- #542: rail rows carry the session's `workdir`, so the folder group
`RailPage.tsx` gained in the merge can fill. The tag's class moves under
  the rail namespace (`rail-wdt`) with its rule in the new
  `features/rail/styles.css`, as check-class-namespace requires.
- #549: the page-side upload ceiling follows `raven/rpc/files.py` to 100
MB.
- The offline fixtures send `needs_auth`;
`session.list.sessions[].workdir`
is pinned unsent in fixture-shape until the chip lands. The folder
chip's
thirty lines of popover rules that `main` added to `styles/page.css` are
  dropped with the chip; the follow-up brings its own.

Follow-up, not in this PR: the folder chip itself (#542's
`shell/workdir.ts`: the composer chip, the `fs.dirs` browser, `workdir`
on
`session.create`) has to be rebuilt on this tree's chip pattern
(`state/perm.ts` + `chrome/PermChip.tsx`). The backend, the i18n keys
and
the rail half are already here.

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other (catch-up merge)

## Verification

Run in the worktree on the merged tree:

- `uv run pytest tests/test_subagent_probe.py tests/test_subagent_acp.py
tests/test_rpc_subagents.py tests/test_rpc_console.py
tests/test_config_update.py tests/test_rpc_session.py
tests/test_rpc_contract_shapes.py tests/test_rpc_schema_match.py
tests/test_rpc_registration.py tests/test_i18n_boundary.py
tests/test_subagent_manager.py tests/test_subagent_dag_runner.py
tests/test_rpc_bootstrap.py tests/test_cli_tui_commands.py
tests/test_cli_gateway_commands.py tests/test_rpc_transport.py
tests/test_cli_serve_commands.py tests/test_importer_orchestrator.py
tests/test_cli_gateway_page.py -q` on the final head -- 1893 passed (the
same files, run per commit as the branch grew: 1141, 590, 501).
- `make lint-python` -- ruff check clean; ruff format clean after
formatting the three spliced test files.
- `npm test` in `ui-web` on the final head -- 189 files, 2468 tests
passed (before the sync merge: 193 files, 2609; the first run had failed
two gates, check-class-namespace and fixture-shape, on the merged `.wdt`
class and the three new optional wire fields, both fixed as described
above).
- `npm run type-check`, `npm run lint` (0 errors, 5 pre-existing
warnings), `npm run gen:check` (193 methods) in `ui-web`.
- `npm run lint:i18n`, `npm run lint:rpc`, `npm run type-check`, `npm
test` in `ui-tui` -- all clean.
- `make build-ui` on the final head -- page built, `boot-snapshot: OK`
on both snapshots (235 nodes, the golden #591 set).
- `scripts/check_source_language.py
origin/refactor/ui_web_architecture..HEAD` and
`scripts/check_large_files.py origin/refactor/ui_web_architecture..HEAD`
-- both exit 0; `git merge-tree --write-tree origin/main HEAD` -- clean.

The full Python suite is left to CI.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

No source change of its own beyond the resolutions and the three carries
above. On the page: a refused external agent now shows a disabled
`Unauthorized` button where it showed Connect; conversations pinned to a
folder group under their own rail heading with the folder's name on the
row; uploads up to 100 MB are sent instead of refused at 25 MB. All
three
match what `main` ships today.

This branch is squash-only, so the merge base stays at `5a29ea22` after
this lands; the next round repeats the `-s ours 6ebc2ba` step first.
Rollback is reverting the squash commit; the branch is back at
`99c37e74`.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A. Unblocks #475.

---------

Co-authored-by: xfng-sd <xufang@shanda.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Co-authored-by: Handsome-wzw <68996445+Handsome-wzw@users.noreply.github.com>
Co-authored-by: Dizhan Xue <dizhan.xue@evermind.ai>
Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
Co-authored-by: admin <admin@SH-HuKai.local>
Co-authored-by: 江国庆/Forrest <mr.jianggq@163.com>
Co-authored-by: 江国庆 <guoqingjiang@deepglint.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: ypflll <ypflll@163.com>
Co-authored-by: yao pengfei <yaopengfei@shanda.com>
Co-authored-by: zhanghui <huizhang1995@gmail.com>
Co-authored-by: Zuyi Zhou <144661423+ZuyiZhou@users.noreply.github.com>
Co-authored-by: Tong Li <litong02@shanda.com>
Co-authored-by: litong <238663200+TongLi31@users.noreply.github.com>
Co-authored-by: Tchen-data <176354753+Tchen-data@users.noreply.github.com>
Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com>
Co-authored-by: Dizhan Xue <xuedizhan17@mails.ucas.ac.cn>
Co-authored-by: silverLXT <25195409+silverLXT@users.noreply.github.com>
Co-authored-by: xiaotian.luo <xiaotian.luo@thetahealth.ai>
Co-authored-by: userName20260323 <zhao.wang@evermind.ai>
Co-authored-by: zhao.wang <270284818+userName20260323@users.noreply.github.com>
Co-authored-by: arelchan <1239372199@qq.com>
Co-authored-by: arelchan <204152633+arelchan@users.noreply.github.com>
gloryfromca pushed a commit that referenced this pull request Sep 21, 2026
## Summary

A tester's report, in two halves: after a rebuild the browser may still
show the old page, and after a `git pull` nothing rebuilds the page or
says that it is stale. This PR closes both.

**A rebuilt page reaches the tab.** `raven serve` sent `dist/index.html`
from `/` with an ETag and a Last-Modified date but no Cache-Control. A
browser then guesses a freshness lifetime from that date (about a tenth
of the file's age) and answers a navigation to `/` from its cache
without asking the server. The sign-in page at `/auth` ends by
navigating the tab to `/` (`location.replace('/')` after the nonce
exchange), and a browser answers that navigation from the copy it still
guesses fresh, so a rebuilt or upgraded page kept opening as the build
before it until a hard reload. Three rebuilds in a row went unseen this
way on the architecture branch; a released install is exposed the same
way after `raven upgrade` whenever `raven web` is run again inside that
guessed lifetime, which for a weeks-old install is days. The assets
already carried `Cache-Control: no-cache` for exactly this reason (the
provider icons, 2026-09-11); the same `on_response_prepare` hook now
covers `/` too, registers whenever the page is served rather than only
when an assets directory exists, and `no-cache` keeps the 304 path (an
unchanged 1.3 MB page still costs no bytes; `no-store` would re-download
it on every navigation). The upgrade watcher's HEAD probe of `/` already
used `cache: 'no-store'` and was never affected.

**A stale page says so.** Only a source checkout can serve a page older
than the code beside it: `ui-web/dist` is git-ignored, so a pull brings
`ui-web/src` and the message catalogue but not a rebuild, and the served
page silently stays on the old build. `resolve_ui_dist` now judges that
the way make would, by mtime: when the checkout's `dist/index.html` is
older than anything under `ui-web/src` (tests, the `src/test` harness
layer and snapshots excluded, since they change without changing the
page), than the build's own files (`build.py`, `vite.config.ts`,
`package.json`, `package-lock.json`, `icon/raven.svg`) or than
`i18n/messages.json`, it logs one WARNING naming `make build-ui`, and
both page hosts (standalone `raven serve` and the gateway's page mount
behind `raven web`) hand that judgement to `build_app`, which answers
`/` with `X-Raven-Page-Behind: sources` -- asked per response, so a
rebuild takes the header away without a restart. The page's existing
30-second HEAD probe of `/` reads it and raises the rail-foot notice row
with a third wording, "Stale page build / How to rebuild"; the click
opens the confirm sheet with the command instead of reloading a page
that would come back the same, and the row goes back down when a later
look no longer carries the header. It outranks the rebuilt-page notice
("UI updated on disk / Reload"), because the reload that one offers
would come back behind as well: the row asks for the rebuild first, and
the probe that sees it land hands the row to the reload. A newer release
outranks both. The wheel's copy ships beside the code it was built with,
so a `raven web` user of an installed raven never sees the warning or
the header.

The terminal alone was not enough for the hint: `raven web` detaches the
gateway and its log goes to `web.log`, so the page is the one place both
launch paths can show it.

Also in this change: `showUpNote` now writes the row's two texts through
one `wording(kind)` helper (four `textContent` writes down to two), so
the DOM-touch ratchet's pin for `app/updates.ts` drops from 4 to 2 and
the debt row in `ui-web/CONTRIBUTING.md` follows. The TUI's generated
copy of the catalogue (`ui-tui/src/i18n/messages.generated.ts`) is
regenerated for the four new keys, which its `lint:i18n` gate requires.

Reviewed before opening by read-only panels (HTTP caching semantics,
aiohttp mechanics and test quality, page integration, completeness),
with adversarial refutation of every blocking claim; what survived is
in. One follow-up came out of it and is deliberately not here:
`/files/download` (raven/rpc/transports/deliverables.py) has the same
shape as the page had -- ETag and Last-Modified, no Cache-Control, a
token URL that stays the same when the deliverable is regenerated,
fetched by `<img src>` and `<a download>` in default cache mode. Same
one-header fix, separate PR.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

Run from the branch checkout, base
`origin/refactor/ui_web_architecture`:

- `uv run --frozen --extra dev pytest tests/test_cli_serve_commands.py
tests/test_rpc_transport.py tests/test_cli_gateway_page.py -q` -> 133
passed in 5.06s
- `make lint-python` -> `ruff check`: All checks passed!; `ruff format
--check`: 2019 files already formatted
- `make test-python` -> 24034 passed, 109 skipped in 254.85s
- `npm run --prefix ui-web gen:check` -> generated.ts matches the
contract (189 methods); `npm run --prefix ui-web type-check` -> clean;
`npm run --prefix ui-web lint` -> 0 errors (5 pre-existing react-hooks
warnings in files this PR does not touch)
- `npm test --prefix ui-web` -> 189 files, 2594 tests passed in 19.68s
(includes the new `src/app/updates.test.ts` and the `state-dom-touch`,
`i18n-keys`, `first-frame-literals` gates)
- `npm run --prefix ui-tui lint`, `lint:rpc`, `lint:i18n`, `type-check`,
`test`, `build` -> 0 errors (23 pre-existing warnings), both generated
tables in sync, 144 files / 2074 tests passed in 12.19s, bundle built
- `npm run --prefix ui-web build`, `python3 ui-web/build.py`, `node
ui-web/scripts/check-page.mjs`, `node ui-web/scripts/check-css.mjs`,
`node ui-web/scripts/check-class-namespace.mjs` -> all OK, both boot
snapshots match their goldens
- `uv run pre-commit run --from-ref origin/refactor/ui_web_architecture
--to-ref HEAD` -> every hook Passed or Skipped; commitlint,
`scripts/check_commit_messages.py`, `scripts/check_large_files.py`,
`scripts/check_source_language.py` -> OK
- Live, `raven serve` from this branch with an isolated `RAVEN_HOME`:
`curl -sI http://127.0.0.1:<port>/` answers 200 with `Cache-Control:
no-cache` and an ETag, the same request with that ETag in
`If-None-Match` answers 304 also carrying the directive; with
`dist/index.html` backdated below its sources the startup log carries
the WARNING and the same request adds `X-Raven-Page-Behind: sources`,
which disappears after `touch dist/index.html` with no restart;
`/assets/raven.svg` carries `no-cache` and never the behind header
- Live, both install shapes for the no-cache half: a wheel built from
this branch and installed into a fresh venv (`resolve_ui_dist` resolves
to the venv's `raven/ui/dist`), and the `raven gateway --page-port`
child that `raven web` starts from a source checkout -- both answer `/`
with `no-cache` and 304 with the directive

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed
(`ui-web/CONTRIBUTING.md` debt row; the notice text itself is in the
catalogue)

## Risk

- [x] Security impact considered (two response headers on `/`; the
behind header only ever appears on a source checkout and names no path;
auth, origin checks and the nonce flow are untouched)
- [x] Backward compatibility considered (browsers now send If-None-Match
on each navigation and get a 304 for an unchanged page; `build_app`
gains an optional keyword; no RPC contract or config change)
- [x] Rollback path is clear for risky changes (revert the three
commits)

A tab that already holds the old copy does not see the new header until
it next asks the server: it may open the old build once more, until a
hard reload or until its guessed lifetime runs out. Verify from a fresh
browser profile or after one hard reload, not from the tab that showed
the bug. The mtime judgement is make's: a checkout or pull that rewrites
a source file with unchanged content also makes the page read as behind
until the next `make build-ui`, which is a spurious warning rather than
a missed one.

## Related Issues

N/A

---------

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants