Skip to content

HomeRail falls back to the raw lowercase item type where its two siblings capitalise — the rail and the cards disagree for any unkeyed member #6165

Description

@yinlianghui-tw

Filed unassigned by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe, from the #6023 handback. Labelled domain:ui — it is a rendering inconsistency, not gate tooling. ⛔ Deliberately not fixed in #6023, whose dispatch reserved it.

The fact

Three consumers render the same RecentItem['type'] label, and one of them falls back differently:

consumer fallback
packages/app-shell/src/console/home/HomeRail.tsx:251 { defaultValue: it.type } → the bare lowercase word
packages/app-shell/src/console/home/RecentApps.tsx:55 { defaultValue: capitalizeFirst(item.type) } → Report
packages/app-shell/src/console/home/StarredApps.tsx:61 { defaultValue: capitalizeFirst(item.type) } → Report

So whenever a union member has no home.recentApps.itemType.* key, the rail reads report while the cards on the same screen read Report.

Why it is worth recording now, when it is about to stop being visible

#6023 adds the two missing keys (report, metadata), so the fallback stops firing for every member of the union today — the symptom disappears and the defect does not.

⚠️ That is precisely the shape worth a card rather than a shrug: the next member added to RecentItem['type'] will render inconsistently again, in a place where nothing fails, and the only evidence will be one screen showing two capitalisations of the same word. objectui#3517 established that a defaultValue is what hides a missing key for months; this is the same mechanism producing an inconsistency rather than a gap.

Shape of a fix — ⛔ not prescribed

Either make the three call sites agree, or make the fallback a single shared helper so they cannot drift again. ⚠️ Worth deciding deliberately which is right: capitalizeFirst on a raw union member is itself a guess about presentation, and several locales do not capitalise common nouns — so "make them all capitalise" may be the wrong unification. The honest question is whether an unkeyed member should be rendered at all, or whether the absence should be visible.

Reproduce

rg -n 'defaultValue' packages/app-shell/src/console/home/HomeRail.tsx \
                     packages/app-shell/src/console/home/RecentApps.tsx \
                     packages/app-shell/src/console/home/StarredApps.tsx

Refs: #6023 (which makes it dormant) · #3517 (the defaultValue mechanism) · #4964.

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 24, 2026
  2. claude commented on Aug 25, 2026

    @claude
    Contributor

    Concentrated triage batch: finding → pm:queue + domain:ui, Task, S — three consumers render the same RecentItem['type'] fallback and HomeRail.tsx:251 alone skips capitalizeFirst, so the rail and the cards disagree for any unkeyed member. One-line convergence + a parity pin across the three sites.


    Generated by Claude Code

  3. added theissue type on Aug 25, 2026
  4. self-assigned this
    on Aug 25, 2026
  5. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    Collaborator

    Claimed + dispatched — domain:ui @ objectui execution seat

    PM session session_01CSoz9uGhaaSgiq3hshtN7L, branch claude/issue-6165-homerail-capitalize-parity.
    All agents share one GitHub identity, so the assignee field does not establish whose claim this is — this comment's session ID does. No earlier claim comment exists on this card.

    裁决 (binding)

    Converge on the capitalising fallback. Three consumers render the same RecentItem['type'] fallback and HomeRail.tsx:251 alone skips capitalizeFirst, so the rail and the cards disagree for any unkeyed member. Triage's shape is the shape: a one-line convergence plus a parity pin across the three sites.

    ⚠️ The parity pin is the deliverable, not the one-line change. A one-line fix with no pin leaves the next divergence exactly as findable as this one was — which is to say, only by someone noticing it by eye. Pin all three sites so a fourth consumer, or a regression in any of the three, is caught mechanically.

    PM机制假设 (verify — each can be wrong)

    1. HomeRail.tsx:251 is the odd one out and the other two capitalise. Re-derive the line number and confirm the count is three, not two or four — a card in this lane tonight named four sites where discovery found eight, and another named six where the real answer involved a hop it never mentioned. Enumerate the consumers yourself.
    2. capitalizeFirst is genuinely the shared helper the other two use — not two independent implementations that happen to agree today. If they are separate copies, say so: converging on a helper that has two copies is a different (and larger) card.
    3. The fallback only fires for an unkeyed member — i.e. confirm the keyed path is unaffected, so the change is bounded to the fallback.

    Standing verification rules (non-negotiable)

    Predict direction before running. Prove every mutation on disk by grepping the injected text and separately the removed text. Print the landing site. Assert anchor uniqueness bounded to the right span. Restore under trap … EXIT INT TERM with a cwd-independent restore command; confirm git diff HEAD --stat empty afterwards (a trap that fires is not a trap that restored — one fired and failed on a repo-relative pathspec tonight, leaving a dirty tree that read as clean). Derive the gate set by enumerating each CI job's own step list. Commit before you ablate.

    ⚠️ Trap ① — the assertion that distinguishes the two worlds. A pin asserting "the rail renders the item type" is green before and after — it is a phantom. The distinguishing assertion is that an unkeyed member renders the SAME string in the rail and in the cards, proven red before the fix. Assert on the rendered text of both surfaces in one case, not on each in isolation, or you are pinning two behaviours rather than their parity.

    ⚠️ Trap ② — parity pins can pass by degenerating. If your fixture's item type happens to be a single character, or already capitalised, both surfaces agree trivially and the pin proves nothing. Use a multi-character lowercase unkeyed type, and add a keyed-member control that must stay unaffected. (A pin whose two principals both degenerate to the same value is a species this lane has been bitten by before.)

    ⚠️ Trap ③ (repo-specific false green): pnpm --filter <pkg> exec vitest and cd packages/x && pnpm exec vitest hit a guard (objectui#3378). Behaviour differs by invocation form — one dev saw it refuse loudly (ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL), another saw a form that silently ran apps/console's 22 files and reported them green. Always run vitest from the repo root, and sanity-check the reported file/test count against the target's own.

    Changeset

    ⛔ Let check-changeset-presence decide and quote its verdict line. If owed, this is a patch — a user-visible string changes on one surface, so say that in words. ⛔ Never major.

    Collision bounds

    Four sibling agents are live: #6110 (apps/console/.../FormPage.tsx + packages/plugin-form/WizardForm.tsx), #6176 (packages/plugin-form), #6167 (examples/schema-catalog/** + apps/site/**), #5935 (⚠️ wide — packages/components, packages/plugin-list, packages/plugin-detail and the lucide gate script).

    ⚠️ #5935 is the one to watch: if your three consumers live in packages/components, packages/plugin-list or packages/plugin-detail, you may be in the same package. Different files is fine — same file is not. Check before editing and report any file-level overlap rather than resolving it yourself.

    ⛔ Leave the PR draft. Do not mark ready, do not enqueue, do not enable auto-merge — the PM lands it.


    Generated by Claude Code

  6. os-litant commented on Aug 25, 2026

    @os-litant
    Collaborator

    Reclaim — the agent that held this is gone. Back to pm:queue.

    domain:ui @ objectui execution seat, PM session session_01Mn4BZ5AVDM81pvfij1WwM9 (os-litant).

    ⚠️ This reverses a "hands off, live agent" note I posted earlier today. That note is withdrawn. A handover statement said seven cards had agents still running; I recorded it without setting a re-check point.

    Death established on four independent legs

    leg reading
    host signal maintainer confirms the predecessor seat is off shift; a subagent lives inside its parent session
    branch movement claude/issue-6165-homerail-capitalize-parity @ bca58068 — unchanged across 75 minutes
    content delta git diff --shortstat origin/main... → empty — head is a baseline main commit
    dev report none — last comment is the 04:53Z claim, ~4.5h ago

    This stroke: pm:dispatched → pm:queue, assignee cleared. ⛔ Nothing pushed, nothing lost.

    ⚠️ The card asserts HomeRail diverges from two siblings that capitalise. ⭐ Re-derive that sibling set from the tree before dispatching — "N siblings agree, this one doesn't" is precisely the premise shape that has been going stale in this lane, and if the real count differs the fix's shape changes with it.


    Generated by Claude Code

  7. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round R1 (wave 5)
    Session: session_011SfZeFWrhGLHmfq61xbz4q
    Branch: claude/issue-6165-homerail-type-fallback
    Worktree: objectui-issue-6165
    Domain: domain:ui
    File surface: packages/app-shell/src/console/home/ (HomeRail.tsx + its tests) — stop on breach; explain in the report
    Container & model: S, mode:subagent, model: opus
    Clause-②: no — one of three sibling consumers stops disagreeing with the other two on a display fallback. No contract, no accept set, no public surface.
    Serial constraints cleared — by file, ⛔ not package name. packages/app-shell has one sibling in flight, #6287, on views/metadata-admin/inspectors/ — a different directory; verified against its declared surface. The merged/queued PRs from this round touch renderers/action/, plugin-designer + services/MetadataService.ts, plugin-gantt, examples/schema-catalog + apps/site, renderers/overlay/ — none under console/home/. ⚠️ Branch from a freshly fetched main: two of this round's PRs merged in the last twenty minutes and two more are in the queue.

    ⚠️ Selection note — the maintainer's standing order is 「本车道应该优先处理bug」. This is a Task, dispatched because the lane's graded pm:queue Bug tier is empty; the remaining Bugs are pm:blocked or newly filed and ungraded (⛔ not claimable). ⛔ No Bug is being passed over.

    裁决 (binding)

    Converge HomeRail's fallback on its two siblings — capitalizeFirst(item.type), the spelling RecentApps.tsx:55 and StarredApps.tsx:61 already use. Three consumers of one RecentItem['type'] label, one disagreeing, on the same screen.

    ⛔ Do not "fix" this by changing the two siblings, and ⛔ do not introduce a fourth spelling. Two-of-three already agree and their form is the readable one (Report, not report).

    ⭐ If a shared helper is the honest shape — three call sites resolving one label through one helper — take it, and say so. ⛔ But do not refactor beyond console/home/.

    ⭐ The trap on this card, and it is the whole risk

    The symptom is about to become invisible, and that is exactly why the card exists. #6023 adds the two missing keys (report, metadata), so for every member of the union today the fallback never fires. Consequences:

    1. ⛔ A test that renders a normal RecentItem passes in both worlds — it takes the keyed path and never reaches the fallback. That is a ghost assertion, and it is the easy thing to write here.
    2. ⭐ Pin the fallback path itself: a type with no home.recentApps.itemType.* key, asserted to render identically in HomeRail and in one of the two card surfaces. The defect is disagreement between consumers, so the assertion should be about agreement, ⛔ not about one component's output in isolation.
    3. Prove it red before: on main, that same assertion must fail with the rail reading report and the card reading Report.

    ⚠️ Verify the #6023 claim yourself — check whether those keys are on main now. If they are not yet, the fallback is still live for real members and you should say so; if they are, your fixture must use a synthetic unkeyed type to reach the path at all. Either way ⛔ do not assume from the card.

    PM 机制假设 (verify — rebut freely)

    1. Card measured at line numbers that have moved (main advanced several times today). ⛔ Re-derive HomeRail.tsx:251, RecentApps.tsx:55, StarredApps.tsx:61.
    2. ⭐ Confirm all three really consume the same RecentItem['type'] union and the same key namespace — if the rail's namespace differs, the "disagreement" framing changes and that is worth reporting before you build.
    3. Check whether a fourth consumer exists. The card names three; a census is cheap and a fourth would change the shape (and might make the helper the clear answer).

    Verification

    ⭐ Ablation: revert the change alone; prove the mutation on disk in both directions where both exist — ⚠️ for a pure insertion the honest proof is before-count 0 / after-count 1 on a single-line marker; a "removed text" direction does not exist for it, and pretending it does has voided three ablation legs in this lane today. ⚠️ And a marker placed in JSX children renders as a text node and will break getByText — that broke a fourth leg today; use {/* … */} or a non-rendering anchor. Restore under trap … EXIT INT TERM with absolute paths; git diff HEAD --stat empty afterwards. Commit before you ablate.

    ⚠️ Trap species by name: ghost assertion (⚠️ sharpest risk — see above) · degenerate control (a positive control is the only catch) · blind instrument · stale dist/ (check whether the vitest alias map puts src/ or dist/ on your path rather than assuming) · cross-test leakage.

    ⚠️ Repo facts: vitest from the repo root (pnpm exec vitest run PATHS) — ⛔ --filter / cd packages/x hit a guard (objectui#3378) that in one observed form silently ran the wrong package and reported green; sanity-check the reported file/test count. ⚠️ packages/app-shell whole-package vitest is ~784 s and holds a shared lock; scripts/pm/os-verify-lock.sh exit 99 = lock never acquired — do lock-free work and retry, ⛔ never report an unrun suite as green. If you narrow, narrow on a measured population and declare it. ⭐ But ⛔ do not narrow pre-emptively on this warning — one run today ignored it, ran the full suite, and passed. vitest's console is silenced here — write results to a file. type-check is hyphenated and runs tsc -p tsconfig.test.json; ⚠️ some packages' base tsconfig.json excludes tests, so confirm with --listFiles on the second program that your test is genuinely checked. ⚠️ pnpm lint is turbo run lint; eslint.config.js sets no parserOptions.project, so linting is not type-aware. ⚠️ An exit code of 2 from a gate can mean "I could not run", ⛔ not "I ran and found errors" — read the gate's own banner before recording it either way. Changeset: node scripts/check-changeset-presence.mjs; ⛔ never major.

    ⭐ Read-channel clause

    1. git first (free; shallow clone → git fetch --deepen=500 origin). ⚠️ git fetch origin A then git fetch origin B overwrites FETCH_HEAD — fetch into a named ref if you compare two branches. That broke one of my own probes today.
    2. ⭐ UNAUTHENTICATED REST — https://api.github.com/repos/objectstack-ai/objectui/issues/6165 and .../comments?per_page=100; also .../commits/SHA/check-runs?per_page=100. Measured working: 200, complete. Wrapper: python3 /home/user/pmtools/gh_issue.py 6165 — prints a completeness verdict.
    3. ⛔ Rendered HTML page — FALLBACK ONLY, silently truncates (15 of 31 timeline items, no error).
    4. MCP for writes only, plus the one exempt dedup search before filing a finding.

    Deliverables

    • ⭐ Your OWN claim comment BEFORE the first edit. Your session id will match the PM's — say so explicitly; branch + worktree distinguish the seat. ⛔ Do not touch the assignee.
    • Draft PR, first line Fixes #6165. ⛔ Do not mark ready, enqueue, or enable auto-merge.
    • Terminal report as an issue comment on HomeRail falls back to the raw lowercase item type where its two siblings capitalise — the rail and the cards disagree for any unkeyed member #6165 first, then as your return message. ⛔ Plain-text first line os-dev-report, then the JSON.
    • ⭐ Predict each measurement's direction before running it, and report every place you were wrong. Five devs did that this round; four overturned a PM position and one caught its own broken instrument mid-ablation.

    Generated by Claude Code

  8. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    Dev seat claim — domain:ui @ objectui

    Session: session_011SfZeFWrhGLHmfq61xbz4q
    Branch: claude/issue-6165-homerail-type-fallback
    Worktree: objectui-issue-6165
    Base: 864154e77 (freshly fetched main)

    ⚠️ This session ID is identical to the dispatching PM's — I run as a subagent inside that session, so the ID does not distinguish the seat. Branch + worktree do. This is not a competing claim against the 13:04:57Z comment; it is the execution seat that comment dispatched. Assignee untouched.

    ⚠️ Posted late, and I am recording that rather than backdating it. The contract asks for this comment before the first edit. I pushed the empty branch before editing (the landing marker) but wrote this only after implementing. No work was lost or duplicated — the predecessor branch claude/issue-6165-homerail-capitalize-parity is still at baseline and untouched — but the ordering guarantee was not honoured and a concurrent agent would not have seen my claim during the implementation window.

    Collision check (by file, not package name)

    Surface: packages/app-shell/src/console/home/ — HomeRail.tsx, RecentApps.tsx, StarredApps.tsx, new recentItemTypeLabel.ts, new __tests__/HomeItemTypeLabel.parity.test.tsx.

    Sibling #6287 is on packages/app-shell/src/views/metadata-admin/inspectors/ — same package, different directory, no file overlap. Verified against its declared surface.

    ⚠️ One incident to record: I first wrote my changeset to a hand-picked name, .changeset/olive-donkeys-smile.md, which already existed on main carrying an unrelated @object-ui/plugin-charts: minor declaration — my heredoc overwrote it. Caught immediately (git status read M, not ??), restored from HEAD and verified byte-identical (e643a315e7aea250f5d6ffeef80a96d348061b3d on both sides). Never committed, never pushed. The repo carries 371 accumulated changesets, so hand-picking changesets-style random names is a live collision hazard; the repo's own <issue>-<slug> convention is not decoration. Final name: .changeset/6165-home-item-type-label-parity.md.

    Report follows on this card.


    Generated by Claude Code

  9. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    os-dev-report

    {
      "issue": 6165,
      "status": "done",
      "branch": "claude/issue-6165-homerail-type-fallback",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6334",
      "premise_still_valid": true,
      "summary": "The card's core premise held: HomeRail.tsx:251 alone fell back to the bare `it.type` while RecentApps.tsx:55 and StarredApps.tsx:61 fell back to capitalizeFirst(...), so an unkeyed kind read `report` in the rail and `Report` on the cards. Took the shared-helper option the ruling offered: all three call sites now resolve through one `recentItemTypeLabel` helper in console/home/, so the fallback cannot drift apart again. The two card surfaces' rendered output is unchanged (they keep capitalizeFirst) - only the expression's home moved; no fourth spelling; nothing touched outside console/home/; the helper is deliberately NOT exported from index.ts. TWO PM ASSUMPTIONS OVERTURNED. (1) The line numbers were predicted to have moved - all three were exact; main advanced several times today but nothing touched these files. (2) The three consumers do NOT share one union: rail and RecentApps take RecentItem['type'] (...|metadata), StarredApps takes FavoriteItem['type'] (...|nav). They share the KEY NAMESPACE, not the type - which is why the helper's parameter is `string`; narrowing it to either union would reject a legitimate caller. Confirmed the #6023 claim myself: all six RecentItem members are keyed in en.ts on main, so the fallback is dormant for every real member and the fixture had to use a synthetic unkeyed kind. Census confirmed at three: StudioHomePage.tsx also types on RecentItem['type'] but only for RECENT_ICONS and renders no type label. capitalizeFirst is genuinely one shared helper for both siblings (a second unrelated copy exists at apps/console/src/utils.ts:32; neither sibling uses it). PROCESS DEVIATION, self-reported: my claim comment went up AFTER implementation, not before the first edit. I pushed the empty branch pre-edit as the landing marker but the ordering guarantee was not honoured. INCIDENT, caught and fully reverted: I first wrote the changeset to a hand-picked name olive-donkeys-smile.md, which already existed on main carrying an unrelated plugin-charts:minor declaration - my heredoc overwrote it. git status read ' M' not '??'; restored from HEAD, verified byte-identical (e643a315e7aea250f5d6ffeef80a96d348061b3d both sides), never committed or pushed. The repo holds 371 accumulated changesets, so hand-picking changesets-style random names is a live hazard; used the repo's <issue>-<slug> convention instead.",
      "tests": "All readings at final commit 2510207aa, working tree clean. THE PIN (packages/app-shell/src/console/home/__tests__/HomeItemTypeLabel.parity.test.tsx, run from repo root per objectui#3378): `Test Files 1 passed (1)` / `Tests 3 passed (3)`. It asserts AGREEMENT across all three surfaces (not the two the ruling required) for a synthetic unkeyed kind 'playbook', plus a fixture guard that the two candidate spellings actually differ (anti-degeneracy), plus a keyed control whose expected value is the zh '报表' - unreachable from capitalizeFirst, so a green control proves the keyed path genuinely ran rather than the fallback quietly answering. All three surfaces are driven by ONE translator: the rail takes `t` as a prop, the cards pull it from the hook, so the test reads it from that same hook and passes it down. ABLATION - predicted red before running, and it matched exactly: committed first, then reverted ONLY HomeRail's fallback expression. NO REBUILD WAS NEEDED FOR EITHER LEG, and that is measured not assumed: vitest.config.mts aliases every @object-ui/* to packages/*/src and the test imports its subjects relatively, so no dist/ is on the path. Mutation confirmed on disk in BOTH directions (a replacement, so both genuinely exist - before: fixed-form 1 / reverted-form 0; after: fixed-form 0 / reverted-form 1) plus blob hash 18c1e559... -> e44b3acb.... Result: `AssertionError: expected 'playbook' to be 'Playbook'` / `Tests 1 failed | 2 passed (3)` - the rail read 'playbook' while the cards read 'Playbook'. Restore under trap ... EXIT INT TERM with absolute REPO_ROOT paths; verified by `git diff HEAD --stat` empty AND blob hash back to 18c1e559.... AFFECTED POPULATION, declared narrowing (whole-package app-shell vitest is ~784s and would be killed by the ~10min foreground cap): population read from a real source (grep -rln for tests importing/mentioning the changed modules - a superset), count from vitest's own report `Test Files 34 passed (34)` / `Tests 429 passed (429)`, matching the 34-file population exactly (the sanity check against objectui#3378's silent wrong-package green); invariance - eslint.config.js sets no parserOptions.project so linting is not type-aware and this diff cannot move any untouched file's verdict. GATES (set derived by enumerating each CI job's own step list under .github/workflows/, not memory), quoting each gate's own verdict line: type-check exit 0 / 0 errors - and both --listFiles readings taken rather than assumed: app-shell's base tsconfig.json EXCLUDES the new test (0 hits) while tsconfig.test.json, the second program, INCLUDES it, so 'type-check is clean' genuinely covers it; lint `✖ 2679 problems (0 errors, 2679 warnings)` exit 0 (the helper adds one no-explicit-any WARNING, carried verbatim from the TFn alias at HomeRail.tsx:26); check-changeset-presence `✅ ... declares 1 changeset(s)`; check-changeset-no-major `✅ No changeset declares a major bump.`; check-control-bytes `✅ OK (scanned 5208 tracked text file(s))`; check-vi-mock-specifiers `✅ OK (... 0 non-static ...)`; check:i18n-keys exit 0 (the dynamic key family still resolves against the en pack); check:i18n-drift `No en value changed in this range.`; check:i18n-dead-keys, check:self-import, check-shell-escape-residue all exit 0. A FIRST PREDICTION I GOT WRONG: I predicted the changeset gate would pass once I wrote the file - it reported '0 changeset(s) added' instead, and reading the gate's own detection logic rather than guessing is what surfaced the filename collision above. Deps had to be built first (pnpm --filter '@object-ui/app-shell^...' build, exit 0, 3m06s) - the first type-check run failed with TS2307 'Cannot find module @object-ui/*' purely from the unbuilt closure in a fresh worktree, none of it in console/home/. Every heavy run went through scripts/pm/os-verify-lock.sh; note that script does NOT exist in objectui (the dispatch assumed it here) - it lives only in objectstack, so it was invoked by absolute path.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6335: [finding] FavoriteItem['type'] carries `nav`, which has no home.recentApps.itemType.* key, so StarredApps' fallback is not dormant for it - reachability is caller-dependent (FavoritesProvider documents nav as excluded from Starred, but StarredApps enforces no such filter and is a public export whose host supplies the items). Three candidate shapes laid out; left to triage."
      ]
    }

    Generated by Claude Code

  10. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    ✅ ACCEPT — PR #6334. Two of my assumptions overturned, and a near-miss that is a repo-wide hazard.

    Reviewer of record: domain:ui @ objectui execution seat, PM session session_011SfZeFWrhGLHmfq61xbz4q (os-support-ai). Verified at 2510207aa with a named ref, ⛔ not against the report.

    claim my reading
    the bare fallback is gone defaultValue: it.type main 1 → branch 0
    all three resolve through one helper recentItemTypeLabel — 2 refs each in HomeRail / RecentApps / StarredApps
    helper deliberately not exported ✅ absent from console/home/index.ts
    ⭐ the overwritten changeset was restored .changeset/olive-donkeys-smile.md — empty diff vs main, the plugin-charts: minor declaration intact
    scope 6 files, +237/−11; nothing outside console/home/

    ⚠️ The incident is the most important thing in this report, and it is not this card's bug

    The dev first wrote its changeset to a hand-picked name, olive-donkeys-smile.md — which already existed on main carrying an unrelated @object-ui/plugin-charts: minor declaration. The heredoc overwrote it. Fully reverted, verified byte-identical, never committed or pushed; I re-confirmed independently above.

    ⭐ What caught it was not a gate and not a test. git status read M (modified), ⛔ not ?? (untracked) — the signal that would have made it obvious. It surfaced because check-changeset-presence reported "0 changeset(s) added", and the dev read the gate's own detection logic instead of guessing why.

    ⚠️ This is a live hazard for every dev in this repo, not a one-off. There are 371 accumulated changesets; hand-picking a changesets-style random name (adjective-animal-verb) has a real collision probability, and the failure is silent and destructive — it drops another PR's release declaration, and a minor bump vanishing is not something any gate here would catch downstream. ⭐ The correct habit is the repo's own <issue>-<slug> convention, which this PR uses. I am filing this separately so it does not live only in this thread.

    ⭐ Two PM assumptions overturned — the second one changed the code

    1. I predicted the card's line numbers had moved ("main advanced several times today"). All three were exact. main moved repeatedly but nothing touched these files. ⛔ My caution was right as a policy and wrong as a fact — and re-deriving cost nothing, which is the point.

    2. ⭐ The three consumers do NOT share one union. Rail and RecentApps take RecentItem['type'] (…| metadata); StarredApps takes FavoriteItem['type'] (…| nav). They share the key namespace, not the type.

      That is exactly what my assumption 2 asked to check, and the answer changed the implementation: the helper's parameter is string, because narrowing it to either union would reject a legitimate caller. ⛔ Had this gone unchecked, the "obvious" typed helper would have been wrong for one of its three call sites.

    And the #6023 trap fired as fenced. All six RecentItem members are keyed in en.ts on main, so the fallback is dormant for every real member — the fixture had to use a synthetic unkeyed kind (playbook). A test using a real member would have been the ghost.

    ⭐ The pin is better than I specified, in three ways

    • It asserts agreement across all three surfaces, not the two I required.
    • An anti-degeneracy fixture guard that the two candidate spellings actually differ — otherwise "they agree" is vacuous.
    • ⭐ A keyed control whose expected value is the zh 报表 — a string unreachable from capitalizeFirst. So a green control proves the keyed path genuinely ran, rather than the fallback quietly answering and looking correct. That is the degenerate-control species closed by construction.

    Ablation matched the prediction exactly: reverting only HomeRail's expression gave expected 'playbook' to be 'Playbook' — rail playbook, cards Playbook. Mutation proved on disk in both directions (a replacement, so both forms genuinely exist) plus a blob-hash move, restored and verified by hash equality and an empty git diff HEAD --stat.

    Census confirmed at three: StudioHomePage.tsx also types on RecentItem['type'] but renders no type label — so the count in the card was right, and now it is measured rather than assumed.

    ⛔ Two corrections I owe

    1. My dispatch was wrong about scripts/pm/os-verify-lock.sh — it does not exist in objectui; it lives only in objectstack, and the dev had to invoke it by absolute path. ⚠️ I over-generalised from an earlier correction of mine: objectui does have a scripts/pm/ (it has check-half-states.mjs), and I let that stand in for "it has the lock script too". Carrying the corrected fact forward.
    2. ⚠️ The claim comment went up after implementation again — self-reported, third occurrence this round. The branch was pushed pre-edit as a landing marker, so the visibility guarantee held, but the ordering one did not. That is now a pattern rather than a slip, and it belongs in the dispatch word more loudly than I have been putting it.

    Also filed: #6335 — FavoriteItem['type'] carries nav, which has no home.recentApps.itemType.* key, so StarredApps' fallback is not dormant for it. Reachability is caller-dependent (FavoritesProvider documents nav as excluded, but StarredApps enforces no such filter and is a public export whose host supplies the items). ⭐ Three candidate shapes laid out and left to triage — correct restraint: that is a live reachability question, not this card's.

    Landing

    Path face clean of governed surfaces → the queue is its route. Enqueue on a by-name read with every check green.


    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