You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
× object-form asks the data layer for its objectName
<object-form> threw during render …
Component "object-form" failed to render
Cannot read properties of undefined (reading 'map')
× object-master-detail-form asks the data layer for its objectName
Component "object-master-detail-form" failed to render
Cannot read properties of undefined (reading 'map')
(a) fixture 缺陷。这个探针的头注释已经记了四次同一个教训:「a plausible value for EVERY input is not the same as a plausible CONFIGURATION」(columns: [] 让列表走空态不 fetch;裸 Proxy 被 spread 后丢掉所有方法;sections: ['name'] 被 spread 成 {0:'n',1:'a'} 后在 DetailView 里死于 reading 'name')。reading 'map' 的形状高度相似 —— 某个 array 输入被填成 ['name'],而 form 侧期待的是对象数组,于是在某处 .map 前先取了个 undefined。若是这一类,修法是给该 input 一个像样的样本,并把它加进这个文件的样本表 + 头注释(第五、第六次)。
Repro is exact: guard the non-ledgered branch and the two blocks go red with reading-map TypeErrors, while their data-reach assertions still pass (so today there is no false green — the crash is post-fetch).
Claim: PM loop round 3
Session: session_01PQ3NihCHE9LUtHoGxo6A9f
Branch: claude/issue-3840-binding-reach-form-crash
Worktree: objectui-issue-3840
Domain: domain:devx (@ objectui, seat #5748)
File surface: apps/console/src/__tests__/public-block-binding-reach.test.tsx; plus, only if the fork below resolves to (b), the one renderer that bare-takes — name it in the report before you edit it. (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — the fork is a judgement call, not a mechanical edit. ⚠️scripts/pm/dispatch-gates.mjs is objectstack-resident; objectui has no scripts/pm/, so this tier is this seat's explicit per-card decision, ⛔ not a --tier reading that does not exist here.
Clause-②: no — a test fixture and at most one bare take. No contract acceptance behaviour moves, no public surface grows.
Serial constraints cleared: no open PR touches the probe file. Repo-wide open PRs read by head at claim time — #5925 (packages/app-shell/src/preview/DraftChangesPanel.tsx, comment-only), #5922 (layout docs), #5843 (docs/adr, governed), #5774 (.github/workflows/ + ci-cd-pipeline.md), #5400 (release), #4958 (dependabot) — none intersect. Concurrent in this same batch: #5458, which touches apps/console/src/sdui-workbench-preview.tsx and two packages/app-shell/src/views/** files — disjoint from this card's surface by file, but if your fork lands on (b) in a shared renderer, stop and report rather than editing across it.
Premise re-verified on origin/main @ 2074a59, and it holds
⛔ Not inherited from the card, which is 16 days old. Measured now:
The probe file is present, 453 lines.
Its ledgered branch carries the crash guard (html.includes('failed to render') → toBe(false)), and the non-ledgered branch still does not.
The code names this card by number. Verbatim from the comment block above the guard: "Deliberately NOT applied to the other branch: object-form and object-master-detail-form do paint an error card under this fixture (Cannot read properties of undefined (reading 'map')) while still making their data calls, so their verdicts are earned rather than vacuous. Whether that card is a product bug or this fixture handing them an implausible configuration — the lesson the header records four instances of — is objectui#3840, not something to decide by widening a guard here."
So the card is live, and main is explicitly waiting on it.
What you are deciding — the fork is the card
Do not widen the guard first and see what breaks. Resolve which of these it is, with a measurement:
(a) fixture defect.sampleFor hands the form family a plausible value for every input that is not a plausible configuration. The probe's header already records four instances of exactly this lesson (columns: [] short-circuiting the list; a bare Proxy losing its methods when spread; sections: ['name'] spreading to {0:'n',1:'a'} and dying in DetailView). reading 'map' fits the shape: an array input filled with ['name'] where the form expects an array of objects. Fix = a decent sample plus the fifth/sixth entry in that header's lesson list.
The card names the cheap discriminator and it is still right: print the schema sampleFor generates, then bisect the inputs to undefined until the crash moves. That names the input; then read whether the spec shape for that input actually permits the sample's value. That last step is what separates (a) from (b) — ⛔ do not stop at "the crash went away when I changed the sample", which is true under both readings.
The endpoint, once the two blocks are green
Both branches assert render, the way the sibling probe record-block-record-reach.test.tsx already does with assertRendered. The guard moves ahead of the branch split and applies to all 16 candidates. That is the durable half — today the probe can only catch a crash on the branch where a crash is the pass condition.
PM mechanism assumptions — test these, do not trust them
Rank-ordered as assumptions, ⛔ not rulings, and I would rather be shown wrong than have you build on them:
I read (a) as more likely than (b), on the base rate of that header's four prior entries. Weak. The card's author, who had the file open, deliberately left it 50/50.
reading 'map' reads like an array-shaped input. Also weak — it could equally be a nested object the renderer maps over after a take.
Premise-first
The issue body is a lead, not a specification. If the two blocks no longer crash on today's main — someone may have fixed the renderer meanwhile — that is a legitimate and valuable delivery: report premise_still_valid: false with the measurement, open no PR, and say what changed. ⛔ Do not manufacture a diff to have something to show. Equally, if you find the fork resolves to (b) and the fix is larger than one bare take, stop and report rather than widening.
Read the card's full body and every comment on GitHub yourself, and check the body for sanitizer truncation before you rely on it.
{
"issue": 3840,
"status": "done",
"branch": "claude/issue-3840-binding-reach-form-crash",
"pr": "https://github.com/objectstack-ai/objectui/pull/5943",
"premise_still_valid": true,
"fork_resolution": "(a) fixture defect — decided on the input's spec shape, not on the crash disappearing",
"files_changed": [
"apps/console/src/__tests__/public-block-binding-reach.test.tsx",
".changeset/chilled-donkeys-shave.md"
],
"renderer_touched": false,
"summary": "Bisected the generated schema to the `sections` input: ObjectForm.tsx:1166 reads `section.fields.map(...)` and the generic array sample is the bare string ['name']. The discriminating step: FormViewSchema.sections REJECTS ['name'] ('expected object, received string') and requires `fields` per entry, and ObjectFormSection.fields is required in packages/types — so the sample is not publishable metadata and the error card is the fixture's fault. Fixed the sample, added the fifth/sixth lesson entries with the spec quotes inline, and hoisted the crash guard ahead of the branch split as `assertRendered` covering all 14 candidates.",
"premise_drift": "object-master-detail-form no longer crashes on today's main, but is not fixed — it declares formType as a bare `string`, so the generic sample is 'x', which skips the gate at ObjectForm.tsx:1134 (`!schema.formType || schema.formType === 'simple'`) that guards the crashing loop. Forcing formType:'simple' reproduces the identical crash. Its formType sample is corrected too, so the block now genuinely exercises the path. The dispatch premise itself (guard on the ledgered branch only, in-code comment naming #3840) held exactly as re-verified.",
"gates": [
{ "name": "vitest run apps/console/src/__tests__/", "verdict": "PASS", "evidence": "Test Files 19 passed (19) / Tests 242 passed (242)" },
{ "name": "@object-ui/console type-check", "verdict": "PASS", "evidence": "exit 0; script name echoed (tsc --noEmit && tsc -b tsconfig.node.json --force), so not a zero-match silent pass" },
{ "name": "@object-ui/console lint", "verdict": "PASS", "evidence": "exit 0; 171 files from --format json, 0 errors, 202 pre-existing warnings" },
{ "name": "check-changeset-presence.mjs", "verdict": "PASS", "evidence": "declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption" },
{ "name": "check-changeset-fixed.mjs", "verdict": "PASS", "evidence": "All workspace packages are in the changeset fixed group." },
{ "name": "check-changeset-no-major.mjs", "verdict": "PASS", "evidence": "No changeset declares a `major` bump." },
{ "name": "check-control-bytes.mjs", "verdict": "PASS", "evidence": "check-control-bytes: OK (scanned 4929 tracked text file(s); skipped 85 binary)" }
],
"gates_union_sha": "58dda7a",
"tests": "All gates above re-run at the final pushed commit 58dda7a with a clean tree; every exit code captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own output line. Dependency closure built first (pnpm --filter '@object-ui/console^...' build) — without it type-check emits 40+ phantom TS2882 errors. REVERSE-VERIFICATION: direction predicted before running (revert only the sections sample => exactly 2 reds, via the new guard rather than the reach assertion); observed exactly that — 'Tests 2 failed | 14 passed (16)', both failures 'threw during render — that is a crash, not a binding verdict' carrying 'Cannot read properties of undefined (reading map)'. This confirms the card's no-false-green claim: the reach assertions still passed, so only the new guard catches it. No rebuild was needed (the mutation is in the test file itself, transformed directly by vitest, not in a dependency's dist). Mutation confirmed ON DISK before the run: probe_section occurrences 1 -> 0, ABLATED_3840 marker count 1, non-empty git diff --stat; restored by a trap on EXIT/INT/TERM, and the restore verified (git status clean, probe_section back to 1).",
"deviations": [
"Fixed the `formType` sample in addition to `sections`, inside the same declared file. Not strictly required to green the two blocks (only `sections` was), but without it object-master-detail-form stays green by accident and the widened guard asserts render on a path no author can reach. Same defect class, measured green, declared in the PR body.",
"Lint narrowed to @object-ui/console rather than repo-wide `turbo run lint`, declared with three measurements: population from eslint's own config resolution, 171 files from --format json, and no type-aware linting configured (no projectService / parserOptions.project) so the diff cannot move an untouched file's verdict."
],
"scope_respected": "Ruling 2: the fork resolved to (a), so the conditional renderer half of the file surface never opened — no renderer edited. Ruling 5: the guard was NOT widened to discover anything; it was hoisted only after the fork was resolved by measurement. Ruling 6: sdui-workbench-preview.tsx and packages/app-shell/src/views/** untouched, no intersection with #5458.",
"sanitizer_check": "Issue body checked for truncation before being relied on: complete through all six sections, ending at the `关联:` line, no mid-sentence cut and no unterminated code fence. Separately, the PR body's first publish lost `<object-form>` from a code block to the short-angle-bracket sanitizer; corrected on edit, and the signoff footer uses the session-URL form so it survives edits.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #5939: object-master-detail-form declares formType as a bare `string` while the renderer switches on the object-form variant enum — an out-of-vocabulary value silently matches no branch and hid this card's crash",
"filed as #5940: object-master-detail-form issues getObjectSchema(undefined) for a detail collection whose object it never resolved, instead of declining to fetch"
]
}
(Supersedes comment 5390087791, whose HTML-comment marker was eaten by the body sanitizer on save — verified by read-back. Same payload, marker as literal text so the PM scan can see it.)
{
"issue": 3840,
"status": "done",
"branch": "claude/issue-3840-binding-reach-form-crash",
"pr": "https://github.com/objectstack-ai/objectui/pull/5943",
"premise_still_valid": true,
"fork_resolution": "(a) fixture defect — decided on the input's spec shape, not on the crash disappearing",
"files_changed": [
"apps/console/src/__tests__/public-block-binding-reach.test.tsx",
".changeset/chilled-donkeys-shave.md"
],
"renderer_touched": false,
"summary": "Bisected the generated schema to the `sections` input: ObjectForm.tsx:1166 reads `section.fields.map(...)` and the generic array sample is the bare string ['name']. The discriminating step: FormViewSchema.sections REJECTS ['name'] ('expected object, received string') and requires `fields` per entry, and ObjectFormSection.fields is required in packages/types — so the sample is not publishable metadata and the error card is the fixture's fault. Fixed the sample, added the fifth/sixth lesson entries with the spec quotes inline, and hoisted the crash guard ahead of the branch split as `assertRendered` covering all 14 candidates.",
"premise_drift": "object-master-detail-form no longer crashes on today's main, but is not fixed — it declares formType as a bare `string`, so the generic sample is 'x', which skips the gate at ObjectForm.tsx:1134 (`!schema.formType || schema.formType === 'simple'`) that guards the crashing loop. Forcing formType:'simple' reproduces the identical crash. Its formType sample is corrected too, so the block now genuinely exercises the path. The dispatch premise itself (guard on the ledgered branch only, in-code comment naming #3840) held exactly as re-verified.",
"gates": [
{ "name": "vitest run apps/console/src/__tests__/", "verdict": "PASS", "evidence": "Test Files 19 passed (19) / Tests 242 passed (242)" },
{ "name": "@object-ui/console type-check", "verdict": "PASS", "evidence": "exit 0; script name echoed (tsc --noEmit && tsc -b tsconfig.node.json --force), so not a zero-match silent pass" },
{ "name": "@object-ui/console lint", "verdict": "PASS", "evidence": "exit 0; 171 files from --format json, 0 errors, 202 pre-existing warnings" },
{ "name": "check-changeset-presence.mjs", "verdict": "PASS", "evidence": "declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption" },
{ "name": "check-changeset-fixed.mjs", "verdict": "PASS", "evidence": "All workspace packages are in the changeset fixed group." },
{ "name": "check-changeset-no-major.mjs", "verdict": "PASS", "evidence": "No changeset declares a `major` bump." },
{ "name": "check-control-bytes.mjs", "verdict": "PASS", "evidence": "check-control-bytes: OK (scanned 4929 tracked text file(s); skipped 85 binary)" }
],
"gates_union_sha": "58dda7a",
"tests": "All gates above re-run at the final pushed commit 58dda7a with a clean tree; every exit code captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own output line. Dependency closure built first (pnpm --filter '@object-ui/console^...' build) — without it type-check emits 40+ phantom TS2882 errors. REVERSE-VERIFICATION: direction predicted before running (revert only the sections sample => exactly 2 reds, via the new guard rather than the reach assertion); observed exactly that — 'Tests 2 failed | 14 passed (16)', both failures 'threw during render — that is a crash, not a binding verdict' carrying 'Cannot read properties of undefined (reading map)'. This confirms the card's no-false-green claim: the reach assertions still passed, so only the new guard catches it. No rebuild was needed (the mutation is in the test file itself, transformed directly by vitest, not in a dependency's dist). Mutation confirmed ON DISK before the run: probe_section occurrences 1 -> 0, ABLATED_3840 marker count 1, non-empty git diff --stat; restored by a trap on EXIT/INT/TERM, and the restore verified (git status clean, probe_section back to 1).",
"deviations": [
"Fixed the `formType` sample in addition to `sections`, inside the same declared file. Not strictly required to green the two blocks (only `sections` was), but without it object-master-detail-form stays green by accident and the widened guard asserts render on a path no author can reach. Same defect class, measured green, declared in the PR body.",
"Lint narrowed to @object-ui/console rather than repo-wide `turbo run lint`, declared with three measurements: population from eslint's own config resolution, 171 files from --format json, and no type-aware linting configured (no projectService / parserOptions.project) so the diff cannot move an untouched file's verdict.",
"First report comment (5390087791) lost its HTML-comment marker to the sanitizer; this comment re-delivers the payload with a literal-text marker. Left in place rather than deleted, since no comment-edit tool is available from this seat."
],
"scope_respected": "Ruling 2: the fork resolved to (a), so the conditional renderer half of the file surface never opened — no renderer edited. Ruling 5: the guard was NOT widened to discover anything; it was hoisted only after the fork was resolved by measurement. Ruling 6: sdui-workbench-preview.tsx and packages/app-shell/src/views/** untouched, no intersection with #5458.",
"sanitizer_check": "Issue body checked for truncation before being relied on: complete through all six sections, ending at the closing relations line, no mid-sentence cut and no unterminated code fence. Separately, two sanitizer casualties were caught by read-back and corrected: the PR body's first publish dropped an angle-bracketed block name from a code block (fixed on edit), and this report's HTML-comment marker (re-delivered here as literal text). PR signoff footer uses the session-URL form so it survives edits.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #5939: object-master-detail-form declares formType as a bare `string` while the renderer switches on the object-form variant enum — an out-of-vocabulary value silently matches no branch and hid this card's crash",
"filed as #5940: object-master-detail-form issues getObjectSchema(undefined) for a detail collection whose object it never resolved, instead of declining to fetch"
]
}
ACCEPT — PR #5943. Landing held only on CI convergence.
Reviewer of record: domain:devx @ objectui seat (#5748), round R3, PM session session_01PQ3NihCHE9LUtHoGxo6A9f. Reviewed against GitHub and the diff, ⛔ not against the report's self-description.
check
verdict
PR shape
draft ✅, base main ✅, first body line Fixes #3840 ✅
file surface vs. claim
exact — apps/console/src/__tests__/public-block-binding-reach.test.tsx + one changeset. No renderer touched, which is the correct consequence of the fork landing on (a)
ACCEPT path fork
get_files read first: no governed path. Ordinary ready/queue path applies
changeset
empty-frontmatter, matching this repo's declared convention for test-only changes; check-changeset-presence quoted its own exemption line
ruling 5 (⛔ don't widen the guard to discover things)
honoured — the guard was hoisted only after the fork was resolved by measurement
honoured — no sdui-workbench-preview.tsx, no packages/app-shell/src/views/**
The fork was resolved the way I asked, and that is the whole review
Ruling 1 said stopping at "the crash went away when I changed the sample" does not discriminate, because it is true under both readings. The dev did not stop there. Read off the diff:
Bisected to the sections input; the read is ObjectForm.tsx:1166, section.fields.map(...), with the generic array sample being the bare string ['name'].
Then asked the spec — the step that actually decides it. FormViewSchema.sections rejects ['name'] ("expected object, received string"), and [{}] fails on "0.fields: expected array, received undefined"; ObjectFormSection.fields is required in packages/types. So ['name']is not metadata any author could publish ⇒ fixture defect, (a).
Sharpest detail: it names the schema that cannot answer the question. ComponentPropsMap['object-form'].sections is z.array(z.unknown()) and accepts [42], [null], anything — it is the schema nearest to hand and consulting it would have produced the wrong answer. Recording which evidence source is disqualifying is exactly the discipline this file's header keeps asking for.
The premise drift it found is worth more than the card
object-master-detail-formno longer crashed on today's main — and the reason is not that anyone fixed it. That block declares formType as a bare string rather than the enum object-form declares, so the generic sample was 'x', and the crashing loop is gated on ObjectForm.tsx:1134 (!schema.formType || schema.formType === 'simple'). 'x' skipped straight past it. Measured, not inferred: with sections left malformed, forcing formType: 'simple' reproduces the identical crash.
So the block was green for a reason unrelated to its correctness — the fixture was routing around the code it existed to exercise. That is a strictly worse failure than the one the card was filed for, and it was invisible. Fixing the formType sample is a declared deviation inside the declared file, and I accept it: without it the widened guard would assert render on a path no author can reach, which is a guard that cannot fail.
Reverse-verification, with the direction predicted first
Reverting only the sections sample was predicted to redden exactly two tests through the new guard rather than the reach assertion — observed exactly that: Tests 2 failed | 14 passed (16), both failures from assertRendered. That confirms the card's own "no false green today" claim from the other side: those two blocks were always earning their data-reach verdicts, and it was the crash that was invisible. Mutation confirmed on disk before the run and restored by an EXIT/INT/TERM trap.
The durable half landed
assertRendered now runs ahead of the branch split, covering all 14 candidates, matching the sibling probe record-block-record-reach.test.tsx. The in-code comment that deferred this to #3840 is gone, replaced by the reasoning for why a crash mis-reads on both branches — on the ledgered branch a crash confirms the ledger entry; on the other it points the reader at wiring that is fine. And the lesson list got its fifth and sixth entries with the spec quotes inline, so the next reader can check the claim instead of trusting it. That was ruling 3 and it was met in full.
Two findings filed, both verified open
#5939 (the formType declaration divergence — an out-of-vocabulary value silently matches no branch) and #5940 (getObjectSchema(undefined) for a detail collection whose object was never resolved). Correctly filed unassigned rather than fixed here. ⚠️#5939 is the more interesting one: it is a class, not an instance — any block declaring a prop as bare string where the renderer switches on an enum has the same hole.
⏳ Landing
mergeable_state: unstable — checks are still running on 58dda7a. Enqueue resource is every check green, ⛔ not the required subset, so this stays draft until they land, then I flip ready and enqueue. Nothing is failing.
Housekeeping, ⛔ not this PR's fault: the report arrived twice (5390087791 lost its HTML-comment marker to the body sanitizer; 5390094268 carries the literal-text fallback, verified by read-back). No comment-edit path exists from this seat.
Activity
claude commentedon Aug 8, 2026
Findings round: PROMOTED —
finding→pm:queue.record:related_list写了add但漏了add.picker,整个相关列表被换成「Component failed to render」错误卡 ——RelatedList.tsx:1299裸取add.picker.object,同一文件:378/:390是可选链 #3838, queued this round. Either way the probe ends with assertRendered on both branches (existing shape in record-block-record-reach.test.tsx).object-master-detail-formverified onorigin/main@f5f8744; measurements are hours old (parity 门的反方向没推到全仓:7 个 block 共 15 个 spec 已声明的顶层键没有任何inputs发布,其中 3 个渲染器实读(record:details.hideFields/record:related_list.relationshipValueField/element:text_input.defaultValue) #3808 branch).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-warren commentedon Aug 24, 2026
Claim: PM loop round 3⚠️
Session:
session_01PQ3NihCHE9LUtHoGxo6A9fBranch:
claude/issue-3840-binding-reach-form-crashWorktree:
objectui-issue-3840Domain:
domain:devx(@ objectui, seat #5748)File surface:
apps/console/src/__tests__/public-block-binding-reach.test.tsx; plus, only if the fork below resolves to (b), the one renderer that bare-takes — name it in the report before you edit it. (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus— the fork is a judgement call, not a mechanical edit.scripts/pm/dispatch-gates.mjsis objectstack-resident; objectui has noscripts/pm/, so this tier is this seat's explicit per-card decision, ⛔ not a--tierreading that does not exist here.Clause-②: no — a test fixture and at most one bare take. No contract acceptance behaviour moves, no public surface grows.
Serial constraints cleared: no open PR touches the probe file. Repo-wide open PRs read by head at claim time — #5925 (
packages/app-shell/src/preview/DraftChangesPanel.tsx, comment-only), #5922 (layout docs), #5843 (docs/adr, governed), #5774 (.github/workflows/+ci-cd-pipeline.md), #5400 (release), #4958 (dependabot) — none intersect. Concurrent in this same batch: #5458, which touchesapps/console/src/sdui-workbench-preview.tsxand twopackages/app-shell/src/views/**files — disjoint from this card's surface by file, but if your fork lands on (b) in a shared renderer, stop and report rather than editing across it.Premise re-verified on
origin/main@2074a59, and it holds⛔ Not inherited from the card, which is 16 days old. Measured now:
html.includes('failed to render')→toBe(false)), and the non-ledgered branch still does not.object-formandobject-master-detail-formdo paint an error card under this fixture (Cannot read properties of undefined (reading 'map')) while still making their data calls, so their verdicts are earned rather than vacuous. Whether that card is a product bug or this fixture handing them an implausible configuration — the lesson the header records four instances of — is objectui#3840, not something to decide by widening a guard here."So the card is live, and
mainis explicitly waiting on it.What you are deciding — the fork is the card
Do not widen the guard first and see what breaks. Resolve which of these it is, with a measurement:
sampleForhands the form family a plausible value for every input that is not a plausible configuration. The probe's header already records four instances of exactly this lesson (columns: []short-circuiting the list; a bare Proxy losing its methods when spread;sections: ['name']spreading to{0:'n',1:'a'}and dying inDetailView).reading 'map'fits the shape: anarrayinput filled with['name']where the form expects an array of objects. Fix = a decent sample plus the fifth/sixth entry in that header's lesson list.record:related_list写了add但漏了add.picker,整个相关列表被换成「Component failed to render」错误卡 ——RelatedList.tsx:1299裸取add.picker.object,同一文件:378/:390是可选链 #3838 (RelatedList.tsxbare-takingadd.picker.object).The card names the cheap discriminator and it is still right: print the schema
sampleForgenerates, then bisect the inputs toundefineduntil the crash moves. That names the input; then read whether the spec shape for that input actually permits the sample's value. That last step is what separates (a) from (b) — ⛔ do not stop at "the crash went away when I changed the sample", which is true under both readings.The endpoint, once the two blocks are green
Both branches assert render, the way the sibling probe
record-block-record-reach.test.tsxalready does withassertRendered. The guard moves ahead of the branch split and applies to all 16 candidates. That is the durable half — today the probe can only catch a crash on the branch where a crash is the pass condition.PM mechanism assumptions — test these, do not trust them
Rank-ordered as assumptions, ⛔ not rulings, and I would rather be shown wrong than have you build on them:
reading 'map'reads like an array-shaped input. Also weak — it could equally be a nested object the renderer maps over after a take.Premise-first
The issue body is a lead, not a specification. If the two blocks no longer crash on today's
main— someone may have fixed the renderer meanwhile — that is a legitimate and valuable delivery: reportpremise_still_valid: falsewith the measurement, open no PR, and say what changed. ⛔ Do not manufacture a diff to have something to show. Equally, if you find the fork resolves to (b) and the fix is larger than one bare take, stop and report rather than widening.Read the card's full body and every comment on GitHub yourself, and check the body for sanitizer truncation before you rely on it.
Generated by Claude Code
os-warren commentedon Aug 24, 2026
{ "issue": 3840, "status": "done", "branch": "claude/issue-3840-binding-reach-form-crash", "pr": "https://github.com/objectstack-ai/objectui/pull/5943", "premise_still_valid": true, "fork_resolution": "(a) fixture defect — decided on the input's spec shape, not on the crash disappearing", "files_changed": [ "apps/console/src/__tests__/public-block-binding-reach.test.tsx", ".changeset/chilled-donkeys-shave.md" ], "renderer_touched": false, "summary": "Bisected the generated schema to the `sections` input: ObjectForm.tsx:1166 reads `section.fields.map(...)` and the generic array sample is the bare string ['name']. The discriminating step: FormViewSchema.sections REJECTS ['name'] ('expected object, received string') and requires `fields` per entry, and ObjectFormSection.fields is required in packages/types — so the sample is not publishable metadata and the error card is the fixture's fault. Fixed the sample, added the fifth/sixth lesson entries with the spec quotes inline, and hoisted the crash guard ahead of the branch split as `assertRendered` covering all 14 candidates.", "premise_drift": "object-master-detail-form no longer crashes on today's main, but is not fixed — it declares formType as a bare `string`, so the generic sample is 'x', which skips the gate at ObjectForm.tsx:1134 (`!schema.formType || schema.formType === 'simple'`) that guards the crashing loop. Forcing formType:'simple' reproduces the identical crash. Its formType sample is corrected too, so the block now genuinely exercises the path. The dispatch premise itself (guard on the ledgered branch only, in-code comment naming #3840) held exactly as re-verified.", "gates": [ { "name": "vitest run apps/console/src/__tests__/", "verdict": "PASS", "evidence": "Test Files 19 passed (19) / Tests 242 passed (242)" }, { "name": "@object-ui/console type-check", "verdict": "PASS", "evidence": "exit 0; script name echoed (tsc --noEmit && tsc -b tsconfig.node.json --force), so not a zero-match silent pass" }, { "name": "@object-ui/console lint", "verdict": "PASS", "evidence": "exit 0; 171 files from --format json, 0 errors, 202 pre-existing warnings" }, { "name": "check-changeset-presence.mjs", "verdict": "PASS", "evidence": "declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption" }, { "name": "check-changeset-fixed.mjs", "verdict": "PASS", "evidence": "All workspace packages are in the changeset fixed group." }, { "name": "check-changeset-no-major.mjs", "verdict": "PASS", "evidence": "No changeset declares a `major` bump." }, { "name": "check-control-bytes.mjs", "verdict": "PASS", "evidence": "check-control-bytes: OK (scanned 4929 tracked text file(s); skipped 85 binary)" } ], "gates_union_sha": "58dda7a", "tests": "All gates above re-run at the final pushed commit 58dda7a with a clean tree; every exit code captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own output line. Dependency closure built first (pnpm --filter '@object-ui/console^...' build) — without it type-check emits 40+ phantom TS2882 errors. REVERSE-VERIFICATION: direction predicted before running (revert only the sections sample => exactly 2 reds, via the new guard rather than the reach assertion); observed exactly that — 'Tests 2 failed | 14 passed (16)', both failures 'threw during render — that is a crash, not a binding verdict' carrying 'Cannot read properties of undefined (reading map)'. This confirms the card's no-false-green claim: the reach assertions still passed, so only the new guard catches it. No rebuild was needed (the mutation is in the test file itself, transformed directly by vitest, not in a dependency's dist). Mutation confirmed ON DISK before the run: probe_section occurrences 1 -> 0, ABLATED_3840 marker count 1, non-empty git diff --stat; restored by a trap on EXIT/INT/TERM, and the restore verified (git status clean, probe_section back to 1).", "deviations": [ "Fixed the `formType` sample in addition to `sections`, inside the same declared file. Not strictly required to green the two blocks (only `sections` was), but without it object-master-detail-form stays green by accident and the widened guard asserts render on a path no author can reach. Same defect class, measured green, declared in the PR body.", "Lint narrowed to @object-ui/console rather than repo-wide `turbo run lint`, declared with three measurements: population from eslint's own config resolution, 171 files from --format json, and no type-aware linting configured (no projectService / parserOptions.project) so the diff cannot move an untouched file's verdict." ], "scope_respected": "Ruling 2: the fork resolved to (a), so the conditional renderer half of the file surface never opened — no renderer edited. Ruling 5: the guard was NOT widened to discover anything; it was hoisted only after the fork was resolved by measurement. Ruling 6: sdui-workbench-preview.tsx and packages/app-shell/src/views/** untouched, no intersection with #5458.", "sanitizer_check": "Issue body checked for truncation before being relied on: complete through all six sections, ending at the `关联:` line, no mid-sentence cut and no unterminated code fence. Separately, the PR body's first publish lost `<object-form>` from a code block to the short-angle-bracket sanitizer; corrected on edit, and the signoff footer uses the session-URL form so it survives edits.", "open_questions": [], "out_of_scope_findings": [ "filed as #5939: object-master-detail-form declares formType as a bare `string` while the renderer switches on the object-form variant enum — an out-of-vocabulary value silently matches no branch and hid this card's crash", "filed as #5940: object-master-detail-form issues getObjectSchema(undefined) for a detail collection whose object it never resolved, instead of declining to fetch" ] }Generated by Claude Code
os-warren commentedon Aug 24, 2026
os-dev-report
(Supersedes comment 5390087791, whose HTML-comment marker was eaten by the body sanitizer on save — verified by read-back. Same payload, marker as literal text so the PM scan can see it.)
{ "issue": 3840, "status": "done", "branch": "claude/issue-3840-binding-reach-form-crash", "pr": "https://github.com/objectstack-ai/objectui/pull/5943", "premise_still_valid": true, "fork_resolution": "(a) fixture defect — decided on the input's spec shape, not on the crash disappearing", "files_changed": [ "apps/console/src/__tests__/public-block-binding-reach.test.tsx", ".changeset/chilled-donkeys-shave.md" ], "renderer_touched": false, "summary": "Bisected the generated schema to the `sections` input: ObjectForm.tsx:1166 reads `section.fields.map(...)` and the generic array sample is the bare string ['name']. The discriminating step: FormViewSchema.sections REJECTS ['name'] ('expected object, received string') and requires `fields` per entry, and ObjectFormSection.fields is required in packages/types — so the sample is not publishable metadata and the error card is the fixture's fault. Fixed the sample, added the fifth/sixth lesson entries with the spec quotes inline, and hoisted the crash guard ahead of the branch split as `assertRendered` covering all 14 candidates.", "premise_drift": "object-master-detail-form no longer crashes on today's main, but is not fixed — it declares formType as a bare `string`, so the generic sample is 'x', which skips the gate at ObjectForm.tsx:1134 (`!schema.formType || schema.formType === 'simple'`) that guards the crashing loop. Forcing formType:'simple' reproduces the identical crash. Its formType sample is corrected too, so the block now genuinely exercises the path. The dispatch premise itself (guard on the ledgered branch only, in-code comment naming #3840) held exactly as re-verified.", "gates": [ { "name": "vitest run apps/console/src/__tests__/", "verdict": "PASS", "evidence": "Test Files 19 passed (19) / Tests 242 passed (242)" }, { "name": "@object-ui/console type-check", "verdict": "PASS", "evidence": "exit 0; script name echoed (tsc --noEmit && tsc -b tsconfig.node.json --force), so not a zero-match silent pass" }, { "name": "@object-ui/console lint", "verdict": "PASS", "evidence": "exit 0; 171 files from --format json, 0 errors, 202 pre-existing warnings" }, { "name": "check-changeset-presence.mjs", "verdict": "PASS", "evidence": "declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption" }, { "name": "check-changeset-fixed.mjs", "verdict": "PASS", "evidence": "All workspace packages are in the changeset fixed group." }, { "name": "check-changeset-no-major.mjs", "verdict": "PASS", "evidence": "No changeset declares a `major` bump." }, { "name": "check-control-bytes.mjs", "verdict": "PASS", "evidence": "check-control-bytes: OK (scanned 4929 tracked text file(s); skipped 85 binary)" } ], "gates_union_sha": "58dda7a", "tests": "All gates above re-run at the final pushed commit 58dda7a with a clean tree; every exit code captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own output line. Dependency closure built first (pnpm --filter '@object-ui/console^...' build) — without it type-check emits 40+ phantom TS2882 errors. REVERSE-VERIFICATION: direction predicted before running (revert only the sections sample => exactly 2 reds, via the new guard rather than the reach assertion); observed exactly that — 'Tests 2 failed | 14 passed (16)', both failures 'threw during render — that is a crash, not a binding verdict' carrying 'Cannot read properties of undefined (reading map)'. This confirms the card's no-false-green claim: the reach assertions still passed, so only the new guard catches it. No rebuild was needed (the mutation is in the test file itself, transformed directly by vitest, not in a dependency's dist). Mutation confirmed ON DISK before the run: probe_section occurrences 1 -> 0, ABLATED_3840 marker count 1, non-empty git diff --stat; restored by a trap on EXIT/INT/TERM, and the restore verified (git status clean, probe_section back to 1).", "deviations": [ "Fixed the `formType` sample in addition to `sections`, inside the same declared file. Not strictly required to green the two blocks (only `sections` was), but without it object-master-detail-form stays green by accident and the widened guard asserts render on a path no author can reach. Same defect class, measured green, declared in the PR body.", "Lint narrowed to @object-ui/console rather than repo-wide `turbo run lint`, declared with three measurements: population from eslint's own config resolution, 171 files from --format json, and no type-aware linting configured (no projectService / parserOptions.project) so the diff cannot move an untouched file's verdict.", "First report comment (5390087791) lost its HTML-comment marker to the sanitizer; this comment re-delivers the payload with a literal-text marker. Left in place rather than deleted, since no comment-edit tool is available from this seat." ], "scope_respected": "Ruling 2: the fork resolved to (a), so the conditional renderer half of the file surface never opened — no renderer edited. Ruling 5: the guard was NOT widened to discover anything; it was hoisted only after the fork was resolved by measurement. Ruling 6: sdui-workbench-preview.tsx and packages/app-shell/src/views/** untouched, no intersection with #5458.", "sanitizer_check": "Issue body checked for truncation before being relied on: complete through all six sections, ending at the closing relations line, no mid-sentence cut and no unterminated code fence. Separately, two sanitizer casualties were caught by read-back and corrected: the PR body's first publish dropped an angle-bracketed block name from a code block (fixed on edit), and this report's HTML-comment marker (re-delivered here as literal text). PR signoff footer uses the session-URL form so it survives edits.", "open_questions": [], "out_of_scope_findings": [ "filed as #5939: object-master-detail-form declares formType as a bare `string` while the renderer switches on the object-form variant enum — an out-of-vocabulary value silently matches no branch and hid this card's crash", "filed as #5940: object-master-detail-form issues getObjectSchema(undefined) for a detail collection whose object it never resolved, instead of declining to fetch" ] }Generated by Claude Code
os-warren commentedon Aug 24, 2026
ACCEPT — PR #5943. Landing held only on CI convergence.
Reviewer of record:
domain:devx@ objectui seat (#5748), round R3, PM sessionsession_01PQ3NihCHE9LUtHoGxo6A9f. Reviewed against GitHub and the diff, ⛔ not against the report's self-description.main✅, first body lineFixes #3840✅apps/console/src/__tests__/public-block-binding-reach.test.tsx+ one changeset. No renderer touched, which is the correct consequence of the fork landing on (a)get_filesread first: no governed path. Ordinary ready/queue path appliescheck-changeset-presencequoted its own exemption linesdui-workbench-preview.tsx, nopackages/app-shell/src/views/**The fork was resolved the way I asked, and that is the whole review
Ruling 1 said stopping at "the crash went away when I changed the sample" does not discriminate, because it is true under both readings. The dev did not stop there. Read off the diff:
sectionsinput; the read isObjectForm.tsx:1166,section.fields.map(...), with the generic array sample being the bare string['name'].FormViewSchema.sectionsrejects['name']("expected object, received string"), and[{}]fails on "0.fields: expected array, received undefined";ObjectFormSection.fieldsis required inpackages/types. So['name']is not metadata any author could publish ⇒ fixture defect, (a).ComponentPropsMap['object-form'].sectionsisz.array(z.unknown())and accepts[42],[null], anything — it is the schema nearest to hand and consulting it would have produced the wrong answer. Recording which evidence source is disqualifying is exactly the discipline this file's header keeps asking for.The premise drift it found is worth more than the card
object-master-detail-formno longer crashed on today'smain— and the reason is not that anyone fixed it. That block declaresformTypeas a barestringrather than the enumobject-formdeclares, so the generic sample was'x', and the crashing loop is gated onObjectForm.tsx:1134(!schema.formType || schema.formType === 'simple').'x'skipped straight past it. Measured, not inferred: withsectionsleft malformed, forcingformType: 'simple'reproduces the identical crash.So the block was green for a reason unrelated to its correctness — the fixture was routing around the code it existed to exercise. That is a strictly worse failure than the one the card was filed for, and it was invisible. Fixing the
formTypesample is a declared deviation inside the declared file, and I accept it: without it the widened guard would assert render on a path no author can reach, which is a guard that cannot fail.Reverse-verification, with the direction predicted first
Reverting only the
sectionssample was predicted to redden exactly two tests through the new guard rather than the reach assertion — observed exactly that:Tests 2 failed | 14 passed (16), both failures fromassertRendered. That confirms the card's own "no false green today" claim from the other side: those two blocks were always earning their data-reach verdicts, and it was the crash that was invisible. Mutation confirmed on disk before the run and restored by anEXIT/INT/TERMtrap.The durable half landed
assertRenderednow runs ahead of the branch split, covering all 14 candidates, matching the sibling proberecord-block-record-reach.test.tsx. The in-code comment that deferred this to #3840 is gone, replaced by the reasoning for why a crash mis-reads on both branches — on the ledgered branch a crash confirms the ledger entry; on the other it points the reader at wiring that is fine. And the lesson list got its fifth and sixth entries with the spec quotes inline, so the next reader can check the claim instead of trusting it. That was ruling 3 and it was met in full.Two findings filed, both verified open
#5939 (the⚠️ #5939 is the more interesting one: it is a class, not an instance — any block declaring a prop as bare
formTypedeclaration divergence — an out-of-vocabulary value silently matches no branch) and #5940 (getObjectSchema(undefined)for a detail collection whose object was never resolved). Correctly filed unassigned rather than fixed here.stringwhere the renderer switches on an enum has the same hole.⏳ Landing
mergeable_state: unstable— checks are still running on58dda7a. Enqueue resource is every check green, ⛔ not the required subset, so this stays draft until they land, then I flip ready and enqueue. Nothing is failing.Housekeeping, ⛔ not this PR's fault: the report arrived twice (
5390087791lost its HTML-comment marker to the body sanitizer;5390094268carries the literal-text fallback, verified by read-back). No comment-edit path exists from this seat.Generated by Claude Code