Repository navigation
fix(spec): re-point the app liveness ledger off objectui's removed AppSidebar; branding.logo is planned - #20302
Conversation
…pSidebar Twelve AppSchema rows in packages/spec/liveness/app.json, and the file _note, cited objectui's AppSidebar.tsx as read evidence. AppSidebar was deprecated, never mounted (the console renders UnifiedSidebar), and has been removed from objectui main, so it was never evidence of a live reader. Each row now cites the mounted reader, file and symbol, at the .objectui-sha pin f8a9d0fb (every one also reads its key unchanged at objectui main fb91ac9b0). branding is drilled: logo is planned with carrier objectui#10827 (the designer writes it, nothing mounted renders it), while primaryColor, accentColor and favicon stay live through ConsoleLayout into AppShell. The app/branding row leaves undrilled-containers.baseline.json with the drill. The two AppSidebar prose mentions in app.zod.ts name the live readers instead. No schema, parse or accept-set change. Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
…nding row app: 56 -> 59 classified (live 47 -> 49, planned 0 -> 1). Written by gen:liveness-counts; check:liveness reads it back as current. Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
liveness/ and src/**/*.zod.ts both ship in the spec tarball, and @objectstack/lint reads the shipped ledgers, so this ledger and prose change publishes. Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewServed-tier: ① Derived judgmentsReviewed at head 1. Every re-pointed row names a real, MOUNTED reader at Preconditions measured: Mount chain, measured at the pin: Per row, the cited symbol and the read, each confirmed at the pin and identical at
One non-blocking nuance: Gate-side: 2. Census at the pin (control: the same grep for Status under the README: 3. No row lost a reader; no row became Structural walk of
Baseline: 4.
5. Semver / changeset — RIGHT. See ②. 6. Top-level app
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Blocking items: none. Generated by Claude Code |
Fixes #20146
Clause-②: no
Twelve
AppSchemarows inpackages/spec/liveness/app.json, and the file_note, cited objectui'sAppSidebar.tsxas read evidence.AppSidebarwas deprecated (objectui#5720), never mounted (the console rendersUnifiedSidebar), and objectui PR #10617 (objectui#5817) has removed it from objectui main. So it was never evidence of a live reader. This PR re-points every one of those rows to the reader that is actually mounted, file and symbol, with no line numbers. It movesbranding.logotoplannedwith carrier objectui#10827, and fixes the twoAppSidebarprose mentions inpackages/spec/src/ui/app.zod.ts.Ledger evidence and comment prose only. ⛔ No schema, parse or accept-set change; no
.describe()and no refusal text changed.Which sha the rows cite, and why
All re-pointed readers are cited as
objectui @f8a9d0fb, the.objectui-shapin this repo builds against. That is the convention thenavigation.runActionrow states ("read at the pin this repo builds against, not at objectui main").AppSidebar.tsx:packages/app-shell/src/layout/AppSidebar.tsxis present atf8a9d0fband absent at objectui mainfb91ac9b0. At the pin its only references are the two barrel exports (packages/app-shell/src/index.tsandpackages/app-shell/src/layout/index.ts, both marked deprecated), tests and comments. Its one JSX occurrence outside tests is a usage example inside a doc comment (providers/ExpressionProvider.tsx). There is no mount site. That is why the old pointers kept resolving while proving nothing.fb91ac9b0(after the removal), answers identically. So the next pin bump does not strand these pointers.check:livenessattributes a path after anobjectuirealm marker to the other repo. It counts that path and never resolves it (packages/spec/scripts/liveness/evidence.mts#scanEvidence). A realm marker only reaches the next;or closing paren, so every clause carries its ownobjectui @f8a9d0fb:marker. The gate's foreign-pointer count moves 202 → 218, and its local-path (614) and symbol-anchor (758) counts are unchanged.Row → reader, at objectui
@f8a9d0fbfb91ac9b0labelpackages/app-shell/src/layout/AppSwitcher.tsx·AppSwitcher;.../layout/ConsoleLayout.tsx·ConsoleLayout;.../console/home/HomeAppsStrip.tsx·HomeAppsStripresolveKeyedI18nLabel(activeApp.label, t)on the pill andapp.labelper row;appLabelintoAppHeaderplus theAppShellbranding title; launcher tilesdescriptionapps/console/src/pages/system/AppManagementPage.tsx·AppManagementPage(routed atsystem/apps)resolveKeyedI18nLabel(app.description, t);filteredAppssearches iticonAppSwitcher.tsx·AppSwitcher;HomeAppsStrip.tsx·HomeAppsStripgetIcon(app.icon)per row / per tilebranding.primaryColorConsoleLayout.tsx·ConsoleLayoutintopackages/layout/src/AppShell.tsx·useAppShellBrandingAppShell'sbranding; mapped onto--primary/--ring/--sidebar-primarybranding.accentColor--accentbranding.faviconbranding.logoplannedactiveAppSwitcher;.../layout/UnifiedSidebar.tsx·UnifiedSidebar;.../utils/appRoute.ts·filterActiveApps;.../layout/AppHeader.tsx·AppHeadera.active !== falsein the switcher list, the sidebar's list and active-app lookup, the openable-apps predicate (called byHomePage,app-launcher-renderer,resolveHostAppSegment), and the avatar menuhiddenAppSwitcher;UnifiedSidebar;filterActiveApps;AppHeadera.hidden !== trueout of the lists; the avatar menu listsa.hidden === trueapps (Account excepted); the in-repoACCOUNT_APPleg is unchangednavigation[].idUnifiedSidebar.tsx·useNavOrderandUnifiedSidebar(useNavPins);packages/layout/src/NavigationRenderer.tsx·NavigationRenderer;.../views/nav-menu-renderer.tsx·NavMenuRenderernavigation[].visibleUnifiedSidebar·evalVis;NavMenuRenderer·evalVisevaluateVisibility, intoNavigationRendererandhasVisibleNavigationItems; per-row skip when false (theNavigationRendereritem-gate leg is unchanged)areas[].idUnifiedSidebaractiveAreaIdstate and the lookup bya.id; each area row's key and clickareas[].labelUnifiedSidebarresolveInlineI18nLabel(area.label, language)areas[].iconUnifiedSidebargetIcon(area.icon)areas[].navigationUnifiedSidebar(theAppSchemaRendererleg is unchanged)navigationreplaces the app's top-level tree_noteMount sites, measured at the pin:
UnifiedSidebarinConsoleLayout;AppSwitcherinAppHeader;HomeAppsStripinHomePageandapp-launcher-renderer;nav-menu-rendererregistered by import inpackages/app-shell/src/index.ts;AppManagementPageon the console routesystem/apps.description: stilllive, narrowerThe one mounted reader is the admin app list (
/system/apps). The card asked whetherAppManagementPagestill makes the rowlive, and it does. Itsappscome fromuseMetadata(), the app metadata list, and the page is routed. The "under the active app's title" presentation has no successor. Not counted:packages/app-shell/src/console/home/AppCard.tsxalso readsapp.description, but at the pin it is only barrel-exported (no mount site), which is the same trapAppSidebarwas.branding.logo→planned(carrier objectui#10827)brandingis now drilled, sologogets its own verdict whileprimaryColor,accentColorandfaviconkeeplive. Census at the pin, with the same grep run forbranding.primaryColoras the positive control (it hitsConsoleLayoutandAppShell):producer):packages/plugin-designer/src/AppCreationWizard.tsx·AppCreationWizardandBrandingEditor.tsx·BrandingEditorwrite it with a preview;pages/EditAppPage.tsx·EditAppPagepre-fills from it.branding.logoin app-shell or layout at the pin isAppSidebar(unmounted). At main there is none.ConsoleLayouthandsAppShellprimaryColor/accentColor/faviconand neverlogo, andAppShellBrandinghas no logo field.EmbeddableForm'sconfig.branding.logois a form embed config, andruntime-config.ts'sbranding.logoUrlis console runtime config; neither is the app key.packages/types/src/app.ts·wizardDraftToAppSchemacopiesdraft.branding.logointo a top-levellogo, andpackages/layout/src/AppSchemaRenderer.tsx·InternalSidebarrenders that top-levelschema.logo.authorWarn: not set, following the ledger's rules. README "Author warnings" rule 1 bars marking benign display metadata (its examples:description,tags,icon). Thefield.useGroupingprecedent is aplannedrow whose consumer is being built: "an author who sets this today loses nothing and is not misled". A logo URL is that case, because it takes effect when objectui#10827 lands, with no re-authoring.@objectstack/lintwarns on aplannedrow only with an explicitauthorWarn: true(packages/lint/src/lint-liveness-properties.ts#shouldWarn), so author-facing lint output does not change.app/brandingfrompackages/spec/scripts/liveness/undrilled-containers.baseline.json, which is shrink-only; the gate fails on a row whose container now drills. Container debt goes from 110 containers / 569 keys to 109 / 565.Spelling (Zone 1 measured)
AppSchemadeclares onlybranding.logo. A top-levellogoon an app is refused, not folded:The alias table (
logo: 'branding') feeds the refusal's "did you mean". It does not rewrite the key (packages/spec/src/shared/strict-object.tsheader), and no ADR-0087 conversion carrieslogo. So the conclusion holds (one declared spelling, nothing to retire), and the word "folds" in the dispatch and in objectui#10827's spelling paragraph is imprecise.One note corrected while re-reading
The
activenote said the sidebar's active-app lookup "spans all apps so a direct /apps/NAME URL keeps rendering". At the pin, that comment is about hidden apps. The sidebar lookup filtersactive !== false. What keeps an inactive app routable is app-shellAppContent, which resolvesmatchAppBySegment(apps, appName)over all apps. The note now says so. The verdict (live, and routing not disabled) is unchanged.Prose in
packages/spec/src/ui/app.zod.ts(comments only)Measured at the pin:
NavigationRenderer.tsxapplyNavTemplateinsideresolveHref, fedtemplateContextbyUnifiedSidebar;AppContent's landing route goesfindFirstRoute→buildItemRoute→resolveHrefwith the session context.Measured at the pin and at main:
UnifiedSidebarfilters then mapsareaswith no sort, andAppSchemaRenderermapsareasas authored.Pins
No test pins these rows' evidence text or the
_note.git grep AppSidebaroutsideapp.jsonhits only the two comments above, plus CHANGELOG and audit history, which are untouched. The tests that read the real ledger (packages/spec/scripts/liveness/check-liveness.test.ts, which copies it and mutatesnavigation.children.children) and the consumer suite (packages/lint/src/lint-liveness-properties.test.ts) were run; the readings are below. No pin flips.Changeset
.changeset/20146-app-ledger-repoint.md,@objectstack/spec: patch.liveness/andsrc/**/*.zod.tsare both inpackages/spec/package.jsonfiles[], and@objectstack/lintreads the shipped ledgers at runtime, so this diff publishes. That is why this PR carries noskip-changeset.Verification (at HEAD
8a4cb2f0)Every reading below was taken at
8a4cb2f0(this branch's head).packages/specdistwas rebuilt from that source before any gate that reads it.check:liveness, before and after. At base1207baf0: exit 0,app 56 classified (live 47, dead 9). At8a4cb2f0: exit 0,app 59 classified (live 49, dead 9, planned 1), with this verdict line:✓ packages/spec/liveness/state-counts.md is current. Also at8a4cb2f0:evidence paths: 614 repo-local … 614 resolved …; 218 attributed to another repo(base: 202);symbol anchors: 758 … 758 naming a symbol the cited file contains;container coverage: 109 container entr(ies) … 565 child key(s)(base: 110 / 569).state-counts.mdwas regenerated withgen:liveness-counts, and the gate reads it back as current.localproject. Run through the verify lock:pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2, exit 0,Test Files 552 passed (552),Tests 16251 passed | 1 todo (16252). This includesscripts/liveness/check-liveness.test.ts, which copies and mutates the real ledger.pnpm --filter @objectstack/spec run typecheck(tsc, thencheck:scripts-typecheck, thencheck:test-typecheck): exit 0.@objectstack/lintreads the shipped ledgers).pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/lint-liveness-properties.test.ts src/component-field-specs-liveness.test.ts: exit 0,Test Files 2 passed (2),Tests 91 passed (91).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived from the actual 5 changed paths: 81 commands. 80 exit 0. Reconciled with--ran:✓ dispatch-gates --ran: 81 derived famil(ies) accounted for — 80 run, 1 NOT-MEASURED, exit 0.pnpm check:dual-build-cjs-loads. Reason: exit 3,PREREQUISITE NOT MET — this gate reads built output. It needs every one of the workspace's 82 packages built, and this diff changes no emitted code (JSON data, one comment, a baseline row, a markdown table). This is a declared narrowing; CI builds the workspace and runs it.check:lean-entry-closureexited 3 on its first run, before@objectstack/objectqlwas built. After that build it exits 0:2 published condition(s) measured from a real load.files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']ineslint.config.mjs; the other four are JSON or markdown).pnpm exec eslint --no-inline-config --format json packages/spec/src/ui/app.zod.ts: exit 0, 1 file, 0 errors, 0 warnings. Invariance: the config never enables type-aware linting (noparserOptions.project, no typed rule sets), so this diff cannot move the verdict on any untouched file.Acceptance notes
@940ba24with line numbers. They are theNavigationRenderer/ContextSelectors/RootLandingRedirect/surfaceAgentlegs, which never namedAppSidebar. They were left as they are.rest-server.tspointers insidenoteprose remain on theareas.navigation(:1870) andnavigation.children(:1837) rows. The file_notealready records that the gate now lives inmeta-item-read-gate.ts#filterAppForUserWithReason.noteis not scanned by any gate. Left alone, because it is outside this card.Deviations from the claim's file surface
packages/spec/scripts/liveness/undrilled-containers.baseline.json: one row deleted (app/branding). The orderedbranding.logo→plannedrequires drillingbranding, andcheck:livenessfails on a baseline row whose container drills. This is a shrink of a shrink-only ratchet..changeset/20146-app-ledger-repoint.md: added, because the diff publishes (see Changeset).Generated by Claude Code