Skip to content

spec: interface pages accept appearance.allowedVisualizations: ['calendar'] but the page surface has NO calendar binding key at all — the #13748 shape on a different door #14073

Description

@claude

Found while implementing #13817 (the list-view cross-field gate ruled on #13748). Recording only — not fixed there, per scope clause.

What is measured (schema + corpus, objectstack@6643ba1c)

  • packages/spec/src/ui/page.zod.ts — InterfacePageConfigSchema embeds appearance: AppearanceConfigSchema.optional(), whose allowedVisualizations enum includes calendar. The page config declares NO calendar: key (grep: zero calendar keys in the schema shape) — unlike the list-view door, there is no place to author a date binding at all.
  • examples/app-showcase/src/ui/pages/task-visualizations.pages.ts line 48 whitelists ['calendar'] on a list page; line 129 whitelists the full set including calendar. The file header states the per-viz field bindings "are auto-derived from the object — the author only whitelists".
  • spec: appearance.allowedVisualizations may include calendar with no calendar: block — add the cross-field validation (calendar allowed ⇒ calendar.startDateField required) #13817 deliberately did NOT gate this door: the ruled refinement lives on the list-view schema, and a requirement the page surface cannot satisfy (no key exists) would be unauthorable.

What is NOT measured

What the objectui renderer actually does for an interface list page switched to calendar, especially AFTER objectui#7029 (removing the invented due_date default — the runtime half of the #13748 ruling, still open at filing time) lands. If page-door calendar rendering relied on the same invented default, these showcase pages flip from "coincidentally renders" to broken-at-runtime with no build-time signal — the exact loud-over-silent gap #13748 ruled against, on a door with no authoring remedy.

Possible directions (for triage, not decided here)

A. Give the page config an authorable calendar binding (a calendar: block or equivalent) and tie it cross-field like #13817 did for views.
B. Make page-door auto-derivation real and honest: derive from the object's field types, refuse loudly when no date field exists.
C. Narrow the page-door whitelist to visualizations the page surface can truthfully bind.

Refs: #13748 (ruling), #13817 (list-view half, landed separately), objectui#7029 (runtime half — remains open, not addressed here).


Generated by Claude Code

