Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062
Conversation
…iews that declared none A view carrying no `calendar:` block used to have a complete-looking calendar config synthesized for it: `ObjectView` fabricated `startDateField: 'due_date'` and `titleField: 'name'`, and `ListView`'s calendar branch floored the same two bindings at `'start_date'` / `'end_date'` one layer down. `ObjectCalendar` decides whether it has a usable configuration by asking whether a start-date binding is PRESENT, so the fabrication short-circuited its own refusal screen — which has existed all along and was simply unreachable from this route. Both faces now forward only what the author declared. With no binding to forward, the capability gate stops offering the Calendar toggle to views that configured none, and a view forced onto the renderer reaches the refusal screen instead of a plausible, fully wrong one. Ruled on objectstack#13748 (director batch #19, option A). The spec half — cross-field validation of a half-written declaration — is objectstack#13817. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
…te too `generateViewSchema` runs when no host supplies `renderListView` — the authored `object-view` element — so it bypasses `ListView` and carried its own copy of the fabrication (`start_date` / `end_date` / `name`). Same defect, same ruling (objectstack#13748: no invented field names either way); fixing only the console route would have left this one producing the same wrong screen. Also retargets `ObjectView.titleFieldConvergence.test.tsx`: its `calendar` column pinned the `|| 'name'` floor this card deletes, and the seam count drops from seven to six. What objectui#6557 owns is unchanged and still pinned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sam
commented
Aug 31, 2026
The two "Filed separately" claims in this PR body now resolve to real cards. They did not when the body was written — the implementing agent's dedupe channel was rate-limited, so it correctly declined to file blind and handed both findings back to the dispatching seat instead. The seat re-measured each against
Both handback claims verified as stated. Two things the seat found while checking them, which are recorded on #7070 because they are worse than the fabrication itself: The
Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because The Auto-merge (squash) armed at head Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
…names, so the refusal screen becomes reachable A view carrying no `gantt:` block used to have a complete-looking date axis synthesized for it: all three faces floored `startDateField` at 'start_date' and `endDateField` at 'end_date' — field names no view had written and most objects do not carry. `ObjectGantt.getGanttConfig` takes its flat branch as soon as BOTH date props are present, so the fabricated pair short-circuited the renderer's own refusal screen, which has existed all along and was simply unreachable from every route. The same fabrication answered ADR-0047's capability gate in `ListView.availableViews`, so the Gantt toggle was live on every object view in the product. The premise was MEASURED before anything was deleted, because #7029's mechanic is only correct where a refusal path exists and that had never been established for this renderer: on the unmodified tree, `ObjectGantt` REFUSES an absent binding — it does not render empty and does not throw. Pinned as the seam in `plugin-gantt/src/ObjectGantt.unconfiguredRefusal-7070.test.tsx`. All three faces now forward only what the author declared. The app-shell inline branch becomes `ganttViewOptions`, the sibling of `calendarViewOptions` and `timelineViewOptions`. Also corrects the objectui#3129 note at the top of `app-shell/ObjectView.tsx`, which certified the gantt branch below it as already using the safe two-rung shape. It did not. The note now states each sibling branch as measured, and says explicitly which fabrication REMAINS — the timeline 'created_at' floor at the two plugin faces — rather than staying silent about it. The #7062 scan control that anchored on this face's 'start_date' floor is re-expressed rather than deleted: a machinery control on the permanent 'name' floor, plus a same-class control on the gallery branch's surviving 'image' floor, with a hand-off note for whoever retires that one. Out of scope, left in place: `progressField` / `dependenciesField`, and the timeline 'created_at' posture conflict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
…names, so the refusal screen becomes reachable (objectstack-ai#7110) * fix(app-shell,plugin-list,plugin-view): no invented gantt date field names, so the refusal screen becomes reachable A view carrying no `gantt:` block used to have a complete-looking date axis synthesized for it: all three faces floored `startDateField` at 'start_date' and `endDateField` at 'end_date' — field names no view had written and most objects do not carry. `ObjectGantt.getGanttConfig` takes its flat branch as soon as BOTH date props are present, so the fabricated pair short-circuited the renderer's own refusal screen, which has existed all along and was simply unreachable from every route. The same fabrication answered ADR-0047's capability gate in `ListView.availableViews`, so the Gantt toggle was live on every object view in the product. The premise was MEASURED before anything was deleted, because objectstack-ai#7029's mechanic is only correct where a refusal path exists and that had never been established for this renderer: on the unmodified tree, `ObjectGantt` REFUSES an absent binding — it does not render empty and does not throw. Pinned as the seam in `plugin-gantt/src/ObjectGantt.unconfiguredRefusal-7070.test.tsx`. All three faces now forward only what the author declared. The app-shell inline branch becomes `ganttViewOptions`, the sibling of `calendarViewOptions` and `timelineViewOptions`. Also corrects the objectui#3129 note at the top of `app-shell/ObjectView.tsx`, which certified the gantt branch below it as already using the safe two-rung shape. It did not. The note now states each sibling branch as measured, and says explicitly which fabrication REMAINS — the timeline 'created_at' floor at the two plugin faces — rather than staying silent about it. The objectstack-ai#7062 scan control that anchored on this face's 'start_date' floor is re-expressed rather than deleted: a machinery control on the permanent 'name' floor, plus a same-class control on the gallery branch's surviving 'image' floor, with a hand-off note for whoever retires that one. Out of scope, left in place: `progressField` / `dependenciesField`, and the timeline 'created_at' posture conflict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM * test(plugin-list): measure the ADR-0047 gate on the exact bag the fixed object page emits The negative capability-gate case supplied no `options.gantt` at all, so it answered "no binding declared" rather than the question objectui#7070 actually asked: does the gate drop the Gantt toggle once the OBJECT PAGE stops fabricating? The fabrication the gate used to read came from `options.gantt`, which after the fix is a bag that still EXISTS (`{ titleField: 'name' }`) but carries no axis. That shape is now pinned directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes#7029
Implements the maintainer ruling on objectstack#13748 (director batch #19, option A) — the objectui half. The spec half (cross-field validation of a half-written declaration) is objectstack#13817 and is not touched here.
The defect
A view carrying no
calendar:block had a complete-looking calendar configuration synthesized for it.ObjectCalendardecides whether it has a usable configuration by asking whether a start-date binding is present, so the fabrication short-circuited its own refusal screen — "Calendar configuration required. Please specify startDateField and titleField." — which has existed all along and was simply unreachable from this route.Mechanic chosen: delete the invented literals
The card delegated the mechanics ("implementer picks the mechanically cleaner, either way no invented field names"). Deleting the literals is the cleaner of the two spellings because it produces the other one for free: ADR-0047's capability gate in
ListView.availableViewsalready offers Calendar only when a start-date binding resolves, so with nothing fabricated to resolve, the toggle disappears for views that configured none — no second mechanism, no new branch. It also converges the calendar on the shape its siblings already have:timelineViewOptionsin the same file (objectui#3129 retired this verydue_dateliteral from the timeline axis, with an exported helper plus a pin test — copied here ascalendarViewOptions),defaultCalendarFromObjectinInterfaceListPage(a binding, orundefined).Three faces were fabricating, on two routes
The card named one. Measurement found the deletion is inert without the other two, so all three are in this PR:
app-shell/src/views/ObjectView.tsx(the card's site)plugin-list/src/ListView.tsxcalendar branchplugin-view/src/ObjectView.tsxgenerateViewSchema'start_date'/'end_date'/'name'Why 2 and 3 are not scope creep. Face 2 sits downstream of face 1 on the same route: with only face 1 fixed,
'start_date'simply takes over as the fabricated name one layer down and the renderer still never sees an absent binding — measured, see the reproduction below, where the ListView spy reads'start_date'on unmodifiedmain. Face 3 is a second, independent route:generateViewSchemaruns precisely when no host suppliesrenderListView(the authoredobject-viewelement), so it bypassesListViewentirely and carried its own copy. Fixing only route A would have shipped a change whose own claim — no invented field names — was false in the repo it merged into. Same defect class, same ruling, mechanically identical edit; no other branch holds either file.Behaviour flips, named rather than discovered in review
The ruled loud-over-silent direction. Enumerated by searching this repo's own metadata, fixtures, examples and e2e specs:
examples/schema-catalog/.../plugin-view/object-view-named-views.json— object-view overusers, two saved views (directory,contacts), neither declaringcalendar:due_datefield theusersobject does not havecalendar:block (the whole product, via face 1) — e.g. the hotcrmcrm_leave_requestcase the card measuredcalendar:block on an object that really carries adue_datefield — "coincidentally right"calendar:block is writtenNo fixture in this repo rides the
due_dateguess. The only twodue_datehits outside test code areexamples/schema-catalog/.../fields-formula/date-calculation.json(a form fixture — never reaches a calendar) ande2e/live/record-history-display.spec.ts(record data for a history assertion, not a view config). So the third row above has no in-repo instance; it is named because it is the flip a downstream app will feel.Correctly configured calendars are unaffected — same fields, same render. Every one of the three faces carries a declared-config CONTROL case, because a fix that refused everything would also pass a refusal-only test.
Fixture triage
app-shell/src/views/ObjectView.titleFieldConvergence.test.tsx(objectui#6557) went red in three places, all of them pinning the literal this card deletes:calendarcolumn asserted'name'for an undeclared view — retargeted toundefined, with the reason recorded in the file's header;What objectui#6557 actually owns is unchanged and still pinned: no seam reads
objectDef, and every seam that still has a floor is a chain of view-declared rungs. Its declared-config control (calendar: 'v_calendar') stayed green throughout — the retarget did not delete coverage.Out of scope, reported not fixed
new Date()atplugin-calendar/src/ObjectCalendar.tsx:445— layer 3 of the measured chain. Judged a separate finding. It answers a different question: not "this view declared no calendar config" (ruled: refuse) but "this record has no value in the date field the author did declare", whose correct behaviour is genuinely undecided — drop the event, bucket it as unscheduled, or badge it — and picking one here would be inventing a direction the card forbids. After this PR it can only fire for a declared field, which is a much narrower situation than the one measured. Filed for triage; deliberately untouched.'start_date'/'end_date'/'progress'/'dependencies', and'created_at'for timeline inplugin-viewandListView— the very literal objectui#3129 retired at one face and left at two others). Same class, different visualizations, whose renderers' refusal semantics are not measured here. Filed separately.Verification
Reproduced first on unmodified
main(a comparison worktree pinned ate33b44796, the branch point, installed and run there):startDateField: 'start_date'for a calendar view that declared nothing —AssertionError: expected 'start_date' to be undefined, twice, plusexpected 'end_date' to be undefinedfor a partially declared block;ObjectCalendarexactly the props face 1 synthesizes (startDateField: 'due_date',titleField: 'name') against records whose real fields arestart_date/end_dateand which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;Reverse verification on face 3 (the one the card did not name, so it carries the evidence): the fix was committed first, then the three
||floors were restored by a trap-guarded script, the mutation was proven on disk both directions (injected=1, old form0, blob48582ab4→e1a15cff), and the suite ran 2 failed / 1 passed — the predicted direction: the "invents NO binding" case and the partial-block control go red, the fully-declared control stays green in both worlds. Restore leg proven byte-identical toHEAD(git diff HEADempty; hash back to48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to theHEADblob).Checks run, all at final commit
680fea795:pnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view+ the 53 app-shell files that can seeObjectView.tsxTest Files 155 passed (155)·Tests 1879 passed (1879)pnpm --filterover app-shell, plugin-list, plugin-calendar, plugin-viewrun type-checkDone, exit 0pnpm exec eslint .(plain form)4053 files, 0 errors, 11597 warnings— warnings are the repo's pre-existingno-explicit-anybaseline; the four new test files contribute 0 errorsgrep -naPover every changed fileTwo scoping notes, declared rather than left implicit. (1) app-shell has 585 test files, well past the container's foreground ceiling, so the app-shell half was narrowed to the receptive set — every test that imports or reads
ObjectView.tsx, unioned with every app-shell test mentioningcalendar(53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (theoptions.calendarkey) plus one added export, and nothing can observe either without importing the file. (2)type-checkrunstsc --noEmit && tsc -p tsconfig.test.json, and--listFilesconfirms all four new test files are inside their package's test program — so "typecheck is clean" is a statement about the new tests too, not merely about the sources. CI runs the full farm regardless.Generated by Claude Code