Repository navigation
feat(app-shell): the Studio landing lists recent packages, stored by identity and labelled at render (objectui#11863, Q3) - #11996
Conversation
…identity and labelled at render (objectui#11863, Q3)
A /studio/PKG/TAB visit is recorded by the console's StudioRoute as a recent
`package` entry: id `package:PKG`, name the package id, href `/studio/PKG`,
no display text. RecentItemType gains `package`; useRecentItemLabel takes an
optional `{ packages }` list that labels it, falling back to the machine name
as objectui#11678 does for a missing item. BuilderLanding lists the last
three above its package cards once its package list has answered. The app
sidebar and the metadata-admin home, which load no full package list, leave
package entries out. One `home.recentApps.itemType.package` row per pack.
Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw
Co-authored-by: Claude <noreply@anthropic.com>
…y, and match the sidebar row by its label (objectui#11863) Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw Co-authored-by: Claude <noreply@anthropic.com>
…er, and drop a test cast (objectui#11863) `Exclude` over the `RecentItem` union removes no arm, since `RecentNamedItem`'s `type` is itself a union, so the icon map still demanded a `package` key and the package build failed (TS2741). The listed kinds are now `Exclude<RecentItem['type'], 'package'>`. Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…keep the README's RecentList true for it (objectui#11863)
Beside the sentence listing the recent-entry kinds stored by identity, the
package README and the user-state guide now say that a Studio package entry
(`type: 'package'`) is stored the same way and is labelled with
`useRecentItemLabel({ packages })`, or left out where no package list is
loaded. The README's RecentList snippet loads no package list, so it leaves
package entries out.
Claude-Session: https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw
Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: PR #11996 against Check-runs on this head: 43 ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged against the ruling (card comments 6054672824 Q3 to A, 6060520389 lane) and the claim 6063712393.
② Semver levelClause-②: yes (widening)
③ Boundary flagsEach dev deviation and each
No FAIL reason found. The landing act (ready, queue) stays the dispatching seat's; this record is the Tier S review of record for this head only. Implemented-by: VERDICT: PASS Generated by Claude Code |
…udio-recent-packages
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: PR #11996 against The head moved: what the move changed, and what it did not. The move is one merge commit,
Check-runs on this head: 43 in all: 40 Bundle, the reason the head moved: the Console Performance Budget on this head (PR comment 6066301017; run 37822666205, the Bundle Analysis check-run) reads an eager closure of 3311.5 KB against the 3312.0 KB budget, PASS. The gate is green and that is the verdict. Headroom is 0.5 KB, so the next card that grows the eager closure meets the budget constant: noted for the seat, not a finding on this PR. ① Derived judgmentsEvery accept-set and public-surface change the diff implies, judged against the ruling (6054672824 Q3 → A; 6060520389 lane) and the claim 6063712393.
② Semver levelClause-②: yes (widening)
③ Boundary flagsEach dev deviation and each
No FAIL reason found. This record supersedes 6066168690 (head Implemented-by: VERDICT: PASS Generated by Claude Code |
…der's new studio variant (objectui#11863, Q1) (objectstack-ai#12004) Part of objectstack-ai#11863 (Q1) Clause-②: yes. This widens a published type of `@object-ui/app-shell`: the exported `AppHeader` component's `variant` prop accepts a fourth member, `'studio'`, beside `'app'`, `'home'` and `'orgs'`. The union (`AppHeaderVariant`) is not exported by name, since the package entry exports `AppHeader` only, so a host meets it as the prop's type, and a host that switches exhaustively over that type must handle `'studio'`. A call with no `variant` resolves as before. No export is added or removed. It also narrows `@object-ui/i18n`: the exported `en` pack and the `TranslationKeys` type derived from it lose `console.studio.backToHome`. The changeset is `minor` for `@object-ui/app-shell` and `@object-ui/i18n`, and `patch` for `@object-ui/console`. This covers only Q1 of objectstack-ai#11863, per the retriage ruling (Q1 → A): the `/studio` header. Q3 landed in objectstack-ai#11996. Q2, search on `/studio`, follows in a later round, so objectstack-ai#11863 remains open. ## What changed - `packages/app-shell/src/layout/AppHeader.tsx`: `AppHeaderVariant` gains `'studio'`, a sibling of `'orgs'`. It draws the brand logo, then a fixed Studio crumb read from `console.studio.title`, as `'orgs'` draws `organizations.title`. Everything else is the header's own, unchanged: the inbox, help and the account menu (profile, workspaces, theme, language, notification switches, sign-out). The `app`, `home` and `orgs` drawing is untouched; the only other edits are comments that list the frames. - `apps/console/src/components/StudioRoute.tsx`: `StudioLanding` mounts `AppHeader` with `variant="studio"` in the header `HomeLayout` uses (same classes), in place of its wordmark link. `StudioPackageBuilder` and the recent-package record (Q3) are untouched. - `packages/i18n/src/locales/*.ts`, all ten packs: `console.studio.backToHome` (the wordmark's tooltip) retires with its reader, and `console.studio.title` ("Studio") takes its place in the same `console.studio` block. "Studio" is a product name every pack already writes in Latin script inside its own Studio strings, so the value is the same in every pack. - `packages/app-shell/README.md`: the one place it lists the header's surfaces ("Platform preview badge") now names `studio`. - `.changeset/11863-studio-header.md`. Why `console.studio.title` and not a new family: the `orgs` crumb reads its own frame's `title` row (`organizations.title`). The Studio front door's own rows already lived in `console.studio` (`backToHome` was its only row), so the crumb is that block's `title`. No new key family. ## Hypotheses, measured **H1 holds.** Browser drive, Chromium at 1440x900 and 390x900, the console's built `dist` served by `vite preview` (base `054fd8441` on one port, this branch on another), API mocked with `page.route` (signed-in admin holding `studio.access`, one org, two packages). Before and after, at `/studio`: | | base `054fd8441` | this branch | |---|---|---| | visible interactive elements in the header (1440) | `a: Back to home -> /home` | `a: ObjectOS -> /home`, `button: Open inbox`, `button: Help & Documentation`, `button: AA` (avatar) | | header text | `ObjectOS` | `PREVIEW / Studio AA` | | header height | 40.5 px | 56 px (the `/home` header's) | | at 390 px | `a: Back to home -> /home` | `a: ObjectOS -> /home`, inbox, avatar (help is `md` and up, as on `/home`) | | avatar menu | (none) | Ada Admin, Profile, My Workspaces, Create workspace, Theme, Language, In-app alerts, Desktop notifications, Log out | | page errors | none | none | CONTROL: `/home` on both trees draws the same three controls at 390 px and the same menu. The provider tree: `/studio` (`StudioRoute`, then `ProtectedRoute`, then `ConnectedShell`, inside `ConsoleShell`) is the tree `/home` (`ProtectedRoute`, then `HomeLayout`) has, so `AppHeader` mounts with no new provider. The drive is the runtime reading of that: the real account menu, inbox and help rendered, with no page error. **H2 holds.** The studio variant's brand is `AppHeader`'s logo link, `to={homePath}` from `useHomePath()`, the line `homeAffordancesFollowDeclaration-7256.test.ts` already pins as "top-bar brand logo". Pinned three ways: the new `AppHeader.studioVariant-11863.test.tsx` runs the real `useHomePath` over metadata apps (no declaration: `/home`; `isDefault` on `helpdesk`: `/apps/helpdesk`); `StudioRoute.test.tsx` "the front door's header brand walks back to the same home the gate bounces to" drives the real route tree with a declared landing (`/apps/cloud_control`); and the browser drive above (`/home`). **H3 holds.** `/studio` mounts no command palette, so under objectui#11987's rule the header draws no search trigger there. Pinned in `AppHeader.searchTriggerNeedsPalette-11912.test.tsx`, which now runs the `studio` variant in its no-provider case: neither trigger testid and no `aria-keyshortcuts` element, while the header itself drew. In the browser: 0 trigger testids, 0 `aria-keyshortcuts` elements, and `Ctrl+K` opens 0 palette overlays. **H4 holds.** One new row, `console.studio.title`, in all ten packs; `console.studio.backToHome` retired from all ten with its one reader. `StudioRoute.landingI18n.test.tsx` now pins the crumb through the real `studioRoutes` and the real `AppHeader` (with `t` answering `«key»`), pins that no text or attribute on the landing asks for the retired key, and keeps the Han sweep with its live control. `check:i18n-keys`, `check:i18n-drift` and `check:i18n-dead-keys` are green (readings below). The i18n suite's `untranslated-identity-4376` flagged the row in zh/ja/ko/ru/ar as identical to en, so it is allowlisted there with its reason (the product name). **H5: every consumer of `AppHeaderVariant`, and what it does with `studio`.** | Consumer | With `studio` | |---|---| | `resolvedVariant` default (`appName ? 'app' : 'home'`) | unchanged | | `isApp` (`=== 'app'`): sidebar trigger, app crumbs, presence, connection dot, Design in Studio, the keyboard-shortcuts help entry, the logo hidden below `sm` | false, so none of them; the logo shows at every width, as on `home` / `orgs` | | `=== 'home'`: the product wordmark text | not drawn | | `=== 'orgs'`: the Workspaces crumb | not drawn | | `=== 'studio'` (new): the Studio crumb | drawn | | `AppHeader.searchTriggerNeedsPalette-11912.test.tsx`, `Record` over the union (exhaustive) | gains `studio: '/studio'`; compiles (`app-shell` `type-check`, whose test project lists the file) | | `AppHeader.inboxVariant.test.tsx`, two "every variant" loops (not exhaustive) | `studio` added to both, so their titles stay true; a comment that spelled the union out now points at the type | | Mount sites: `HomeLayout`, `AiChatPage` (`home`), `OrganizationsLayout`, `OrganizationLayout` (`orgs`), `ConsoleLayout` (`app`) | unchanged; `StudioLanding` is the one `studio` mount | No `switch` over the variant exists in `app-shell` or `apps/console`. `apps/console` has no comparison over it. **H6: the budget.** `pnpm check:eager-closure`, local `vite build` of the console under the shared verify lock, each tree on its own build: - base `054fd8441`: 3,391,274 B gzipped (`3311.8 KB`, headroom `0.2 KB`, 336 of 2458 chunks); - this branch: 3,391,194 B gzipped (`3311.7 KB`, headroom `0.3 KB`, 336 of 2458 chunks); - ceiling `MAX_EAGER_CLOSURE_GZIP_BYTES` 3,391,484 B, untouched. Delta: -80 B. The `index` chunk (the one whose built code carries the `studio` variant prop) fell 51 B; the rest is hash-rename noise spread across chunks. The branch build ran at `19fb06ccb`; every commit after it touches test files only (`git diff --stat 19fb06c HEAD` lists five test files), none of which is in the bundle. The local base reading is 9 B above the 3,391,265 B the seat quoted from the queue-built tree. ## Tests New: `AppHeader.studioVariant-11863.test.tsx` (the frame, brand first then the crumb, and neither sibling variant's drawing; CONTROL: `home` and `orgs` draw no Studio crumb; the brand's target with and without a declared landing; the right-hand cluster's inbox, help and account-menu keys). Changed: `StudioRoute.landingI18n.test.tsx` (rewritten to pin the crumb), `AppHeader.searchTriggerNeedsPalette-11912.test.tsx` and `AppHeader.inboxVariant.test.tsx` (the `studio` member), and `untranslated-identity-4376.test.ts` (one allowlist row). `StudioRoute.test.tsx`, `StudioRoute.lazyBuilder-11798.test.tsx` and `StudioRoute.recentPackage-11863.test.tsx` render the landing, so they now mount the real `AppHeader`, whose inbox, approvals and AI-catalogue reads reached a real socket (the network-escape guard turned red on the first run); each stubs `fetch` with an empty answer, and the entry gate's read stays on the mocked `createAuthenticatedFetch`. Their wordmark wording now names the header brand. No assertion in those three changed. Reverse verification, from the committed `555028609`, run under the verify lock: - Leg A (behaviour): `AppHeader.tsx` and `StudioRoute.tsx` swapped to their base content (disk blobs `0de4209e7fc1` and `e657a9f63280`, equal to the base blobs; anchor counts `console.studio.title` 1 to 0, `variant="studio"` 1 to 0, `console.studio.backToHome` 0 to 1). Four files: `Tests 5 failed | 29 passed (34)`. Red: the new test's "draws the brand, then the Studio crumb"; the landing test's crumb, retired-key and brand-link cases, and its Han sweep (it waits for the crumb first). Green: the 22 `StudioRoute.test.tsx` and lazy-builder cases, the two Han-instrument controls, and the new test's two variant CONTROLs, brand-target and cluster cases, which hold on any off-app variant. A trap restored both from `HEAD`: blobs `c9bf2c535f50` and `9e2098ca83a9` equal to HEAD's, `git diff HEAD` empty. - Leg B (types): `node scripts/ablation-replace.mjs` (objectstack) on `StudioRoute.tsx`, `variant="studio"` to `variant="studioX"` (anchor 1 to 0, blob `9e2098ca83a9` to `a4295167badf`), then the console's `tsc --noEmit` against the rebuilt `app-shell` dist: exit 2, `error TS2820: Type '"studioX"' is not assignable to type 'AppHeaderVariant | undefined'. Did you mean '"studio"'?`. The suggestion names the rebuilt member, so the console read the rebuilt `.d.ts`. Restored: blob equal to HEAD, `git diff HEAD` empty. Union, at `c48ef5bf8` (`git rev-parse --short HEAD`, the last commit), from the worktree root under the verify lock: - every test that imports or names `AppHeader` or `StudioRoute`, or names a `console.studio.` key (39 files, from `git grep -l`; the new test is one of them): `Test Files 39 passed (39)` · `Tests 330 passed (330)`; - `packages/i18n/`: `Test Files 82 passed (82)` · `Tests 1316 passed | 13 skipped (1329)`; - `scripts/__tests__/`: `Test Files 179 passed | 2 skipped (181)` · `Tests 5452 passed | 2 skipped (5454)`; - type-check, after building the console's dependency closure (`turbo run build --filter='@object-ui/console^...'`, `Tasks: 34 successful, 34 total`): `pnpm --filter @object-ui/i18n --filter @object-ui/app-shell --filter @object-ui/console run type-check`, exit 0, each script name echoed (`packages/i18n type-check$`, `packages/app-shell type-check$`, `apps/console type-check$`). `tsc --listFilesOnly` shows the app-shell test project and the console project list every touched test file. ## Gates At `c48ef5bf8`, all exit 0: - `pnpm check:i18n-keys`: "Every in-scope call-site key resolves against the en pack (3307 keys)…"; - `pnpm check:i18n-drift`: "No en value changed in this range." (1 key added, 1 removed); - `pnpm check:i18n-dead-keys` (report-only): the same 3307 pack keys and 340 candidates as on base `054fd8441`; no `console.studio` key among them; - `pnpm check:readme-exports`: OK, 579 self-imports judged, 579 real, 0 fabricated, 0 unbuilt (after the doc-snippet build closure); - `pnpm check:skill-examples`: "Every marked skill example holds up against the built types."; - `pnpm check:doc-snippets`: "784 of 784 block(s) judged, 0 failed."; - `node scripts/check-changeset-presence.mjs`: "20 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)"; `check-changeset-no-major`: no `major`; - `pnpm check:changeset-claims` (report-only): one pending changeset names a file this edits, `.changeset/11806-login-server-unreachable.md` naming `packages/app-shell/README.md`. Its paragraph is about the sign-up table, which this does not touch, so it is still true; - `pnpm check:pending-changeset-literals`: no test source names a pending changeset; - `pnpm check:new-line-citations`: "VERDICT new-cross-file-line-citations: 0 new citation(s)"; - `pnpm check:control-bytes`: OK. Lint, narrowed: `eslint --no-inline-config --format json` over the 20 changed `.ts`/`.tsx` files (the count is from its JSON output): 0 errors. Each of the 19 files that exist on base carries the same error and warning counts as its base content linted the same way; the new test file has 0 errors and 13 warnings, of the two kinds its sibling `11912` test carries. The config applies to `**/*.{ts,tsx}` and is not type-aware (`eslint.config.js` sets no `parserOptions.project` or `projectService`), so this diff cannot move a verdict on an untouched file. The repo-wide `pnpm lint` is CI's. Bundle Analysis on this PR: NOT MEASURED here, it had not posted when this was written. ## Acceptance notes - `packages/app-shell/src/__tests__/homeRecoveryRedirectsFollowDeclaration-7373.test.ts` labels its `StudioRoute.tsx` site "the /studio entry gate + the front door wordmark", and its header lists `StudioRoute.test.tsx` the same way. The front door's link is now `AppHeader`'s logo, which `homeAffordancesFollowDeclaration-7256.test.ts` already pins; the site's regex still matches the entry gate. The label is stale prose only, and the file is outside this claim, so it is not edited. Carrier: Q2 of objectstack-ai#11863 or whoever next touches that file. - No guide under `content/docs/guide/` names the header's variants (`git grep AppHeader` there: two mentions, neither a variant list), so no guide changed. - The header's Help menu shows "This app's docs" from the navigation context's `currentAppName`, which no off-app frame clears; `/studio` inherits that from `home` / `orgs` unchanged. Read-only inference, noted, not filed. This was implemented by the dev seat for objectui#11863 (Q1), session `https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw`. --- _Generated by [Claude Code](https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgw)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #11863 (Q3)
Clause-②: yes. This widens the published
RecentItemTypeunion of@object-ui/app-shellby one member,'package', soRecentNamedItemandRecentItemcarry it too. A host's exhaustiveswitchorRecordover the union must handle it. The publisheduseRecentItemLabelalso takes one new optional argument,{ packages }. No export is added or removed. The changeset isminorfor@object-ui/app-shell.This covers only Q3 of #11863, per the retriage ruling (Q3 → A): recent packages on the Studio landing. Q1 (the
/studioheader) and Q2 (search) follow in later rounds, so #11863 remains open. Recent objects and flows wait for a later card. #11912 is not addressed here.What changed
RecentItemsProvider.tsx):RecentItemTypegains'package', aRecentNamedItemkind. A package entry is stored by identity, in the objectui#11678 shape:idispackage:PKG,nameis the package id, andhrefis the bare/studio/PKGroute, which opens the Data pillar. It stores no display text. If a caller hands in alabel, the reader drops it.StudioRoute.tsx, the route owner): the:packageId/:tableg is nowStudioPackageBuilder, which records the visit and then renders the builder exactly as before. It sits behind the entry gate, so a refused principal records nothing. It is keyed on the package, not the pillar, so moving between one package's pillars changes nothing and writes nothing. The package-less scope~orgis skipped.StudioLanding's header is untouched (it belongs to Q1).useRecentItemLabel): a newpackagecase returns thenameof the package's row in thepackageslist the caller hands in. With no row, or no list, it shows the machine name. That is what objectui#11678 draws for a missing object, page or report.BuilderLanding.tsx): a "Recently viewed" row (the existingengine.home.recentkey) sits above the package cards. It shows the last three package entries, labelled from the package list the page already loads, and each links to its/studio/PKGroute. It appears only once that list has answered, so no entry is drawn as its id while the list loads, and it is absent when there are none. The existing cards are unchanged.home.recentApps.itemType.packagerow in each of the ten packs.pnpm check:i18n-keyschecks this key family against the union exactly.H2: every consumer of the recent list, and what it does with a
packageentryHomePage, thenHomeRail.HomeContinue)RecentApps(exported, not mounted in this tree)UnifiedSidebar)GET /api/v1/packagesis a Studio read, so drawing the entry could only show the id.type === 'record'onlyStudioHomePage)Recordover the typebuildPackageScopeOptions) holds writable bases only, so a read-only package would draw as its id.useRecentItemLabelitem.labelpackagecase (above)BuilderLandingNo surface draws a live package as its raw id.
Hypotheses, measured
1473757bfand the new tests run over it. With the builder mounted at/studio/com.acme.crm/data, the realRecentItemsProviderheld[](expected [] to have a length of 1 but got +0). After the change, the same visit stores exactly one entry:{"id":"package:com.acme.crm","type":"package","name":"com.acme.crm","href":"/studio/com.acme.crm","visitedAt":…}. It has nolabel, and the persisted copy equals it. In the browser (below), two visits and one pillar switch inside the first package left exactly two entries, each in that shape.Acme CRMbecameAcme Sales). A package the list no longer has draws its id,com.example.showcase, following the missing-object precedent rather than a new rule. A package's manifestnameis a plain string in the spec (ManifestSchema.nameisz.ZodString), so it has no per-language form. On the landing, a language switch can only change the row's heading, the existingengine.home.recentkey. I did not pin a language switch: the name has nothing to switch.c55b3f62b(the later commit touches neither the landing nor the route), with three packages (two writable, one read-only):Showcase | com.example.showcaseandAcme CRM | com.acme.crm(newest first), and the first one opened/studio/com.example.showcase/data.Tests
All readings are at head
c39af0271(git rev-parse --short HEAD) unless a line names another commit. Commands were run from the worktree root, through the shared verify lock.New:
StudioRoute.recentPackage-11863.test.tsx(the realstudioRoutesinside the real store),BuilderLanding.recentPackages-11863.test.tsx,UnifiedSidebar.recentPackage-11863.test.tsxandStudioHomePage.recentPackage-11863.test.tsx. New cases were also added touseRecentItemLabel.test.tsxandRecentItemsProvider.test.tsx.Reverse verification, run from the committed change
c55b3f62bwith all six source files at the base content. The swap was checked on disk by blob hashes, and a trap restored fromHEAD, proven by blob hashes and an emptygit diff HEAD. Result across the six files:Tests 17 failed | 22 passed (39). The 22 that stayed green are the 14 pre-existing provider cases, the 4 pre-existing resolver cases, and 4 negative controls: the landing with no recent package, the sidebar control, and the two route cases that must record nothing. Direction: every case that asserts the new behaviour went red.Test union at
c39af0271:RecentItemsProvider,useRecentItems,useTrackRouteAsRecent,useRecentItemLabel,BuilderLanding,StudioRoute,UnifiedSidebarorStudioHomePage(68 files, fromgit grep -l):Test Files 68 passed (68)·Tests 616 passed (616);scripts/__tests__/:Test Files 179 passed | 2 skipped (181)·Tests 5452 passed | 2 skipped (5454);packages/i18n/atc55b3f62b:Test Files 82 passed (82)·Tests 1316 passed | 13 skipped (1329). The later commit touches no i18n file.Type-check, at
c39af0271and after building the console's dependency closure (turbo run build --filter='@object-ui/console^...',Tasks: 34 successful, 34 total), viapnpm --filter @object-ui/app-shell --filter @object-ui/console --filter @object-ui/i18n run type-check. Each script name was echoed (packages/i18n type-check$,packages/app-shell type-check$,apps/console type-check$) and the exit was 0. The first build, atc55b3f62b, failed with TS2741 inStudioHomePage.tsx:Excludeover theRecentItemunion removes no arm, so the icon map still needed apackagekey.c39af0271narrows on the type member instead.Gates at
c39af0271, all exit 0:pnpm check:i18n-keys:116 member key(s) checked exactly, with thehome.recentApps.itemType.family read againstRecentItemType;check:i18n-drift:No en value changed in this range.;check:i18n-dead-keys, report-only: the new key sits under the livehome.recentApps.itemType.head;check:new-line-citations:VERDICT new-cross-file-line-citations: 0 new citation(s);check:control-bytes: OK;node scripts/check-changeset-presence.mjs:22 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s);check:changeset-claimsandcheck:pending-changeset-literals: no findings;check:skill-examples:Every marked skill example holds up against the built types.check:readme-exports: exit 1, from an unmet precondition only. 8 bindings were unjudged because@object-ui/cliand@object-ui/plugin-aiare not in the console closure, so they were not built. All 8 are inpackages/cli/README.mdandpackages/plugin-ai/README.md, which this diff does not touch. Of the 579 self-bindings, 571 were judged real, 0 fabricated and 0 wrong-path. Every@object-ui/app-shell(22) and@object-ui/i18n(29) README binding is real,useRecentItemsanduseRecentItemLabelincluded.Bundle (eager closure): I ran two
vite builds of the console on the same tree and dependency builds. The second had every changed source file and the ten packs at the base content (restored fromHEADby trap, proven by blob hashes). Readings frompnpm check:eager-closure:3311.5 KB gzipped(3390969 B);3311.6 KB gzipped(3391056 B), against a budget of3312.0 KB, headroom0.4 KB;useRecentItemLabelchunk intonav-selection, now thatBuilderLandingimports it too.The PR's own Bundle Analysis verdict: NOT MEASURED, because it had not posted when this was written.
Lint, narrowed:
eslint --format jsonover the 22 changed.ts/.tsxfiles (the count is from its JSON output) gave 0 errors. Each file's warning count equals the count for its base content, linted through stdin under the same path. The config applies to**/*.{ts,tsx}and is not type-aware (eslint.config.jssets noparserOptions.projectorprojectService), so this diff cannot move a verdict on an untouched file. The repo-widepnpm lintis CI's.Not run: a reverse check of
check:i18n-keyswith the new en row removed. The session's safety classifier refused the scripted edit, so it is NOT MEASURED.Acceptance notes
packages/app-shell/README.md("A recent object, dashboard, page or report entry stores its identity only…") andcontent/docs/guide/user-state-persistence.md("An object, dashboard, page or report entry in the recent list is stored as itstypeandname…") do not yet name the package kind. Suggested sentence for both: "A Studio package entry (type: 'package') is stored the same way; label it withuseRecentItemLabel({ packages }), passing the package list you loaded, or leave it out where you have no list." I left this for the seat to add to the claim or to carry.RecentApps(exported, not mounted in this tree) draws whatever list its caller passes and labels it withuseRecentItemLabel()and no package list. A host that hands it an unfiltered list would see a package entry drawn as its id. No caller in this tree does that: Home filters first. Carrier: none. Noted here, not filed.Dev session:
https://claude.ai/code/session_01DBZ9bntPZ7VKyQNtJeNsgwGenerated by Claude Code