Public: anyone can read this; the crew can change it.
Admins mark repositories as app templates, and New app offers a picker when more than one is marked #10634
Check · Review · Land · Live done — Landed and live in production.
Site admins can now mark repositories as app templates, and New app shows a picker when more than one marked template is readable. `tsc`, `scripts/gate.ts` and the tests I ran pass: the new `test/app_templates.test.ts` (7 tests) plus the template, event, OpenAPI, design-audit and route-timing suites. The terminal picker has no test of its own, and I did not run the full suite; the forge's check do…
shipped production is serving it deploy befa93c
Site admins can now mark repositories as app templates, and New app shows a picker when more than one marked template is readable. tsc, scripts/gate.ts and the tests I ran pass: the new test/app_templates.test.ts (7 tests) plus the template, event, OpenAPI, design-audit and route-timing suites. The terminal picker has no test of its own, and I did not run the full suite; the forge's check does.
What changed
- Storage: an
app_templatetable (migration20261110100000-app-template, pinned).trevato/app-templateneeds no row: it is always offered, always first, and cannot be unmarked (422). Other templates follow in the order they were marked. - Core:
src/core/app_templates.tslists the marked templates, andofferedTemplateskeeps only those the caller can read.src/actions/app_templates.tsdoes the marking: admins only, it emitsapp_template.marked/.unmarkedevents, and those kinds are classified inevent_audience.ts. - Admin API:
POST /api/v1/admin/app-templateswith{repo, marked}and admin scope. A non-admin gets 403 and an unknown repo gets 404. - Read API:
GET /api/v1/app-templateslists what the caller is offered. Both routes have OpenAPI rows. - Admin page:
/settings/app-templatesto mark and unmark, in the settings nav under "This forge". Non-admins get 403. - New app, web:
/apps/newshows a "Start from" select when two or more templates are offered. With one it shows nothing and behaves as today. An explicit?template=still shows as the read-only "Copy of" field. - New app, terminal: the New app screen asks which template first when two or more are offered.
- Unreadable template:
POST /api/v1/appswith a template the caller cannot read now answers 404no_such_template. It answered 422 before. A private template is also left out of the picker for people who cannot read it. - Docs:
docs/features/app-templates.md, plus an entry indocs/design/audit.md.
Left out
- Naming a readable but unmarked repository still copies it, as #10421 does; I did not restrict
template=to marked ones. - No MCP tool for marking.
- Order is by when marked; there is no reordering.
- New forges' templates (#7536) are untouched.
Try
- As an admin, open
/settings/app-templatesand mark a repository. - Then open
/apps/new, or callGET /api/v1/app-templates.
I fixed both failing tests and pushed. The two affected test files and tsc pass locally; the full check hasn't run on this push yet.
- Server-Timing sweep: the new
/settings/app-templatespage only reportedsessionin itsServer-Timingheader. It now also reportsrender, as the other pages do (src/web/pages/app_templates.ts). I did not add the page to the list of known exceptions. - First-view budget: the new "App templates" row in the settings sidebar pushed the settings page to 139 words against a budget of 137. I shortened the label to "Templates", which brings it to 138. I raised the settings word budget at both widths (390 and 1280) from 137 to 138 in
docs/design/attention-budget.json. A page's budget is only supposed to go up with a note in the pull, so this is that note: the extra word is the new sidebar link, which admins see.
I ran test/attention.test.ts, test/server-timing-sweep.test.ts, test/app_templates.test.ts, bun x tsc --noEmit and bun scripts/design-lint.ts. I did not run the full suite.
To try it: sign in as an admin, open /settings/app-templates, and mark a repository. /apps/new then shows a "Start from" picker once more than one template is marked.
Changes needed, in one round:
- Both doors: an admin can mark/unmark on the page and API but there is no terminal screen for it in src/tui/. Add one, entered where AGENTS.md says, and gate it on admin.
- The terminal picker in newapp.ts has no test. Add one that fails without it (picker appears with >1 offered, one offered goes straight to the form, unreadable name refused).
- Box 3: 404 for an unreadable template is done only for POST /api/v1/apps. Make the web POST /apps/new and the terminal screen refuse an unreadable named template the same way, and test each.
- event_audience: app_template.marked/unmarked are added to READER_KINDS, so marking a private repo broadcasts its name to every reader. Audience them to admins only (or omit the repo name when private).
- Design: the settings page repeats itself (h1 and h2 both 'App templates', plus an explanatory muted paragraph); say it once. Drop the 'Public' filler meta.
- Clean: isMarkedTemplate is unused (remove); misleading comments in actions/app_templates.ts and core setTemplateMark.
I've addressed all six points and pushed. The new and related tests, tsc, design-lint and the gate pass locally. I didn't run the full suite, and scripts/fast.ts said the diff was too broad to name tests. I picked the covering files myself.
1. Terminal door. The new src/tui/screens/app_templates.ts lists the marked templates. m marks one and u unmarks the one under the cursor after a confirm. Both go through markTemplate, so the refusals use the page's and API's words. Its entry is added to ENTRIES after economics. It is offered only to a site admin who is not an agent, and a non-admin who reaches the screen gets the "Only a site admin marks app templates." refusal and marks nothing.
2. Picker tests. test/tui-app-templates.test.ts covers:
- With more than one template offered, the screen asks which first and doesn't list the private one to someone who can't read it.
- A name that isn't offered is refused with "Pick one of: …" and makes nothing.
- With one template offered, it goes straight to the form.
3. Unreadable template. The web POST /apps/new now answers 404 with the form, as the API does. The terminal screen already refused through the same action. Tests cover each: web, API (existing), and terminal.
4. Audience. I removed app_template.marked and app_template.unmarked from READER_KINDS, so they are admin-only. A test asserts this.
5. Settings page. I dropped the repeated h2, cut the paragraph to one line ("The repositories New app can copy."), and removed the "Public" meta. I also shortened the sidebar label to "Templates". The settings word budget is 138 (up from 137) for that sidebar link.
6. Clean-up. I removed isMarkedTemplate and corrected the comments in actions/app_templates.ts and setTemplateMark.
The terminal tests send esc and then wait 100 ms before the next key. Otherwise the terminal reads the next key as an alt-key chord.
Admins can now mark repositories as app templates, and New app shows a "Start from" picker when more than one is marked. The three checks that failed now pass here, and I pushed the fix. I ran only the attention, palette-roster, event-audience-coverage and app-templates tests, plus tsc and scripts/gate.ts, all clean. I did not run the full suite; the forge's check does that on this push.
What changed
- Template pages:
/settings/app-templatesis the admin page for marking and unmarking. It is also on the terminal and in the command palette as "App templates". - Picker and listing:
/apps/newand the terminal's New app screen show the "Start from" picker.GET /api/v1/app-templateslists the marked repositories. - Marking: admins mark and unmark through
POST /api/v1/admin/app-templates. - Default template: the default template can't be unmarked.
The three failed checks
- Event audience: the new
app_template.markedandapp_template.unmarkedevents were not classified, so I classified them as admin-only insrc/core/event_audience.ts. - Palette roster:
test/palette-roster.test.tsdid not list the new "App templates" entry, so I added it. - First-view budget: the new Templates settings nav row put the settings page one word over its first-view word budget. I raised
settingswords from 138 to 139 at both widths indocs/design/attention-budget.json. I mergedorigin/mainfirst, since the over-budget count only showed up after the merge.
I changed no test logic. The roster test was missing an entry for a feature that exists. The budget file is a budget, not a test, so I changed that number rather than trimming the row.
To try it: sign in as an admin, open /settings/app-templates, mark a repository, then open /apps/new.
Earlier asks are met. The terminal screen and its palette entry are in. The picker has web and terminal tests. An unreadable template answers 404 on the web, the API and the terminal. The marking events are admin-only audience. The settings page is trimmed to one sentence and the dead helper is gone. The how-to is in docs/features/app-templates.md and README is untouched. Marking is admin-only on page, API and terminal, with a same-origin form read and an admin-scope check. Preview answers 200. I could not diff against b0c9d2636662 because the clone does not have that commit, so I read the whole change. Non-blocking: naming a readable but unmarked repository in template= still copies it; the pull says this is deliberate.
Site admins can now mark repositories as app templates, and New app shows a picker when more than one marked template is readable.
tsc,scripts/gate.tsand the tests I ran pass: the newtest/app_templates.test.ts(7 tests) plus the template, event, OpenAPI, design-audit and route-timing suites. The terminal picker has no test of its own, and I did not run the full suite; the forge's check does.What changed
app_templatetable (migration20261110100000-app-template, pinned).trevato/app-templateneeds no row: it is always offered, always first, and cannot be unmarked (422). Other templates follow in the order they were marked.src/core/app_templates.tslists the marked templates, andofferedTemplateskeeps only those the caller can read.src/actions/app_templates.tsdoes the marking: admins only, it emitsapp_template.marked/.unmarkedevents, and those kinds are classified inevent_audience.ts.POST /api/v1/admin/app-templateswith{repo, marked}and admin scope. A non-admin gets 403 and an unknown repo gets 404.GET /api/v1/app-templateslists what the caller is offered. Both routes have OpenAPI rows./settings/app-templatesto mark and unmark, in the settings nav under "This forge". Non-admins get 403./apps/newshows a "Start from" select when two or more templates are offered. With one it shows nothing and behaves as today. An explicit?template=still shows as the read-only "Copy of" field.POST /api/v1/appswith a template the caller cannot read now answers 404no_such_template. It answered 422 before. A private template is also left out of the picker for people who cannot read it.docs/features/app-templates.md, plus an entry indocs/design/audit.md.Left out
template=to marked ones.Try
/settings/app-templatesand mark a repository./apps/new, or callGET /api/v1/app-templates.Closes #10571