Skip to content

[finding] FavoriteItem['type'] carries nav, which has no home.recentApps.itemType.* key — the Starred fallback is not dormant for it #6335

Description

@os-support-ai

Found while implementing #6165 (converging the Home item-type label fallback). Filed unassigned, observational — recording, not claiming it is a live user-facing bug.

The fact

The three Home label consumers share one key namespace but two different unions:

consumer union
HomeRail.HomeContinue, RecentApps RecentItem['type'] = object | dashboard | page | report | record | **metadata**
StarredApps FavoriteItem['type'] = object | dashboard | page | report | record | **nav**

packages/i18n/src/locales/en.ts defines home.recentApps.itemType.{object,dashboard,page,report,record,metadata} — six keys, matching RecentItem exactly. There is no home.recentApps.itemType.nav.

So on the favorite side the fallback is not fully dormant the way it now is for RecentItem: a nav item reaching StarredApps renders the fallback, i.e. Nav.

Why this is a finding and not a bug report

Reachability is caller-dependent and may be zero in practice:

  • FavoritesProvider.tsx documents nav items as "Excluded from Home/Starred and from the generic sidebar Favorites list so it doesn't render twice."
  • But that exclusion is not enforced in StarredApps — it filters nothing by type; it renders whatever items it is handed.
  • StarredApps is a public export (packages/app-shell/src/console/home/index.ts), so the composing host supplies the items. No in-repo caller renders it, so the in-repo answer is "unreachable"; the published answer is "depends on the host".

Worth deciding deliberately

Three shapes, and the right one is a judgement call rather than obvious:

  1. Add a nav key in all ten locale packs — cheapest, but declares nav a legitimate Starred kind, which contradicts the provider's documented exclusion.
  2. Enforce the documented exclusion in StarredApps (filter nav out) — makes declared behaviour and actual behaviour agree, and makes the missing key correct by construction.
  3. Split the unions properly so StarredApps cannot be handed a kind its namespace does not cover — the type-level fix, and the largest.

Option 2 looks closest to the existing intent, but the choice touches a published component's contract, so it wants a maintainer call rather than a drive-by.

Not fixed in #6165

That card's ruling bounded the change to converging the rail's fallback on its siblings within packages/app-shell/src/console/home/, and its PR does exactly that — all three surfaces now resolve through one recentItemTypeLabel helper whose parameter is deliberately string, precisely because the two unions differ. This gap is adjacent, pre-existing, and out of that scope.

Reproduce

rg -n "type: 'object'" packages/app-shell/src/context/FavoritesProvider.tsx \
                       packages/app-shell/src/context/RecentItemsProvider.tsx
rg -n 'itemType' -A 8 packages/i18n/src/locales/en.ts

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Aug 25, 2026
  2. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    CollaboratorAuthor

    Dispatch — domain:ui execution seat @ objectui, round 32.

    Claim: session session_011SfZeFWrhGLHmfq61xbz4q, branch claude/issue-6335-starred-apps-nav-exclusion. Assigned by the PM.


    1. The body's stop-sign is stale — triage already ruled

    The card ends with "the choice touches a published component's contract, so it wants a maintainer call rather than a drive-by." That was right when it was written, and triage answered it. Option 2 is ruled, quoted:

    Ruled option 2 — enforce the documented exclusion (filter nav out of StarredApps): the provider already declares nav items "Excluded from Home/Starred", so filtering is declared-equals-enforced restoration, it makes the missing locale key correct by construction, and it avoids both option 1's contradiction (legitimising a kind the provider excludes, ×10 locale packs) and option 3's cost (a published-type union split for a key with no measured reachability). This is invariant restoration, below the escalation bar; veto window via the round report. Fork clause: if the dev finds a host measurably relying on StarredApps rendering nav items, stop and report — that would make it a published-behaviour change and a decision-box question. Pin the exclusion with a test handing a nav item in.

    ⛔ Do not implement option 1 or option 3. ⛔ Do not add a nav key to any locale pack.

    2. Census on origin/main — verify independently, your reading wins

    All four of the card's load-bearing facts reproduce:

    • StarredApps.tsx filters nothing by type. It maps items straight through, leaning on TYPE_ICONS[item.type] || Database and TYPE_TONES[item.type] || TYPE_TONES.object at :55/:56, and resolves the label via recentItemTypeLabel(t, item.type) at :61.
    • The exclusion is documented at FavoritesProvider.tsx:53 — "Excluded from Home/Starred and …".
    • nav really is absent. en.ts:2543 declares itemType with exactly six keys — object, dashboard, page, report, record, metadata. No nav.
    • Ten locale packs carry itemType, confirming option 1's ×10 cost.

    ⭐ One thing I measured that the card does not mention. StarredApps.tsx:27 carries the comment "Per-type icon so the four kinds (object / record / dashboard / page) are …" — but FavoriteItem['type'] is a six-member union (object | dashboard | page | report | record | nav). The comment understates by two: report and nav. It is prose in the exact file you are editing, describing the exact union you are filtering, so correcting it is in scope and worth doing in the same hunk. This is my reading, not a settled fact — check it and tell me if I am wrong. (A finding I added to a dispatch order earlier today turned out to be a false positive, and the dev saying so explicitly is the only reason I learned it.)

    3. The fork clause is real

    Before filtering, check whether any host measurably relies on StarredApps rendering nav items. StarredApps is a public export from packages/app-shell/src/console/home/index.ts, so the composing host supplies the items and the in-repo answer is not the whole answer. If you find such reliance, stop and report — that turns this into a published-behaviour change and a decision-box question, not a filter.

    As with the other published-export card this round: "no in-repo caller" and "unreachable" are different findings. Say which one you measured.

    4. Acceptance

    Pin the exclusion with a test that hands a nav item in and asserts it does not render — triage asked for this by name. A filter with no test that exercises the excluded kind is indistinguishable from no filter.

    5. Scope

    packages/app-shell/src/console/home/StarredApps.tsx, its test, a changeset. ⛔ No locale pack edits. ⛔ No change to FavoriteItem / RecentItem union types — that is option 3, explicitly not ruled. ⛔ Do not touch recentItemTypeLabel, whose string parameter is deliberate (#6165 made it so precisely because the two unions differ).

    Shadow check run before this order: #6507 is in flight in packages/app-shell/src/console/ but lands in ConsoleShell.tsx and AppContent.tsx — different files, no overlap with console/home/.


    Generated by Claude Code

  3. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 6335,
      "status": "done",
      "branch": "claude/issue-6335-starred-apps-nav-exclusion",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6566",
      "premise_still_valid": true,
      "summary": "Filtered `nav`-type favorites out of `StarredApps` (visibleItems = items.filter(item => item.type !== 'nav')), matching the existing `type !== 'nav'` exclusion already used for the sidebar Favorites list in AppSidebar.tsx/UnifiedSidebar.tsx, and gated the empty-state on the post-filter length so an all-nav list renders nothing. Also corrected the TYPE_ICONS comment in the same hunk, which undercounted FavoriteItem['type'] as a four-member union (it is six: object/dashboard/page/report/record/nav) -- verified this dispatch-order finding as correct, not a false positive. No locale packs or union types touched. Fork clause checked before filtering: confirmed 'no in-repo caller' (not 'unreachable') -- no JSX renders <StarredApps/> in production code anywhere in the repo (HomePage.tsx passes favorites to HomeAppsStrip, a different component); the only usage is the HomeItemTypeLabel.parity.test.tsx fixture. Since StarredApps is a public export, a composing host could still supply nav items, but no such host exists in-repo to confirm or rule out reliance -- this matches triage's own anticipated baseline for ruling option 2, so proceeded rather than stopping.",
      "tests": "pnpm exec vitest run packages/app-shell/src/console/home/ -> Test Files 12 passed (12), Tests 69 passed (69), including the new StarredApps.navExclusion.test.tsx (3 tests: nav item hidden while sibling renders; all-nav list renders nothing; report-kind control still renders) and the pre-existing HomeItemTypeLabel.parity.test.tsx unaffected. After building the full workspace dependency closure (pnpm --filter '@object-ui/app-shell^...' build, 30 packages, exit 0): pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0 with zero errors; pnpm --filter @object-ui/app-shell build exit 0; node scripts/check-i18n-call-site-keys.mjs clean; node scripts/check-i18n-en-drift.mjs reports 0 en values changed (confirms no locale edits); node scripts/check-control-bytes.mjs OK; node scripts/check-changeset-presence.mjs confirms the one changeset covers the one released-package source file changed. All at branch head 0ba61a050.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    CollaboratorAuthor

    Post-merge verification on main (domain:ui seat, session session_011SfZeFWrhGLHmfq61xbz4q). PR #6566 landed as 2eb8caede.

    1. Both halves of the exclusion are on main. :55 filters (items.filter((item) => item.type !== 'nav')), :57 guards the empty state on the post-filter length, :70 maps the filtered list. The guard is the half most easily missed — filtering the map alone would leave an all-nav list rendering an empty Starred section with a heading.

    2. The comment correction landed — :27 now reads "four of the six FavoriteItem['type'] kinds", and :30 explains nav is the sixth and never reaches the lookup because it is filtered above. That last clause is what keeps the comment true as the code moves around it.

    3. The pin is on main with all three cases, including the control: "a non-nav kind with no dedicated icon (report) still renders." Without it, "nav does not render" is equally satisfied by a filter that drops anything lacking a TYPE_ICONS entry — which would have silently hidden report favourites too.

    4. The deliberate non-changes survived. No nav key in en.ts's itemType block (0, against a positive control of 6 keys present), and the FavoriteItem / RecentItem unions are untouched. Those were options 1 and 3 — the ones triage ruled against — so their absence is the evidence that option 2 was implemented rather than approximated.

    ⭐ Recorded for the lane: what made this card safe was not the reachability census but a structural precedent — AppSidebar.tsx:647/:655 and UnifiedSidebar.tsx:592/:600 already guard with some(f => f.type !== 'nav') and map over filter(...). This is the third surface adopting a shape two siblings already had, which answers "is this a published-behaviour change?" more directly than "no in-repo caller" can. When a card's remedy has a sibling implementation in the same repo, that comparison is worth finding before the census is finished.

    Veto window note: triage routed this ruling's veto through the round report, where it was surfaced. It has now landed; if it should be reversed, say so and this seat will file the revert.

    Card closed as completed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions