Public: anyone can read this; the crew can change it.

Absorb: the menu entry's name and page follow the app's latest production commit, not the admin's act #9642

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A production push can no longer rename or repoint an absorbed app's menu entry; an admin has to accept the change first. `tsc`, `gate.ts`, `design-lint` and `test/features.test.ts` plus `test/apps-features.test.ts` pass (23 tests). I did not run the full suite or load the menu in a browser.

shipped production is serving it deploy 8c46a8d

opened this

A production push can no longer rename or repoint an absorbed app's menu entry; an admin has to accept the change first. tsc, gate.ts, design-lint and test/features.test.ts plus test/apps-features.test.ts pass (23 tests). I did not run the full suite or load the menu in a browser.

What changed

  • Absorbed values stay. featureVarsAt no longer rewrites the name and page on production starts. The menu keeps the ones the admin saw at absorb. In src/core/features.ts, refreshAbsorbed became keepDeclaration, which only the accept action calls.
  • Review row. When the production tip declares something different, FeatureView carries it as pending. The Features row shows "declaration changed: review", the name and page still in the menu, the new ones, and an Accept button.
  • Accepting. acceptDeclaration in src/actions/features.ts is for site admins only. It is wired as POST /settings/features/:slug/accept and appends app.feature_declared, with the previous values in its payload. The activity feed has a line for the event.
  • Menu grouping. Feature entries now sit under a "Features from apps" heading, after a separator. To draw it I added a non-interactive heading kind to MenuItem in src/web/ui.ts.
  • Reserved names. parseFeature refuses a name equal to a built-in menu label. The comparison is case-insensitive, folds Unicode compatibility forms (e.g. full-width letters) and ignores punctuation, so "Sign-Out" and "Settings" are refused. The label list in src/apps/feature.ts is hand-kept, so a new menu item needs a matching entry there.
  • Tests. The new test in test/features.test.ts pushes a rename and a new page to production. It checks the menu is unchanged, a non-admin is refused, and the menu changes after an admin accepts, with exactly one event. A second new test covers the heading and the refused names. The existing "preview doesn't rename" test in test/apps-features.test.ts expected production to rename; I changed it to expect "Radar" to stay after a production start, since that behaviour is what the issue removes. I also added a short section to docs/features/absorb.md.

Not done

  • Accepting is on the settings page only, with no API or MCP route.
  • A visually identical look-alike built from other scripts (Cyrillic "Ѕ" for "S", say) is not caught. The heading is what keeps such a name from passing for a forge item.

Closes #9541

commented

A production push can no longer rename or repoint an absorbed app's menu entry; an admin has to accept the change first. tsc, gate.ts, design-lint and test/features.test.ts plus test/apps-features.test.ts pass (23 tests). I did not run the full suite or load the menu in a browser.

What changed

  • Absorbed values stay. featureVarsAt no longer rewrites the name and page on production starts. The menu keeps the ones the admin saw at absorb. In src/core/features.ts, refreshAbsorbed became keepDeclaration, which only the accept action calls.
  • Review row. When the production tip declares something different, FeatureView carries it as pending. The Features row shows "declaration changed: review", the name and page still in the menu, the new ones, and an Accept button.
  • Accepting. acceptDeclaration in src/actions/features.ts is for site admins only. It is wired as POST /settings/features/:slug/accept and appends app.feature_declared, with the previous values in its payload. The activity feed has a line for the event.
  • Menu grouping. Feature entries now sit under a "Features from apps" heading, after a separator. To draw it I added a non-interactive heading kind to MenuItem in src/web/ui.ts.
  • Reserved names. parseFeature refuses a name equal to a built-in menu label. The comparison is case-insensitive, folds Unicode compatibility forms (e.g. full-width letters) and ignores punctuation, so "Sign-Out" and "Settings" are refused. The label list in src/apps/feature.ts is hand-kept, so a new menu item needs a matching entry there.
  • Tests. The new test in test/features.test.ts pushes a rename and a new page to production. It checks the menu is unchanged, a non-admin is refused, and the menu changes after an admin accepts, with exactly one event. A second new test covers the heading and the refused names. The existing "preview doesn't rename" test in test/apps-features.test.ts expected production to rename; I changed it to expect "Radar" to stay after a production start, since that behaviour is what the issue removes. I also added a short section to docs/features/absorb.md.

Not done

  • Accepting is on the settings page only, with no API or MCP route.
  • A visually identical look-alike built from other scripts (Cyrillic "Ѕ" for "S", say) is not caught. The heading is what keeps such a name from passing for a forge item.
commented

The check failed because no commit on the pull had a Try: line. I added an empty commit that has one and pushed it. The code is unchanged, so the earlier test results still stand. I haven't seen the forge's re-run of the check.

What changed

  • Pinned menu entry: A production push that changes an absorbed app's name or page no longer changes the user menu. The menu keeps the name and page the admin saw at absorb.
  • Review row: In Settings › Features the row shows "declaration changed: review", with the new values beside the current ones and an Accept button. Only a site admin can accept (POST /settings/features/:slug/accept), and accepting records an app.feature_declared event.
  • Menu grouping: Absorbed apps sit under a "Features from apps" heading in the user menu.
  • Refused names: A feature name equal to a built-in menu label is refused. The check ignores case and punctuation and folds full-width letters, so "Sign-Out" and "Settings" are refused.
  • Tests and docs: Two new tests in test/features.test.ts cover the pinned entry, accepting, and the heading and refused names. I changed one existing test in test/apps-features.test.ts, which expected production to rename the entry. I also added a short section to docs/features/absorb.md.

Not done

  • Accepting works from the settings page only, with no API or MCP route.
  • A look-alike built from letters of another script (a Cyrillic "Ѕ" for "S", say) is not refused. The heading is what keeps it from passing as a forge item.

To try it Sign in as a site admin and open /settings/features on the preview. Accept a row marked "declaration changed: review". The user menu lists absorbed apps under "Features from apps".

Changes requested — before the latest push

The pin itself is right: a production push no longer rewrites the menu entry, and accept is admin-only and emits app.feature_declared. Four things to fix in one round.

  1. (high, security) Accept takes whatever the tip declares at click time, not what the admin reviewed. If production changes again between the review and the click, the admin accepts values they never saw. The form must post the reviewed name and page, and acceptDeclaration must refuse (with a re-review flash) when they differ from the current declaration. Add a test for that race.
  2. (both doors) Accepting exists on the settings page only. There is no screen in src/tui/ with its entry where AGENTS.md says, so a terminal admin cannot clear a "declaration changed: review" row. Add the terminal accept, gated as the page is (admin only) and with a test. Add its how-to to docs/features/absorb.md.
  3. (clean) featureVarsAt keeps an unused _o parameter. Drop it and the production option from callers. The reason for the old option is gone.
  4. (box 2, drift) The built-in label list is hand-typed and nothing ties it to the real menus. A new menu item silently escapes the rule. Derive the list from the menu definitions, or add a test that fails when a menu label is missing from it.

Design: the Accept button is variant primary beside Un-absorb. Check design-lint and principle 5 (one accent per page) on a page with several pending rows. The pending meta prose repeats what the badge says (principle 1).

Boxes 1 and 3 are met in the code. Box 2 is left because of item 4.

commented

This round fixes all four of sentinel's items and adds the design changes they asked for. tsc, scripts/gate.ts, design-lint and test/features.test.ts plus test/apps-features.test.ts pass. I did not run the full suite or the tests beyond those two files. scripts/fast.ts said the diff reaches 1764 test files, which is too broad to run here, so the forge's check covers the rest.

What changed

  • Accept takes what the admin reviewed.

- The Accept form posts the reviewed name and page as hidden fields. - acceptDeclaration(app, actor, slug, reviewed) returns the new error declaration_changed when the current declaration differs. - The page then flashes "The app declared something else since you looked. Review the new declaration and accept again." - src/http/api/features.ts maps the new error to a 409. - A new test commits a second declaration between review and click, and checks the menu stays unchanged.

  • Terminal accept.

- New screen src/tui/screens/features.ts, palette entry "Menu entries to review", wired into ENTRIES in src/tui/screens/entries.ts. - It lists only the rows with a changed declaration. j/k move and a accepts the selected row, posting the values the screen showed. - It is offered only to a site admin who is a person, like the page. - A test covers the entry gate, the denied key for a non-admin, the stale refusal and a successful accept. - The how-to is in docs/features/absorb.md.

  • Dead parameter removed. featureVarsAt no longer takes _o, and the production option is gone from src/apps/runtime.ts and from the tests.
  • Built-in labels tied to the real menus.

- A new test reads every label: "…" in src/web/layout.ts and fails if parseFeature accepts any of them. - It found four missing labels: "Appearance and help", "New forge", "Create new" and "Features from apps". I added those, plus "Keyboard shortcuts", to the list in src/apps/feature.ts. - I chose a test over deriving the list because apps/ should not import web/.

  • Design.

- Accept is no longer primary, so a page with several pending rows has no extra accent beside Un-absorb. - The pending meta line now carries only the values, "Menu: A at /x → app: B at /y". The badge already says the declaration changed.

Not done

  • The label test reads the source text of layout.ts. A menu label built outside a label: "…" literal would not be caught.

To try Sign in as a site admin, open /settings/features on the preview and Accept a row marked "declaration changed: review". Or open the palette in the terminal and choose "Menu entries to review".

🤖 Generated with Claude Code

commented

The check failed because the pull adds src/tui/screens/features.ts and no commit message said why it goes ahead of the pipeline. I fixed that and pushed again. bun scripts/gate.ts now passes locally with no output. I did not re-run the rest of the check.

The fix is an empty commit, 02b57cf6, whose message carries this line:

Why now: sentinel's review of #9642 found a terminal admin cannot clear a changed-declaration row, so the pin needs its second door in the same pull.

I used a new commit because I can't force-push to rewrite the earlier message. No code changed. The last push's changes are the same as the earlier description:

  • Accept: it posts the reviewed name and page, and acceptDeclaration refuses with declaration_changed when they differ from the current declaration.
  • Terminal: the new "Menu entries to review" screen accepts under the same admin-only rule as the page.
  • Cleanup: the dead _o parameter and the production option are removed.
  • Menu labels: the built-in label list is checked by a test against the labels in src/web/layout.ts.

To try it, sign in as a site admin, open /settings/features on the preview and Accept a row marked "declaration changed: review". In the terminal, choose "Menu entries to review" from the palette.

🤖 Generated with Claude Code

commented

I merged main into the branch, resolved the two conflicts and pushed the merge commit c695b39b. bun x tsc --noEmit and bun scripts/gate.ts are clean. test/features.test.ts and test/apps-features.test.ts pass; I did not run any other tests. The forge runs the full check on this push.

  • src/web/layout.ts: I took main's version, where absorbedForNav hands each feature its app record, so the menu no longer looks it up with getAppById. The "Features from apps" heading stays.
  • test/features.test.ts: I kept both import lists, so acceptDeclaration (this pull) and declaredFeature (main) are both imported.

The pull's content is otherwise unchanged. The menu entry's name and page stay pinned until an admin accepts a changed declaration. Accept posts the reviewed name and page, and the terminal screen "Menu entries to review" accepts under the same admin-only rule.

To try it, sign in as a site admin, open /settings/features on the preview and Accept a row marked "declaration changed: review".

🤖 Generated with Claude Code

commented

I fixed the one failing check test, "a site-admin person is offered the whole roster", and pushed the fix.

This pull adds a command palette entry, "Menu entries to review" (feature-review), for site admins. test/palette-roster.test.ts still listed the old roster, so it failed. I added the new entry to the admin roster, just before "front", where the palette puts it. The test was out of date; the code is unchanged.

bun test test/palette-roster.test.ts passes (15 tests). I didn't re-run the gate, the type check or the full suite; the forge runs the full check on this push.

Approved

Earlier asks are met: Accept posts the reviewed name and page and is refused as declaration_changed when the app declares something else by now. The terminal has its own door, the "Menu entries to review" screen, admin-only and tested. The dead parameter is gone. A test ties the built-in label list to the labels in layout.ts. Accept is no longer primary. The preview answers 200. Non-blocking: the label test reads layout.ts source text, so a label built outside a label: "…" literal would not be caught.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs