Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit fb095a538c20297617a8446413f22c193d250bd3
parent befef15c84dca98ff2ec22d57e9ee3f38f06f44e
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue,  6 Oct 2026 13:28:52 -0400

plans: release 18 — slice S4, as shipped (the publish surfaces), and the index's settings signature in What makes the index stale

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mplans/release-18.md | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 152 insertions(+), 0 deletions(-)

diff --git a/plans/release-18.md b/plans/release-18.md @@ -1283,6 +1283,158 @@ deploys: "none"}`); `enqueuePublishRun(paths, plan, {runId?})` and `enqueueStage `resumeLaneAction("publish")` (`editor/app/operations/actions.ts`) and `isGateHeld(settings, "publish")`; the policies `sitePublishPolicy(site)`, `sitePublishProblem(site)`, `settings.publish`. +### Slice S4, as shipped — the publish surfaces: /sites Publish panel, /operations/publish, the ops API (2026-10-06) + +Branch `r18/surfaces` off `r18/integration` `85a38e92` (main `edadc712` merged in first, step 0, as `f2fd11a7`; then +main's own red pin fixed, `85a38e92`), built in the integration worktree `~/Projects/r18-integration` (editor 7101, +test 7111, export 7110) by the orchestrating Opus session — this session can run git only in its own worktree, so +the prepared `r18-publish-surfaces` worktree was not used — with two Opus helpers (the build.ts deletion; the +review). Scratch files `s4-*` in the job's `tmp`. The plan is "Surfaces", the step 1 pre-fix and S3's "Seams". + +**What it does.** +- **The index is judged by the settings it reads** (step 1). `indexSettingsSig` (`publish/inputSig.ts`) signs + `socialLinks`, `homepageUrl`, `buildArchives`, `archiveStorage`, `social.x.visibility`, + `maxTranscriptPageBytes` and the storage locations' roots; update-index records it as `settingsSig` in the + stamp and `needs()` says "the settings the index reads changed" when it differs (an older stamp reads as + changed, once). The settings file's mtime is no longer an index input: a pause click, a priority change or a + drive auto-pause no longer stales the index. The charts config stays an mtime (its own file). +- **/sites → Publish** (`sites/components/PublishPanel.tsx`) replaces "Build all sites", the batch panel and the + hub's and homepage's build sections: a row per site, then the hub, then the homepage, each with the status's + four chips (index | built | deployed | live), its policy and what is next. A site row: **Build**, **Deploy + preview** (box "preview branch", starts on `settings.publish.previewBranch`; status "preview problem"), + **Deploy production**, **Deploy local** (only with `ARCHILYZER_SITE_OUT`); a private site says it never + deploys, a site with no project says so. The hub and homepage rows keep their names ("Build hub", "Deploy + hub", "Build homepage", "Deploy homepage", "Deploy after build", an empty preview box = production, testid + `homepage-ships`, group "Homepage build" / "Hub build"). Above the rows: **Publish now**, **Build all stale**, + the index chip, the lane chip (a link to /operations/publish) and the plan Publish now would run. Each button + is one JobLane; the hub's config form stays below as "Hub config". +- **The Pool**: **Build index** is the update-index stage (no queue control: it is a publish stage); **Build stats + dataset is removed** (the stats are part of the index update); normalize and the archives unchanged. +- **A site's Publish tab**: Build & deploy (exact names kept) = the index update when stale, the build `--force`, + the production deploy with `builtAfter`; "Build static export" without "Skip data rebuild"; "Deploy to + production" / "Deploy preview"; "Last deployed" is this site's `deployed.json` — production, the last preview, + the live-check verdict — and "Built" its `built.json`. +- **One console per run** (`sites/lib/publishRunStream.ts`): the run's jobs' streams joined in order under `=== + <stage> (job <id>) ===`; the verdict is the first job not `done`; a job of the run that ends `cancelled` + cancels the jobs after it (the console's Cancel is the run's — else it would wait on a stage queued behind + something else). +- **The actions** (`sites/lib/publishActions.ts`, `"use server"`, over `publishCore.ts`): `publishNowAction`, + `buildAllStaleAction`, `updateIndexAction`, `buildTargetAction`, `deployTargetAction`, + `buildAndDeployTargetAction`, and the lane's `start|stop|drainPublishLaneAction`. Every one enqueues a plan + through `enqueuePublishRun` under one run id; a manual build or deploy is forced, and a build carries the index + update first when the index is not fresh. A deploy is refused before any job in the deploy stage's own words: + steps 1–3 of `runDeployStage` moved unchanged into `resolveDeployRequest` (`publish/deployStage.ts`), which the + stage and the surfaces both ask ("no build of X in <dir> — archilyzer publish build X" is one sentence). +- **/operations/publish** (static route beside `[id]`): the runner pill, Start / Drain / Stop (`Start publish + lane` …), the hold (`pause publishing` / `resume publishing`), lane on/held/quiet, a pass due or not and why, + last check / next check / last pass / last decision, the runner's job log, the plan a pass would run, and + `settings.publish`'s form (`savePublishSettingsAction`: enabled, check/refresh minutes, quiet hours, runner, + preview branch — refused with the deploy buttons' sentence —, hub and homepage policies). The page refreshes + itself every 5 s. `setLaneHeld` revalidates it and /sites for the publish lane; the operations board links it. +- **SiteForm**: "Publish policy" (Off / Build / Preview / Production) writes `site.json` `publish.auto`; + `writeSite`'s refusal of a deploying policy with no Pages project is the form's error. A save no longer drops + the key (the form rebuilt the site without it). +- **ops API**: `POST /api/ops/publish` `{verb: index | build | deploy | hub | homepage | now | stale, …}` → + `{ok, runId, jobs: [{target, kind, jobId, previewUrl?, existing?}], skipped, refused}` (+ `jobId` with one job; + every request refused → 400), a key a verb does not take is a 400; `GET` → the publish status. The eight old + routes are aliases with their bodies and answers (`build-index` ignores `queueKey`, `build-site` ignores + `skipData`; `build-deploy` `all` = every deployable site to production; per site the DEPLOY job is the one + reported). Shared adapter code in `api/ops/_publish.ts`. `pnpm ops publish` and `pnpm ops get publish`. +- **CLI**: `deploy hub` / `deploy homepage` print and run `publish hub|homepage --deploy-only` (new flag) through + the deploy stage (S2 review I2). `build.ts` loses `buildSite`, `deploySite`, `buildAll`, `deployHub`, + `deployHomepage`, `runDockerBuildAllPhase`, `runDockerDeployAllPhase` and what only they used + (`runDeployIntoLog`, `runPagesDeployIntoLog`, `homepageDeployArgs`, the outcome types): 1452 → ~980 lines. +- **A stage ends "Done"**: `[stage] <kind> <target>: Done — …` / `Done (no-op) — …` (was "done"/"no-op"); the + e2e `buildIndex()` helper waits for that line. +- **e2e seams**: `EXPORT_NEXT_BIN` (`build.ts nextBuildStep`, ENVIRONMENT.md) runs `<bin> build` in place of + `pnpm exec next build` for a site's and the hub's build — the test server points it at + `e2e/fixtures/bin/fake-next.mjs`, which copies the composed public dir to export/out (compose runs for real; + the export app is never rebuilt beside its dev server); `ARCHILYZER_BRANCH=main`; a dummy + `CLOUDFLARE_API_TOKEN` (the preflight; the fake never sends it); the fake wrangler's mode sidecar + `<exportBuildsDir>/.fake-wrangler-mode.json` `{"authFail": true}`. + +**Deviations from the plan** (one sentence each): +1. Built on a branch of the integration worktree (`r18/surfaces`), not in `r18-publish-surfaces`: this session + could run git only in its own worktree. +2. A manual Build (row, tab, hub, homepage) enqueues the index update first when the index is not fresh — the + data phase a build used to run — so "Build & deploy" is two jobs only when the index is fresh. +3. Manual deploys are forced (the plan's "--force (manual buttons only)"); `POST publish {verb: "deploy"}` forces + only with `force: true`. +4. `EXPORT_NEXT_BIN` and the fake `next` are new: the plan's publish.spec needed a real build, and a real `next + build` of the export app inside the editor's e2e would race its dev server. +5. A cancel of a run cancels its later jobs (publishRunStream) — the plan did not say; without it the hub and + homepage rows' consoles never settled. +6. The /sites "Hub" heading is "Hub config" (the panel's row is "Hub"); the homepage's own section is gone (its + row is in the panel). +7. GET /api/ops/publish answers `{ok: true, …status}` (the status at the top level, so `lane.held` reads directly). +8. sites-homepage.spec's "run Build index" line is the row's chips now ("no index yet", "update the index first"), as + planned. +9. `writeSite` (e2e helper) passes `audience` through. +10. Files beyond the slice's list: `videoChoreCards.test.ts` (main's red pin, on integration), `source.test.ts` + (the manifest refusal kept a test), two comments in `builtExport.test.ts`. + +**Found and fixed on the way.** +- main was red: `videoChoreCards.test.ts` pinned every import from `./cards` as a chore card, and main's + multi-track merge added `TranscriptTracksReader` there (`85a38e92`, on integration). +- The site form dropped `site.json` `publish` on every save (S3 added the key, the form never knew it). + +- The e2e build stage left the worktree's `export/out` linked to a test bundle that resetData then deleted, and a + bare `next build` of the export app failed on it (`5a77682e`: the suite's setup and teardown drop such a link). + +**Found and left** — in "Follow-ups carried over" above. + +| commit | what | +|---|---| +| `f2fd11a7` | (integration) merge `main` `edadc712` — multi-track captions, en-track fallback, Wayback, Odysee/BitChute spacing; the changelog keeps both sides | +| `85a38e92` | (integration) main's red pin: the chore-card test leaves the transcript reader out | +| `195e54f8` | publish: the index judged by `settingsSig`, not the settings file's mtime (step 1) | +| `2b533c6c` | publish: `resolveDeployRequest`; a stage ends "Done" | +| `a30581f7` | editor: the /sites Publish panel, the site Publish tab, `POST|GET /api/ops/publish` and the eight aliases | +| `e360f7da` | editor: /operations/publish; the site form's publish policy | +| `ce20b387` | cli: `deploy hub|homepage` → `publish hub|homepage --deploy-only` | +| `4cb8e81b` | ops: `pnpm ops publish`, `pnpm ops get publish` | +| `514e9674` | publish: build.ts loses the pre-stage entry points (helper agent) | +| `fce11b60` | e2e seams (`EXPORT_NEXT_BIN`, the mode sidecar, `ARCHILYZER_BRANCH`), the moved labels' specs | +| `4813bafd` | e2e: publish.spec, publish-lane.spec, ops-api / jobs / duplicate-shorts | +| `6882b507` | e2e: alerts past Next's route announcer; the job page's first compile; the changelog fold | +| `5a77682e` | e2e: setup / teardown drop a test `export/out` link | +| `6a697701` | the review's fixes (below) | + +**Gates** (worktree root; logs `$T/s4-*.log`, `$T/s4f-*.log`): tsc clean at every commit; common **3394 passed**, +**3395** after the review round (9 tests went with the deleted build.ts code; new: settingsSig 2, `EXPORT_NEXT_BIN` +1, the source manifest refusal 1, the CLI flags 2); editor unit **142**; `test:scripts` **599 + 3 skipped**, **600 + 2 +skipped** after the round (the ops client's publish test new); mcp +**292**; export unit **116**; homepage unit **23**; `pnpm --filter editor exec next build` ok (47 s); `pnpm --filter +export exec next build` ok — first FAILED (`stat export/out` ENOENT: the e2e build stage's link left dangling by +resetData; fixed by `5a77682e`), then ok over the committed fixture compose linked into the worktree's +`export/public`; `pnpm --filter homepage run build:nodata` ok (15 s); umtool's capped build ok (19 s, link removed). +e2e (editor suite, `$T/s4-specs.txt`: publish, publish-lane, ops-api, build, deploy-page, site-scope, +site-publish-preview, sites-homepage, duplicate-shorts, sites-crud, digest, jobs, lane-runner): first run **104 +passed, 4 failed, 9.6 min** — two spec bugs (Next's route announcer is an alert too), `jobs.spec` "tails its log" +and `site-scope.spec` "charts is a site's tab" (both pass alone: **5 passed, 0 failed, 46 s**); after the fixes and the +review round **109 passed, 0 failed, 8.8 min**. The whole suite runs once at the end of the release (step 4). + +**Review** (`$T/s4-review.md`, a separate Opus agent, read-only): **SHIP AFTER FIXES**, nine findings, all fixed in +`6a697701`: + +| # | finding | fix | +|---|---|---| +| 1 | a console's Cancel cancelled only the run's first job — a no-op once it had ended, so a queued production deploy could not be stopped from the console | `cancelPublishRunAction`: every live stage with the console job's run id, newest first; every publish console uses it (`JobLane` takes `cancelAction`) | +| 2 | a part another run had queued (`existing`) could be the console's job, be cascaded, and was not waited on | `RunPart.existing`; never the console's job, never cascaded | +| 3 | publish-lane.spec's queue holder started its clock before the page compiled | taken after the page is up, 45 s | +| 4 | a preview with no branch name fell through to production | refused; `wantedPlan` throws on it | +| 5 | `POST publish {build, runner: docker}` skipped the stale index | the index update first, `indexAfter` on the containers' build | +| 6 | the hub/homepage verbs dropped a preview on a local deploy | refused with the deploy verb's sentence | +| 7 | `build-site` / `build-deploy` `all` lost the top-level `jobId` | the run's last job; comments and the changelog say so | +| 8 | `--deploy-only --force` dropped the force; `--deploy --deploy-only` passed | force reaches the deploy; the pair is a usage error | +| 9 | one target's throw was the whole request's 500 | that target's refusal | + +Cleanups taken: `fanOutSiteJobs` deleted; `followRun` refuses an empty run; `enqueueRun` releases its kept branches on a +throw; the plan's list keys carry the preview; saving the lane on starts its runner; the foreground/background window +noted in `publishStages.ts`. From the S6 drafts' readings, in the same commit: the charts page no longer points at +"Build stats dataset"; the source gate's refusal names `archilyzer publish homepage` (only a stage stamps what a +deploy ships); `build hub` stays a raw, unstamped build and says so (e2e:2origin's `build:hub`); three stale comments. + ## Rollout (Steps 1–7 above; "### As it went" is written as the rollout runs.)