Skip to content

Reconcile exportOptions with the spec's new object form (#8010 spec half): false alignment comment, local 'pdf', and the as any streaming read #4535

Description

@hotlong

Follow-up card filed at acceptance of objectstack-ai/objectstack#8010's spec half (PR objectstack-ai/objectstack#8324) by the domain:spec seat (session session_01Euoy6wyfzgiWtgCg4s6JK2), per the maintainer's 2026-08-12 option-A ruling ("the objectui renderer/type reconciliation follows with Blocked-by: on this card").

Blocked-by: objectstack-ai/objectstack#8010

What the spec now says (once #8324 merges)

ListView.exportOptions is a strict object: { formats?, maxRecords?, includeHeaders?, fileNamePrefix?, streaming? } — exactly the five keys ObjectGrid.tsx reads (measured at origin/main@878140b, :1596–:1642). The legacy bare format array is still accepted and lifts to { formats: [...] } at parse. 'pdf' left the format enum (#1301 NOT_PLANNED); declaring it is now a loud parse-time refusal with a prescription.

What objectui owes (measured on 878140b — re-verify on current main)

  1. packages/types/src/objectql.ts:783–785 — the local exportOptions object type and its "Aligned with @objectstack/spec ListViewSchema.exportOptions" comment: the comment was false for the old spec and is nearly-true for the new one; align the type to the spec's five keys (or import/derive from the spec type) and make the comment true.
  2. 'pdf' in the local type/format handling: remove — the platform refuses it at publish now; the runtime console.warn drop path for it becomes dead.
  3. The streaming read (ObjectGrid.tsx ~:1600/:1642, currently through as any): the key is now DECLARED in the spec — type it properly and delete the cast.
  4. Renderer continues to read .formats — with the spec's parse-time array lift, a stored bare-array declaration arrives as the object form through any spec-parsing path; confirm whichever path feeds ObjectGrid its schema (spec-parsed vs raw JSON) and keep the renderer's own array tolerance only if raw un-parsed metadata can still reach it (state which in the PR).

Constraints

  • No new keys beyond the spec's five — a sixth undeclared-but-read key recreates the defect test(scripts): check-shell-escape-residue reds on an agent-tree document no scan root reaches (#7413) #8010 closed.
  • Re-check commands: git grep -n "exportOptions" -- packages/plugin-grid/src/ObjectGrid.tsx packages/types/src/objectql.ts; spec side git grep -n "ListViewExportOptionsSchema" -- ../objectstack/packages/spec/src/ui/view.zod.ts (or the published 17.x dist once the pin bumps).
  • Business context: unblocks the XLSX half of steedos-labs/os-project-titanwind-ehr#1161 (§7.7) end-to-end once both halves land.

Activity

  1. self-assigned this
    on Aug 13, 2026
  2. yinlianghui commented on Aug 13, 2026

    @yinlianghui
    Collaborator

    CLAIM — session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4535-export-options-reconcile. Dispatching a dev agent now.

    PM ruling (delegated decision authority; maintainer veto window open — record objections here): the card's four items, with the Blocked-by resolved by measurement:

    1. Gate-check FIRST: measure whether objectui's pinned @objectstack/spec (node_modules / lockfile version) carries the new ListViewExportOptionsSchema object form (the fix(combobox): honour description, retire defaultValue, pin the name delivery #8324 change). The answer splits the card:
      • Pin carries it ⇒ all four items land: local type aligned to the five keys (prefer importing/deriving from the spec type over restating it — measure whether the spec type is importable from objectui's types package without a dependency-direction violation; if not importable, restate the five keys and make the alignment comment TRUE by citing the spec symbol), 'pdf' removed (its warn-drop path dead), streaming typed and the as any deleted, and item 4's parse-lift confirmation measured (which path feeds ObjectGrid its schema; keep the renderer's array tolerance ONLY if raw un-parsed metadata can still reach it — state which in the PR).
      • Pin does not carry it ⇒ land items 1–3 (objectui-local, forward-compatible: the five-key type IS the new spec shape), keep the renderer's array tolerance with the reason stated, and record item 4 as completing when the pin bumps — the card stays open on that residue with a note, PR titled Part of #4535.
    2. Constraint honored as a TEST: no sixth undeclared-but-read key — a pin asserting the read-key set ⊆ the declared five (the drift-guard pattern), so the defect test(scripts): check-shell-escape-residue reds on an agent-tree document no scan root reaches (#7413) #8010 closed cannot recreate.
    3. Red-first: the as any deletion is type-level red (tsc rejects the untyped read pre-change... measure the direction — actually the cast SUPPRESSES the error, so the red is: removing the cast on unfixed types errors verbatim; post-fix compiles). 'pdf' removal: the warn-path's test (if any) updated as an authorized pin move, declared.
    4. Changesets: '@object-ui/types' per its .d.ts (local type narrowing/alignment — if the published type changes shape, grade minor with the reasoning; if byte-compatible, patch); '@object-ui/plugin-grid' patch expected. Never major.

    Mutual exclusion: ⛔ ObjectGrid.tsx regions owned by landed work (LinkCell #4531, date cells #4544/#4552, kebab) — touch ONLY the exportOptions read region (~:1596–:1642, re-locate by content). packages/types was just touched by #4569 (landed) — base on current main. Disjoint from in-flight #4548 (react+dashboard/report), #4566 (core+DatasetWidget), #4567 (StudioDesignSurface), #4563 (apps/console).


    Generated by Claude Code


    Generated by Claude Code

  3. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    on Aug 21, 2026
  4. self-assigned this
    on Aug 31, 2026
  5. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    Claim: PM loop round 1 (2026-08-31 shift, spec lane cross-repo — maintainer pull of this date: objectui spec-face cards processed by the domain:spec seat)
    Session: session_01PBjwYLS6BciTQW3c9xQiD2
    Branch: claude/issue-4535-export-options-reconcile-2
    Worktree: objectui-issue-4535
    Domain: domain:spec (objectui contract face, three-way split)
    File surface: packages/types/src/objectql.ts (the local exportOptions type + alignment comment) + packages/plugin-grid/src/ObjectGrid.tsx (the exportOptions read region ONLY, ~:1596–:1642 on the card's measured head — re-locate by content) + tests + .changeset/* (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — objectui-local reconciliation to an already-landed, already-released spec contract; parks under needs:contract-review for the review-chain-tier check (opus-build/fable-review compensation).
    Clause-②: no — the spec-side accept/reject change landed and released upstream (#8010 / PR #8324); this is the consumer-leg alignment. Parks for contract review regardless.
    Serial constraints cleared: stale-claim supersession recorded — the 2026-08-13 claim (comment 5280206992, session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4535-export-options-reconcile) is dead by every protocol test: no assignee (already removed), no remote branch (ls-remote zero hits at claim), no PR, 18 days old. Nothing to salvage — zero commits ever reached origin. Its PM ruling remains valid input and its pin-split resolves TODAY to the "pin carries it" arm: installability probe measured at claim — objectui's lockfile installs @objectstack/spec@17.2.0 (published 2026-08-23) and the @objectstack/spec@17.2.0 tag carries the #8010 object form (exportOptions object + parse-time array lift + 'pdf' out of the enum) ⇒ all four card items land in full, Fixes #4535. Cross-lane in-flight (domain:ui seat, 01:46–51Z claims): #6349 batch-1 (types/src/crud.ts/ui-action.ts/data-display.ts/navigation.ts), #6298 (types/src/base.ts), #6318/#6150/#5877/#4664/#6776 — all file-disjoint from this surface; my own in-flight objectui#6594 touches types/src/views.ts+zod/views.zod.ts — disjoint. ObjectGrid.tsx regions owned by landed work (LinkCell #4531, date cells #4544/#4552, kebab) stay untouched per the prior claim's fence, carried forward.

    Parking commitment: PR opens DRAFT; needs:contract-review goes on both carriers in the same stroke the PR exists; review chain owns enqueue.


    Generated by Claude Code

  6. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    DEV CLAIM — session session_01PBjwYLS6BciTQW3c9xQiD2, branch claude/issue-4535-export-options-reconcile-2 (pushed empty at claim; write path probed green), worktree objectui-issue-4535, base b03ba3a (objectui origin/main, 2026-08-31).

    Implementing the four card items under the PM claim of 2026-08-31 (comment 5473174753) — the "pin carries it" arm. The 2026-08-13 claim (comment 5280206992, session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4535-export-options-reconcile) is superseded/dead; its PM ruling is carried forward as input.

    File surface: packages/types/src/objectql.ts (local exportOptions type + alignment comment), packages/plugin-grid/src/ObjectGrid.tsx (exportOptions read region ONLY — LinkCell/date-cell/kebab regions untouched), tests, .changeset/*.


    Generated by Claude Code

  7. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 4535,
      "status": "done",
      "branch": "claude/issue-4535-export-options-reconcile-2",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6957",
      "premise_still_valid": false,
      "premise_detail": "Items 2 and 3 were ALREADY LANDED on origin/main@b03ba3a by changeset 1f9b905 — 'pdf' is out of ListViewExportFormat and ObjectGrid.tsx reads streaming uncast at :2563/:2605, no cast present. Both constraint guards existed too (objectql.exportOptions.test.ts, ObjectGrid.exportOptionsKeys.test.ts). Item 1 was live but for a DIFFERENT reason than the card states: the alignment comment had gone false a second time, on a stale NOTE claiming objectui pins @objectstack/spec@17.0.0-rc.6 — measured, the lockfile installs 17.2.0, which carries the object form. Item 4 was unanswered. The PR lands the surviving part.",
      "summary": "Corrected the stale pin NOTE on ListViewExportOptions (packages/types/src/objectql.ts) and replaced prose-only alignment with a mechanical check: new packages/types/src/__tests__/export-options-spec-parity.test.ts reads the object branch out of the INSTALLED @objectstack/spec at test time and asserts the key set, format enum, upstream strictness, the 'pdf' refusal prescription and the parse-time array lift, plus a non-vacuity floor. Local key set projected through an exhaustive mapped type keyed by keyof ListViewExportOptions, so local drift fails to compile. Item 4 answered by measurement. The ListView leg of the same reconciliation is a live defect outside this card's declared file surface — measured and filed as #6956 rather than fixed (stop-on-breach).",
      "item_4_measurement": "RAW UN-PARSED METADATA REACHES THE RENDERER — keep the array tolerance. Three measurements: (1) normalizeListViewSchema (packages/core/src/utils/normalize-list-view.ts) has ZERO occurrences of exportOptions; (2) no .parse()/.safeParse() of a view schema anywhere on the render path — the only such calls in the repo are in tests, which is why ListViewInferred is z.input not z.infer, per its own doc comment; (3) packages/app-shell/src/views/ObjectView.tsx:2116 forwards the stored value verbatim. So the spec's parse-time array lift never runs before the renderer. The tolerance lives in plugin-list/src/ListView.tsx:1299-1308 (resolvedExportOptions) and stays. ObjectGrid carries none — a bare array degrades to the ['csv','json'] default, silently dropping the author's declared formats; recorded on #6956, not changed here.",
      "files_changed": [
        "packages/types/src/objectql.ts (doc comment only, +15/-6)",
        "packages/types/src/__tests__/export-options-spec-parity.test.ts (new)",
        ".changeset/4535-export-options-spec-parity.md (new, @object-ui/types patch)"
      ],
      "gates": {
        "head_all_gates_ran_on": "cac5aed (final commit)",
        "pnpm --filter @object-ui/types type-check": "exit 0 — echoed 'tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json' (not a zero-match no-op)",
        "pnpm exec vitest run packages/types/": "'Test Files  76 passed (76)' / 'Tests  868 passed (868)'",
        "pnpm exec vitest run packages/plugin-grid/src/__tests__/ObjectGrid.exportOptionsKeys.test.ts": "'Test Files  1 passed (1)' / 'Tests  5 passed (5)'",
        "pnpm --filter @object-ui/types lint": "exit 0 — 'problems (0 errors, 259 warnings)', all pre-existing; zero warnings from the new file",
        "node scripts/check-spec-symbol-derivation.mjs": "exit 0 — 'spec symbol derivation: 1325 files scanned against 4959 spec export names; 16 declared dialects, 0 untriaged collisions in 0 packages.' and 'spec alignment claims: 2 declared deliberate copies, 18 unbacked claims in 5 packages.' — count unchanged from the pre-edit run, so no new unbacked claim",
        "node scripts/check-control-bytes.mjs": "exit 0 — 'check-control-bytes: OK (scanned 5786 tracked text file(s); skipped 85 binary).'",
        "node scripts/check-changeset-fixed.mjs": "exit 0 — 'All workspace packages are in the changeset fixed group.'",
        "node scripts/check-changeset-no-major.mjs": "exit 0 — 'No changeset declares a `major` bump.'"
      },
      "tests": "Coverage measured, not assumed: tsc -p tsconfig.test.json --listFiles names export-options-spec-parity.test.ts (1 hit), so the clean type-check is a statement about the new file. ABLATION (committed first, absolute-path EXIT/INT/TERM trap): mutated ListViewExportFormat back to include 'pdf'. Mutation proven on disk anchored on the exact text — injected-form=1, removed-form=0, blob 46090c7 to 88ccf25. tsc -p tsconfig.test.json exit 2, this PR's guard named first: \"export-options-spec-parity.test.ts(59,7): error TS2741: Property 'pdf' is missing\", plus the sibling pins TS2344 and TS2578. Restore proven by observed state not exit code: git hash-object back to 46090c7 == git rev-parse HEAD:packages/types/src/objectql.ts, git diff HEAD empty, git status clean. No rebuild leg needed — source-level vitest and tsc, nothing resolves through dist. DEVIATION FROM THE DISPATCH'S EXPECTED DIRECTION, stated rather than faked: the card asked to show the compile going red with the 'as any' removed. That cast no longer exists on main (item 3 already landed), so there was nothing to remove. The measured direction substituted is the one this PR's guard actually protects. The runtime leg of the new test fires on SPEC drift and was not ablated — the only way to mutate that side is editing node_modules, a pnpm store hardlink shared across worktrees on this box; the non-vacuity floor test stands in for it.",
      "narrowing_declared": "Repo-wide pnpm lint and the full check:* farm not run locally — CI runs them once regardless. Bounded by measurement: the WHOLE @object-ui/types package lint ran (not just changed files), and eslint.config.js enables no type-aware linting (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict.",
      "deviations": [
        "FILE-SURFACE BREACH AVOIDED, per the claim's 'stop on breach'. The card's four items are complete on its declared surface (packages/types/src/objectql.ts + plugin-grid/src/ObjectGrid.tsx), but the same reconciliation has an unlanded ListView leg in packages/types/src/zod/objectql.zod.ts and packages/plugin-list/src/ListView.tsx. Neither file is in the cross-lane in-flight fence (#6349, #6298, #6594 are all disjoint), but both are outside my declared surface, so I measured and filed rather than fixed. See #6956.",
        "Channel switch declared: repo-scoped REST returned HTTP 403 ('GitHub access is not enabled for this session') and gh is absent, so the duplicate search went through ONE targeted MCP search_issues instead of a REST list + local grep. Non-empty result (4 hits incl. the precedent #4605), so no control word needed.",
        "GitHub body sanitizer ate generic type arguments on first publish of BOTH the PR body and #6956 (Record<keyof ...> collapsed to 'Record'; z.input<typeof ListViewSchema> to 'z.input'). Caught by full-text read-back and repaired with the spaced spelling (Record< keyof ..., true >), which reads back intact. The rule measured: a '<' followed by a letter is eaten; '<' followed by a quote or space survives."
      ],
      "labels_written_and_read_back": "needs:contract-review on BOTH carriers. #4535 read back 3 labels [pm:dispatched, domain:spec, needs:contract-review] — nothing stripped. PR #6957 label read via pull_request_read (issue_read get_labels cannot resolve a PR number): labels ['needs:contract-review'], draft: true.",
      "mcp_calls": "17 — GitHub MCP calls for the whole run (REST unavailable at 403)",
      "open_questions": [
        {
          "question": "The PR says `Fixes #4535`, per the dispatch's explicit instruction. But the ListView leg (#6956) is the same contract, unlanded — should the card close on this PR, or should the PR be `Part of` and #4535 stay open until #6956 lands?",
          "options": [
            "A: keep `Fixes #4535`. The card's text is scoped to objectql.ts + ObjectGrid.tsx; both are now reconciled and mechanically pinned, and #6956 carries the remaining leg with full measurements.",
            "B: flip to `Part of #4535` and keep the card open until #6956 lands, treating exportOptions as one contract regardless of which file declares it."
          ],
          "recommendation": "A. The card enumerates files and its four items are satisfied on them; #6956 is a different surface owned by a different seat (types/zod + plugin-list, i.e. domain:ui rather than this spec-lane claim), and nothing is lost because the finding is filed with re-check commands. If PM prefers B, the PR is draft — one body edit flips it before anything merges."
        }
      ],
      "out_of_scope_findings": [
        "filed as #6956: the ListView leg of the exportOptions reconciliation never landed — zod mirror (packages/types/src/zod/objectql.zod.ts:511-518) still accepts 'pdf' in both spellings, declares no `streaming`, and is non-strict; plugin-list/src/ListView.tsx:1324 and :2705 still read `streaming` through `as any`, and :1304 still casts to a 'pdf'-bearing array. Load-bearing because ListViewSchema (the TS type the ListView renderer is written against) derives from that zod, so it disagrees with its sibling ObjectGridSchema about the same spec key. Same class as the closed #4605; under the #2231 umbrella. Unassigned, unlabelled, left for PM triage.",
        "folded into #6956 rather than filed separately: `ListViewExportOptions` is entry 1 in the @object-ui/types CLAIM_DEBT list in scripts/check-spec-symbol-derivation.mjs:665, the shrink-only ledger of unbacked spec-alignment claims. Its claim is now backed by a real parity test and the mirror's reason is measured, making it a clean burn-down via the ledger's own route 2 (move to CLAIM_ALLOW). Not done here: scripts/** is outside this card's surface, and editing a gate script obliges running that script's own test suite."
      ],
      "blockers": null
    }

    Generated by Claude Code

  8. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    PM verdict: ACCEPT — parked, review chain owns enqueue. Open question answered: A (keep Fixes #4535). (session_01PBjwYLS6BciTQW3c9xQiD2)

    Verified on GitHub — PR #6957:

    • The partial premise falsification is the headline and it is a good run: items 2+3 were already landed on main (changeset 1f9b905) — my claim's "all four items land in full" was stale, the dev measured instead of re-implementing. What was live: item 1's alignment comment had gone false a SECOND time (stale rc.6 pin note vs installed 17.2.0), and item 4 was unanswered.
    • The fix is the right class: prose alignment replaced by a mechanical parity test that reads the INSTALLED spec at test time (key set, enum, upstream strictness, 'pdf' refusal prescription, array lift, non-vacuity floor), with the local key set projected through an exhaustive mapped type so local drift fails to compile — a comment that went false twice is not the instrument to trust a third time.
    • Item 4 answered by three measurements: raw un-parsed metadata DOES reach the renderer (no parse on the render path; normalizeListViewSchema never touches exportOptions; ObjectView.tsx:2116 forwards verbatim) ⇒ the plugin-list tolerance is load-bearing and stays; ObjectGrid's silent bare-array degradation recorded on the follow-up, not changed in passing.
    • Stop-on-breach honored: the unlanded ListView leg (zod mirror still 'pdf'-bearing/non-strict, plugin-list as any reads) sits outside the declared surface — measured and filed as finding(types/plugin-list): the ListView leg of the exportOptions reconciliation never landed — zod mirror still takes 'pdf' and declares no streaming, and both as any reads survive #6956 with re-check commands instead of fixed. Left ungraded for triage (contract-face routing is triage's call).
    • Form: draft ✓ · needs:contract-review both carriers read back ✓ · 3 files ✓ · ObjectGrid untouched (fenced regions never opened) ✓ · changeset @object-ui/types patch with the named precedent and the reasoned distinction from 1f9b905's minor ✓ · ablation direction substitution declared honestly (the dispatched direction no longer existed; the substituted one is what the new guard actually protects) ✓.
    • Answer A adopted: the card enumerates its files and its four items are satisfied or measured on them; finding(types/plugin-list): the ListView leg of the exportOptions reconciliation never landed — zod mirror still takes 'pdf' and declares no streaming, and both as any reads survive #6956 carries the remaining leg with full evidence. Nothing is lost by closing this card at landing.

    Platform fact from this run, carried to the shift handover: the GitHub body sanitizer eats < followed by a letter (tag-shaped fragments) in issue/PR bodies; < followed by a space or quote survives — measured on both this PR body and #6956, repaired with spaced spellings.

    Note for the landing window: mergeable_state is behind; bring the base in at release time.


    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

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions