commit 1b558050f453634f3f1fdcd3abb8db2ee23c4766
parent 1b6d4e9da3121fbca6ddbdbf31ecd13a01eccc49
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 01:56:32 -0400
plans: slice O4 as shipped — the homepage builds and deploys from /sites (three job kinds, the refusals, a preview box, two ops verbs), JobLane's Strict Mode bug found and fixed; the plan's "As shipped" note (ops preview is a body key, no jobKinds entries, homepageOutDir exported)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 155 insertions(+), 0 deletions(-)
diff --git a/plans/homepage-deploy-from-ui.md b/plans/homepage-deploy-from-ui.md
@@ -116,3 +116,30 @@ Numbers tool: none.
The live :3001 editor must be restarted to show the section, which fits the next editor restart.
After it: Build homepage with Deploy after build unticked, then Deploy homepage, and the job's log
ends with the Pages URL.
+
+## As shipped (2026-09-28, release 11 slice O4)
+
+Built as planned; record: [`release-11.md`](release-11.md), "Slice O4, as shipped". What the
+build corrected here:
+- **The ops preview is a body key, not a flag.** `pnpm ops deploy-homepage [--preview <branch>]`
+ (decision 4) is `pnpm ops deploy-homepage --json '{"preview":"<branch>"}'`: the ops client has no
+ per-verb flags, and `deploy-hub` takes its preview the same way.
+- **No job-kind registry entries.** The hub's three kinds have none in `common/jobs/jobKinds.ts`,
+ and neither does any publish kind (`build-export`, `deploy-export`, `build-deploy`, `build-all`,
+ `build-deploy-all`). A kind with no entry shows its raw name on `/jobs` and is not drainable, not
+ replayable and needs no media, which is right for all three. Mirrored: the kinds are declared
+ where the hub's are, in the action's `kind` and in `/sites`' `BUILD_KINDS` comment.
+- **`homepageOutDir(paths)` is exported from `common/publish/build.ts`.** The editor needs
+ `homepage/out` for `builtHomepageProblem` and for "built <when>", and `homepageDir` was private.
+ `buildHomepage` and `deployHomepage` are unchanged, so the job's own refusal keeps its sentence
+ ("… — run archilyzer build homepage first"). The refusal before the job says "… — build the
+ homepage first".
+- **`JobLane` needed a fix to be testable at all.** Under `next dev`, Strict Mode's
+ mount → cleanup → mount dropped every update of a lane's one launch, so it sat on "Starting…"
+ while its job ran. Production mounts once and never showed it; no spec had clicked a lane
+ button before.
+- **How the e2e avoids a real `next build` and wrangler.** There is no fake wrangler, and the hub's
+ buttons have no spec. The Build homepage spec holds the `build` queue with `/api/test/stuck-job`,
+ so its job only queues, and cancels it from the lane before it starts. The deploy-homepage
+ refusals run with the `deploy` queue held the same way. The missing-build refusal is asked only
+ when the checkout has no `homepage/out/index.html`, and asked as a preview.
diff --git a/plans/release-11.md b/plans/release-11.md
@@ -36,6 +36,134 @@ merges `main` once O1–O5 have landed, then does its `E2E_` rename. Then one in
## Record
+### Slice O4, as shipped — deploy the homepage from /sites (2026-09-28)
+
+The operator's ask (2026-09-26): **"Is there a way to redeploy archilyzer.pages.dev from the UI?"**
+There was not: `/sites` built and deployed the hub, and the homepage was a CLI
+(`archilyzer build|deploy homepage`) with no job, no `/jobs` row and no `pnpm ops` verb. Plan:
+[`homepage-deploy-from-ui.md`](homepage-deploy-from-ui.md), decisions 1–5, now with an "As shipped"
+note. Branch `r11/homepage-deploy-ui` off `main` `2162db92`, worktree
+`/home/user/Projects/r11-homepage-deploy-ui`, one Opus implementer. Scratch files `o4-*` in the
+job's `tmp/overnight`.
+
+**What shipped.**
+- **A Homepage section on `/sites`, after Hub.** The Hub paragraph lost its homepage sentence, and
+ the new section says it instead: the `homepage` package, `homepage/out`, the project `archilyzer`,
+ and the same code as `archilyzer build|deploy homepage`. Its group, **Homepage build**, is
+ `HubBuildButtons`' shape: **Build homepage**, **Deploy after build** (unticked), **Deploy
+ homepage**, one `JobLane` at a time (**Build homepage** / **Build & deploy homepage** /
+ **Deploy homepage**).
+- **A preview branch box** (textbox "preview branch", empty = production). It is judged by
+ `previewBranchProblem`, the function the action and the route refuse with. A bad name greys out
+ Deploy homepage, and Build homepage when Deploy after build is ticked, and shows the sentence
+ (`role="status"`, "preview problem").
+- **One line saying what a deploy ships:** "Deploy homepage ships `homepage/out`, built <when>, to
+ `archilyzer` (production): https://archilyzer.pages.dev." (`PROJECT_URL`). With no build it says
+ "`homepage/out` holds no build yet …". With a preview it gives the alias and says "the live site
+ is left alone". Under it: the homepage reads the index as it stands, so run Build index first.
+- **Three job kinds,** via `runManagedFunction` in `editor/app/sites/lib/homepageDeployActions.ts`:
+ - `build-homepage` on queue `build`;
+ - `deploy-homepage` and `build-deploy-homepage` on queue `deploy`;
+ - the bodies are `buildHomepage` / `deployHomepage`, unchanged. The build-deploy deploys only
+ on exit 0.
+- **Refusals before any job:** a bad preview name, and for a deploy-only,
+ `builtHomepageProblem(homepageOutDir(paths))` ("homepage/out holds no build — build the homepage
+ first"). `deployHomepage` re-checks both inside the job with its own sentences, untouched.
+- **Ops:**
+ - `/api/ops/build-homepage` `{deploy?, preview?}`: a preview without deploy is a 400.
+ - `/api/ops/deploy-homepage` `{preview?}` answers `previewUrl` =
+ `previewAliasUrl(HOMEPAGE_PAGES_PROJECT, preview)`.
+ - `pnpm ops build-homepage` / `deploy-homepage`, in the verb list, the header examples and the
+ usage.
+- **common:** `builtHomepageProblem` and `builtHomepageAt` (index.html's mtime) beside
+ `builtHubProblem` in `builtExport.ts`. `homepageOutDir(paths)` exported from `publish/build.ts`,
+ so the editor does not re-derive the path.
+- **Found and fixed: `JobLane` under Strict Mode.** The launch effect gated its updates on a flag
+ local to it, set by its cleanup. `next dev`'s mount → cleanup → mount set that flag, and the
+ second run returned early on `startedRef`, so the job ran while the lane sat on "Starting…"
+ forever. Liveness is now a ref kept by its own effect. Production mounts once and is unchanged.
+ No spec had ever clicked a lane button (batch, specific sites, hub), which is how it went unseen.
+
+**Corrections to the plan** (also in its "As shipped" note):
+- The ops preview is `--json '{"preview":"<b>"}'`, not a `--preview` flag.
+- No `jobKinds.ts` entries: the hub's kinds have none, and neither does any publish kind.
+- `homepageOutDir` had to be exported.
+
+**How nothing in e2e builds or deploys.** There is no fake wrangler, `homepage/out` is the
+checkout's own directory (the primary's holds the live homepage's build), and the hub's buttons have
+no spec to copy. So:
+- **No spec clicks Deploy homepage.** None ticks Deploy after build and then clicks Build.
+- **Build homepage is clicked only with the `build` queue held** by `/api/test/stuck-job`, which is
+ never released in the spec. The `build-homepage` job only queues, and the spec cancels it from its
+ lane. Its `start()` never runs: no log file, no child, nothing written under `homepage/`.
+- **If that spec fails before its Cancel,** the next `resetData` cancels live jobs newest first
+ (`registry.list()` sorts by `queuedAt` descending). The queued build is removed before the
+ holder's slot is freed, so nothing is ever promoted.
+- **The deploy-homepage refusals run with the `deploy` queue held the same way.** A deploy a refusal
+ failed to stop would only queue, and the spec's job-list check would fail.
+- **The missing-build refusal is asked only when the checkout has no `homepage/out/index.html`,** as
+ `deploy-hub`'s is (`211d4666`), and it is asked as a preview.
+- **Checked after every run:** the worktree has no `homepage/out`, and `homepage/public` holds only
+ its tracked `_headers`. Its gitignored data was not copied, because nothing here builds the
+ homepage.
+
+| sha | what |
+|---|---|
+| `b047b1be` | `common:` `builtHomepageProblem` + `builtHomepageAt` (`builtExport.ts`), `homepageOutDir` (`publish/build.ts`); +3 unit tests |
+| `7ba41164` | `sites:` `homepageDeployActions.ts`, `HomepageBuildButtons.tsx`, the Homepage section; the Hub paragraph loses its homepage sentence |
+| `54021099` | `ops:` `/api/ops/build-homepage`, `/api/ops/deploy-homepage`; `archilyzer-ops.mjs` verbs + usage (+1 test) |
+| `ba4ff0bb` | `e2e:` `ops-api.spec` +1 (the refusals), `sites-homepage.spec` (3) |
+| `d52d017d` | `sites:` `JobLane` survives Strict Mode |
+| `d9218cb7` | `sites:` the group stamps `data-hydrated`; the specs wait for it |
+| `40b6a0d7` | `changelog:` `[Unreleased]` above `[0.9.4]` in `editor/CHANGELOG.md` |
+| _this_ | `plans:` this record; the plan's "As shipped" note |
+
+**Gates** (logs `o4-*.log`):
+- **tsc** clean before every code commit (the last run on `d9218cb7`'s tree).
+- **common 2,041/2,041** (2,038 + 3), editor unit **85/85**, `test:scripts` **175 + 1 skipped**
+ (174 + 1, +1), mcp **269/269**.
+- **Editor build** ok (59 s); `/api/ops/build-homepage` and `/api/ops/deploy-homepage` are in the
+ route list.
+- **e2e:**
+ - Run 1 on `ba4ff0bb` (`ops-api`, `sites-homepage`, `sites-crud`, `site-publish-preview`,
+ `deploy-page`, `build`, `settings`): **63 passed, 1 failed** (2.8 min). The failure was
+ `sites-homepage`'s Build homepage test: the lane stayed on "Starting…", which is the `JobLane`
+ bug above.
+ - `sites-homepage` ×3 with the old `JobLane`: **6 passed, 3 failed**.
+ - The same ×3 on `d9218cb7`: **9 passed** (27 s).
+ - The final list on `d9218cb7` (the same seven specs): **64 passed** (1.8 min).
+ - No spec here greps `deploy-hub|build-hub|HubBuild` except `ops-api`, which is in the list.
+- **Numbers tool:** none.
+
+**They bite:**
+- **Unit, against `2162db92`'s source:**
+ - `builtExport.test.ts` and `build.test.ts` fail to load (no `builtHomepageAt` /
+ `homepageOutDir` export).
+ - A mutation, `builtHomepageProblem` checking the directory instead of `index.html`, fails 1 of 8.
+ - `archilyzer-ops.test.mjs` against the old script: 1 of 33 fails, the new test.
+- **e2e, against `2162db92`'s `page.tsx`, `builtExport.ts` and `build.ts`,** with the new action,
+ component and routes moved out: **4 of 4 new tests fail.** `ops-api` hits a 404 page ("Unexpected
+ token '<'"), and `sites-homepage` finds no Homepage group. The tree was restored by a trap
+ afterwards.
+- **The `JobLane` fix:** the Build homepage test fails 3 of 3 with the old lane and passes 3 of 3
+ with the fix.
+
+**Found and left**
+- **A job cancelled while still queued keeps `"status":"queued"` in its `.meta.json`.** The
+ `onCancel` path never rewrites the sidecar. The registry says cancelled, and the boot pass settles
+ the sidecar. This is pre-existing and was not touched.
+- **The "built <when>" line is read when `/sites` renders,** so a lane that finishes a build does
+ not refresh it. The Hub's lanes do not refresh the page either.
+- **`/jobs` shows the three kinds by their raw names,** as it does the hub's.
+- **Nothing has been built or deployed through the new jobs:** no build and no deploy tonight, by
+ rule. The proof is the rollout below.
+
+**Rollout (owed):** the section and the routes exist only on an editor built from this code, so it
+waits on the :3001 restart. After it:
+1. **Build homepage** with Deploy after build unticked, or `pnpm ops build-homepage --wait`.
+2. **Deploy homepage**, or `pnpm ops deploy-homepage --wait`. The job's log ends with
+ `[deployed] https://<hash>.archilyzer.pages.dev`.
+
## Rollout
Nothing is rolled out tonight. The morning runbook lists what is owed: the :3001 editor restart,