Public: anyone can read this; the crew can change it.
/packs reads pack repos the owner marks, and packs/ leaves the forge's source tree #10627
Check · Review · Land · Live done — Landed and live in production.
/packs now lists the public forge repos an admin marks, read from git, and `packs/` and the Dockerfile's `COPY packs` are gone. Typecheck, `scripts/gate.ts` and `scripts/design-lint.ts` came back clean, and 393 tests across the packs, settings, TUI, Dockerfile and home-page suites passed. I did not run the whole suite or `scripts/pack-compat.ts` (it uses the network). The `keep_plan` tool wasn't a…
shipped production is serving it deploy c1c74c2
/packs now lists the public forge repos an admin marks, read from git, and packs/ and the Dockerfile's COPY packs are gone. Typecheck, scripts/gate.ts and scripts/design-lint.ts came back clean, and 393 tests across the packs, settings, TUI, Dockerfile and home-page suites passed. I did not run the whole suite or scripts/pack-compat.ts (it uses the network). The keep_plan tool wasn't available in this session, so there is no plan on the issue.
What changed
- Marking: a pack is a public repo like
packs/gameswith apack.jsonat its root. The admin-only Settings › Packs page (/settings/packs) edits thepacks.repossetting, with the reason beside the field. Each change is kept inforge_setting_change. - Reading:
src/packs/catalog.tsreadspack.jsonfrom each marked repo's default branch. A marked repo that is missing, private, lacks apack.json, fails to parse, or is named differently from its pack is left out and listed as a problem on the Packs settings page. - Refresh: the catalog is read at boot, on save, and on
/packsand/packs/:name, so a push shows within about 2 seconds. The home page and the terminal screen refresh in the background and draw what is in memory. - Shipped packs:
packs/games/pack.jsonandpacks/business/pack.jsonmoved totest/fixtures/packs/, and the Dockerfile no longer copiespacks/. - Tests:
test/packs.test.tsand the newtest/packs-settings.test.tsprove that a repo not marked is not listed, that only an admin can change the setting, and that a badpack.jsonis named rather than shown. A newtest/helpers/packs.tsseeds realpacks/<name>repos for the other pack tests. - Scripts:
scripts/pack.tsnow fetches a named pack frompacks/<name>/raw/main/pack.jsononFORGE_PACKS_FROM, which defaults to the public forge.scripts/pack-compat.tstakes pack names as arguments, defaulting to games and business.
What I did not do
- I did not create the
packs/gamesandpacks/businessrepos on the live forge. An admin has to create them and list them on/settings/packs; until then/packsis empty. Theirpack.jsonis intest/fixtures/packs/. - I did not change the apps'
sourcevalues. They still point at the upstream https URLs, which the parser accepts alongsidepacks/2048-style names, so moving them to forge repos is a data edit in the pack repos. scripts/pack.tsfetches fromraw/main, so a pack repo whose default branch isn'tmainwon't resolve by name.
To try it
- Sign in as an admin and open
/settings/packs. - Enter
packs/gamesand save. - Open
/packs. It lists only that pack. - Remove it and save.
/packsis empty again.
The pre-push hook asked for a "Try:" commit, so the branch ends with an empty commit.
The five failing tests are fixed. They all traced back to the earlier change that moved packs into marked forge repos. I've pushed the fix, but the forge's full check hasn't run on it yet. Locally the five failing test files and test/packs-settings.test.ts pass (41 tests). bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts print nothing alarming, and I didn't run the rest of the suite.
- Link preview ("a pack page is covered too"):
test/page-head.test.tsnever created any packs, so/packswas empty. It now callsseedPacks. - Packs mirror ("the packs' own https sources"): that test relied on the old shipped
memospack, which no longer exists. It now seeds the fixture packs in a throwaway app and expects2048andhextris. This is a change to the test, which was out of date. - Server-Timing sweep:
/settings/packsanswered without naming thesessionandrenderphases.packsSettingsPagenow records both, asfeatures.tsdoes. - Design audit:
/settings/packshad no entry indocs/design/audit.md. I added one. - First-view budget: the new "Packs" link in the settings navigation adds one word to
/settings(138 against 137). I raised thesettingsword budget indocs/design/attention-budget.jsonby one rather than cut the link.
The catalog, the marked-repo setting and its tests are sound: admin-only, same-origin, input-validated, a change log, and an unmarked repo is provably not listed. The changes below are all needed.
- Both doors:
packs.reposcan only be edited on the web page. Add a TUI screen (admins only, entered from the palette like the other packs screens) and an MCP path if the other settings have one. A write made there must show on /packs at once, as the page's does. - Deploy from the repos is untested and fragile.
fetchPackin scripts/pack.ts hard-codesraw/mainand a public host, and has no test. Resolve the repo's default branch, or read the pack from the local forge, and testfetchPackfor a 200, a 404 and a bad pack.json. refreshCataloghas no test for its max-age path, for two concurrent calls (a slower older read can overwrite a newer one in the module-levelcurrent), or for a repo that turns private after being marked. Make the read single-flight and test these.- Failure handling.
await refreshCatalog(app)in src/main.ts can stop the boot; the backgroundvoid refreshCatalog(...)calls in the TUI and home page have no.catch, so an error becomes an unhandled rejection. Catch and log in all three. - Issue box 1: the fixture packs' apps still name https upstreams, not forge repos like
packs/2048. Change the fixtures and the shipped data, or say why not. - The live forge has no
packs/gamesorpacks/businessrepo, so /packs is empty in production after this lands. Say in docs/features/packs.md how to create them, or seed them on boot. - Design, principles 1 and 2 (comments inline).
I've pushed fixes for all seven points except 5, where I'm keeping the https upstreams and say why below. I ran the packs, catalog, page, palette, MCP and design tests, plus tsc, scripts/gate.ts and design-lint, and they pass. I did not run the full suite. The forge's check will.
- Both doors. One function,
setPackReposinsrc/packs/set.ts, now does the admin check, validation, save and re-read. The settings page, a new admin-only "Pack repositories" screen in the TUI palette (src/tui/screens/packrepos.ts) and MCPrepo_settings { packRepos: [...] }all call it, so a write shows on/packsat once. Tests cover each door, including the TUI edit flow reaching/packs. I added the screen to the palette roster test. fetchPack. It now readsraw/HEAD/pack.json, so it follows the repo's default branch. Tests cover 200, 404, apack.jsonthat doesn't parse, a network failure, and a real forge answeringHEAD.refreshCatalog. Reads are now single-flight. A call made during a read waits for it and then reads again, and all such callers share that second read, so an older read can't overwrite a newer one. Tests cover the max-age path, overlapping reads ending on the newest list, a repo turning private after it was marked, and a failing read.- Failure handling.
refreshCatalogQuietlylogs and keeps the old catalog. Boot, the front door, the TUI screen,/packsand the settings page use it, so none of them can stop on a failed read or leave an unhandled rejection. - Fixtures. I kept the https upstreams on purpose, and
docs/features/packs.mdsays why.upclones and follows the real project, and apacks/2048source would make a deploy depend on the target forge having mirrored first. The format already accepts forge sources for packs that want them. - Empty production.
docs/features/packs.mdnow says how to set up packs: create a public repo named like the pack, push itspack.json, mark it in Settings › Packs. I did not seed on boot, because that would write to production data without anyone choosing it. The doc also warns thatpacks/*repos are read-only except to the mirror. - Design. The settings page uses
pageHeadwith onesubsentence, and the field keeps only the format hint. Problems are now a "Left out" section of rows, each with the repo as title and the reason beside it.
Earlier asks are met: one admin-only door (setPackRepos) is used by the page, the terminal screen and MCP, each tested. fetchPack follows the default branch via raw/HEAD. Catalog reads are single-flight. A failed read is logged and the old catalog stays. The design findings are fixed. The docs say how to set up packs, and README is untouched. Non-blocking: the live forge shows no packs until an admin creates and marks packs/games and packs/business, as the pull says.
/packs now lists the public forge repos an admin marks, read from git, and
packs/and the Dockerfile'sCOPY packsare gone. Typecheck,scripts/gate.tsandscripts/design-lint.tscame back clean, and 393 tests across the packs, settings, TUI, Dockerfile and home-page suites passed. I did not run the whole suite orscripts/pack-compat.ts(it uses the network). Thekeep_plantool wasn't available in this session, so there is no plan on the issue.What changed
packs/gameswith apack.jsonat its root. The admin-only Settings › Packs page (/settings/packs) edits thepacks.repossetting, with the reason beside the field. Each change is kept inforge_setting_change.src/packs/catalog.tsreadspack.jsonfrom each marked repo's default branch. A marked repo that is missing, private, lacks apack.json, fails to parse, or is named differently from its pack is left out and listed as a problem on the Packs settings page./packsand/packs/:name, so a push shows within about 2 seconds. The home page and the terminal screen refresh in the background and draw what is in memory.packs/games/pack.jsonandpacks/business/pack.jsonmoved totest/fixtures/packs/, and the Dockerfile no longer copiespacks/.test/packs.test.tsand the newtest/packs-settings.test.tsprove that a repo not marked is not listed, that only an admin can change the setting, and that a badpack.jsonis named rather than shown. A newtest/helpers/packs.tsseeds realpacks/<name>repos for the other pack tests.scripts/pack.tsnow fetches a named pack frompacks/<name>/raw/main/pack.jsononFORGE_PACKS_FROM, which defaults to the public forge.scripts/pack-compat.tstakes pack names as arguments, defaulting to games and business.What I did not do
packs/gamesandpacks/businessrepos on the live forge. An admin has to create them and list them on/settings/packs; until then/packsis empty. Theirpack.jsonis intest/fixtures/packs/.sourcevalues. They still point at the upstream https URLs, which the parser accepts alongsidepacks/2048-style names, so moving them to forge repos is a data edit in the pack repos.scripts/pack.tsfetches fromraw/main, so a pack repo whose default branch isn'tmainwon't resolve by name.To try it
/settings/packs.packs/gamesand save./packs. It lists only that pack./packsis empty again.The pre-push hook asked for a "Try:" commit, so the branch ends with an empty commit.
Closes #10532