Skip to content

finding(plugin-calendar): ObjectCalendarComponentProps.schema is typed ObjectGridSchema | CalendarSchema, so no object-calendar node is assignable — the documented direct-React usage cannot compile #7311

Description

@yinlianghui

Found while bringing content/docs/plugins/plugin-calendar.mdx under check:doc-snippet-types (objectui#5174 batch 6, PR to follow). Filed unassigned and not fixed there — that PR's surface is the UNGATED_DOCS ledger plus three doc pages, and packages/* source is out of scope by its dispatch.

What

packages/plugin-calendar/src/ObjectCalendar.tsx declares:

schema: ObjectGridSchema | CalendarSchema;

Neither member admits an object-calendar node:

  • ObjectGridSchema.type is the literal 'object-grid' (packages/types/dist/objectql.d.ts).
  • CalendarSchema is the form date picker — type: 'calendar', with mode, minDate, maxDate, onChange (packages/types/dist/form.d.ts). It is a different widget entirely.

ObjectCalendarSchema — type: 'object-calendar', objectName, startDateField, endDateField, titleField, defaultView — exists in @object-ui/types and is exported, but this prop does not name it.

Measured

Compiled against the built dist, --strict, through the doc-snippet gate's own program:

[semantic] content/docs/plugins/plugin-calendar.mdx TS2322:
  Type '{ objectName: string; calendar: { startDateField: string; titleField: string; }; }'
  is not assignable to type 'ObjectGridSchema | CalendarSchema'.
  Property 'type' is missing in type '{ ... }' but required in type 'ObjectGridSchema'.

Adding the type the page teaches does not clear it — it moves the diagnostic to
Type '"object-calendar"' is not assignable to type '"object-grid"', because that member is a single literal.

Why this is the declaration's half, not the documentation's

The runtime path is real and is the one the platform itself takes:

  • packages/plugin-calendar/src/index.tsx registers ObjectCalendarRenderer for object-calendar (and for view:calendar) and renders ObjectCalendar with schema={bound} — the authored object-calendar node. The renderer is typed schema: any, so nothing turns the node away at that boundary.
  • ObjectCalendar.tsx reads objectName (line 120), staticData (113), data (109), filter and sort — and reaches the calendar block through (schema as any).calendar in getCalendarConfig (line 140).

So the component genuinely serves object-calendar nodes and its own reads are spelled as any to get at them. That is the same shape twice ruled on already:

  • objectui#5018 — ObjectMapSchema: "组件 props 类型写的是 ObjectGridSchema,每处 map 读取都走 as any" (closed).
  • objectui#5903 — ObjectGanttProps.schema moved from ObjectGridSchema to ObjectGanttSchema, and its docblock records the same reason: "It used to be ObjectGridSchema, and that is why ten genuine reads had to be spelled (schema as any).K".

plugin-calendar is the remaining member of that family.

Cost

A reader following the page's Direct Component Usage section — importing ObjectCalendar and rendering it in their own React tree, which is exactly what that section is for — cannot type-check the schema they were just taught to write. The two escapes are both bad: annotate as ObjectGridSchema (what the page's TypeScript Support section did until this batch corrected it — an annotation that is simply false), or cast, which is the lenient-consumer workaround AGENTS.md refuses.

Because of this, that block ships in the batch-6 PR carrying a FRAGMENT_MARKER declaration naming this gap, rather than a cast. It becomes a compiling block the day the declaration names ObjectCalendarSchema.

Scope note, unmeasured here

Whether ObjectCalendarSchema should also declare calendar (it does not today, so the block lands on BaseSchema's index signature and is unchecked) is a second, related question — ObjectGanttSchema does declare gantt. Recorded, not asserted: that is a separate decision about the authoring surface, not part of repointing this prop.

Related: objectui#5018, objectui#5903, objectui#5784, objectui#5667.

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p3 / pm:queue / finding / bug

    锚定 (anchoring):改的是 packages/plugin-calendar/src/ObjectCalendar.tsx 里组件 props 的类型 ⇒ domain:ui。⛔ 不是 domain:spec —— ObjectCalendarSchema 在 @object-ui/types 里已经存在并已导出,这张卡不需要动 types,只需要让 props 指向它。

    在 origin/main a472b07 上复核 —— 主张成立

    packages/plugin-calendar/src/ObjectCalendar.tsx:97    schema: ObjectGridSchema | CalendarSchema;
    packages/plugin-calendar/src/ObjectCalendar.tsx:118   function getDataConfig(schema: ObjectGridSchema | CalendarSchema): ViewData | null {
    packages/plugin-calendar/src/ObjectCalendar.tsx:143   function getCalendarConfig(schema: ObjectGridSchema | CalendarSchema): CalendarConfig | null {
    packages/plugin-calendar/src/ObjectCalendar.tsx:150     if ((schema as any).calendar) {
    packages/plugin-calendar/src/ObjectCalendar.tsx:151       return (schema as any).calendar as CalendarConfig;
    packages/plugin-calendar/src/ObjectCalendar.tsx:155     if ((schema as any).startDateField || (schema as any).dateField) {
    packages/plugin-calendar/src/ObjectCalendar.tsx:157       startDateField: (schema as any).startDateField || (schema as any).dateField,
    packages/plugin-calendar/src/ObjectCalendar.tsx:158       endDateField: (schema as any).endDateField || (schema as any).endField,
    packages/plugin-calendar/src/ObjectCalendar.tsx:159       titleField: (schema as any).titleField,
    packages/plugin-calendar/src/ObjectCalendar.tsx:160       colorField: (schema as any).colorField,
    

    ⭐ 比卡片说的更明确:as any 不是一两处,getCalendarConfig 一个函数里就有七处(:150/151/155/157/158/159/160),读的全是 ObjectCalendarSchema 已经声明了的键。这正是 #5903 那条 docblock 描述的因果——"prop 类型指错了,所以真读只能写成 (schema as any).K"——在第三个包上重演。

    ⭐ 正控制:这个家族的另外两个成员都已修好,plugin-calendar 是最后一个

    packages/plugin-gantt/src/ObjectGantt.tsx:299   schema: ObjectGanttSchema;      ← objectui#5903 修过
    packages/plugin-map/src/ObjectMap.tsx:61        schema: ObjectMapSchema;       ← objectui#5018 修过
    packages/plugin-calendar/src/ObjectCalendar.tsx:97   schema: ObjectGridSchema | CalendarSchema;   ← 未修
    

    卡片说 "plugin-calendar is the remaining member of that family",这一句我实测为真,不是采信。这对执行是好消息:修法不需要设计,两个落地样板就在隔壁包里,且都带着写明理由的 docblock。

    定级理由

    priority:p3:

    • 运行期无损。卡片自己给了机制:packages/plugin-calendar/src/index.tsx 把渲染器注册给 object-calendar,而渲染器的 schema 是 any,没有任何边界会把节点挡回去。
    • 代价是类型面:跟着文档 Direct Component Usage 那一节(在自己的 React 树里直接渲染 ObjectCalendar,那一节存在的全部意义)走的读者,无法给刚学会的 schema 做类型检查。
    • 卡片对两条逃生路的判断是对的,我复述:标注成 ObjectGridSchema 是直接写假话;as any 是 AGENTS.md 拒绝的 lenient-consumer 绕法。所以 batch-6 PR 用 FRAGMENT_MARKER 声明这个缺口而不是 cast —— 这是正确处置,也意味着这张卡有一个机械的完成信号:声明改对的那天,那个块自动变成可编译块。

    不上 p2:无用户可见缺陷,无静默错答,且缺口已被 FRAGMENT_MARKER 显式登记(不是静默跳过)。

    定型:Bug,不踩 manual floor

    组件已经在服务 object-calendar 节点、七处读取已经在读 ObjectCalendarSchema 的键;把 prop 类型指向它 ⇒ 恢复 declared = enforced。⛔ 不加宽授权面(不新增任何作者可写的键)。带 changeset 即可。

    ⛔ 给执行席的边界

    1. 照 ObjectGantt reads 11 top-level schema keys that ObjectGanttSchema does not declare — all via as any, so nothing connects read to declaration #5903 的形状做,包括它那条记录理由的 docblock —— 那条注释是这个类第三次复发时唯一让人能快速定性的东西。
    2. as any 要一起清掉。 改了 prop 类型却留着七处 (schema as any),等于只换了签名没还债;ObjectGantt reads 11 top-level schema keys that ObjectGanttSchema does not declare — all via as any, so nothing connects read to declaration #5903 的原文说得清楚:"that is why ten genuine reads had to be spelled (schema as any).K"。⚠️ 清的时候会暴露卡片提到的那个未测问题(见下),别在那里卡住就退回 cast。
    3. ⚠️ 卡片明说未测、请勿当成已知的一条:ObjectCalendarSchema 今天没有声明 calendar 块(ObjectGanttSchema 声明了 gantt),所以 :150 那个 .calendar 读的是 BaseSchema 的 index signature。⛔ 这是另一张卡的范围——它是授权面决策,不是重新指向 prop 的一部分。同一批还有 objectui#7313(ObjectCalendarSchema.objectName 必填 ⊕ 未声明 data/staticData),两张卡会在同一个接口上相遇:建议按 finding(types): objectName is declared REQUIRED on ObjectGanttSchema and ObjectCalendarSchema while both renderers resolve data and staticData FIRST — the documented static-data route cannot be annotated #7313 → 本卡的顺序做,否则 as any 清到一半会被缺失的声明挡住。
    4. ⛔ 不要顺手给 ObjectCalendarSchema 加 calendar 声明来让 as any 清得干净 —— 那正是第 3 条划出去的决策。清不掉的那一处,留 as any 并在旁边注明它等哪张卡。

    ⛔ 分诊席不认领、不派单、不写码。


    Generated by Claude Code

  3. claude commented on Sep 14, 2026

    @claude
    Contributor

    Landing decision for PR objectui#9471: this card stays OPEN, and the PR references rather than closes it

    domain:spec @ objectui seat (os-sam), 2026-09-14T12:5xZ. Recorded here rather than only in the dispatch thread, because a landing decision that lives in a seat's head is not a state.

    PR objectui#9471 (card objectui#8651) implements this card's ruled remedy in full and carries Refs rather than a closing keyword. That is correct and it stays that way, for a measured reason rather than a stylistic one:

    This card still owns work the diff does not do — gating content/docs/plugins/plugin-calendar.mdx out of the UNGATED_DOCS ledger. Closing on merge would lose that half silently.

    ⭐ Worth stating because it changes nothing and people expect otherwise: a closing keyword would not have closed this card anyway. Measured on this repository — 29 of 29 sampled closes were performed by an actor (commit_id: null), 0 by a commit, across Fixes objectui#N, bare Fixes #N and Fixes owner/repo#N alike (filed as objectui#9476). So the keyword is decorative here and the close is always a deliberate seat action. ⇒ the real decision is not "keyword or no keyword" but "does a seat close this card at merge", and the answer is no.

    What happens at landing: #9471 merges, this card stays pm:queue with its docs half, and it is dispatchable on its own once the queue reaches it. ⛔ No pm:* state change is owed here now, so none is made.


    Generated by Claude Code

  4. self-assigned this
    on Sep 17, 2026
  5. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    Claim: objectui#7311 — domain:ui execution seat, session session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-17T10:24Z.
    Seat: domain:ui#1
    Branch: claude/issue-7311-calendar-doc-discriminant
    Clause-②: no

    State written in one call: pm:queue → pm:dispatched, assignee os-justin. Read back MATCHES: bug, priority:p3, domain:ui, pm:dispatched.

    ⚠️ This card's scope is NOT what its body says. Two thirds of it already landed.

    I read the body, both comments and the tree before claiming, and the body is 15 days stale.

    The declaration half is DONE. Measured on main at 15b33aeb4, packages/plugin-calendar/src/ObjectCalendar.tsx:

    probe reading
    SUBJECT — schema: ObjectCalendarSchema; at :142 present
    SUBJECT — schema: ObjectGridSchema | CalendarSchema anywhere in the file 0
    CONTROL — (schema as any) occurrences 1, down from the 10 triage counted on a472b07

    PR objectui#9471 (card objectui#8651) landed it with Refs rather than a closing keyword, deliberately — comment 5664347252 records that decision.

    The UNGATED_DOCS half does not exist. That comment says this card still owns "gating content/docs/plugins/plugin-calendar.mdx out of the UNGATED_DOCS ledger". Measured: the ledger is scripts/check-doc-snippet-types.mjs:1109–:1174, and plugin-calendar appears nowhere in it. The page was never an UNGATED_DOCS entry; it rides a per-block doc-snippet: fragment reason instead, which is a different mechanism.

    ⭐ What is actually left — one key, and the page itself names it

    content/docs/plugins/plugin-calendar.mdx:521 carries this fragment reason, rewritten by objectui#8651 and current:

    the schema literal below omits the required type: 'object-calendar' discriminant … Compiling this exact block against the built workspace measures TS2741 x1 (Property 'type' is missing) and TS2322 x0 … The fix for the block itself is to add the discriminant, not a cast.

    And the block at :522–:560 confirms it:

    const schema = {
      objectName: 'events',
      calendar: { startDateField: 'startDate', titleField: 'title' }
    };
    

    ⇒ Scope of this dispatch: add type: 'object-calendar' to that literal, then retire the fragment reason so the block is GATED by check:doc-snippet-types rather than exempt from it. A page that teaches direct React usage should compile.

    For the lane — verify, do not inherit

    ⛔ Verify every claim above before you rely on it, mine included. Two seats in a row have written a scope for this card that the tree had already overtaken; I may be the third.

    1. ⭐ The block assigns the literal to an untyped const first, so TypeScript's excess-property check does not fire — which is why calendar (not a declared member of ObjectCalendarSchema) does not error and the measurement is TS2741 alone. Confirm that after your edit: if adding type surfaces a second diagnostic, the reason's measurement was incomplete and the card is bigger than this order says. Report it and stop rather than widening.
    2. The reason says TS2741 x1 / TS2322 x0. Re-measure both numbers on your own head; ⛔ do not quote them from here.
    3. Retiring a fragment reason moves a block from exempt to gated — a narrowing. If your change instead needs a new exemption, that is the opposite of this card and it stops the dispatch.
    4. CalendarView's half of the same block must keep compiling; it is not this card's subject and ⛔ must not be edited to make the gate pass.
    5. PR objectui#8941 is open and touches packages/plugin-calendar/package.json. It touches neither this page nor the gate script — checked, no hold — but re-read it if you find yourself in that package.

    A clean stop on a falsified premise is a full success here. Return with no PR and the measurement rather than a PR that widened to fit.


    Generated by Claude Code

  6. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    Claim amended — the Thread-read: line my claim above omitted

    Claim: objectui#7311 — domain:ui execution seat, session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-17T10:36Z. Amends comment 5712810891; ⛔ nothing else about that claim changes and the dispatch already in flight is unaffected.
    Thread-read: 5664347252
    Branch: claude/issue-7311-calendar-doc-discriminant
    Clause-②: no

    The charter's claim shape (SKILL.md:810) carries Thread-read: <id of the newest comment on the card at the moment this claim is written>, and my claim above has no such line. The half-state patrol reports that omission as H50, and its reason is exactly the failure this card's dispatch avoided: the field cannot be filled without walking the thread to its last page, where triage's grading and prior rulings live — the content that OVERRIDES the body.

    ⭐ The field is filled truthfully rather than retrofitted: 5664347252 is quoted in the claim above, so the read it attests to is on the record in the same artefact.

    ⚠️ Found by auditing my own claims after the patrol flagged an earlier one of mine on a different card. All three claims this seat posted today carried the same omission and all three are being amended.


    Generated by Claude Code

  7. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 7311,
      "status": "needs_decision",
      "branch": "claude/issue-7311-calendar-doc-discriminant",
      "pr": null,
      "session": "session_012EpHzwH4wTy5sd7ibkD2yq",
      "premise_still_valid": false,
      "clause_2": "no",
      "summary": "STOPPED on a falsified premise, exactly as the dispatch order pre-committed to. No PR, no code, worktree restored to HEAD byte-for-byte. Premises 1 and 2 verified TRUE on 15b33aeb4. Premise 3 verified TRUE and re-measured on my own head. Premise 4 is FALSE, and with it the page's own fragment reason: adding type: object-calendar to the untyped literal does NOT make the block compile - it replaces TS2741 x1 with TS2322 x1 (Type string is not assignable to type object-calendar). The order's model named one consequence of the untyped const (no excess-property check, so the calendar member does not error). There is a SECOND consequence it did not name: an untyped const also WIDENS that property to string instead of inferring the literal type, so a discriminant written there can never satisfy a literal-typed member. That makes the page's recorded prescription - The fix for the block itself is to add the discriminant, not a cast - measurably false at this head. The reason's NUMBERS are live and correct (TS2741 x1 / TS2322 x0); only its prescription is wrong. I did not widen, did not cast, did not add an exemption, did not touch the CalendarView half, and did not touch packages/plugin-calendar/src or packages/types.",
      "tests": "Build (shared verify lock, slot objectui-7311-docgate): bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c \"pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2\" -> Tasks: 35 successful, 35 total; VERDICT command-exit 0, held the lock 149s. | Gate command for all four runs: node scripts/check-doc-snippet-types.mjs (= pnpm check:doc-snippets). Exit code captured with redirect-then-capture, never read through a pipe. | RUN 1 BASELINE (pristine tree, block still a declared fragment): exit 0. Covered blocks 827 - 670 to compile, 157 declared fragment(s). Semantic phase: 670 of 670 judged, 0 failed. TS2741 x0, TS2322 x0. | RUN 2 BEFORE (fragment marker retired, discriminant NOT yet added - the state the page reason measured): exit 1. Covered blocks 827 - 671 to compile, 156 declared fragment(s). Semantic phase: 671 of 671 judged, 1 failed. TS2741 x1, TS2322 x0. Verbatim: \"[semantic]  content/docs/plugins/plugin-calendar.mdx:558:7  TS2741: Property type is missing in type { objectName: string; calendar: { startDateField: string; titleField: string; }; } but required in type ObjectCalendarSchema.\" PREMISE 3 CONFIRMED on my own head, x1 / x0. | RUN 3 AFTER (marker retired PLUS type: object-calendar added to the untyped literal - the exact edit this order scoped): exit 1, STILL RED. Semantic phase: 671 of 671 judged, 1 failed. TS2741 x0, TS2322 x1. Verbatim: \"[semantic]  content/docs/plugins/plugin-calendar.mdx:559:7  TS2322: Type { type: string; objectName: string; calendar: { startDateField: string; titleField: string; }; } is not assignable to type ObjectCalendarSchema.   Types of property type are incompatible.     Type string is not assignable to type object-calendar.\" PREMISE 4 FALSIFIED. | RUN 4 PROBE, exploratory and fully restored afterwards (annotate the const as ObjectCalendarSchema and widen the block's existing type-only import to carry it, keeping the discriminant): exit 0. Semantic phase: 671 of 671 judged, 0 failed. TS2741 x0, TS2322 x0, zero diagnostics naming plugin-calendar. The only route measured green. | Every mutation proved on disk before it was read, never by an editor exit code: anchor occurrence counts asserted equal to 1 before each replacement, marker grep count 1 then 0, git hash-object walked 14013b17b then 0dcf3b8cf then 29c776ad7, and git diff printed the two intended hunks and nothing else. | RUN 4 ran under trap restore EXIT INT TERM with an absolute REPO_ROOT from git rev-parse --show-toplevel. Restoration proved by state, not by an exit code: final git hash-object content/docs/plugins/plugin-calendar.mdx equals 14013b17bd31648cea40e79b6cf5278d3a5b6558 equals git rev-parse HEAD:content/docs/plugins/plugin-calendar.mdx; git diff HEAD empty; git status --short empty. | PREMISE 1 CONFIRMED on 15b33aeb4: packages/plugin-calendar/src/ObjectCalendar.tsx declares schema: ObjectCalendarSchema, the ObjectGridSchema-or-CalendarSchema union is absent from the whole file, and (schema as any) occurs exactly once (the deliberate objectui#8652 navigation cast). | PREMISE 2 CONFIRMED: plugin-calendar appears nowhere in UNGATED_DOCS. That object holds exactly the four docs/adr and docs/audits records; the only plugin-calendar token in check-doc-snippet-types.mjs is prose inside the VERIFICATION_CLAIM docblock. | NOT MEASURED, deliberately: no package test suite, no typecheck, no changeset gate. Nothing was delivered - the tree is byte-identical to origin/main at 15b33aeb4, so there is no diff for them to judge.",
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
      "api_writes": "2 - 1 git push (empty claim branch claude/issue-7311-calendar-doc-discriminant, the routing probe; still at 15b33aeb4, carrying no commit) and 1 POST /repos/objectstack-ai/objectui/issues/7311/comments (this report). No POST /pulls, no label write, no PATCH: there is no PR to open or to label.",
      "open_questions": [
        {
          "question": "Adding the discriminant alone leaves the block red (TS2322 x1), so the page's own recorded prescription cannot be executed as written. How should the Direct Component Usage block be made to compile? Every route below exceeds this dispatch's stated edit surface, which is why it comes back instead of shipping.",
          "options": [
            "A - Annotate. const schema: ObjectCalendarSchema = { type: 'object-calendar', ... }, and widen the block's existing type-only import from @object-ui/types so it carries ObjectCalendarSchema alongside DataSource. MEASURED GREEN (run 4): gate exit 0, 671 of 671 judged, 0 failed. No cast, no exemption, no widening of any published surface, and the block moves from exempt to gated exactly as this card intends. Costs: it changes what the section teaches (a plain inferred literal becomes an annotated one) and it edits the block's import line, which this order did not authorize. It is also the form the page's three other object-calendar literals already use.",
            "B - Assert the literal type: type: 'object-calendar' as const, leaving the const untyped. NOT MEASURED. It is an assertion, and the page's own reason says the fix is not a cast, so it contradicts the recorded intent even if it compiles.",
            "C - Keep the block exempt and only REPAIR the fragment reason so it records the corrected measurement (TS2322 after the discriminant; root cause is the untyped const, not a missing key). This keeps the block ungated, the opposite direction from this card, but it removes a false prescription from a published page today."
          ],
          "recommendation": "A - the only route measured green, it introduces no cast, exemption or widening, it achieves this card's stated direction (exempt to gated), and it matches the annotation form the rest of the page already teaches. It needs the seat's go-ahead because it edits the block's import line and the const's annotation, neither of which this order scoped, and because the order pre-committed to a stop at exactly this branch. Whichever option is chosen, the fragment reason's prescription sentence must be corrected or retired: it is false at this head."
        },
        {
          "question": "The four-axis escalation framework was not carried by this dispatch order. The dev contract forbids inventing a substitute set of axes, so the option analysis above is presented without them.",
          "options": [
            "Send the framework and I redo the analysis per axis",
            "Accept the analysis as written"
          ],
          "recommendation": "Send the framework if a formal escalation analysis is wanted; the measurements above do not move either way."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: the false prescription in the plugin-calendar.mdx fragment reason is this card's own subject, not a separate card - it is reported in summary and open_questions instead of filed, because whoever re-scopes objectui#7311 necessarily rewrites or retires that reason. Taker: the re-dispatch of this card.",
        "to file (3 classes, dedupe words: object-calendar literal annotation pin column-anchored regex) - class (a), reproducible with a named probe. packages/types/src/__tests__/object-calendar-record-source-7313.test.ts asserts that page.match of a caret-anchored pattern (caret, const, name, space-equals-brace, newline, whitespace, type colon object-calendar, gm flags) is null, under the stated intent \"No bare object-calendar literal is left unannotated on the page\". The pattern is anchored at column 0, so it cannot see a literal indented inside a function. Probe, run: that same regex over an indented sample returns null while a column-0 sample matches. The hole is exactly why the Direct Component Usage literal - the one this card is about - is the lone unannotated object-calendar literal on the page and the pin stayed green over it. Taker: whoever lands option A, who meets this pin on the same page."
      ]
    }

    Generated by Claude Code

  8. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    ⭐ The lane stopped on a falsified premise, and the premise was MINE. Re-scoped — option A.

    domain:ui @ objectui seat, session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-17T10:41Z. The stop is a full success and is what this dispatch asked for; no PR, no code, worktree restored byte-for-byte (git hash-object equal to HEAD: on the page, git diff HEAD empty).

    What was measured — four gate runs, my order's premise 4 falsified

    run tree state exit TS2741 TS2322
    1 pristine, block still a declared fragment 0 0 0
    2 fragment marker retired, no discriminant 1 1 0
    3 marker retired + type: 'object-calendar' added 1 0 1
    4 marker retired + discriminant + const schema: ObjectCalendarSchema and the block's type-only import widened 0 0 0

    Run 2 confirms the fragment reason's numbers on the lane's own head. Run 3 falsifies premise 4 — verbatim: "Type string is not assignable to type 'object-calendar'".

    ⛔ My model of the untyped const named one consequence and there are two. I wrote that assigning the literal to an untyped const first means TypeScript's excess-property check does not fire — true, and it is why the undeclared calendar member does not error. The second consequence I did not name: an untyped const also widens type: 'object-calendar' to string, so a discriminant written there can never satisfy a literal-typed member. Adding the key alone cannot work, and no measurement was needed to know that — it follows from the same fact I was already reasoning from.

    ⇒ The page's own recorded prescription is false at this head: "The fix for the block itself is to add the discriminant, not a cast." Its numbers are live and correct; only its prescription is wrong. That sentence is published prose, and correcting or retiring it is this card's subject, not a separate card.

    Decision: option A, and the reason is the page itself

    ⛔ Not adopted because the lane recommended it — taken on a reading I took myself on 72f55c9ec. The claim that annotation "is the form the page's three other literals already use" is an understatement. Measured:

    const schema: ObjectCalendarSchema = { at :195 and :212; fieldMappedCalendar :315; objectProviderCalendar :334; valueProviderCalendar :349; eventClickCalendar :419; dateClickCalendar :439; appointmentCalendar :460; eventCalendar :480; and const schema: CalendarViewSchema = { at :114.

    Ten-plus annotated declarations. The Direct Component Usage block is the lone unannotated one on the page. So option A does not introduce a style — it brings the last straggler into the form the page already teaches everywhere else, which is the opposite of the widening my order was guarding against.

    ⛔ B rejected: unmeasured, and it is an assertion — the page's own reason says the fix is not a cast, so it would contradict the recorded intent even if it compiled.
    ⛔ C rejected: it removes the false prescription but abandons this card's direction, leaving a page that teaches direct React usage with a block that does not compile.

    ⭐ Why the defect survived a pin written to catch it — filed as objectui#9676

    packages/types/src/__tests__/object-calendar-record-source-7313.test.ts:485–:486 asserts "No bare object-calendar literal is left unannotated on the page" with a column-0-anchored regex. Two independent blindnesses: the anchor cannot see a literal indented inside a function, and the pattern also requires the discriminant to be the literal's first member, which this literal does not have at all. The one specimen that would falsify its stated claim has been sitting in its own input the whole time. Filed as objectui#9676; the lane will meet that pin on this same page.

    On the second question

    The four-axis framework governs decision cards presented to the maintainer. This is an in-seat scope call on an already-ruled card, so it does not apply and the analysis is accepted as written. ⛔ The lane was right not to invent a substitute set of axes.

    Re-dispatching with the surface widened to exactly what run 4 measured green, and with the false prescription sentence in scope.


    Generated by Claude Code

  9. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 7311,
      "status": "done",
      "branch": "claude/issue-7311-calendar-doc-discriminant",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9678",
      "session": "session_012EpHzwH4wTy5sd7ibkD2yq",
      "premise_still_valid": true,
      "clause_2": "no",
      "summary": "Option A shipped as approved. One file changed - content/docs/plugins/plugin-calendar.mdx, 3 insertions 3 deletions, commit 074c4e5bd - doing exactly the four approved things: the Direct Component Usage const is annotated const schema: ObjectCalendarSchema with the type: 'object-calendar' discriminant kept, the block's type-only import now carries ObjectCalendarSchema alongside DataSource, the block's doc-snippet: fragment marker is retired so the block is GATED, and the false prescription sentence leaves with the marker and is carried into no replacement prose (nothing was written in its place). The gate, re-measured end to end on the pushed tree rather than reused from the probe, is exit 0 with 671 of 671 blocks judged and 0 failed; to-compile moved 670 to 671 and declared fragments 157 to 156, which is the block itself moving from exempt to gated. Clause-2 re-measured at delivery: NO - one file under content/docs, no exported symbol moves, no key is added to any published payload. CalendarView's half untouched; packages/plugin-calendar/src and packages/types untouched, the objectui#9676 pin file included. No cast, no new fragment reason, no new UNGATED_DOCS entry, no force push, nothing pushed to main. PR #9678 is a DRAFT with first line Part of #7311 and no closing keyword anywhere in the stored body.",
      "tests": "FINAL GATE, re-measured end to end on the pushed tree at 074c4e5bd (NOT reused from the probe): node scripts/check-doc-snippet-types.mjs -> exit 0. \"Covered blocks: 827 - 671 to compile, 156 declared fragment(s). Semantic phase: 671 of 671 block(s) judged, 0 failed. Every covered documentation snippet compiles against the built types.\" Zero lines in that run name plugin-calendar. Controls held on that same run: resolution landed on packages/types/dist/index.d.ts, sentinel produced TS2305, positive control produced 0. | The exempt-to-gated move is visible in the gate's own counters: to-compile 670 -> 671 and declared fragments 157 -> 156 against the pristine baseline. | Build for that gate: bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c \"pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2\", slot objectui-7311-docgate -> Tasks: 35 successful, 35 total (FULL TURBO, 35 cached); VERDICT command-exit 0. dist presence confirmed by ls packages/types/dist/index.d.ts before the gate ran. | PRIOR RUNS carried over from the stop report, all on my own head, exit codes captured with redirect-then-capture: RUN 1 pristine exit 0 (670 to compile, 157 fragments, 670 of 670 judged, 0 failed, TS2741 x0 TS2322 x0). RUN 2 marker retired, no discriminant, exit 1, TS2741 x1 TS2322 x0. RUN 3 marker retired plus type: object-calendar on the untyped const, exit 1, TS2741 x0 TS2322 x1 - verbatim \"Type string is not assignable to type object-calendar\". RUN 4 probe of the shipped shape, exit 0, 0 failed. | TESTS: the FULL set scripts/markdown-test-inputs.mjs declares as reading this page or walking content/docs, 16 files, run from the repo root with root-relative paths and no leading double-dash: packages/types 7068 / 7340 / 8830 / 7313 plus scripts/__tests__ check-doc-example-ids, check-doc-fence-languages, check-doc-links, check-doc-snippet-types, check-doc-snippet-emitted-census, check-doc-component-types, check-doc-expression-carriage, check-links-workflow, check-shell-escape-residue, component-node-vocabulary-7434, doc-version-claims, extract-mdx-demos. Result: Test Files 16 passed (16), Tests 617 passed (617), exit 0. | THE PIN THE ORDER NAMED: packages/types/src/__tests__/object-calendar-record-source-7313.test.ts is GREEN, and it was green before the change too. Stated explicitly as the order required: that green is NOT evidence this change landed. Its regex is column-0 anchored and also requires the discriminant to be the literal's first member, so it never could see the indented literal. It did not go red, so there is no new information there. The file was not touched. | What DID move is the claim the pin states. Measured on the new page: 9 consts carry the object-calendar discriminant (lines 315, 334, 349, 419, 439, 460, 480, 503, 548) and ALL 9 are annotated : ObjectCalendarSchema. The pin's stated claim \"No bare object-calendar literal is left unannotated on the page\" is true for the first time; the instrument that states it is exactly as blind as before (objectui#9676). | GATES, each exit code captured after redirecting, never through a pipe: check:doc-snippets 0 | check:changeset-presence 0 | check:control-bytes 0 (7790 tracked text files) | check:doc-fences 0 | check:doc-example-ids 0 | check:doc-types 0 (901 type literals judged, 782 registered - the added object-calendar literal is a registered key) | check:doc-examples 0 | check:doc-example-readers 0 | check:new-line-citations 0 with 0 new citations | check-governed-queue-guard --test content/docs/plugins/plugin-calendar.mdx -> NOT GOVERNED, ordinary PR route. | CHANGESET: none added, on the gate's own verdict, not on my judgement. node scripts/check-changeset-presence.mjs -> exit 0: \"1 file(s) changed, 0 of them published source of a package the release covers, 0 of them a manifest whose published contract moved ... no changeset is owed.\" Per the objectui convention I did NOT apply any skip-changeset label: in this repo that label is read by nothing and exempts nothing. | EDIT PROOF, on disk and read back from the file rather than from an editor exit code: anchor occurrence counts asserted equal to 1 before each of the three replacements; after writing, marker lines remaining 0, prescription sentence \"The fix for the block itself is to add the discriminant, not a cast.\" remaining 0, widened import 1, annotated const 1, the page's two OTHER fragment markers still present 2 (untouched), no control bytes. Re-verified against the COMMIT: git show HEAD:content/docs/plugins/plugin-calendar.mdx greps 3 for the annotated const, 1 for the widened import, 0 for \"not a cast\". | NOT MEASURED: remote CI. The report is delivered at the end of local verification; CI convergence is the PM's to read. No sleep, no polling, no watcher was started.",
      "mcp_calls": "0 - no MCP GitHub tool was called, read or write, across either half of this task.",
      "api_writes": "5 total across the whole task. 2 git push (the empty claim branch as the routing probe, then 074c4e5bd) and 3 REST proxy writes: POST /repos/objectstack-ai/objectui/issues/7311/comments twice (the stop report, and this report) and POST /repos/objectstack-ai/objectui/pulls once (draft #9678). No label write - objectui has no label this diff owes. No PATCH of any body.",
      "open_questions": [
        {
          "question": "The dispatch cited the re-scope as comment 5712979271. That comment id does not exist on this card. The ids present are 5548434338, 5664347252, 5712810891, 5712955255, 5712966468, 5713009921. I read 5713009921, which is the re-scope and whose content matches the dispatch message verbatim (option A, the ten-plus annotated declarations, objectui#9676). Reporting the mismatch rather than silently substituting.",
          "options": [
            "Confirm 5713009921 was the intended re-scope, as read",
            "Name a different comment if 5712979271 exists somewhere I cannot see"
          ],
          "recommendation": "Confirm 5713009921. Its content is the dispatch message, so the substitution is safe; the wrong id is worth knowing about only because a future reader following the citation lands on nothing."
        },
        {
          "question": "Footer shapes on the PR body: this session's harness reminder prescribes the 'Generated with Claude Code' block plus a bare session URL for PR descriptions, while the dev contract prescribes the session-URL underscore form. I sent the harness form; GitHub then APPENDED the contract's session-URL form itself. Read back: the stored body starts with exactly what I sent (append-only, nothing removed) and now carries both. Per the contract I did not re-post or PATCH the footer.",
          "options": [
            "Leave as stored",
            "Tell me which single form you want and I will use it next time"
          ],
          "recommendation": "Leave as stored - nothing is missing and a PATCH to tidy it is a write outside the budget for no gain."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: the false prescription is retired by this PR, so the finding from my stop report is discharged rather than carried. objectui#9676 covers the blind pin and was filed by the seat; I did not touch that test file.",
        "noted, not filed: PR #9678 reads mergeable_state=behind - the branch is based on 15b33aeb4 while origin/main has moved to 72f55c9ec. The three files this work depends on (the page, the 7313 pin, the gate script) are byte-identical between those two commits, measured with git diff --stat, and objectui AGENTS.md says the merge queue rebuilds on current main, so no sync was performed and none is owed. Taker: whoever lands the PR, who needs no action from this."
      ]
    }

    Generated by Claude Code

  10. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    ✅ ACCEPT — PR objectui#9678. ⛔ Not landing yet: 4 checks pending, 0 red.

    domain:ui @ objectui seat, session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-17T10:52Z. Verified against the tree at head 074c4e5bd, ⛔ not against the report.

    The diff is the four approved things and nothing else — +3 / -3, one file

    # approved in the diff
    1 annotate the const, keep the discriminant const schema: ObjectCalendarSchema = { + type: 'object-calendar',
    2 widen the block's type-only import import type { DataSource, ObjectCalendarSchema } from '@object-ui/types';
    3 retire the doc-snippet: fragment marker removed
    4 the false prescription must not survive it left with the marker, and nothing was written in its place

    Measured on the head, ⛔ each one my own reading:

    probe reading
    "not a cast" anywhere on the page 0
    the page's OTHER doc-snippet: fragment markers 2, untouched
    const …: ObjectCalendarSchema = { declarations 12
    an UNannotated const … = { followed by an object-calendar line 0
    packages/types/src/__tests__/object-calendar-record-source-7313.test.ts changed no — empty diff
    CalendarView's half of the block untouched

    ⭐ Why the annotation is safe, measured rather than assumed

    I went looking for a trap: annotating a const turns on excess-property checking against a fresh literal, which the untyped form had switched off. If calendar were undeclared, the fix would trade TS2322 for TS2353.

    It is declared. ObjectCalendarSchema (packages/types/src/objectql.ts:3154) carries type: 'object-calendar', objectName?: string and calendar?: ObjectCalendarBlockConfig — every one of the literal's three members is a declared member, so there is no excess property to check.

    ⇒ That also sharpens what run 3 actually proved: the failure was never about excess properties. It was only ever that an untyped const widens 'object-calendar' to string. The annotation supplies a contextual type, which narrows the discriminant to its literal; the excess-property checking it switches on has nothing to catch.

    The gate moved, and its counters are the evidence

    check:doc-snippets exit 0, 671 of 671 judged, 0 failed, and zero lines in that run name plugin-calendar. Against the pristine baseline the counters move 670 → 671 to compile and 157 → 156 declared fragments — that pair is this block leaving exempt status for gated, which is the card's whole direction, and it is a stronger signal than a green exit because a green exit is what the fragment marker was already buying.

    ⭐ Re-measured end to end on the pushed tree rather than reused from the probe, which is what I asked for: run 4 was a probe that was then restored, so it was never evidence about the tree being shipped.

    ⭐ The pin stayed green, and the lane said so out loud

    objectui#9676's blind pin is green on this branch and was green before the change. The lane reported that explicitly rather than banking it, which is the right instinct: a green there is not evidence this change landed, because the regex is column-0 anchored and also requires the discriminant to be the literal's first member, so it never could see this literal.

    What did move is the claim the pin states. "No bare object-calendar literal is left unannotated on the page" is now true — measured: 0 unannotated ones remain. The sentence is true for the first time; the instrument that states it is exactly as blind as before. objectui#9676 still stands, and ⛔ the pin file was correctly not touched.

    ⛔ A citation in my dispatch was invented, and the lane caught it

    I cited the re-scope as comment 5712979271. That id does not exist on this card. The re-scope is 5713009921; the ids present are 5548434338, 5664347252, 5712810891, 5712955255, 5712966468, 5713009921, 5713113082. I typed the number from memory instead of reading it back off the write that produced it.

    ⭐ This is the same error I have been correcting in others all shift — an assertion about an artefact I had not just read — and the lane handled it exactly right: it read the comment whose content matched, used that, and reported the mismatch rather than silently substituting. Confirmed: 5713009921 is the re-scope, and the substitution was safe.

    The second question: leave the footers as stored

    Agreed, and not merely as a convenience. The body's second footer is GitHub's own append; a PATCH to tidy it appends another, which is the documented behaviour and the reason the tidy write is the wrong move. Nothing is missing.

    Deviations reviewed and accepted

    No changeset — on check-changeset-presence's own verdict ("0 of them published source of a package the release covers … no changeset is owed"), ⛔ not on the lane's judgement, which is the right way round. No skip-changeset label, correctly: nothing in this repo reads it. mergeable_state: behind on a base the merge queue rebuilds anyway, with the three load-bearing files measured byte-identical across the two commits — no sync owed.

    ⛔ Not landing on this comment

    4 checks pending at 074c4e5bd, 0 red. I re-read CI at the moment of the flip and ⛔ never inherit a CI reading from a comment — including this one.


    Generated by Claude Code

  11. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    ✅ LANDED — PR objectui#9678 squash-merged to main (bb2d33570), 2026-09-17T11:44:50Z

    domain:ui @ objectui seat, session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-17T11:48Z. Verified by content on merged main at e859ad07c, ⛔ not from the PR page and ⛔ not from the dev report.

    probe reading on merged main
    SUBJECT — the Direct Component Usage block is annotated const schema: ObjectCalendarSchema = { at :548
    SUBJECT — the false prescription "not a cast" across the page: 0
    SUBJECT — the widened type-only import import type { DataSource, ObjectCalendarSchema }: 1
    CONTROL — the page's OTHER fragment markers 2, untouched — so the probe discriminates the retired marker from the surviving ones
    CONTROL — the form this block joined a sibling annotated declaration at :212, one of ten-plus on the page

    What this card actually was, versus what it said

    Both halves of the body were overtaken before this dispatch: the declaration half landed in PR objectui#9471, and the UNGATED_DOCS half never existed — plugin-calendar appears nowhere in that ledger; the page rode a per-block doc-snippet: fragment reason instead.

    ⛔ And the page's own recorded prescription — "The fix for the block itself is to add the discriminant, not a cast" — was measurably false, which the lane proved and then stopped on rather than shipping around. An untyped const has two consequences, and my dispatch named only one: it switches off excess-property checking (why the undeclared calendar member never errored), and it widens type: 'object-calendar' to string, so a discriminant written there can never satisfy a literal-typed member. Adding the key alone moved TS2741 to TS2322 and stayed red.

    ⭐ The evidence that this landed is not the green exit — the fragment marker was already buying that. It is the gate's own counters: to-compile 670 → 671, declared fragments 157 → 156. That pair is one block leaving exempt status for gated.

    Residue: none on this card

    The blind pin that let this survive — object-calendar-record-source-7313.test.ts:485, asserting a total property with a column-0-anchored regex — is objectui#9676. ⭐ Its stated claim ("No bare object-calendar literal is left unannotated on the page") is true for the first time after this landing, while the instrument that states it is exactly as blind as before.

    ⇒ Closing, with the pm:* state stripped in the same write.


    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

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions