You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
ObjectView invents startDateField: 'due_date' / titleField: 'name' for views with no calendar: block — the guess short-circuits ObjectCalendar's own refusal screen and piles every record on today #7029
Ruled on objectstack#13748 (2026-08-31, director batch #19, option A — fix both halves). This is the objectui half; the spec half (cross-field validation) is objectstack#13817.
The three-layer chain (measured)
spec passes the half-written declaration — allowedVisualizations and the calendar: block are each optional with no cross-field check (objectstack#13817 closes this);
ObjectView invents a config — packages/app-shell/src/views/ObjectView.tsx:2220: startDateField: viewDef.calendar?.startDateField || 'due_date', titleField: … || 'name' — invented field names for a view that declared none;
the renderer falls back silently — packages/plugin-calendar/src/ObjectCalendar.tsx:445: start: startDate ? new Date(startDate) : new Date() — a record whose invented field is absent lands on "today".
The renderer HAS a refusal screen — ObjectCalendar.tsx:657, "Calendar configuration required. Please specify startDateField and titleField." — but it is unreachable from this route: layer 2 always synthesizes a config that looks complete. Measured on hotcrm (crm_leave_request, real fields start_date / end_date): all 9 records piled on today's cell, titles resolved via the display-name chain — a plausible, fully wrong screen with zero signal to the author.
Deliverable
Remove the invented defaults. A view whose calendar: block is missing passes NO calendar config through, so the existing refusal screen catches it — or disable the calendar toggle for such views; implementer picks the mechanically cleaner, ⛔ either way no invented field names. This makes the runtime honest independent of which spec version the host pins.
⚠️ Views currently rendering "coincidentally right" via the due_date guess (objects that really carry a due_date field) flip to the refusal screen — that is the ruled loud-over-silent direction; name any hits in the PR rather than discovering them after.
Refs: objectstack#13748 (phenomenon record, closed by the ruling), objectstack#13817 (spec half), objectstack#13751 (unrelated navigation-step bug on the same surface, separately queued).
{
"issue": 7029,
"status": "done",
"branch": "claude/issue-7029-objectview-invented-calendar-fields",
"pr": "https://github.com/objectstack-ai/objectui/pull/7062",
"pr_url": "https://github.com/objectstack-ai/objectui/pull/7062",
"premise_still_valid": true,
"mechanic_chosen": "Delete the invented literals (not a separate toggle-disable branch).",
"why": "The two ruled spellings converge: ADR-0047's capability gate in ListView.availableViews already offers Calendar only when a start-date binding resolves, so deleting the fabrication disables the toggle for unconfigured views for free, with no second mechanism. It also converges the calendar on the shape its siblings already have - timelineViewOptions in the same file (objectui#3129 retired this exact 'due_date' literal from the timeline axis, exported helper plus pin test, copied here as calendarViewOptions), the kanban detector (ADR-0085), and defaultCalendarFromObject (a binding or undefined).",
"no_invented_names": true,
"faces_fixed": [
"packages/app-shell/src/views/ObjectView.tsx - the card's named site; 'due_date'/'name' replaced by exported calendarViewOptions(viewDef), which returns the declared block or undefined so no `calendar` key is emitted at all",
"packages/plugin-list/src/ListView.tsx calendar branch - 'start_date'/'end_date' floors removed; REQUIRED, not scope creep: it sits downstream on the same route, so with only face 1 fixed 'start_date' takes over as the fabricated name one layer down (measured on unmodified main)",
"packages/plugin-view/src/ObjectView.tsx generateViewSchema - 'start_date'/'end_date'/'name' removed; a SECOND independent route (runs when no host supplies renderListView, bypasses ListView entirely), so leaving it would have made the PR's own claim false in the repo it merged into. Bounded-in-place criteria all hold: same defect class, shape pinned by the ruling, no competing branch holds the file, same gate family."
],
"behaviour_flips_enumerated": [
{
"view": "examples/schema-catalog/src/schemas/plugin-view/object-view-named-views.json - object-view over `users`, saved views `directory` and `contacts`, neither declaring a calendar block",
"was": "Calendar AND Timeline offered in the switcher (Timeline accepts a calendar binding as a legitimate axis, so the fabricated name answered for it too); choosing Calendar rendered every user piled on today's cell, keyed on a `due_date` field the `users` object does not have",
"now": "Neither Calendar nor Timeline is offered for these two views; the grid is unchanged"
},
{
"view": "Every console object view with no calendar block, via app-shell/ObjectView - the hotcrm crm_leave_request case the card measured",
"was": "Calendar toggle live on every object view in the product; nine records on today's cell with titles resolved through the display-name chain",
"now": "Toggle absent; a view forced onto the calendar renderer reaches the existing refusal screen"
},
{
"view": "A view with no calendar block on an object that really carries a `due_date` field - the 'coincidentally right' case the card asked to be named",
"was": "Rendered by luck, on a field name the view never wrote",
"now": "Refuses until its calendar block is written. NO IN-REPO INSTANCE: the only two due_date hits outside test code are a FORM fixture (fields-formula/date-calculation.json, never reaches a calendar) and record DATA in e2e/live/record-history-display.spec.ts, not a view config. Named because it is the flip a downstream app will feel."
}
],
"correct_views_unaffected": true,
"correct_views_evidence": "Each of the three faces carries a declared-config CONTROL case asserting identical forwarding (spec-canonical `calendar` block, legacy `options.calendar` nesting, and a partially declared block). ObjectCalendar.unconfiguredRefusal-7029 additionally renders a correctly configured calendar and asserts its events appear and the refusal does not. Written deliberately because a fix that refused EVERYTHING would pass a refusal-only test.",
"new_date_445_disposition": "SEPARATE FINDING - reported, deliberately NOT fixed. It answers a different question from the ruled one: 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 as unscheduled / badge it) - choosing one would be inventing a third direction the card forbids. After this PR it can only fire for a declared field, a much narrower situation than the one measured.",
"defect_reproduced_before_fix": {
"how": "Comparison worktree pinned at the branch point e33b44796 (unmodified main), installed and run there, then removed. Two probes: (a) the committed ListView calendar-binding test; (b) a scratch test 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 no due_date at all.",
"observed": "(a) AssertionError: expected 'start_date' to be undefined (twice), and expected 'end_date' to be undefined - ListView fabricates a binding for a view that declared none, which is why fixing face 1 alone would have been inert. (b) PASSED on main: the refusal screen was ABSENT and both records rendered - the plausible, fully wrong screen, confirmed. The same three files are green on this branch (20/20)."
},
"reverse_verification": "Run on face 3 (the one the card did not name, so it carries the evidence). Fix committed FIRST, then the three || floors restored by a trap-guarded script with absolute REPO_ROOT paths. Mutation proven on disk in both directions (injected form count 1, old form count 0, blob 48582ab4 -> e1a15cff) before reading any result. Observed: 2 failed / 1 passed - the predicted direction, with the fully-declared CONTROL green in both worlds. Restore leg proven byte-identical to HEAD: `git diff HEAD` empty and hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob.",
"summary": "Deleted the invented calendar field names from all three faces that produced them, on both routes to ObjectCalendar, so the renderer's existing refusal screen becomes reachable for a view that declared no calendar block. Added a changeset and four test files (one per face plus the renderer seam), and retargeted the objectui#6557 titleFieldConvergence fixture, whose calendar column pinned the exact `|| 'name'` floor this card deletes. The silent new Date() fallback at ObjectCalendar.tsx:445 is reported as a separate finding, not fixed.",
"tests": "All at final commit 680fea795. vitest (root-relative canonical form): `pnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view` plus the 53 app-shell files that can see ObjectView.tsx -> 'Test Files 155 passed (155) / Tests 1879 passed (1879)', exit 0 captured before any pipe. type-check over app-shell, plugin-list, plugin-calendar, plugin-view -> all four 'Done', exit 0; it runs `tsc --noEmit && tsc -p tsconfig.test.json` and --listFiles confirms all four new test files are inside their package's test program, so the clean typecheck covers the new tests too. eslint plain form (never --no-inline-config) over the WHOLE repo: 4053 files, 0 errors, 11597 warnings (the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors). Control-byte grep over every changed file: no matches. Declared narrowing: app-shell has 585 test files, past the container's foreground ceiling, so its half was narrowed to the receptive set - every test importing or reading ObjectView.tsx unioned with every app-shell test mentioning calendar - sound because the diff changes exactly one externally observable thing (the options.calendar key) plus one added export, unobservable without importing the file. CI runs the full farm regardless.",
"checks_run": [
{
"name": "affected-package vitest",
"command": "pnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view (+53 receptive app-shell files)",
"result": "PASS - Test Files 155 passed (155), Tests 1879 passed (1879)"
},
{
"name": "new tests only",
"command": "pnpm exec vitest run (the 4 new 7029 test files)",
"result": "PASS - 23 tests across 4 files"
},
{
"name": "typecheck",
"command": "pnpm --workspace-concurrency=2 --filter app-shell/plugin-list/plugin-calendar/plugin-view run type-check",
"result": "PASS - all four 'Done', exit 0 (after building the dependency closures first; the fresh worktree had no dist/*.d.ts and typecheck read that as TS2307)"
},
{
"name": "eslint",
"command": "pnpm exec eslint . (plain form)",
"result": "PASS - 4053 files, 0 errors, 11597 pre-existing warnings"
},
{
"name": "control bytes",
"command": "grep -naP over all changed files",
"result": "PASS - no matches"
},
{
"name": "reverse verification (face 3 ablation)",
"command": "trap-guarded restore of the three || floors, mutation proven on disk, then vitest",
"result": "RED as predicted - 2 failed / 1 passed; restore proven byte-identical to HEAD"
},
{
"name": "reproduction on unmodified main",
"command": "comparison worktree at e33b44796, vitest",
"result": "DEFECT CONFIRMED - ListView fabricates 'start_date'; ObjectCalendar shows no refusal and renders both records on an absent field"
}
],
"claim_comment_url": "https://github.com/objectstack-ai/objectui/issues/7029#issuecomment-5480421907",
"github_api_ok": false,
"mcp_calls": "4 - issue_read(get), issue_read(get_comments), add_issue_comment(claim), search_issues(FAILED, rate-limited). All subsequent GitHub work went through repo-scoped REST.",
"out_of_scope_findings": [
"NOT FILED - needs PM to file, see blockers. The silent `new Date()` fallback at packages/plugin-calendar/src/ObjectCalendar.tsx:445: a record with no value in the calendar's DECLARED date field silently lands on today instead of being surfaced. Layer 3 of this card's measured chain, deliberately left; needs a product decision (drop / unscheduled bucket / badge).",
"NOT FILED - needs PM to file, see blockers. The identical fabrication survives on the gantt and timeline branches of all three faces: app-shell/ObjectView.tsx:2302-2304 ('start_date'/'end_date'/'name'), plugin-list/ListView.tsx:2309 ('created_at') and :2325-2326, plugin-view/ObjectView.tsx:1339 ('created_at') and :1347-1350 ('start_date'/'end_date'/'progress'/'dependencies'). Same defect class, different visualizations. Note 'created_at' is the very literal objectui#3129 retired at ONE face and left at two others."
],
"open_questions": [],
"blockers": [
"GitHub API rate limit on the shared MCP identity (user ID 318158314): `search_issues` returned 'API rate limit already exceeded'. The dispatch asked me to report this immediately - reporting it here.",
"Consequently I could NOT duplicate-check the two out-of-scope findings above. The MCP search channel is rate-limited and the REST /search/issues path is blocked by the agent proxy ('sessions are bound to their configured repositories. Use repository-scoped endpoints'), and paging the repo-scoped issues list would be the wide-table scan the dispatch contract forbids. Per the contract's first-class exit I am handing both findings to the PM to file rather than filing blind. Nothing else was blocked: repo-scoped REST reads and writes are healthy, which is how the claim comment, this report and the draft PR were posted.",
"NOT a blocker, flagged per instruction: assignees are still empty on the issue - the PM sets them when its limit clears. I did not touch them."
]
}
Activity
claude commentedon Aug 31, 2026
os-dev-report
{ "issue": 7029, "status": "done", "branch": "claude/issue-7029-objectview-invented-calendar-fields", "pr": "https://github.com/objectstack-ai/objectui/pull/7062", "pr_url": "https://github.com/objectstack-ai/objectui/pull/7062", "premise_still_valid": true, "mechanic_chosen": "Delete the invented literals (not a separate toggle-disable branch).", "why": "The two ruled spellings converge: ADR-0047's capability gate in ListView.availableViews already offers Calendar only when a start-date binding resolves, so deleting the fabrication disables the toggle for unconfigured views for free, with no second mechanism. It also converges the calendar on the shape its siblings already have - timelineViewOptions in the same file (objectui#3129 retired this exact 'due_date' literal from the timeline axis, exported helper plus pin test, copied here as calendarViewOptions), the kanban detector (ADR-0085), and defaultCalendarFromObject (a binding or undefined).", "no_invented_names": true, "faces_fixed": [ "packages/app-shell/src/views/ObjectView.tsx - the card's named site; 'due_date'/'name' replaced by exported calendarViewOptions(viewDef), which returns the declared block or undefined so no `calendar` key is emitted at all", "packages/plugin-list/src/ListView.tsx calendar branch - 'start_date'/'end_date' floors removed; REQUIRED, not scope creep: it sits downstream on the same route, so with only face 1 fixed 'start_date' takes over as the fabricated name one layer down (measured on unmodified main)", "packages/plugin-view/src/ObjectView.tsx generateViewSchema - 'start_date'/'end_date'/'name' removed; a SECOND independent route (runs when no host supplies renderListView, bypasses ListView entirely), so leaving it would have made the PR's own claim false in the repo it merged into. Bounded-in-place criteria all hold: same defect class, shape pinned by the ruling, no competing branch holds the file, same gate family." ], "behaviour_flips_enumerated": [ { "view": "examples/schema-catalog/src/schemas/plugin-view/object-view-named-views.json - object-view over `users`, saved views `directory` and `contacts`, neither declaring a calendar block", "was": "Calendar AND Timeline offered in the switcher (Timeline accepts a calendar binding as a legitimate axis, so the fabricated name answered for it too); choosing Calendar rendered every user piled on today's cell, keyed on a `due_date` field the `users` object does not have", "now": "Neither Calendar nor Timeline is offered for these two views; the grid is unchanged" }, { "view": "Every console object view with no calendar block, via app-shell/ObjectView - the hotcrm crm_leave_request case the card measured", "was": "Calendar toggle live on every object view in the product; nine records on today's cell with titles resolved through the display-name chain", "now": "Toggle absent; a view forced onto the calendar renderer reaches the existing refusal screen" }, { "view": "A view with no calendar block on an object that really carries a `due_date` field - the 'coincidentally right' case the card asked to be named", "was": "Rendered by luck, on a field name the view never wrote", "now": "Refuses until its calendar block is written. NO IN-REPO INSTANCE: the only two due_date hits outside test code are a FORM fixture (fields-formula/date-calculation.json, never reaches a calendar) and record DATA in e2e/live/record-history-display.spec.ts, not a view config. Named because it is the flip a downstream app will feel." } ], "correct_views_unaffected": true, "correct_views_evidence": "Each of the three faces carries a declared-config CONTROL case asserting identical forwarding (spec-canonical `calendar` block, legacy `options.calendar` nesting, and a partially declared block). ObjectCalendar.unconfiguredRefusal-7029 additionally renders a correctly configured calendar and asserts its events appear and the refusal does not. Written deliberately because a fix that refused EVERYTHING would pass a refusal-only test.", "new_date_445_disposition": "SEPARATE FINDING - reported, deliberately NOT fixed. It answers a different question from the ruled one: 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 as unscheduled / badge it) - choosing one would be inventing a third direction the card forbids. After this PR it can only fire for a declared field, a much narrower situation than the one measured.", "defect_reproduced_before_fix": { "how": "Comparison worktree pinned at the branch point e33b44796 (unmodified main), installed and run there, then removed. Two probes: (a) the committed ListView calendar-binding test; (b) a scratch test 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 no due_date at all.", "observed": "(a) AssertionError: expected 'start_date' to be undefined (twice), and expected 'end_date' to be undefined - ListView fabricates a binding for a view that declared none, which is why fixing face 1 alone would have been inert. (b) PASSED on main: the refusal screen was ABSENT and both records rendered - the plausible, fully wrong screen, confirmed. The same three files are green on this branch (20/20)." }, "reverse_verification": "Run on face 3 (the one the card did not name, so it carries the evidence). Fix committed FIRST, then the three || floors restored by a trap-guarded script with absolute REPO_ROOT paths. Mutation proven on disk in both directions (injected form count 1, old form count 0, blob 48582ab4 -> e1a15cff) before reading any result. Observed: 2 failed / 1 passed - the predicted direction, with the fully-declared CONTROL green in both worlds. Restore leg proven byte-identical to HEAD: `git diff HEAD` empty and hash back to 48582ab47a15452fc4b46edd4fe1f71b96d275cf, equal to the HEAD blob.", "summary": "Deleted the invented calendar field names from all three faces that produced them, on both routes to ObjectCalendar, so the renderer's existing refusal screen becomes reachable for a view that declared no calendar block. Added a changeset and four test files (one per face plus the renderer seam), and retargeted the objectui#6557 titleFieldConvergence fixture, whose calendar column pinned the exact `|| 'name'` floor this card deletes. The silent new Date() fallback at ObjectCalendar.tsx:445 is reported as a separate finding, not fixed.", "tests": "All at final commit 680fea795. vitest (root-relative canonical form): `pnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view` plus the 53 app-shell files that can see ObjectView.tsx -> 'Test Files 155 passed (155) / Tests 1879 passed (1879)', exit 0 captured before any pipe. type-check over app-shell, plugin-list, plugin-calendar, plugin-view -> all four 'Done', exit 0; it runs `tsc --noEmit && tsc -p tsconfig.test.json` and --listFiles confirms all four new test files are inside their package's test program, so the clean typecheck covers the new tests too. eslint plain form (never --no-inline-config) over the WHOLE repo: 4053 files, 0 errors, 11597 warnings (the repo's pre-existing no-explicit-any baseline; the four new test files contribute 0 errors). Control-byte grep over every changed file: no matches. Declared narrowing: app-shell has 585 test files, past the container's foreground ceiling, so its half was narrowed to the receptive set - every test importing or reading ObjectView.tsx unioned with every app-shell test mentioning calendar - sound because the diff changes exactly one externally observable thing (the options.calendar key) plus one added export, unobservable without importing the file. CI runs the full farm regardless.", "checks_run": [ { "name": "affected-package vitest", "command": "pnpm exec vitest run packages/plugin-list packages/plugin-calendar packages/plugin-view (+53 receptive app-shell files)", "result": "PASS - Test Files 155 passed (155), Tests 1879 passed (1879)" }, { "name": "new tests only", "command": "pnpm exec vitest run (the 4 new 7029 test files)", "result": "PASS - 23 tests across 4 files" }, { "name": "typecheck", "command": "pnpm --workspace-concurrency=2 --filter app-shell/plugin-list/plugin-calendar/plugin-view run type-check", "result": "PASS - all four 'Done', exit 0 (after building the dependency closures first; the fresh worktree had no dist/*.d.ts and typecheck read that as TS2307)" }, { "name": "eslint", "command": "pnpm exec eslint . (plain form)", "result": "PASS - 4053 files, 0 errors, 11597 pre-existing warnings" }, { "name": "control bytes", "command": "grep -naP over all changed files", "result": "PASS - no matches" }, { "name": "reverse verification (face 3 ablation)", "command": "trap-guarded restore of the three || floors, mutation proven on disk, then vitest", "result": "RED as predicted - 2 failed / 1 passed; restore proven byte-identical to HEAD" }, { "name": "reproduction on unmodified main", "command": "comparison worktree at e33b44796, vitest", "result": "DEFECT CONFIRMED - ListView fabricates 'start_date'; ObjectCalendar shows no refusal and renders both records on an absent field" } ], "claim_comment_url": "https://github.com/objectstack-ai/objectui/issues/7029#issuecomment-5480421907", "github_api_ok": false, "mcp_calls": "4 - issue_read(get), issue_read(get_comments), add_issue_comment(claim), search_issues(FAILED, rate-limited). All subsequent GitHub work went through repo-scoped REST.", "out_of_scope_findings": [ "NOT FILED - needs PM to file, see blockers. The silent `new Date()` fallback at packages/plugin-calendar/src/ObjectCalendar.tsx:445: a record with no value in the calendar's DECLARED date field silently lands on today instead of being surfaced. Layer 3 of this card's measured chain, deliberately left; needs a product decision (drop / unscheduled bucket / badge).", "NOT FILED - needs PM to file, see blockers. The identical fabrication survives on the gantt and timeline branches of all three faces: app-shell/ObjectView.tsx:2302-2304 ('start_date'/'end_date'/'name'), plugin-list/ListView.tsx:2309 ('created_at') and :2325-2326, plugin-view/ObjectView.tsx:1339 ('created_at') and :1347-1350 ('start_date'/'end_date'/'progress'/'dependencies'). Same defect class, different visualizations. Note 'created_at' is the very literal objectui#3129 retired at ONE face and left at two others." ], "open_questions": [], "blockers": [ "GitHub API rate limit on the shared MCP identity (user ID 318158314): `search_issues` returned 'API rate limit already exceeded'. The dispatch asked me to report this immediately - reporting it here.", "Consequently I could NOT duplicate-check the two out-of-scope findings above. The MCP search channel is rate-limited and the REST /search/issues path is blocked by the agent proxy ('sessions are bound to their configured repositories. Use repository-scoped endpoints'), and paging the repo-scoped issues list would be the wide-table scan the dispatch contract forbids. Per the contract's first-class exit I am handing both findings to the PM to file rather than filing blind. Nothing else was blocked: repo-scoped REST reads and writes are healthy, which is how the claim comment, this report and the draft PR were posted.", "NOT a blocker, flagged per instruction: assignees are still empty on the issue - the PM sets them when its limit clears. I did not touch them." ] }Generated by Claude Code
fix(app-shell,plugin-list,plugin-view): no invented gantt date field …
fix(app-shell,plugin-list,plugin-view): no invented gantt date field …