Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

Page-block canvas draws the generic unknown-block icon for types that render fine but are excluded from the palette (record:chatter) #5837

Closed

Description

@os-zhuang

Observation filed unassigned by the domain:ui dev seat implementing #5495 (palette offers the canonical record:discussion). Not a regression that change introduced — the same gap existed before it, on the other spelling — but #5495 moves which spelling is affected, so it is worth recording rather than leaving for the next reader to re-derive.

What was measured

packages/app-shell/src/views/metadata-admin/previews/PageBlockCanvas.tsx draws every node's icon and colour tone from the palette catalogue:

const meta = BLOCK_TYPE_META[typeStr as BlockTypeId];
const Icon = meta?.Icon ?? UnknownBlockIcon;
const tone = resolveBlockTone(typeStr);        // -> BLOCK_CATEGORY_TONE['misc'] on a miss

BLOCK_TYPE_META is the palette's offer list, and PALETTE_EXCLUSIONS (same file, block-types.ts) is its documented ledger of block types that are deliberately not offered. Those two lists answer "what may an author drag in", which is a different question from "what may an author already have in this page" — but the canvas chrome reads only the first.

For every exclusion whose reason is "no renderer" (ai:chat_window, element:form, element:record_picker, element:text_input) the generic box is arguably right: those nodes genuinely do not render. One exclusion is different: the record:chatter / record:discussion pair is excluded because the pair is one renderer under two names, so exactly one of the two spellings is always an unoffered type that renders perfectly. Opening an existing page that carries the unoffered spelling shows that node in the outline with UnknownBlockIcon (a plain box) and the neutral misc grey tone, instead of MessageSquare and the blue record tone its twin gets one line above.

blockLabel() is not affected — it never reads BLOCK_TYPE_META, and falls back to the raw type string. The gap is icon + tone only.

Why this is not new, and what #5495 changed about it

Before #5495 the unoffered spelling was the canonical record:discussion — the one packages/plugin-detail/src/synth/buildDefaultPageSchema.ts emits, i.e. the one in every page the platform generates for itself. After #5495 it is the legacy record:chatter alias, which is the rarer spelling. So the change strictly reduces how often this is seen; it does not close it.

Shape of a fix, if it is wanted

The information needed already exists — the pair shares a renderer, a category and an icon. Options, roughly in order of size:

  • a small alias→canonical display map consulted by PageBlockCanvas when BLOCK_TYPE_META misses, so an unoffered-but-renderable type borrows its twin's icon and tone without becoming offerable (block-config.test.ts's "no type is both offered and excluded" must keep holding — which is why this cannot simply be a second BLOCK_TYPE_META entry);
  • or split the catalogue into "display meta" (every type the canvas can encounter) and "palette offer list" (the subset an author may drag), making the two questions above two lists instead of one.

Either is larger than the S-size #5495 card and touches its hot file, so it was deliberately not folded in.

Refs: #5495 (where it surfaced, and the PR that moves which spelling is affected) · #2943 (the guard that made PALETTE_EXCLUSIONS an explicit ledger).

Activity

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

    @claude
    Contributor

    Finding concentration round (triage seat, session session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24, maintainer-invoked): promoted → pm:queue (domain:ui), Task (S). Give the canvas an icon/tone source that covers renderable-but-unoffered types (e.g. record:chatter): consult the renderer registry (or extend BLOCK_TYPE_META with a non-palette entry class) instead of falling to the unknown-block icon for types the page can legitimately contain.


    Generated by Claude Code

  4. self-assigned this
    on Aug 24, 2026
  5. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Contributor

    Claim: PM loop round 38 (refill slot)
    Session: session_01CSoz9uGhaaSgiq3hshtN7L
    Branch: claude/issue-5837-canvas-icon-for-unoffered-types
    Worktree: objectui-issue-5837
    Domain: domain:ui
    File surface: packages/app-shell/src/views/metadata-admin/previews/PageBlockCanvas.tsx, block-types.ts (the catalogue + PALETTE_EXCLUSIONS), their tests + a changeset
    Container & model: S, mode:subagent, model: opus
    Clause-②: no — designer chrome (icon + tone) on a node that already renders. No declaration moves, nothing becomes offerable.

    ⚠️ SERIAL — a sibling is live in your directory

    #5725 is IN FLIGHT in the SAME subtree, packages/app-shell/src/views/metadata-admin/previews/ — its files are view-column-io.ts + its tests + the pin in ViewColumnInspector.identityRead.test.tsx. Different files, so no write collision, but the directory is shared. ⛔ Do not touch view-column-io.ts, FieldsListEditor.tsx, or ViewColumnInspector.*. If your fix needs any of them, ⛔ stop and report.

    Other siblings: #5874+#5875 (plugin-kanban, plugin-detail ×2, app-shell/src/utils/ ×2 — ⛔ stay out), #5893 (packages/types/src/{base,plugin-scope}.ts), plus #5903's PR #6053 and #5676's PR #6061 awaiting gate reads.

    裁决 — triage's direction

    Triage seat, 2026-08-24 (finding concentration round), → pm:queue / Task (S). Verbatim: "Give the canvas an icon/tone source that covers renderable-but-unoffered types (e.g. record:chatter): consult the renderer registry (or extend BLOCK_TYPE_META with a non-palette entry class) instead of falling to the unknown-block icon for types the page can legitimately contain."

    The mechanism, and the one constraint that shapes every fix

    PageBlockCanvas.tsx draws icon and tone from the palette catalogue:

    const meta = BLOCK_TYPE_META[typeStr as BlockTypeId];
    const Icon = meta?.Icon ?? UnknownBlockIcon;
    const tone = resolveBlockTone(typeStr);   // -> BLOCK_CATEGORY_TONE['misc'] on a miss
    

    ⭐ BLOCK_TYPE_META answers "what may an author drag in". The canvas is asking "what may an author already have in this page". Two different questions, one list. That framing is the card's real contribution and it should drive the fix.

    For exclusions whose reason is "no renderer" (ai:chat_window, element:form, element:record_picker, element:text_input) the generic box is arguably right — those nodes genuinely do not render. ⚠️ One exclusion is different: record:chatter / record:discussion is excluded because the pair is one renderer under two names, so exactly one of the two spellings is always an unoffered type that renders perfectly. It draws UnknownBlockIcon and the neutral misc grey where its twin one line above gets MessageSquare and the blue record tone.

    ⛔ The hard constraint: block-config.test.ts's "no type is both offered and excluded" must keep holding. That is why this cannot simply be a second BLOCK_TYPE_META entry — the card says so explicitly, and it is the trap that makes the obvious fix wrong.

    The card names two shapes; ✅ either is acceptable and you choose on measurement:

    1. a small alias→canonical display map consulted when BLOCK_TYPE_META misses, so an unoffered-but-renderable type borrows its twin's icon and tone without becoming offerable;
    2. split the catalogue into display meta (every type the canvas can encounter) and palette offer list (the subset an author may drag) — two questions, two lists.

    ⚠️ (2) is the honest shape and (1) is the small one. If (2) turns out to be more than an S card's worth, take (1) and say so, naming (2) as the follow-up.

    PM mechanism assumptions — measure, do not assume

    1. The card is from 2026-08-23 and packages/app-shell moved twice today (fix(console): trim hidden: true fields from the approvals drawer summary card #6031, refactor(app-shell): type RecordDetailView's confirm handler as the published ConfirmationHandler #6035). Re-derive the BLOCK_TYPE_META read, resolveBlockTone, and the PALETTE_EXCLUSIONS ledger on your merge-base; report any delta.
    2. ⚠️ Which spelling is currently unoffered is a moving fact. Before The page-editor palette offers the legacy record:chatter and (as of the 17.1.0 pin) explicitly excludes the canonical record:discussion — the pair is advertised under the alias #5495 it was the canonical record:discussion — the one plugin-detail/src/synth/buildDefaultPageSchema.ts emits, i.e. in every page the platform generates for itself. After The page-editor palette offers the legacy record:chatter and (as of the 17.1.0 pin) explicitly excludes the canonical record:discussion — the pair is advertised under the alias #5495 it is the legacy record:chatter. ⛔ Do not hard-code either; the fix must work whichever way the pair is oriented, or a future flip re-opens this.
    3. blockLabel() is NOT affected — it never reads BLOCK_TYPE_META and falls back to the raw type string. ⛔ The gap is icon + tone only; do not widen into labels.
    4. ⚠️ Do not make the "no renderer" exclusions borrow an icon. They are correctly generic. A fix keyed on "is excluded" rather than "is renderable" would give element:text_input a friendly icon it has not earned. Key on renderability, and say how you determined it.

    Verification

    Direction predicted before running: a page carrying the unoffered spelling of the pair must draw the same icon and tone as its offered twin; revert and show the pin goes red.

    Counter-probes, three, and they are the fix's whole safety margin: (a) block-config.test.ts's "no type is both offered and excluded" must stay green — run it explicitly and quote it; (b) a genuinely unrenderable exclusion (element:text_input) must still draw UnknownBlockIcon and misc; (c) the palette's offer list must be unchanged — nothing became draggable. ⛔ Without (c), "the icon appears" is satisfiable by quietly making the type offerable, which is the exact invariant #2943 made an explicit ledger to protect.

    Prove every mutation on disk by grepping the injected text and separately the removed text; ⚠️ print the landing site, and ⚠️ assert anchor uniqueness before writing. Restore under trap … EXIT INT TERM with a cwd-independent restore command — a trap that fires is not a trap that restored (measured on #5926 today). Confirm git diff HEAD --stat empty afterwards.

    ⚠️ Run app-shell path-filtered — whole-package vitest is ~784 s and holds the shared lock; ⛔ do not run it. Gates by name with exit codes: pnpm --filter @object-ui/app-shell type-check (⚠️ hyphenated; echo the script name; distinguish the unbuilt-closure red from the missing-node_modules red — both hit today, different fixes), the path-filtered vitest including block-config.test.ts, eslint on changed files with merge-base delta. ⚠️ Plain eslint . per package — ⛔ no --no-inline-config.

    Run node scripts/check-changeset-presence.mjs rather than guessing. Draft PR, Fixes #5837. ⛔ Do not mark it ready, ⛔ do not enqueue, ⛔ do not enable auto-merge — the PM lands it. Post the terminal report here (plain-text first-line marker os-dev-report; ⛔ avoid short angle-bracket placeholders in prose — the sanitizer ate one on #5926 today) and return it.


    Generated by Claude Code

  6. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Contributor

    os-dev-report

    {
      "issue": 5837,
      "status": "done",
      "branch": "claude/issue-5837-canvas-icon-for-unoffered-types",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6072",
      "premise_still_valid": true,
      "summary": "Took shape (1), the alias display map, and named shape (2) as the follow-up in the PR body. block-types.ts now declares BLOCK_RENDERER_ALIAS_GROUPS (block types that are one renderer under several spellings) plus resolveBlockDisplayMeta(type), which returns the palette entry when there is one and otherwise borrows an alias sibling's Icon+category; resolveBlockTone routes through it and PageBlockCanvas reads it instead of BLOCK_TYPE_META. Renderability is determined by RENDERER IDENTITY, not by 'is excluded': a group may only list spellings that ComponentRegistry.getConfig resolves to the very same component function, asserted against the real registry (identity, not both-defined), which is why element:text_input, element:record_picker, element:form and ai:chat_window keep the generic box. No orientation is hard-coded — every assertion derives the offered/unoffered split from BLOCK_TYPE_META, and one test flips the catalogue outright (try/finally, restoration asserted) to prove the resolver follows if #5495's ruling is ever reversed. BlockDisplayMeta deliberately carries only Icon and category, no label, so display resolution cannot widen into blockLabel(). The palette offer list is unchanged.",
      "tests": "All gate runs on merge-base a1c41c516; the verification union re-ran on the final commit 7114898f0 (git rev-parse --short HEAD), which is the sha in the PR body. Exit codes captured before any pipe, and each verdict quotes the line the gate itself printed. (1) Dependency closure: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build -> 'os-verify-lock: VERDICT command-exit 0 · held the lock 182s'. (2) pnpm --filter @object-ui/app-shell type-check (hyphenated; package.json script is 'tsc --noEmit && tsc -p tsconfig.test.json', echoed in the run) -> 'VERDICT command-exit 0'. A first run had failed with 'VERDICT command-exit 2' and TS1002/TS1005 in my new test file — a syntax error I introduced, not the unbuilt-closure red (which reads 'Cannot find module @object-ui/...') and not the missing-node_modules red; fixed and re-run green. (3) Path-filtered vitest from the repo root (NOT whole-package app-shell): pnpm exec vitest run --maxWorkers=2 --reporter=verbose over canvas-display-meta.test.tsx, block-config.test.ts, palette-discussion-alias.test.tsx, PageBlockCanvas.test.tsx -> 'Test Files 4 passed (4) / Tests 53 passed (53)', 'VERDICT command-exit 0'. (4) eslint: no narrowing was needed — the whole package ran inside the foreground cap. cd packages/app-shell && pnpm exec eslint --format json . -> exit 0, population 948 files read from eslint's own config, 0 errors, 2659 pre-existing warnings; the three changed files contribute 0 errors (the config sets no project/projectService, so linting is not type-aware). (5) node scripts/check-changeset-presence.mjs -> exit 0, 'this change declares 1 changeset(s): .changeset/rotten-pugs-invent.md'. COUNTER-PROBE (a), run explicitly and quoted verbatim: '✓ |unit| .../__tests__/block-config.test.ts > page palette ↔ spec PageComponentType coverage > no type is both offered and excluded 0ms'. COUNTER-PROBE (b): 'an exclusion with no renderer twin keeps the generic box' asserts resolveBlockDisplayMeta undefined and tone === BLOCK_CATEGORY_TONE.misc for element:text_input, element:record_picker, element:form and ai:chat_window, and the DOM probe asserts element:text_input's badge keeps the zinc misc classes in the SAME render where the pair is fixed. COUNTER-PROBE (c): 'the drag-in list is exactly the palette catalogue, nothing more' derives TYPES_BY_CATEGORY from BLOCK_TYPE_META and asserts no PALETTE_EXCLUSIONS key is draggable; 'no alias-group member became offerable' asserts it for the group members. REVERSE VERIFICATION — direction predicted before running: removing the alias fallback must turn the four fix pins red while (a) and (c) stay green. Ablation ran after the fix was committed. Mutation was proved on disk both ways: anchor uniqueness asserted before writing (occurrences=1, landing byte 14733, line 310, printed), then grep -c of the INJECTED text ('ABLATION: pre-fix behaviour') = 1 and, separately, grep -c of the REMOVED text ('for (const group of BLOCK_RENDERER_ALIAS_GROUPS)') = 0, plus git diff --stat showing '1 file changed, 1 insertion(+), 7 deletions(-)'. Restore armed BEFORE the mutation under trap ... EXIT INT TERM with a cwd-independent command (git -C /home/user/objectui-issue-5837 checkout HEAD -- packages/...); the trap printed 'TRAP: restore rc=0', and afterwards git diff HEAD --stat was EMPTY with the removed text back (grep -c = 1) and the injected text gone (grep -c = 0). No rebuild leg was needed and none is claimed: the suite imports ../block-types by relative path, so no dist sits between the mutation and the run — this is not an exports-resolved pair. Observed result matched the prediction exactly: 'Tests 4 failed | 37 passed (41)', with × on 'the unadvertised spelling borrows the icon and tone of its twin', 'follows the pair when the palette flips which spelling it advertises', 'PageBlockCanvas ... gets the tone of its twin on the type badge' and 'PageBlockCanvas ... gets the icon of its twin', while ✓ held on 'no type is both offered and excluded' and 'the drag-in list is exactly the palette catalogue, nothing more'. Control-byte scan (grep -naP over the three changed files and the PR body) clean.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6071 (unassigned, labels finding + domain:ui): two PALETTE_EXCLUSIONS reason strings say 'no renderer' for block types that DO have registered renderers — element:text_input (components/src/renderers/basic/text-input.tsx:161, namespace element, with a declared inputs list) and element:record_picker (record-picker.tsx:303). The substantive half of each reason ('bare inputs belong to a form' / 'record picking is a field widget') is still correct and still the decision; only the leading clause is false, and core/src/registry/public-blocks.ts already words the same two exclusions without it. block-config.test.ts enforces that a reason EXISTS, never that it is true. ai:chat_window's reason is accurate (deliberately unregistered); element:form was not checked closely enough to claim either way. This is exactly why the fix keys on renderer identity within an alias group rather than on bare registration — a registry-membership key would have handed those two an icon and broken counter-probe (b)."
      ]
    }

    Notes for the PM, outside the JSON

    PM assumption checks.

    1. Re-derived on merge-base a1c41c516 after fix(console): trim hidden: true fields from the approvals drawer summary card #6031/refactor(app-shell): type RecordDetailView's confirm handler as the published ConfirmationHandler #6035: the BLOCK_TYPE_META read, resolveBlockTone and the PALETTE_EXCLUSIONS ledger are exactly as the card quotes them. No delta.
    2. Orientation is not hard-coded anywhere — the group is unordered, and the flip test proves the resolver follows a reversal of The page-editor palette offers the legacy record:chatter and (as of the 17.1.0 pin) explicitly excludes the canonical record:discussion — the pair is advertised under the alias #5495's ruling.
    3. blockLabel() untouched; BlockDisplayMeta structurally cannot carry a label.
    4. Keyed on renderability, determined by renderer identity in the registry, not by exclusion. Stated in full in the JSON and in the PR body.

    One measured delta from the card's symptom description. The canvas draws a node's icon only in its container branch (isContainer); a leaf node gets a live preview plus a type badge. So for the discussion/chatter pair specifically, the user-visible symptom today is the badge tone, not the icon. Both halves are fixed and pinned — the DOM icon probe uses container-shaped nodes to reach that branch — but the card's "icon + tone" reads as one symptom when today it is one-and-a-latent-one.

    Shape choice. Took (1), the small one, and said so: (2) — splitting into display meta plus offer list — moves BLOCK_TYPE_META from an exhaustive Record keyed by the BlockTypeId union into two structures read by the Add-block picker, the config-panel coverage tests and the spec-derived palette tests. That is past an S card. Named as the follow-up in the PR body; the alias-group resolver is the seam it would grow from.

    Serial constraint honoured. Nothing under view-column-io.*, ViewColumnInspector.* or FieldsListEditor.tsx was read into a diff or touched; no app-shell/src/utils/, no packages/types/, no apps/console/src/__tests__/. Files changed: previews/block-types.ts, previews/PageBlockCanvas.tsx, new previews/__tests__/canvas-display-meta.test.tsx, .changeset/rotten-pugs-invent.md. The one existing test file in the same subtree that is conceptually adjacent (palette-discussion-alias.test.tsx) was deliberately not edited — the new guards live in a new file so there is zero write collision in the shared directory.

    PR is draft, not enqueued, auto-merge not enabled. CI convergence is yours.


    Generated by Claude Code


    Generated by Claude Code

  7. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Contributor

    PM review — ACCEPT

    PR #6072, head 7114898f0, merge-base a1c41c516. Gate read at the usual mark.

    ⭐ Assumption 4 was the trap, and #6071 shows it was a live one

    I wrote: "⛔ Do not make the 'no renderer' exclusions borrow an icon. A fix keyed on is excluded rather than is renderable would give element:text_input a friendly icon it has not earned. Key on renderability, and say how you determined it."

    You keyed on something stricter than I asked for — renderer identity: a group may only list spellings that ComponentRegistry.getConfig resolves to the very same component function, asserted against the real registry, identity rather than both-defined. And then you found why that mattered:

    #6071 — two PALETTE_EXCLUSIONS reason strings say "no renderer" for block types that DO have registered renderers: element:text_input (text-input.tsx:161) and element:record_picker (record-picker.tsx:303).
    "This is exactly why the fix keys on renderer identity within an alias group rather than on bare registration — a registry-membership key would have handed those two an icon and broken counter-probe (b)."

    ⛔ So the obvious reading of my own instruction — "renderable = registered" — would have failed, on two of the four exclusions I told you to protect. ✅ The finding is not a side observation; it is the evidence that the design choice was necessary. And the accompanying detail is sharp: block-config.test.ts enforces that a reason EXISTS, never that it is true — so a ledger built to be explicit (#2943) carries two false clauses that no gate can see. ✅ Correctly scoped, too: the substantive half of each reason ("bare inputs belong to a form", "record picking is a field widget") is still correct and still the decision; only the leading clause is false — and core/src/registry/public-blocks.ts already words the same two exclusions without it. ✅ And element:form explicitly not claimed either way, because it was not checked closely enough. Declining to assert about the one you did not measure is what makes the two you did assert trustworthy.

    The two other assumptions, enforced structurally rather than by care

    ✅ Assumption 2 (which spelling is unoffered is a moving fact): no orientation is hard-coded — every assertion derives the offered/unoffered split from BLOCK_TYPE_META, and one test flips the catalogue outright (try/finally, restoration asserted) to prove the resolver follows if #5495's ruling is ever reversed. ⛔ A comment saying "don't hard-code the orientation" would have rotted; a test that flips it cannot.

    ✅ Assumption 3 (do not widen into labels): BlockDisplayMeta deliberately carries only Icon and category, no label — so display resolution structurally cannot reach blockLabel(). That is the instruction made unbreakable rather than obeyed.

    The measured correction to the card's own symptom

    the canvas draws a node's icon only in its container branch (isContainer); a leaf node gets a live preview plus a type badge, so for the discussion/chatter pair the visible symptom today is the badge TONE, not the icon

    ✅ The card describes the icon as the visible defect. It is half right, and only measurement would show which half. Both are fixed and pinned, with the DOM icon probe using container-shaped nodes to reach that branch — otherwise the icon pin would have been asserting against a branch the fixture never enters, which is a vacuous pin wearing a DOM assertion's clothes.

    Shape choice and counter-probes

    ✅ Shape (1) taken, shape (2) named as the follow-up in the PR body — exactly the instruction for the case where the honest shape exceeds an S card. ⛔ Silently taking (1) without naming (2) is how a catalogue stays conflated for another year.

    All three counter-probes run and quoted:

    • (a) '✓ … no type is both offered and excluded 0ms' — quoted verbatim from the run, not summarised. That invariant (Tier 3 (#2901): unregistered PageComponentType values, and the ai:chat_window palette trap #2943's) was the hard constraint.
    • (b) the four genuinely-unrenderable exclusions keep undefined meta and BLOCK_CATEGORY_TONE.misc, ✅ and the DOM probe asserts element:text_input's badge keeps the zinc misc classes in the SAME render where the pair is fixed — one render, both outcomes, so the two cannot drift apart.
    • (c) the drag-in list derived from BLOCK_TYPE_META, no PALETTE_EXCLUSIONS key draggable, plus a dedicated "no alias-group member became offerable". ⛔ That was the probe I said the fix's whole safety margin rests on, and it is two probes rather than one.

    ✅ The ablation matched the prediction exactly — 4 failed / 37 passed, the four fix pins red by name while (a) and (c) stayed green. ✅ Anchor uniqueness asserted before writing with the landing byte offset (14733) and line (310) printed; injected and removed text grepped separately; trap armed before the mutation, cwd-independent, printing TRAP: restore rc=0, with git diff HEAD --stat empty and both greps inverted afterwards. ✅ And "no rebuild leg was needed and none is claimed" with the reason — the suite imports ../block-types relatively, "this is not an exports-resolved pair".

    ✅ The first type-check failure was correctly identified as a syntax error you had introduced (TS1002/TS1005 in the new test file) and distinguished from both the unbuilt-closure red and the missing-node_modules red. Three red signatures, told apart by reading them.


    Generated by Claude Code

  8. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Contributor

    PM review — ACCEPT, enqueued

    PR #6072 read at head, gates read once at 15:52Z off the run's own started_at (15:35:06Z, ~17 min elapsed — deliberately late rather than early; three reads in this round were taken too early and had to be re-armed).

    Gate reading: 22 checks, 19 success + 3 skipped, 0 failing, 0 still running. ⚠️ Read as every check green, not the required subset — Test (shard 1..4/4), Type Check, Lint, Build & E2E, Build Docs, Bundle Analysis, Doc Snippet Type Check, Doc Component Type Check, Internal Docs Link Check, Skill Guide Path Check, Control Byte Scan, Changeset Declaration / Fixed Group / Bump Policy, Live E2E (informational), label. The three skipped are the coverage jobs and dependabot, which are conditional on this event type.

    Not a governed surface. Four files, all under packages/app-shell/src/views/metadata-admin/previews/ plus the changeset. No docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md. Marked ready and enqueued through the merge queue; ⛔ nothing was merged by hand.

    What earned the accept

    The fix is keyed on renderer identity, not on "is excluded" — and the suite makes that claim falsifiable rather than asserting it in prose:

    • BLOCK_RENDERER_ALIAS_GROUPS may only list spellings that resolve through the real ComponentRegistry to the same component object (toBe, not "both defined"). Two independently registered renderers that happened to look alike would pass a defined/defined check and fail this one.
    • The lookup itself carries a non-vacuity control — config('record:zzNotARegisteredType') must be undefined, so a getConfig that returned something truthy for anything cannot make the identity assertions meaningless.
    • The counter-probe is the part that matters most. element:text_input, element:record_picker, element:form, ai:chat_window are excluded for reasons that have nothing to do with aliasing, have no twin, and are asserted to keep the generic box and the misc tone. A fix keyed on "is excluded" would have handed all four an icon they have not earned — and would still have passed the positive half.
    • No orientation is hard-coded. The page-editor palette offers the legacy record:chatter and (as of the 17.1.0 pin) explicitly excludes the canonical record:discussion — the pair is advertised under the alias #5495 already flipped which member of the pair the palette advertises; every assertion derives the offered/unoffered split from BLOCK_TYPE_META rather than naming a side, and one test flips the catalogue outright to prove the resolver follows — restoring in finally and then asserting the restore (offered[wasOffered]).toBe(saved)), so a leaked mutation cannot silently re-colour every later assertion in the process.
    • The invariant Tier 3 (#2901): unregistered PageComponentType values, and the ai:chat_window palette trap #2943 exists to protect is pinned separately. "The icon appears" is also satisfiable by quietly making the type draggable; the third describe block asserts TYPES_BY_CATEGORY is exactly Object.keys(BLOCK_TYPE_META) and that no exclusion became draggable. The offer list is unchanged, and that is measured, not claimed.
    • The DOM leg asserts the tone class on the badge and the icon class on the row, each with the un-twinned exclusion rendered in the same render as an in-frame control — so "both fell through to grey" cannot pass as "both share the record tone".

    The side-effect import of @object-ui/plugin-detail is called out in the test file as deliberate (the app-shell setup does not pull it in, and depending on another file having imported it first would make the suite order-dependent). That is the right call and the right reason.

    Noted, not blocking

    • BlockDisplayMeta is deliberately narrower than BlockTypeMeta — no label — with the reason stated: blockLabel() never consulted the catalogue, and widening display resolution into labels would change author-visible naming, which is a separate decision. Correct fencing.
    • Changeset is patch on @object-ui/app-shell. The new names are additive, not a break, so patch is right and Changeset Bump Policy agrees; ⛔ major is refused by this repo outright regardless.
    • PALETTE_EXCLUSIONS reasons say "no renderer" for two block types that do have registered renderers #6071 (PALETTE_EXCLUSIONS reasons say "no renderer" for two block types that do have registered renderers) was filed rather than folded in. Right call — it is a different defect class (stale reason prose in the ledger) and folding it would have widened this PR past its fence.

    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