Skip to content

feat(spec): declare viewMode on GanttConfigSchema (#9463) - #9489

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-9463-gantt-viewmode
Aug 18, 2026
Merged

feat(spec): declare viewMode on GanttConfigSchema (#9463)#9489
os-steve merged 2 commits into
mainfrom
claude/issue-9463-gantt-viewmode

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9463

Spec half of the objectui#5074 maintainer ruling (2026-08-18, verbatim 「同意」: declare viewMode and wire BOTH branches). This PR declares the key on GanttConfigSchema; the objectui half (objectui#5074, pm:blocked on this card) declares it on ObjectGanttSchema and wires the timeline branch after this lands. objectui#5074 is not addressed here and remains open; the PM posts the unlock there after landing.

Measurement receipts (enum from the renderer, not invented)

Measured on objectui origin/main @ 68d9e282c83af26f639180fc22dacba4c26dc201 (fetched 2026-08-18):

  • packages/plugin-gantt/src/GanttView.tsx:197export type GanttViewMode = 'day' | 'week' | 'month' | 'quarter' | 'year'
  • packages/plugin-gantt/src/GanttView.tsx:199const VIEW_MODES: GanttViewMode[] = ['day', 'week', 'month', 'quarter', 'year'] (the runtime membership guard actually applied to the prop at :645, :803, :808)
  • packages/plugin-gantt/src/GanttView.tsx:207-213NOMINAL_DAYS maps all five members (each granularity really renders; year included, despite the stale JSDoc at :195 that names only four — filed as objectui#5132)
  • Default:packages/plugin-gantt/src/GanttView.tsx:802-805viewModeProp && VIEW_MODES.includes(viewModeProp) ? viewModeProp : restoredLayout?.viewMode ?? 'day'; same 'day' default in the resource branch (ResourceWorkload.tsx:63) and at the existing cast read (ObjectGantt.tsx:1445, (schema as any).viewMode … || 'day')

So the declared enum is exactly the measured set: day | week | month | quarter | year.

Why optional with no spec-side .default('day'): the renderer's absence behaviour is not a constant — an omitted viewMode lets a persisted layout (persistLayoutKey, GanttView.tsx:784-805) seed the granularity before falling back to 'day'. A materialized default would arrive at the renderer as an explicit author choice and defeat that seeding — the same reasoning the map block records for its defaultless zoom/center. This also mirrors the gantt block's own sibling convention (autoZoomToFilter, effortField: optional + default documented in prose).

What landed

  • packages/spec/src/ui/view.zod.tsviewMode: z.enum(['day','week','month','quarter','year']).optional().describe(…) on GanttConfigSchema. The block's deliberate .passthrough() posture is unchanged (renderer-ahead knobs still flow); declaring the key means an out-of-vocabulary value is now refused at authoring instead of silently falling back to 'day'.
  • packages/spec/src/ui/view.test.ts — accept pin per measured member (it.each), an absence pin (omitted viewMode stays absent — no materialized default), and a refusal pin (viewMode: 'hour' — a real granularity on the timeline block's scale, deliberately not in the gantt vocabulary — rejected with invalid_value naming the full member list).
  • packages/spec/liveness/view.json — the view/list/gantt blanket note now records viewMode at PLANNED strength (mirroring the spec: ListViewSchema(strictObject)没有 map 可视化配置块 —— map 列表视图的 titleField/locationField/相机整体不可声明,showcase 全部 marker 标题为 undefined #9340 map precedent one level down): today only the resourceView branch reads it (objectui ObjectGantt.tsx:1445, an as any cast); flips to plain live when objectui#5074 wires both branches.
  • packages/spec/authorable-surface/ui.json + content/docs/references/ui/view.mdx — regenerated (check:generated --fix; only the artifacts it proved stale).
  • .changeset/gantt-viewmode-declared.md@objectstack/spec minor (widens the accept set; not breaking, no ADR-0087 disposition required).

Out of scope, untouched: the component's other undeclared as any reads (readOnly, markers, holidays, …) — objectui#5043's family track.

Verification

All at head 84a5f2185, run after the final commit (the working tree was byte-identical to HEAD for every run; git status --porcelain empty):

  • pnpm --filter @objectstack/spec test409 files / 10922 tests passed; pnpm --filter @objectstack/spec typecheck — green (incl. check:test-typecheck: spec test layer compiles, debt ledger untouched)
  • pnpm --filter @objectstack/spec build + check:generated — 1 artifact proved stale (content/docs/references/**), regenerated with --fix; authorable-surface/ui.json picked up exactly ui/GanttConfig:viewMode
  • Full derived gate union (node scripts/pm/dispatch-gates.mjs over the changed paths, 24 families + check:nul-bytes): all green — incl. check:liveness, check:strictness-ledger, check:empty-state, check:variant-docs, check:authorable-surface (via check:generated), check:merge-driver, check:adr-0087-registration, check:changeset-no-major, check:engine-double-contract, check:where-matcher, check:type-check-coverage, and — after building the workspace closure exactly as lint.yml does — check:dev-prereqs, lint check:doc-formula-expressions, and check:type-check-debt --re-measure (33 entries re-measured, none above its recorded number)

Reverse verification

From the committed state, git restore --source=origin/main -- packages/spec/src/ui/view.zod.ts (schema alone reverted, tests kept). Direction predicted before running: exactly 1 of the 7 new pins red — the refusal pin — because the deliberately-passthrough parent already tolerated the key's presence, so declaring it changes value judgment only. Observed exactly that: 1 failed | 266 passed, the failure being the refusal pin (view.test.ts:304 — viewMode: 'hour' parses successfully once the key is undeclared). Restored with git checkout HEAD -- ..., re-run: 267 passed (267), tree clean at 84a5f2185.


Generated by Claude Code

Enum measured from objectui plugin-gantt GanttView.tsx (GanttViewMode /
VIEW_MODES): day | week | month | quarter | year, renderer default 'day'.
Optional with no spec-side default — absence lets the renderer's
persisted-layout seeding run. Passthrough posture of the gantt block
unchanged. Spec half of the objectui ruling; the consumer wiring follows
in the objectui repo once this lands.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fs18A2DdXLVN2h8PaaFBcP
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/view.json) — pages documenting those are invisible to this run

Coarse fallback — 113 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json origin/mainpackageMentionDocs.

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM — holding this PR for a manual landing: three SQUASH auto-merge arms all echoed method: MERGE and the queue never admitted it. Receipts below.

domain:spec seat (#6017, session session_01Fs18A2DdXLVN2h8PaaFBcP).

State: head 84a5f218, 28/28 check runs green (last: Test Core 3/3 success 04:43:27Z), mergeable_state: blocked, review of record ACCEPTED on #9463. Not dirty, not lacking checks — and never admitted to the merge queue.

Receipts — three enablePullRequestAutoMerge calls, all explicitly SQUASH, all echoed MERGE:

armreplyqueue admission
04:42:07Z (Test Core 3/3 still running)method: MERGE, enabled at 2026-08-18T04:42:07Znone
04:58:17Z (all 28 green)method: MERGE, enabled at 2026-08-18T04:58:17Znone (probed 04:59Z and 05:07Z — gh-readonly-queue/main/* empty of pr-9489)
05:11:56Z (disable→re-enable, controlled third try)method: MERGE, enabled at 2026-08-18T05:11:56Znone

Contrast, same session, same tool, same repo: four arms tonight echoed EMPTY method/enabledAt fields and every one admitted to the queue within ~2 minutes and squashed (PRs #9370, #9393, #9405, #9411). The correlation "empty echo → admitted; MERGE echo → never admitted" now has three failures and four successes across two sessions (the third failure being PR #9355's 23:2xZ episode, which was confounded by a dirty state this PR does not have).

Possible confound, flagged not asserted: main's tip is now 81316864b#9421's rename of the ESLint required context to Lint & Repo Gates, whose own subject says the branch-protection Settings swap is still pending and needs the maintainer present. During the same window #9440/#9455/#9421 landed through the queue while this PR was never admitted, so a total queue freeze is ruled out — but a half-renamed required-context configuration interacting with NEW auto-merge arms is exactly the kind of state I cannot inspect from here.

Held: auto-merge is OFF (a MERGE-pinned trigger in a squash repo is not something to leave armed). The PR is green, reviewed, and ready — it needs either a manual queue entry / merge by the maintainer, or a retry once the #9421 Settings swap is done. Wave-8 siblings (#9493 next) will attempt their own arms when their CI is green; their receipts will tell us whether this is PR-specific or systemic.


Generated by Claude Code

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

Root cause found — my hold comment above is superseded; no maintainer action needed for THIS PR.

The discriminating experiment answered it: PR #9493 (same wave, CI started 04:44Z) carries the renamed required context Lint & Repo Gates — no ESLint job at all — and its auto-merge arm echoed the healthy EMPTY fields and took immediately. So the #9421 Settings swap has already happened. This PR's CI ran 04:23Z, before the rename reached it: its head carries ESLint (old name) and simply does not have the now-required Lint & Repo Gates context — an unsatisfiable required check, which explains mergeable_state: blocked, the queue refusing admission, and (evidently) the method: MERGE echo on every arm.

Refined reading of the receipt shapes for the ops ledger: enable_pr_auto_merge echoing method: MERGE ≈ the arm cannot take effect on this head (unsatisfiable/incomplete required contexts — here a pre-rename CI run; in the #9355 episode, a dirty state). The empty echo is the arm taking effect. Still a correlation, but now with a mechanism and 5-for-5 / 3-for-3 on both sides.

Fix applied: branch update triggered (merge main into the head) — the fresh CI runs the renamed workflow and produces the required context. Once green, this seat re-arms auto-merge (SQUASH) and expects the empty echo + queue admission.


Generated by Claude Code

@os-steve
os-steve added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 870f710Aug 18, 2026
27 checks passed
@os-steve
os-steve deleted the claude/issue-9463-gantt-viewmode branch August 18, 2026 06:03
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/steststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: declare viewMode on GanttConfigSchema — the contract half of objectui#5074's ruled both-branches wiring

2 participants

@os-steve@claude