commit ef05a179874acdf2d101d1ffc346e56fc2d42ffb
parent f20c1af2ee3bde3ab42cf24272d32df55fed3191
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 26 Sep 2026 04:02:34 -0400
plans: release 10 slice L1 review fixes — /ask with every archive off, compose never writes through a link (proved: e2e:2origin with the links in place left the primary's eight files byte-identical), subs 404 = empty + real errors fail with Retry, networkMode always
Gates on f20c1af2:
- tsc clean.
- Unit tests: common 1,923, editor unit 79, scripts 162 + 1 skip, mcp 219.
- Builds ok: editor, export site, export hub, homepage.
- e2e: export 204, hub 24, 2origin 3 (no swap), homepage 27.
- Both new tests are red on the pre-fix source.
Also records the sw.js and compose-output worktree workarounds as retired
at merge (FACTS is left to the merger).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
| M | plans/release-10.md | | | 127 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
1 file changed, 123 insertions(+), 4 deletions(-)
diff --git a/plans/release-10.md b/plans/release-10.md
@@ -228,7 +228,8 @@ Branch `r10/hub-lows` off `main` `5dfc9c3a`, worktree `/home/user/Projects/r10-h
block #6). All five items are done. Item 4 follows the homepage: the code gave no reason to keep
alphabetical (below).
-1. **`retry: false` on the federated subs manifest** (`common/components/SearchDataContext.tsx`,
+1. **`retry: false` on the federated subs manifest** (superseded by review fix 4 below: a 404 is an
+ empty manifest, and a real error is retried once) (`common/components/SearchDataContext.tsx`,
the `subsQueries` of `MultiSiteDataProvider`). Readiness waits for the subs query to settle. A
member with no live chat answers 404, and the client default (`retry: 1`, `QueryProvider.tsx`)
held its chip at `loading` for one more second. The single-site `useSubsManifest("")` shares the
@@ -319,7 +320,8 @@ alphabetical (below).
and `hub-summary.json` are unchanged on disk: the order is applied in the browser.
**Found and left**
-- **The `/ask` composer reads ready before the hub's list loads** (pre-existing). With no archives
+- **The `/ask` composer reads ready before the hub's list loads** (pre-existing; CLOSED by review
+ fix 1 below: an empty scope is never ready on `/ask`). With no archives
listed yet, the provider has nothing in scope, so it counts as settled and `summariesReady` is
true for that instant. A question sent then would ground in nothing. `ask.spec` relies on the
empty-hub case being ready. The window used to end when `hub-sites.json` arrived. It now ends
@@ -335,13 +337,130 @@ alphabetical (below).
same order.
- **FACTS is stale after merge:** "Replace `export/public/sw.js` with a copy before any export e2e in
a worktree" (Release 9 facts, "Worktree e2e can write INTO the primary") no longer holds for
- `sw.js`. It still holds for the compose outputs `e2e:2origin` writes. Not edited here (a shared
- file).
+ `sw.js`. Review fix 2 below retires the compose-output swap too. Not edited here (a shared file).
`[Unreleased]` bullets: `export/CHANGELOG.md` (three: order and colour, `/ask` scope, subs).
`editor/CHANGELOG.md` and `homepage/CHANGELOG.md` are unchanged, because neither app changed. The
item-5 harness fix is not in the public changelog.
+**Review fixes** (review SHIP AFTER FIXES, `l1-review.md`; four findings, all fixed on `r10/hub-lows`).
+
+1. **Must-fix: `/ask` with every archive switched off** (`efe6e48f`).
+ - **The failure.** With L1's scope, an empty in-scope list counted as settled, so the composer
+ enabled and a question went out over zero records.
+ - **Readiness.** `MultiSiteDataProvider` now counts an empty scope as NOT ready unless it is
+ `progressive`. `/ask` is not progressive, so it stays disabled. That also closes the instant
+ before the hub's list arrives (the early-ready window from "Found and left", which no longer
+ applies). The progressive front page still settles on an empty scope. Its results read "No
+ videos match" rather than "loading index…" forever, and its chips and "Searching 0 archives"
+ say why.
+ - **The line.** `AskChat` shows one line in the composer when the hub has archives and every one
+ is off: `NO_ARCHIVES_IN_SCOPE`, `role="status"`, `data-testid="ask-blocked"`. It replaces the
+ placeholder and the key hint. The box and the Ask button are disabled.
+ - **The copy** is a single exported constant in `export/app/ask/hubScopeCopy.ts`, draft text "No
+ archives selected. Turn one on above to ask.", for the operator. **Copy note:** `/ask` shows no
+ chips, since they are on the hub's front page, so "above" does not match the page yet.
+ - **A hub with no archives at all** now reads "Loading transcripts…" on `/ask`, instead of being
+ ready over nothing. `ask.spec` accepts either.
+2. **Should-fix: the compose scripts wrote through the worktree links** (`1203f4a6`).
+ - **The helpers.** `common/bin/_publicFile.ts`: `writePublicFile` and `copyPublicFile` `rm` the
+ path first (`fs.rm` uses lstat, so only a link is removed, never its target). `ownDir` replaces
+ a linked directory with an empty real one.
+ - **compose-hub** routes every write through them.
+ - **compose-site** routes through them every top-level file and the per-site subs/posts/digests
+ manifests, and `reconcileChannelTree` owns its tree first. The paths that already removed first
+ (summaries/stats `replaceDir`, archives, `sw.js`, chart-templates) are unchanged.
+ - **Real builds are unchanged.** Where the paths are real (the primary checkout's
+ `export/public`, which holds no links, and the docker per-site `/site/public` dirs), the result
+ is what a plain write gave.
+ - **Tests.** `_publicFile` (4). `compose-hub`: a worktree compose leaves all seven of the
+ primary's files' content and mtime as they were. This one was red on the old `compose-hub`.
+ `compose-site`: a linked tree is owned, and the primary's is untouched.
+ - **Proof:** below.
+3. **Nit** (`609a7fe1`): `useHubSummary` runs with `networkMode: "always"`, so a first mount
+ after an `offline` event settles as "no summary" instead of pausing.
+4. **Nit: the subs query** (`78e837f1`, replacing `cf5455c0`'s `retry: false`).
+ - **A 404** resolves to an empty manifest at once, the way posts does. It is one request.
+ - **Any other error** keeps one retry (explicit on this query) and then fails the archive like a
+ failed manifest or page: the chip reads failed with Retry, and the "N of M archives answered"
+ line names it.
+ - **Why a 404 is readable.** A member's `/subs/*` carries CORS on 404s too: checked live,
+ `https://jeralyzer.pages.dev/subs/<missing>.json` → `404` + `access-control-allow-origin: *`.
+ So a missing file reads as a 404, not as a network error. An added archive on a host that
+ sends no CORS header on a 404 would read as failed.
+
+| sha | what |
+|---|---|
+| `78e837f1` | `hub:` subs: a 404 is an empty manifest; a real error is retried once, then fails its archive with Retry; e2e (finding 4) |
+| `efe6e48f` | `hub: /ask` with every archive off is not ready and says why (`NO_ARCHIVES_IN_SCOPE`); an empty scope is never ready when not progressive; e2e (finding 1) |
+| `609a7fe1` | `hub: useHubSummary` `networkMode: "always"` (finding 3) |
+| `1203f4a6` | `common:` `_publicFile.ts`; compose-hub and compose-site never write through a link; 6 unit tests (finding 2) |
+| `323c878f` | `export: [Unreleased]` — the two reader-visible fixes |
+| _this_ | `plans:` these review fixes |
+
+**Gates on `323c878f`**
+- **tsc:** clean before every code commit (`l1-tsc-6..8`). `78e837f1` was staged as a subset of the
+ tsc-green `l1-tsc-7` tree.
+- **Unit tests:**
+ - common **1,923** (1,917 + 6);
+ - editor unit **79**;
+ - `test:scripts` **162 + 1 skip**;
+ - mcp **219**.
+- **Builds** (`l1-fix-gates.log`), all ok:
+ - editor (53 s);
+ - export site (43 s);
+ - export hub (33 s);
+ - homepage (20 s).
+- **e2e** (`l1-fix-e2e.log`):
+ - export full **204 passed**, 7.1 min (after 9.0 min in the queue behind L2);
+ - `e2e:hub` **24 passed** (22 + the 2 new tests), 1.4 min;
+ - `e2e:2origin` (`TWO_ORIGIN_REBUILD=1`, links in place, **no swap**) **3 passed**, 34.0 s;
+ - homepage full **27 passed**, 44.8 s.
+- **During development:** the three touched hub specs went 11 passed, 1 failed. The failure was the
+ new `/ask` test waiting for an answer the fixture member cannot produce: it serves no transcript
+ tree, so the chat's own search never finishes. The test now asserts the question reaches the
+ provider. After that, 1/1.
+- **Red on the pre-fix source** (`l1-fix-e2e-red.log`, with the four `/ask` and provider files at
+ `a9d31063`): the two new tests fail for the reason each exists.
+ - The subs-500 chip read `ready` (expected `failed`).
+ - With every chip off, `/ask` showed no blocked line.
+
+ The source was restored and the tree left clean.
+- **Proof of finding 2:** the export and hub runs left the seven composed entries as LINKS into
+ the primary (`sw.js` was already the worktree's own, from item 5).
+ - **Before**, primary `export/public` (size, mtime, md5):
+
+ | file | size | mtime | md5 |
+ |---|---|---|---|
+ | `sw.js` | 10,027 | 20:47:37.183119759 | `55cbf381…` |
+ | `hub-summary.json` | 1,441 | 20:48:07.499176932 | `7d8a101e…` |
+ | `hub-sites.json` | 579 | 20:47:37.173642710 | `28b9893c…` |
+ | `corpus.json` | 1,535 | 20:47:37.177642649 | `54b64280…` |
+ | `llms.txt` | 1,031 | 20:47:37.177642649 | `87255fa7…` |
+ | `robots.txt` | 130 | 20:47:37.178642634 | `60217070…` |
+ | `sitemap.xml` | 364 | 18:08:42.340736263 | `76ffaa3c…` |
+ | `_headers` | 459 | 20:47:37.178642634 | `a5f88f82…` |
+
+ All mtimes are 2026-09-25 (`l1-fix-primary-before.txt`).
+ - `e2e:2origin` ran `build:hub` through those links.
+ - **After, the worktree** holds its own plain files: `hub-sites.json` 2 B (`[]`), `corpus.json`
+ 468 B, `llms.txt` 494 B, `robots.txt` 76 B, `_headers` 459 B, `sw.js` 11,005 B.
+ `hub-summary.json`'s link was removed (no index). `sitemap.xml`, which compose-hub does not
+ write, is still a link.
+ - **After, the primary's eight files** are byte-identical, with the same sizes and mtimes
+ (`l1-fix-primary-after-2origin.txt`, `diff` empty). They were identical again after the homepage
+ run (`l1-fix-primary-after.txt`).
+
+**Retired at merge (for FACTS; not edited here):** both worktree workarounds become unnecessary.
+- "Replace `export/public/sw.js` with a copy before any export e2e in a worktree" (Release 9 facts):
+ `playwright.config.ts` unlinks it (item 5).
+- "Swap the compose outputs for copies before `e2e:2origin` and relink after" (Brand facts,
+ "Worktree e2e and builds write through the `export/public` links"): compose-hub and compose-site
+ unlink before writing.
+
+The seed (per-path links from the primary) stays as it is. A worktree run now replaces a link with
+its own file, and the next seed relinks it.
+
## Rollout
Nothing is rolled out. The live :3001 editor still runs `0213f6c8` (the pre-brand build); the five