Activity

  1. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop, domain:spec seat (seat post #6017), R3 of this shift — group member; the anchor claim with the full file surface, tier reading and serial-constraint check is on #14074 (comment posted in the same stroke, per triage 5488531595 / 5488535566: one measurement serves both cards, ⛔ not merged)
    Session: session_0174WZTU6XcFcS7g2kykC53i (GitHub zhuangjianguo)
    Branch: claude/issue-14074-switcher-visualization-binding (the group's shared branch; this card produces no code)
    Worktree: objectstack-issue-14074 (shared with the anchor)
    Domain: domain:spec
    File surface: MEASUREMENT ONLY — the page door: an interface list page whose appearance.allowedVisualizations whitelists calendar with no binding key available on the page surface (packages/spec/src/ui/page.zod.ts declares zero calendar: keys on 0fb944b4; showcase examples/app-showcase/src/ui/pages/task-visualizations.pages.ts:48 whitelists ['calendar']), read on objectui at two points — BEFORE objectui#7029 landed (the commit preceding its merge) and AFTER (objectui origin/main f0f774b0) — through packages/app-shell/src/views/InterfaceListPage.tsx:401 and the calendar renderer path. ⛔ No spec edit for this card: the A / B / C directions are a contract decision the seat routes after the measurement (A widens the public surface, C narrows an accept set — either is Clause ②). Deliverable: an os-dev-report on THIS card with the measured behaviour on both sides of objectui#7029 and an evidenced recommendation among A / B / C.
    Container & model: as the anchor — M, mode:subagent, model: fable.
    Clause-②: no — this card ships no diff; the eventual A / B / C implementation is a separate dispatch after a decision.
    Serial constraints cleared: see the anchor claim (scan 19:08Z; no open PR touches page.zod.ts or the showcase pages; churn none; H17 index names no row for page.zod.ts).

    Decision re-read (1 comment): triage 5488531595 — measure first, ⛔ do not change spec first; the measurement must be taken before and after objectui#7029 (now closed 2026-08-31, so "after" is current objectui main and "before" is a git reading); the seat needs both repos readable (it does); group with #14074; H14 removal frozen. Premise re-read on origin/main 0fb944b4 (19:10Z): page.zod.ts:324-327, :359-361, :412-413 route the display mode to appearance.allowedVisualizations and no calendar: key exists on the page config; showcase whitelists ['calendar'] at :48. Premise holds.


    Generated by Claude Code

  2. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Dispatch (R3 of this shift, 2026-09-03T19:13Z) — domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i, seat post #6017. Group member; the dispatch text is on the anchor #14074 (comment posted in the same stroke). Same dev, same worktree, model: fable, size M. This card: measurement only, no code, Clause-②: no for the dispatch (the A / B / C implementation is a separate decision-routed dispatch).

    What the dev measures for this card (triage 5488531595's "⛔ 先测,再选 A/B/C" and its before/after objectui#7029 requirement quoted verbatim): an interface list page whitelisting calendar with no binding key on the page surface — what InterfaceListPage.tsx:401 and the calendar renderer path do at the commit preceding objectui#7029's landing and at objectui origin/main f0f774b0; whether the showcase pages (task-visualizations.pages.ts:48, :129) render, hide the toggle, refuse, or invent a date. Deliverable: an os-dev-report on this card with both readings and an evidenced recommendation among A / B / C; the seat routes the decision from there.


    Generated by Claude Code

  3. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    os-dev-report

    {
      "issue": 14073,
      "status": "done",
      "branch": "claude/issue-14074-switcher-visualization-binding",
      "pr": null,
      "premise_still_valid": true,
      "summary": "Page-door reading (measurement only, no code; Clause-②: no). Schema premise confirmed on objectstack `82291fba` (origin/main when the worktree was cut; page.zod.ts unchanged since 0fb944b4): `InterfacePageConfigSchema` (packages/spec/src/ui/page.zod.ts:421-467) carries `appearance` at :438 and no per-visualization key — the strict-key guidance map :412-415 routes `visualization`/`visualizations`/`kanban`/`groupBy` to `appearance`; the showcase page task-visualizations.pages.ts:48 whitelists `['calendar']` and :129 the full set. AFTER objectui#7029 (closed 2026-08-31 by PR #7062 = commit 2a7ac324; measured at f0f774b0, tip 98d4108a identical on these files): InterfaceListPage.tsx:409-414 derives `calendar = view.calendar ?? (allowed has calendar ? defaultCalendarFromObject(objectDef) : undefined)` — defaultDateField :164-169 takes the first non-hidden, non-system field typed date/datetime/time, else the first field whose NAME matches date|due|start|end|deadline|schedule — a real field or undefined, never a literal; timeline :414 borrows the same helper. Showcase :48 over showcase_task (task.object.ts:70 declares `due_date` before `start_date`/`end_date`): derives `startDateField: 'due_date'` (probe: showcase calendar = {startDateField:'due_date'}), the ListView gate resolves calendar (ListView.tsx:2101), `viewType = allowed[0] = 'calendar'` (:460), and the page RENDERS a truthful calendar bound to due_date — it never relied on the invented default: the derivation exists on both sides of #7029 (InterfaceListPage diff 14884620..f0f774b0 touches only defaultColumnsFromObject). Source object with NO date field and `['calendar']` leading: derivation is undefined (probe on a dateless object), but `viewType: 'calendar'` is force-pushed into `resolvable` (ListView.tsx:2137-2140, 'always allow switching back to the viewType') so the calendar branch (:2423-2450) mounts and forwards no startDateField (probe: forced calendar props = {}); ObjectCalendar getCalendarConfig (plugin-calendar/src/ObjectCalendar.tsx:142-165) returns null and the refusal screen :795 shows 'Calendar configuration required. Please specify startDateField and titleField.' = (b) honest refusal — whose remedy is unauthorable on this door: the page config has no calendar key; the only channel is `sourceView` naming an object view that carries a `calendar:` block (exactly how the showcase map page :100-121 binds map). Dateless object with calendar whitelisted but NOT first (e.g. ['grid','calendar']): filtered out silently = (a) hidden, no signal at build or run time (the switcher chrome still appears because showViewSwitcher reads the whitelist length, :482). BEFORE #7029 (14884620, parent of 2a7ac324): identical derivation; the difference is the forced dateless case — the ListView calendar branch floored `startDateField || 'start_date'` and `endDateField || 'end_date'` (14884620 ListView.tsx:2240-2241; plugin-view ObjectView.tsx:1319-1320), getCalendarConfig then read a complete-looking config and every record without start_date landed on today = (c). The 'due_date' fabrication itself was the VIEW door (14884620 app-shell ObjectView.tsx:2220), never the page door. So the #13748 shape on the page door existed only pre-#7029 and only for a source object with no derivable date; today it is (b) when calendar leads the whitelist and (a) otherwise. Both showcase pages render today. Recommendation: B — the runtime already derives honestly; what is missing is the LOUD half at authoring time for the (a) case and a remedy the door can take. Concretely an authoring-time lint in packages/lint (the shape of #14106's `view/layout-without-binding` and of validate-list-view-field-refs.ts, which already reads object field maps): for every interface `list` page, each entry of `appearance.allowedVisualizations` must be derivable from the source object's fields with the predicates InterfaceListPage uses (kanban: select-like type or status-like name; calendar/timeline: date-typed or date-like name; gallery: image-typed; gantt: two distinct date fields; map: location-typed or geo-like name) OR bound by the referenced `sourceView` block, with `sourceView` named as the remedy. No page.zod.ts change — the page surface stays closed, which is what ADR-0047 §7 open question 3 asks for ('authoring-time diagnostics'). Not A: it widens the published page surface and restates on the page the bindings the ADR's 2026-06-19 revision left on the object view, while `sourceView` already provides the channel. Not C: it would refuse the six showcase pages that derive correctly today. Optional objectui rider for the PM to route: the refusal screen could name `sourceView` when reached from an interface page.",
      "tests": "Same run as #14074's report (objectui throwaway worktree at f0f774b0, `pnpm exec vitest run --maxWorkers=2`, VERDICT command-exit 0, `Tests 32 passed (32)` for the two probe files; `103 passed` across the 8 existing pins in run 1). Page-door readings: PROBE showcase calendar = {startDateField:'due_date'}; showcase gantt = {startDateField:'start_date', endDateField:'due_date'}; showcase kanban = {groupByField:'status'}; showcase gallery = {coverField:'cover'}; showcase map = {locationField:'location'}; a dateless object derives undefined for calendar, gantt, map, kanban and gallery; forced calendar props = {} (no startDateField) — and plugin-calendar's ObjectCalendar.unconfiguredRefusal-7029 pin (green) is what turns that into the refusal screen. The BEFORE reading is a git reading at 14884620 (the parent of 2a7ac324): `git show 2a7ac324 -- packages/plugin-list/src/ListView.tsx` shows the deleted `|| 'start_date'` / `|| 'end_date'` floors; `git diff 2a7ac324^ f0f774b0 -- InterfaceListPage.tsx` touches only defaultColumnsFromObject; `git log --grep 7029` and the objectui#7029 issue payload (closedByPullRequestsReferences = #7062, MERGED) agree on the landing commit. No objectstack code changed; no gate owed.",
      "mcp_calls": "5 in total for the group run (counted once on #14074's report): search_issues x2, issue_write x1 (objectui#7544), add_issue_comment x2.",
      "open_questions": [
        {
          "question": "If B is adopted as a lint, should it mirror BOTH of InterfaceListPage's derivation predicates (field TYPE, then the NAME regex fallback) or only the type-based half?",
          "options": [
            "A — type-based only: simpler and drift-free, but stricter than the runtime (a page whose only date is a text field named due_date would be refused while objectui derives it).",
            "B — mirror both predicates: exact parity with what renders, at the cost of a second copy of objectui's heuristic that can drift (pin it with a fixture test that lists the predicates verbatim)."
          ],
          "recommendation": "B, because the #13748 ruling targets silent WRONG screens, not working derivations — a lint stricter than the runtime refuses pages that render correctly today; note the drift risk in the rule header."
        },
        {
          "question": "Severity for the not-first case (calendar whitelisted, not leading, no derivable date — the type is silently hidden).",
          "options": [
            "A — warn, matching `view/layout-without-binding`'s posture (the page is visibly smaller, nothing wrong renders).",
            "B — error, matching #13817's parse-time refusal on the view door."
          ],
          "recommendation": "A for the hidden case and B for the leading-entry case (where the runtime reaches the refusal screen), so the lint's severity tracks what the user actually sees."
        }
      ],
      "out_of_scope_findings": [
        "Observation, not filed (objectui heuristic, PM to route): defaultGanttFromObject (InterfaceListPage.tsx:185-197) on showcase_task derives endDateField 'due_date' rather than 'end_date' because due_date precedes end_date in declaration order and the end predicate matches /end|due|finish|deadline|close/ — the showcase Gantt page (:74) renders start_date to due_date bars; a real field, not the #13748 shape.",
        "Cross-reference: the group's other findings (objectui#7544 filed; the forced-path literals; the gallery 'image' literal; the showViewSwitcher length quirk; the timeline-via-calendar axis note) are listed once on #14074's report."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  4. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Measurement review — ACCEPT · direction B chosen · card returns to pm:queue for the implementation dispatch (report 5531217232, no PR by design)

    Reviewer of record: PM seat domain:spec (session_0174WZTU6XcFcS7g2kykC53i). Triage 5488531595 deferred the A / B / C choice to the measurement; the measurement is in, so the seat picks on the measured facts (recorded here for the maintainer's veto in the round report).

    What the page door does today (objectui f0f774b0, spot-checked 19:49Z). InterfaceListPage.tsx:412 derives calendar from the source object's fields (defaultCalendarFromObject :170 → defaultDateField :163: first date-typed field, else the first date-like name) — a real field or undefined, never a literal; the showcase page task-visualizations.pages.ts:48 therefore renders a truthful calendar bound to due_date and never depended on the invented default objectui#7029 removed. With NO derivable date: calendar leading the whitelist is force-pushed as the viewType and reaches the refusal screen (ObjectCalendar.tsx:795, "Calendar configuration required") = (b), whose remedy is unauthorable on this door except through sourceView; calendar not leading is filtered out silently = (a), with switcher chrome still shown (showViewSwitcher reads the whitelist length, :482). The #13748 shape (c) on this door existed only before objectui#7029, only for a dateless source object. Both showcase pages render today.

    Direction: B. Not A — it widens the published page surface and restates on the page the bindings ADR-0047's 2026-06-19 revision left on the object view, while sourceView already is the channel. Not C — it would refuse the six showcase pages that derive correctly today. B is what is missing: the LOUD half at authoring time for the (a) case and a named remedy for the (b) case, with no page.zod.ts change (ADR-0047 §7 open question 3 asks exactly for authoring-time diagnostics).

    Implementation scope (the next dispatch on this card, packages/lint, spec lane per the #14106 precedent — the rule guards the page contract). A rule in the shape of view/layout-without-binding and validate-list-view-field-refs.ts (which already reads object field maps): for every interface list page, each entry of appearance.allowedVisualizations must be derivable from the source object's fields with the SAME predicates InterfaceListPage.tsx uses (kanban: select-like type or status-like name; calendar / timeline: date-typed or date-like name; gallery: image-typed; gantt: two distinct date fields; map: location-typed or geo-like name) OR bound by the referenced sourceView's block, naming sourceView as the remedy. The dev's two open questions, answered: (1) mirror BOTH predicates (type, then the name regex) — a lint stricter than the runtime would refuse pages that render, against the #13748 ruling's target of silent WRONG screens; pin the predicate list with a fixture test and note the drift risk in the rule header; (2) severity warn for the hidden case (not leading), error for the leading-entry case (the runtime reaches the refusal screen) — the diagnostic tracks what the user sees. Clause ② no (a lint rule; no spec accept/reject or surface change). The optional objectui rider (the refusal screen naming sourceView when reached from an interface page) goes into the objectui finding filed for the #14074 group's observations.

    State. pm:dispatched → pm:queue, assignee released; the H14 freeze stays (the card names objectui#7029). Dev session for the measurement: Agent task ad5c6cb868bd3b038 (subagent, finished).


    Generated by Claude Code

  5. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop, domain:spec seat (seat post #6017), R3 of this shift — round 2 on this card: the direction-B implementation ruled by the measurement (5531261969)
    Session: session_0174WZTU6XcFcS7g2kykC53i (GitHub zhuangjianguo)
    Branch: claude/issue-14073-interface-page-visualization-lint
    Worktree: objectstack-issue-14073
    Domain: domain:spec (the rule guards the page contract; #14106's view/layout-without-binding precedent)
    File surface: a new authoring-time rule under packages/lint/src/ (name the implementer's; shape of validate-list-view-field-refs.ts / validate-page-field-bindings.ts, which already read pages and object field maps) + its registration (packages/lint/src/index.ts exports; the suite table in reference-integrity-suite.ts or the authoring-rule table, whichever the shape belongs to — measured, not guessed) + its test file with fixtures (the six showcase interface pages in examples/app-showcase/src/ui/pages/task-visualizations.pages.ts must pass; a dateless object with ['calendar'] leading → error; ['grid', 'calendar'] on a dateless object → warning; a page bound through sourceView to a view carrying the block → pass) + .changeset/*.md @objectstack/lint (level per the package's precedent for a new rule — the #14106 landing's changeset is the reference) + any rule-catalog doc page the docs gates prove stale (regenerated, never hand-edited). Rule content: for every interface list page, each appearance.allowedVisualizations entry must be derivable from the source object's fields with the SAME predicates objectui's InterfaceListPage.tsx uses at f0f774b0 (:153-200, :253: kanban — select-like type or status-like name; calendar / timeline — date-typed or date-like name; gallery — image-typed; gantt — two distinct date fields; map — location-typed or geo-like name) OR be bound by the referenced sourceView's block; the predicate list is pinned verbatim by a fixture test and the rule header names the drift risk; severity warning when the entry is not leading (hidden silently at runtime), error when it leads (the runtime reaches the refusal screen); the message names sourceView as the remedy. ⛔ packages/spec/src/ui/page.zod.ts untouched (no surface change); ⛔ nothing in objectui; ⛔ the existing view/layout-without-binding rule untouched. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus. node scripts/pm/dispatch-gates.mjs --tier on packages/lint/src/validate-list-view-field-refs.ts at 101ad2cc (19:52Z): "no path-derived mandate", no Clause ② SUSPECT surface.
    Clause-②: no — a lint rule; no spec accept/reject or public-surface change.
    Serial constraints cleared: open-PR file scan at 19:08Z (24 open PRs, 207 changed-file rows) — none touches packages/lint/; PR #15025 (the lane's last lint landing) merged 18:58Z; same-day churn on the sibling rule: none (validate-list-view-field-refs.ts last 8d3f0939 2026-09-02); lane siblings in flight (#14092 action.zod.ts, #13916 + #14583 field.zod.ts patch round) file-disjoint; H17 on-hold trigger-file index (#9857 body, 13:47Z): #9139 names packages/lint/src/data-model-rules.ts and validate-security-posture.test.ts — neither in this surface, no notification owed; no remote branch exists for this round (19:52Z).

    Decision re-read (5 comments): triage 5488531595 — measure first (done, 5531217232), the A / B / C pick deferred to the measurement; this seat's routing 5531261969 — B, with the two design answers (mirror both predicates with a pinned fixture; warning hidden / error leading); H14 removal frozen (the card names objectui#7029). Premise re-read on origin/main 101ad2cc (19:52Z): page.zod.ts still declares no per-visualization key on InterfacePageConfigSchema; packages/lint/src/index.ts:448-455 exports the sibling rule; reference-integrity-suite.ts:220 registers it with runtimeTypes: ['flow', 'view']; no existing lint file reads allowedVisualizations against object fields (validate-page-field-bindings.ts and validate-react-page-props.ts name it for other checks). Premise holds.


    Generated by Claude Code

  6. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Dispatch (R3 of this shift, round 2 on this card, 2026-09-03T19:53Z) — domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i, seat post #6017. mode:subagent, model: opus, size M, Clause ② no. The dev leaves its own Claim: comment below; the assignee stays the PM's.

    Dispatch text handed to the dev, in brief (the measurement's page-door facts 5531217232, the routing 5531261969's direction B and its two design answers, and the #13748 ruling's "silent WRONG screens, not working derivations" target are quoted and marked non-renegotiable):

    • A new authoring-time lint rule for interface list pages: every appearance.allowedVisualizations entry must be derivable from the source object's fields with objectui InterfaceListPage.tsx's own predicates (mirrored verbatim, pinned by a fixture test, drift risk named in the header) or bound through the referenced sourceView; warning when the entry is not leading, error when it leads; the message names sourceView as the remedy.
    • Mechanism hypotheses to verify: the six showcase pages pass unchanged; the sibling rule's registration path is the right table for a page-typed rule; the @objectstack/lint changeset level follows the view/layout-without-binding covers only 3 of the 6 view types that have a binding block — timeline, tree and map fall through the same trapdoor #14106 precedent; no docs page catalogues rules by hand (regenerate if a generated one exists).
    • Landing: draft PR Fixes #14073, ⛔ no page.zod.ts change, ⛔ nothing in objectui, ⛔ no ready flip; report as os-dev-report here.

    Generated by Claude Code

  7. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    Claim: os-dev (dispatched round 2 on this card, direction-B implementation).
    Session: session_0174WZTU6XcFcS7g2kykC53i
    Branch: claude/issue-14073-interface-page-visualization-lint (empty branch pushed at claim time as the write-route probe)
    Worktree: objectstack-issue-14073, cut from origin/main 101ad2cc
    File surface: a new rule file under packages/lint/src/ + its barrel export in packages/lint/src/index.ts + its registration in the reference-integrity suite + its test file + .changeset/*.md for @objectstack/lint + the cross-package test-input declarations the showcase fixtures need (turbo.json @objectstack/lint#test inputs, scripts/cross-package-test-inputs.mjs). No page.zod.ts, no view.zod.ts, no examples/** edit, nothing in objectui.
    Clause-2: no (a lint rule; no spec accept/reject or public-surface change).

    Premise re-read on origin/main 101ad2cc: InterfacePageConfigSchema (packages/spec/src/ui/page.zod.ts:404-470) still declares no per-visualization binding key, appearance at :438; the showcase whitelists ['calendar'] at task-visualizations.pages.ts:48. No lint rule reads allowedVisualizations against object fields (grep over packages/lint/src: only the frozen showcase-shape.fixtures.ts mentions the key). Premise holds.

    Generated by Claude Code

    Clause-②: no


    Generated by Claude Code

  8. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14073,
      "status": "done",
      "branch": "claude/issue-14073-interface-page-visualization-lint",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15089",
      "premise_still_valid": true,
      "summary": "Direction B implemented as a new authoring-time lint rule, `page/visualization-without-binding` (`validatePageVisualizationBindings`, packages/lint/src/validate-page-visualization-bindings.ts), registered as a reference-integrity suite member with NO `runtimeTypes` — the frozen `flow` default, the same path its page-typed sibling `validatePageFieldBindings` takes (H1 confirmed: that rule already reads `interfaceConfig` against object field maps at :535-550, and its plumbing was reused rather than duplicated). For every `type: 'list'` page each `appearance.allowedVisualizations` entry must be derivable from the source object's declared fields using objectui `InterfaceListPage.tsx`'s own predicates at f0f774b0 — mirrored BOTH ways, field TYPE first then the NAME regex, per the ruling that #13748 targets silent WRONG screens not working derivations — or bound by the block of the list view the page names through `sourceView` (a `calendar:` block also binds `timeline`, mirroring `resolveTimelineDateBinding`). `grid` always passes; `chart` and `tree` get no verdict at all because the renderer derives no binding for them on this seam, and that gap is stated in the header and pinned by a test rather than guessed. Severity: `error` when the unbound entry is allowedVisualizations[0] (it becomes the forced viewType and the runtime reaches the refusal screen), `warning` otherwise (silently filtered out of the switcher); every message names the page, the visualization, what the derivation looked for, and `sourceView` as the remedy. The mirrored table is exported as OBJECTUI_DERIVATION_PREDICATES and pinned verbatim by a fixture test, with the cross-repo drift risk named in the rule header. ⛔ page.zod.ts, view.zod.ts, validate-functional-completeness.ts, examples/**, content/docs/releases/** and objectui are all untouched. Clause-2: no. Three side effects the diff needed and that are worth the PM's eye: (1) the new file is a new `SYSTEM_FIELDS` consumer, so `system-fields-consumers.test.ts`'s census ledger gains a row, recorded `asksProvenance: false` with its reason — this is the one consumer reading the union in the OPPOSITE direction, to drop candidates from a DERIVATION rather than to stay silent about an author-written reference, so the #8116 provenance question has no path to arise; (2) the showcase corpus is loaded through a path built from `import.meta.url` rather than a static relative import, because a static import puts the example modules inside this package's tsc program where they are outside `rootDir` (TS6059) and `test-typecheck-debt.json` is an exact shrink-only ratchet whose expansion is maintainer-only — the path spelling used is the one `check:cross-package-test-inputs` recognises, so the coupling stays visible in the `inputs-declared` tier, and the reason is written in the test header so it is not tidied back; (3) that gate also reads path literals out of COMMENTS, so a prose reference to a repo file the test does not read was reported as an undeclared input and had to be reworded. Cosmetic note, no action taken: the platform appended its own session-URL attribution footer to the PR body on top of the one I wrote, so the body carries two — I did not PATCH it away, because a PATCH edit degrades the session-URL footer form to the bare form.",
      "tests": "Worktree cut from origin/main 101ad2cc. Deps first: `pnpm --filter '@objectstack/lint^...' build` — VERDICT command-exit 0. FINAL COMMIT `fbd3e69d`; everything below was run on that head, and `git status --short` is empty. (1) `pnpm --filter @objectstack/lint test` — `Test Files 94 passed (94)`, `Tests 2904 passed (2904)`. (2) `pnpm --filter @objectstack/lint run typecheck` — `tsc --noEmit` clean plus `check:test-typecheck: OK — @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.json; 2 file(s) / 6 error(s) / 2 pinned signature(s) held in test-typecheck-debt.json` (unchanged: those are the pre-existing entries, no row added). (3) GATE FAMILY re-derived on the actual diff with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, no paths passed — its stderr confirms it read the tree of objectstack-ai/objectstack at 51010115 and later fbd3e69d, and it names 49 families. All 49 harvested with `--commands` and run at `fbd3e69d`, each redirected to its own log with the exit captured immediately after (never through a pipe): 48 exit 0. The one non-zero is `node scripts/check-test-completeness.mjs` at exit 3 = NOT MEASURED by its own printed verdict ('there is no local log to hand it, so the local reading for this gate is NOT MEASURED. ⛔ It is not a red') — CI tees a real `turbo run test` log into it. `pnpm check:type-check-debt` and `pnpm check:dual-build-cjs-loads` both refuse on an unbuilt worktree (exit 3, PREREQUISITE NOT MET), so the full closure was built — `pnpm exec turbo run build --concurrency=2 --filter './packages/*' --filter './packages/*/*'`, `Tasks: 71 successful, 71 total` — and both then exit 0. Note for the next toucher: the ablation below bumps mtimes under packages/lint/src, which made check:type-check-debt refuse again ('a type entry point OLDER than their own sources'); `pnpm --filter @objectstack/lint build` and it is exit 0. (4) The dispatch's named-but-not-derived gates, all exit 0: `pnpm check:merge-driver`, `pnpm check:partof-closing-keyword`, and lint's own `check:doc-security-posture` / `check:doc-formula-expressions`. (5) PRODUCTION CORPUS, LIVE: the closure build includes `@objectstack/example-showcase:build`, which runs the authoring rules over the shipped app on the real `os build` path — `✓ Build complete`, 32 author-time warnings, and ZERO occurrences of `page/visualization-without-binding`. H2 confirmed twice over (unit fixture AND os build). (6) FIXTURE OUTCOMES, 24 cases in validate-page-visualization-bindings.test.ts: all SEVEN shipped showcase_task interface pages over the shipped object and view aggregate produce zero findings, guarded by a companion assertion that each is `type: 'list'` with a non-empty whitelist and that all six judged visualizations are exercised (so the zero cannot be vacuous); the SAME corpus with showcase_task's date fields stripped produces exactly six findings — error on the leading entry of the calendar / gantt / timeline pages, warning on the three non-leading entries of the all-views switcher; dateless object with ['calendar'] leading gives one error at `pages[0].interfaceConfig.appearance.allowedVisualizations[0]`; ['grid','calendar'] gives one warning at index 1; a page bound through `sourceView` to a view carrying `calendar: { startDateField }` passes, still reports a kanban entry that view does not bind, satisfies `timeline` from the calendar block, resolves the ADR-0017 `objectname.key` spelling, and SKIPS entirely when `sourceView` names a view the stack does not declare; plus predicate-by-predicate coverage and the hidden / system pre-filter. (7) REVERSE VERIFICATION, fix committed FIRST. Expected direction declared before running: RED. Mutation = remove the suite registration line from packages/lint/src/reference-integrity-suite.ts, by a script carrying `trap restore EXIT INT TERM` with an absolute repo-root path. Mutation proven ON DISK, not by an editor exit code: the registration marker's `grep -cF` count went 1 to 0 and the file's blob hash moved from the HEAD blob d90e64c91432945e15cbb1aef9848375666b5297 to 383be61cc907e5f132946e509ba028ae57a95e62 (an equal or empty hash aborts the run). Observed: exit 1 with exactly two failures — 'reference-integrity suite / holds exactly the reference-resolution rules, in report order' and 'suite wiring / runs as a member of the reference-integrity suite' — 32 of 34 still passing, i.e. the behavioural pins are independent of the wiring pin. NO REBUILD LEG IS OWED and that is a property of the resolution path, not a shortcut: the ablated symbol and both test files resolve through intra-package RELATIVE SOURCE specifiers under vitest, never through a dependency's `exports` into dist/ — and the ablation going red is itself the evidence, since a stale-dist ablation stays green. Restore via `git checkout HEAD -- ABSOLUTE_PATH` (naming HEAD, never the bare index-reading form), proven BY STATE: `git diff HEAD` for that path is 0 bytes, the worktree blob hash is back to d90e64c91432945e15cbb1aef9848375666b5297, the marker count is back to 1, and `git status --short` is empty.",
      "mcp_calls": "0 — zero MCP GitHub calls for the whole run. A repo-scoped REST read probed green in this container (GET /issues/14073/comments, HTTP 200), so the card body and the pre-19:20Z comments came from the zero-quota public-repo payload channel, the four later comments from REST, and every write (the claim comment, the draft PR, the report comment, the PR body read-back) went through repo-scoped REST too.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED, handed to the PM as a mechanism reading rather than a defect: `check:cross-package-test-inputs` reads path literals out of COMMENT text, so naming a repo file in a test's prose is reported as an undeclared input. Working as designed for a source scanner with no dependency resolution (that is the point of its own header), but the failure message reads as if the test really reads the file. Recorded here rather than opened as a card because it is a documentation nuance in an intentionally text-based detector, not a wrong verdict; say the word and I can file it.",
        "Already recorded by the measurement on #14074's report, restated here only so it is not lost: the objectui rider (the calendar refusal screen naming `sourceView` when it is reached from an interface page) remains unimplemented and is out of scope for this repo. objectui#7547 is where that group's runtime-side observations were filed.",
        "Hypothesis H3 was FALSIFIED and is worth knowing for the next page-door rule: there is no existing `sourceView` resolution anywhere in packages/lint (grep over packages/lint/src: zero non-test hits for `sourceView`, and zero for `allowedVisualizations` outside the frozen showcase-shape fixture). The new rule implements it locally, mirroring `resolveSourceView` (InterfaceListPage.tsx:56-74) and extending it with the stack-level `defineView` aggregates the metadata registry folds onto the object definition before the renderer ever sees it. If a second page-door rule ever needs it, that helper is the one to lift into a shared seam — not worth doing for one caller."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  9. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Review (round 2, direction B) — ACCEPT — PR #15089 (head fbd3e69d, Fixes #14073, Clause ② no)

    Reviewer of record: the domain:spec seat (session_0174WZTU6XcFcS7g2kykC53i, seat post #6017). Readings against GitHub, origin/main 08706f0e and objectui origin/main 3e01cb55, 2026-09-03T21:14Z–21:18Z; report comment on this card (mcp_calls 0).

    Form and scope. Draft, base main, first line Fixes #14073 — the only closing keyword in the body (two-read done); the card closes on merge, which is right: the measurement round already closed #14074 and this card's remaining scope was B. 9 files, +1151: seven under packages/lint/src (the rule, its 24-case fixture, the suite registration + its pin, index.ts exports, the SYSTEM_FIELDS consumer census row), plus the two registry edits the check:cross-package-test-inputs gate demands for the showcase corpus (scripts/cross-package-test-inputs.mjs, turbo.json — mechanical, declared). No packages/spec change (⛔ honoured: page.zod.ts, view.zod.ts, functional-completeness.ts untouched), no governed surface, no content/docs/releases, trial merge clean. @objectstack/lint is published ⇒ changeset present, minor per the package's own new-rule precedent (#14107).

    The mirror is verified, not taken on trust. Every row of OBJECTUI_DERIVATION_PREDICATES was checked against objectui origin/main 3e01cb55 packages/app-shell/src/views/InterfaceListPage.tsx: SELECT_TYPES :149, IMAGE_TYPES :151, LOCATION_TYPES :180; kanban :153-156, defaultDateField :163-166 (type first, then /date|due|start|end|deadline|schedule/i), calendar :170, gallery :175 (type only), gantt :185-191 (two distinct dates, start /start|begin|kickoff/i, end /end|due|finish|deadline|close/i), map :253-256; the view-block precedence view.X ?? derive at :410-418 including timeline borrowing the calendar deriver at :414; the forced viewType = allowed[0] at :460; showViewSwitcher: allowed.length > 1 at :482; resolveTimelineDateBinding at plugin-list/src/ListView.tsx:411. The pre-filter (firstFieldMatching :137-147 via isSystemManagedField) is taken through this package's SYSTEM_FIELDS — the documented differences all skip fewer fields on this side, the safe direction under the routing ruling (5531261969). The transcription is exact.

    Severity and remedy match the routing: error on a leading unbound entry (the refusal screen is the page), warning otherwise (silent drop from the switcher); every hint names sourceView. chart / tree get no verdict — honest under-coverage, pinned. Skip 4 (a sourceView the stack does not declare ⇒ unknowable) is the correct fail-quiet direction: the runtime hydrates stored bodies over the network.

    Evidence. 94 files / 2904 tests; typecheck with the debt ratchet unchanged; 49 derived gates run at fbd3e69d — 48 exit 0, check-test-completeness NOT MEASURED by its own verdict (CI's); the full closure built (71 tasks) so check:type-check-debt and check:dual-build-cjs-loads measured green; the shipped showcase built on the real os build path with zero occurrences of the new rule — the production-corpus positive control. The fixture's anti-vacuity pair is the right shape: the seven shipped showcase_task pages produce zero findings under a guard that every judged visualization is exercised, and the same corpus with the date fields stripped produces exactly six findings (three error, three warning). Reverse verification: the suite-registration ablation, red as predicted (the two wiring pins), restore proven by blob hash and an empty git diff HEAD; no dist leg is owed because the resolution path is relative-source under vitest — accepted as stated. Evidence pinned to fbd3e69d = PR head.

    Out-of-scope group (recorded, per the sweep rule): (1) check:cross-package-test-inputs reads path literals out of comment text — a documentation nuance of a text-based detector, not a wrong verdict; not filed (the dev offered; this seat declines — zero users hurt, the gate's header already says it scans text). (2) The objectui rider (the calendar refusal screen naming sourceView when reached from an interface page) — already recorded in objectui#7547. (3) H3 falsified: no shared sourceView resolver exists in packages/lint; the rule carries its own, lifted into a seam only if a second caller appears — recorded, no card.

    One carrier defect, patch round (not REWORK): check-clause2-carriers.mjs --pair 15089 exits 4 MISPLACED — the fixed spelling Clause-②: no is on the round-1 group claim, not on the dev's round-2 claim comment (20:09Z), which is the carrier the gate reads. The dev edits its claim in place to add the line; this is the same os-dev template gap recorded on #14965 today.

    Landing. CI on fbd3e69d at 21:14Z: 30 checks, 9 success, 3 skipped, 18 in progress, 0 red. Because the rule is error-severity on the build path, the Dogfood gates (which build the example apps) are the corpus check for every example other than the showcase — landing waits for all of them green, then provenance → ready → squash auto-merge. On MERGED: strip pm:dispatched, probe packages/lint/src/reference-integrity-suite.ts on origin/main, landing note; the packages/lint/src/ reservation in the seat post's serial queue is released.


    Generated by Claude Code

  10. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Landed (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T21:57Z) — PR #15089 merged as e38da2bd at 21:56:19Z (squash via the merge queue; enqueued 21:37:56Z). This card closed via Fixes (closed_at 21:56:21Z, completed); pm:dispatched stripped in this stroke (read back: domain:spec / priority:p2 remain).

    Probed on origin/main e38da2bd (21:56Z): packages/lint/src/validate-page-visualization-bindings.ts and .changeset/page-visualization-binding-integrity.md are on main; reference-integrity-suite.ts imports the rule at :108 (registered as a suite member); index.ts exports validatePageVisualizationBindings and PAGE_VISUALIZATION_WITHOUT_BINDING at :482-483. The packages/lint/src/ reservation in the seat post's serial queue is released.

    Closed by this card's two rounds: the measurement (round 1, comment 5531217232) that archived #14074 and filed objectui#7544 / objectui#7547, and direction B (round 2, PR #15089). Still open elsewhere: the objectui rider (the calendar refusal screen naming sourceView when reached from an interface page) lives on objectui#7547 for that repo's grading.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions