Repository navigation
Commit 6ac33a5
fix(spec): re-point the app liveness ledger off objectui's removed AppSidebar; branding.logo is planned (#20302)
Fixes #20146
Clause-②: no
Twelve `AppSchema` rows in `packages/spec/liveness/app.json`, and the
file `_note`, cited objectui's `AppSidebar.tsx` as read evidence.
`AppSidebar` was deprecated (objectui#5720), never mounted (the console
renders `UnifiedSidebar`), 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 moves
`branding.logo` to `planned` with carrier objectui#10827, and fixes the
two `AppSidebar` prose mentions in `packages/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-sha` pin this repo builds against. That is the convention the
`navigation.runAction` row states ("read at the pin this repo builds
against, not at objectui main").
- The pin **still carries** `AppSidebar.tsx`:
`packages/app-shell/src/layout/AppSidebar.tsx` is present at `f8a9d0fb`
and absent at objectui main `fb91ac9b0`. At the pin its only references
are the two barrel exports (`packages/app-shell/src/index.ts` and
`packages/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.
- Every named reader file exists at the pin. Every reader was re-read
there. **Control per row:** the same read, re-run at objectui main
`fb91ac9b0` (after the removal), answers identically. So the next pin
bump does not strand these pointers.
- `check:liveness` attributes a path after an `objectui` realm 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 own `objectui @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 `@f8a9d0fb`
| row | mounted reader (file · symbol) | what it reads | control at
objectui main `fb91ac9b0` |
|:--|:--|:--|:--|
| `label` | `packages/app-shell/src/layout/AppSwitcher.tsx` ·
`AppSwitcher`; `.../layout/ConsoleLayout.tsx` · `ConsoleLayout`;
`.../console/home/HomeAppsStrip.tsx` · `HomeAppsStrip` |
`resolveKeyedI18nLabel(activeApp.label, t)` on the pill and `app.label`
per row; `appLabel` into `AppHeader` plus the `AppShell` branding title;
launcher tiles | same reads |
| `description` | `apps/console/src/pages/system/AppManagementPage.tsx`
· `AppManagementPage` (routed at `system/apps`) | each list row renders
`resolveKeyedI18nLabel(app.description, t)`; `filteredApps` searches it
| same reads |
| `icon` | `AppSwitcher.tsx` · `AppSwitcher`; `HomeAppsStrip.tsx` ·
`HomeAppsStrip` | `getIcon(app.icon)` per row / per tile | same reads |
| `branding.primaryColor` | `ConsoleLayout.tsx` · `ConsoleLayout` into
`packages/layout/src/AppShell.tsx` · `useAppShellBranding` | handed to
`AppShell`'s `branding`; mapped onto `--primary` / `--ring` /
`--sidebar-primary` | same reads |
| `branding.accentColor` | same handoff | mapped onto `--accent` | same
reads |
| `branding.favicon` | same handoff | sets the favicon link's href |
same reads |
| `branding.logo` | **none mounted** → `planned` | see below | no reader
at main either |
| `active` | `AppSwitcher`; `.../layout/UnifiedSidebar.tsx` ·
`UnifiedSidebar`; `.../utils/appRoute.ts` · `filterActiveApps`;
`.../layout/AppHeader.tsx` · `AppHeader` | `a.active !== false` in the
switcher list, the sidebar's list and active-app lookup, the
openable-apps predicate (called by `HomePage`, `app-launcher-renderer`,
`resolveHostAppSegment`), and the avatar menu | same reads |
| `hidden` | `AppSwitcher`; `UnifiedSidebar`; `filterActiveApps`;
`AppHeader` | `a.hidden !== true` out of the lists; the avatar menu
lists `a.hidden === true` apps (Account excepted); the in-repo
`ACCOUNT_APP` leg is unchanged | same reads |
| `navigation[].id` | `UnifiedSidebar.tsx` · `useNavOrder` and
`UnifiedSidebar` (`useNavPins`);
`packages/layout/src/NavigationRenderer.tsx` · `NavigationRenderer`;
`.../views/nav-menu-renderer.tsx` · `NavMenuRenderer` | persisted
reorder by id; pins; sortable id, render keys, active row | same reads |
| `navigation[].visible` | `UnifiedSidebar` · `evalVis`;
`NavMenuRenderer` · `evalVis` | CEL through `evaluateVisibility`, into
`NavigationRenderer` and `hasVisibleNavigationItems`; per-row skip when
false (the `NavigationRenderer` item-gate leg is unchanged) | same reads
|
| `areas[].id` | `UnifiedSidebar` | `activeAreaId` state and the lookup
by `a.id`; each area row's key and click | same reads |
| `areas[].label` | `UnifiedSidebar` |
`resolveInlineI18nLabel(area.label, language)` | same reads |
| `areas[].icon` | `UnifiedSidebar` | `getIcon(area.icon)` | same reads
|
| `areas[].navigation` | `UnifiedSidebar` (the `AppSchemaRenderer` leg
is unchanged) | the active area's `navigation` replaces the app's
top-level tree | same reads |
| file `_note` | shell list re-pointed to UnifiedSidebar / AppSwitcher /
AppHeader / ConsoleLayout / ContextSelectors, HomeAppsStrip,
NavigationRenderer / AppShell, RootLandingRedirect / AppManagementPage |
plus a dated paragraph recording the sha choice | — |
Mount sites, measured at the pin: `UnifiedSidebar` in `ConsoleLayout`;
`AppSwitcher` in `AppHeader`; `HomeAppsStrip` in `HomePage` and
`app-launcher-renderer`; `nav-menu-renderer` registered by import in
`packages/app-shell/src/index.ts`; `AppManagementPage` on the console
route `system/apps`.
## `description`: still `live`, narrower
The one mounted reader is the admin app list (`/system/apps`). The card
asked whether `AppManagementPage` still makes the row `live`, and it
does. Its `apps` come from `useMetadata()`, 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.tsx` also reads
`app.description`, but at the pin it is only barrel-exported (no mount
site), which is the same trap `AppSidebar` was.
## `branding.logo` → `planned` (carrier objectui#10827)
`branding` is now **drilled**, so `logo` gets its own verdict while
`primaryColor`, `accentColor` and `favicon` keep `live`. Census at the
pin, with the same grep run for `branding.primaryColor` as the positive
control (it hits `ConsoleLayout` and `AppShell`):
- **Authored** (the row's `producer`):
`packages/plugin-designer/src/AppCreationWizard.tsx` ·
`AppCreationWizard` and `BrandingEditor.tsx` · `BrandingEditor` write it
with a preview; `pages/EditAppPage.tsx` · `EditAppPage` pre-fills from
it.
- **Rendered by nothing mounted.** The only reader of `branding.logo` in
app-shell or layout at the pin is `AppSidebar` (unmounted). At main
there is none. `ConsoleLayout` hands `AppShell` `primaryColor` /
`accentColor` / `favicon` and never `logo`, and `AppShellBranding` has
no logo field. `EmbeddableForm`'s `config.branding.logo` is a form embed
config, and `runtime-config.ts`'s `branding.logoUrl` is console runtime
config; neither is the app key.
- **The nearest thing to a read:** `packages/types/src/app.ts` ·
`wizardDraftToAppSchema` copies `draft.branding.logo` into a top-level
`logo`, and `packages/layout/src/AppSchemaRenderer.tsx` ·
`InternalSidebar` renders that top-level `schema.logo`.
- **`authorWarn`: not set,** following the ledger's rules. README
"Author warnings" rule 1 bars marking benign display metadata (its
examples: `description`, `tags`, `icon`). The `field.useGrouping`
precedent is a `planned` row 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/lint` warns on a `planned` row only with an
explicit `authorWarn: true`
(`packages/lint/src/lint-liveness-properties.ts#shouldWarn`), so
author-facing lint output does not change.
- The drill removes `app/branding` from
`packages/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)
`AppSchema` declares only `branding.logo`. A top-level `logo` on an app
is **refused**, not folded:
```text
AppSchema.safeParse({ name: 'probe_app', label: 'Probe', logo: 'https://x/l.png' })
-> unrecognized_keys, path [], keys ['logo']
"Unrecognized key(s) on this app: `logo`. Did you mean `logo` → `branding`? ..."
AppSchema.safeParse({ name: 'probe_app', label: 'Probe', branding: { logo: 'https://x/l.png' } })
-> accepted, branding.logo kept
```
The alias table (`logo: 'branding'`) feeds the refusal's "did you mean".
It does not rewrite the key (`packages/spec/src/shared/strict-object.ts`
header), and no ADR-0087 conversion carries `logo`. 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 `active` note 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 filters `active !== false`.
What keeps an inactive app routable is app-shell `AppContent`, which
resolves `matchAppBySegment(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)
```diff
- * time by the shell (see Console's `AppSidebar` / `AppContent`):
+ * time by the shell (see objectui's `NavigationRenderer`, whose `resolveHref`
+ * substitutes them from the session ids the mounted `UnifiedSidebar` and the
+ * console's `AppContent` supply):
```
Measured at the pin: `NavigationRenderer.tsx` `applyNavTemplate` inside
`resolveHref`, fed `templateContext` by `UnifiedSidebar`; `AppContent`'s
landing route goes `findFirstRoute` → `buildItemRoute` → `resolveHref`
with the session context.
```diff
- * sorted (`NavigationRenderer.tsx:1154`). Area-level order is not — `AppSidebar`
+ * sorted (`NavigationRenderer.tsx:1154`). Area-level order is not — `UnifiedSidebar`
```
Measured at the pin and at main: `UnifiedSidebar` filters then maps
`areas` with no sort, and `AppSchemaRenderer` maps `areas` as authored.
## Pins
No test pins these rows' evidence text or the `_note`. `git grep
AppSidebar` outside `app.json` hits 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 mutates `navigation.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/` and `src/**/*.zod.ts` are both in
`packages/spec/package.json` `files[]`, and `@objectstack/lint` reads
the shipped ledgers at runtime, so this diff publishes. That is why this
PR carries no `skip-changeset`.
## Verification (at HEAD `8a4cb2f0`)
Every reading below was taken at `8a4cb2f0` (this branch's head).
`packages/spec` `dist` was rebuilt from that source before any gate that
reads it.
- **`check:liveness`, before and after.** At base `1207baf0`: exit 0,
`app 56 classified (live 47, dead 9)`. At `8a4cb2f0`: exit 0, `app 59
classified (live 49, dead 9, planned 1)`, with this verdict line: `✓
packages/spec/liveness/state-counts.md is current`. Also at `8a4cb2f0`:
`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.md`** was regenerated with `gen:liveness-counts`, and
the gate reads it back as current.
- **Spec package, full `local` project.** 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 includes
`scripts/liveness/check-liveness.test.ts`, which copies and mutates the
real ledger.
- **Spec typecheck.** `pnpm --filter @objectstack/spec run typecheck`
(tsc, then `check:scripts-typecheck`, then `check:test-typecheck`): exit
0.
- **Consumer suite** (`@objectstack/lint` reads 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)`.
- **Derived gate families.** `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.
- **NOT MEASURED: `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-closure` exited 3 on its first run, before
`@objectstack/objectql` was built. After that build it exits 0: `2
published condition(s) measured from a real load`.
- **ESLint, narrowed and proven.** Of the 5 changed paths, 1 is in
eslint's population (`files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']`
in `eslint.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 (no
`parserOptions.project`, no typed rule sets), so this diff cannot move
the verdict on any untouched file.
## Acceptance notes
- **Rows not in this card's scope still cite `@940ba24` with line
numbers.** They are the `NavigationRenderer` / `ContextSelectors` /
`RootLandingRedirect` / `surfaceAgent` legs, which never named
`AppSidebar`. They were left as they are.
- **Stale `rest-server.ts` pointers inside `note` prose** remain on the
`areas.navigation` (`:1870`) and `navigation.children` (`:1837`) rows.
The file `_note` already records that the gate now lives in
`meta-item-read-gate.ts#filterAppForUserWithReason`. `note` is 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 ordered `branding.logo` →
`planned` requires drilling `branding`, and `check:liveness` fails 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](https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7b1e4a4 commit 6ac33a5
5 files changed
Lines changed: 94 additions & 41 deletions
File tree
- .changeset
- packages/spec
- liveness
- scripts/liveness
- src/ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
0 commit comments