fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable - #7062

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields
Aug 31, 2026
Merged

fix(app-shell,plugin-list,plugin-view): no invented calendar field names, so the refusal screen becomes reachable#7062
os-sam merged 3 commits into
mainfrom
claude/issue-7029-objectview-invented-calendar-fields

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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. 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 — "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.availableViews already 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:

  • timelineViewOptions in the same file (objectui#3129 retired this very due_date literal from the timeline axis, with an exported helper plus a pin test — copied here as calendarViewOptions),
  • the kanban lane detector (ADR-0085, "never invents a field the object doesn't have"),
  • defaultCalendarFromObject in InterfaceListPage (a binding, or undefined).

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:

#FaceWasNow
1app-shell/src/views/ObjectView.tsx (the card's site)`startDateField: …
2plugin-list/src/ListView.tsx calendar branch`…
3plugin-view/src/ObjectView.tsxgenerateViewSchema'start_date' / 'end_date' / 'name'conditional restatement

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 unmodified main. Face 3 is a second, independent route: generateViewSchema runs precisely when no host supplies renderListView (the authored object-view element), so it bypasses ListView entirely 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:

ViewWasNow
examples/schema-catalog/.../plugin-view/object-view-named-views.json — object-view over users, two saved views (directory, contacts), neither declaring calendar:Calendar (and Timeline, which accepts a calendar binding as a legitimate axis) offered in the switcher; choosing Calendar rendered every user piled on today's cell, keyed on a due_date field the users object does not haveCalendar and Timeline are no longer offered for these two views; the grid is unchanged
Any console object view with no calendar: block (the whole product, via face 1) — e.g. the hotcrm crm_leave_request case the card measuredCalendar toggle live on every object view; nine records on today's cell, titles resolved via the display-name chainToggle absent; a view forced onto the renderer reaches the refusal screen
A view with no calendar: block on an object that really carries a due_date field — "coincidentally right"Rendered by luck, on a field the view never namedRefuses until its calendar: block is written

No fixture in this repo rides the due_date guess. The only two due_date hits outside test code are examples/schema-catalog/.../fields-formula/date-calculation.json (a form fixture — never reaches a calendar) and e2e/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:

  • its calendar column asserted 'name' for an undeclared view — retargeted to undefined, with the reason recorded in the file's header;
  • "there are exactly seven of them" — the seam count is now six, plus a new case pinning that the calendar seam is not among them, so a future re-add fails with the reason rather than an off-by-one.

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

  • The silent new Date() at plugin-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.
  • The identical fabrication on the gantt and timeline branches of all three faces ('start_date' / 'end_date' / 'progress' / 'dependencies', and 'created_at' for timeline in plugin-view and ListView — 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.
  • objectstack#13751 is a separate bug on the same surface and is not addressed here.
  • The refusal screen itself is not redesigned — these changes make it reachable, nothing more.

Verification

Reproduced first on unmodified main (a comparison worktree pinned at e33b44796, the branch point, installed and run there):

  • the ListView spy read startDateField: 'start_date' for a calendar view that declared nothing — AssertionError: expected 'start_date' to be undefined, twice, plus expected 'end_date' to be undefined for a partially declared block;
  • feeding the real ObjectCalendar exactly the props face 1 synthesizes (startDateField: 'due_date', titleField: 'name') against records whose real fields are start_date / end_date and which carry nodue_date: the refusal screen was absent and both records rendered — the plausible, fully wrong screen, confirmed;
  • the same three files are green on this branch.

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 form 0, blob 48582ab4e1a15cff), 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 to HEAD (git diff HEAD empty; hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob).

Checks run, all at final commit 680fea795:

CheckCommandResult
Affected-package testspnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view + the 53 app-shell files that can see ObjectView.tsxTest Files 155 passed (155) · Tests 1879 passed (1879)
Typecheckpnpm --filter over app-shell, plugin-list, plugin-calendar, plugin-view run type-checkall four Done, exit 0
Lintpnpm exec eslint . (plain form)4053 files, 0 errors, 11597 warnings — warnings are the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors
Control bytesgrep -naP over every changed fileno matches

Two 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 mentioning calendar (53 files). The narrowing is sound because the diff changes exactly one thing observable outside that module (the options.calendar key) plus one added export, and nothing can observe either without importing the file. (2) type-check runs tsc --noEmit && tsc -p tsconfig.test.json, and --listFiles confirms 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

…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
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3149.2 KB3191.4 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-ocQr36DP.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)14.51KB5.35KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)247.30KB63.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.07KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.86KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-samClaude

Copy link
Copy Markdown
Collaborator

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 origin/main at 592acafbe and filed them:

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 #3129 note at app-shell/src/views/ObjectView.tsx:139-160 certifies the broken branches as already fixed. It retires the 'due_date' literal, explains exactly why the pattern is harmful, then states: "the same two-rung shape the calendar and gantt branches below already use." The gantt branch 40 lines below uses the one-rung fabrication that note declares retired. This PR makes the calendar half of that sentence true; the gantt half stays false, and the note is what a future fixer will read.

'created_at' is retired-as-harmful at one face and documented-as-intentional at another.ListView.tsx:2292 states: "Deprecated top-level props for backward compat. created_at stays the last resort for a view that declares no date axis anywhere."#7070 routes that as a question rather than presuming which posture wins.

Also recorded on #7070: the mechanic this PR chose may not transfer. Deleting the literals worked here because ObjectCalendar has a refusal screen that becomes reachable and ADR-0047's capability gate drops the toggle for free. Neither premise is measured for ObjectGantt or ObjectTimeline, so #7070 requires establishing their absent-binding semantics before the equivalent edit — rather than copying this PR's success.

The 'start_date' control in this PR's scan test (expect(...).toBeGreaterThan(0)) is correct as written and should stay; #7070 is scoped to be the card that eventually flips it.

Auto-merge (squash) armed at head 680fea795 — 31 checks green, Governed Surface Queue Guard passed, mergeable_state: clean. It was ready and simply had not been armed.


Generated by Claude Code

Merged via the queue into main with commit 2a7ac32Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7029-objectview-invented-calendar-fields branch August 31, 2026 20:12
os-warren pushed a commit that referenced this pull request Sep 1, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@os-sam@claude