Repository navigation
PageHeaderProps.title is required, but the platform's own default page:header deliberately has no title — validateComponentProps would flag every synthesized header #7702
Description
Activity
Triage:
needs-user-decision+domain:spec— this is a public-contract fork (maketitleoptional vs. sanction a sentinel), and resolving it in any direction changes the accepted metadata set, so it routes to the spec lane per the acceptance-surface criterion (seat directive ③).Premise verified on
origin/main@a3c0865:packages/spec/src/ui/component.zod.ts:225—title: I18nLabelSchema.describe('Page title'), no.optional()(contrastsubtitle:226, optional). The filer's claim holds: the schema requires what the platform's own defaultpage:headerdeliberately omits.Dedup: repo-scoped search ("PageHeaderProps title required optional") returns only this card; #6776 (added
PageHeaderProps) is closed; no open PR touchescomponent.zod.ts:224-232. objectui#4232 / PR objectui#4290 are the measurement source, not shadows — the fix lands here, not there.Four-lens block (#7498):
- Platform long-term coherence: A/B removes the standing special case where the platform's canonical emitter violates the platform's own schema; C adds a sentinel convention (contract accretion) and contradicts the record-derived renderer heading.
- Measured business pull: zero today — the write path accepts the node (opaque
propertiesrecord, proven by objectui#4290 pins); pull arrives the moment any props-level validation/designer lint runs against header nodes. - AI-agent error-resistance: a required key the canonical emitter omits teaches agents to distrust the validator; "omitted ⇒ renderer-derived, documented in the docstring" is the spelling hardest to misuse.
- Startup scope discipline: A is a one-line relaxation + docstring, no new key; C creates a permanent sentinel obligation on every seeded page.
Not
target:v17: four-class test fails — nothing on a shipped path flags today (latent declared≠emitted contradiction; the validator in question runs on no live path yet).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Maintainer ruling recorded 2026-08-11 (spec-lane PM session chat, verbatim: 「接受你的建议,开始加速处理」, accepting the lane sweep's recommendation on this card).
Ruling: A/B merged.
PageHeaderProps.titlebecomes OPTIONAL, and its describe states the sanctioned spelling: title omitted ⇒ the renderer derives the heading from the record (matching the shipped renderer andbuildDefaultPageSchema's emission). Option C (synthesizer emits a sentinel) is rejected — it would push a placeholder into every seeded page against the renderer's record-derived heading.State:
needs-user-decision→ dispatch (spec lane, sessionsession_01JY2Q5Xto1u8YHADgrZDTnk).
Generated by Claude Code
Claim: PM loop round 2 (spec lane)
Session:session_01JY2Q5Xto1u8YHADgrZDTnk
Branch:claude/issue-7702-pageheader-title-optional
Worktree:objectstack-issue-7702
Domain:domain:spec
File surface:ComponentPropsMap/PageHeaderPropsdeclaration site (locate — the card refs #6776 which added it), its describe, pins for the ruled optionality, generated closure. Stop on breach; explain in the report.
Container & model: S mechanical (ruled optionalization + describe + pin flip; correctness is gate-determined),mode:subagent, model: sonnet
Serial constraints cleared: no in-flight writer on the component-props surface (open PRs + queue checked 12:30Z); no overlap with in-flight #7596/#7572/#7634 file faces.
Generated by Claude Code
OS-DEV-REPORT — #7702
{ "issue": 7702, "status": "done", "branch": "claude/issue-7702-pageheader-title-optional", "pr": "https://github.com/objectstack-ai/objectstack/pull/7756", "premise_still_valid": true, "summary": "PageHeaderProps.title (packages/spec/src/ui/component.zod.ts:225) was required; made it optional per the 2026-08-11 maintainer ruling, with a describe stating the sanctioned spelling (title omitted => renderer derives the heading from the record, matching objectui's buildDefaultHeader emission and PageHeaderRenderer's schema?.title ?? schema?.properties?.title read). Replaced the overturned 'reject header without title' pin with positive pins using the synthesizer's real emission shape, added a positive pin on validateComponentProps (the named consumer) no longer flagging the synthesized header, and regenerated content/docs/references/ui/component.mdx via gen:docs to close check:generated.", "tests": "pnpm --filter '@objectstack/spec' exec vitest run src/ui/component.test.ts src/ui/page.test.ts --maxWorkers=2 -> 237 passed. Full pnpm --filter '@objectstack/spec' test -- --maxWorkers=2 -> 377 files/9885 tests passed. pnpm --filter '@objectstack/lint' test -- --maxWorkers=2 -> 70 files/1905 passed, 4 skipped (pre-existing). pnpm --filter '@objectstack/spec' typecheck and pnpm --filter '@objectstack/lint' typecheck both clean. pnpm --filter '@objectstack/spec' check:authorable-surface green (anchor lag informational only). pnpm --filter '@objectstack/spec' check:generated found content/docs/references/** stale, regenerated with gen:docs, re-ran -> all 13 green. check-adr-anchors.mjs, check-spec-parsed-alias.mjs, git-merge-regen.mjs+check-regen-pending.mjs (all --self-test + real run), check-nul-bytes.mjs all OK. Reverse verification: isolated the schema fix via git checkout origin/main -- packages/spec/src/ui/component.zod.ts (tests kept) -> 2 spec tests + 1 lint test failed with exactly the issue's title: Invalid input / component-props-invalid error; restored via git apply --include= from a saved patch file (never a second hand-revert) -> both suites green again.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
Step-7 review — ACCEPT (spec-lane PM, session
session_01JY2Q5Xto1u8YHADgrZDTnk). PR: #7756 (draft,Fixes #7702).Verified against GitHub:
- Ruling lands exactly:
title→I18nLabelSchema.optional()with the describe stating the sanctioned spelling (omitted ⇒ renderer derives from the record); docstring records the ruling; option C's sentinel nowhere in the diff. - Pin discipline: the old "reject header without title" pin asserted the OVERTURNED behavior — replaced (not deleted alongside) with three pins, the acceptance one using the synthesizer's REAL emission shape (
{ recordChrome: true }), not a minimal stub; present-titletyping still guarded. - The named consumer is pinned positively:
validateComponentProps(the surface where the contradiction bites, SDUI 组件 props 没有解析闸门:PageComponent.properties是开放 record,ComponentPropsMap的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068) no longer reportscomponent-props-invalidfor the synthesized header — cross-package test inpackages/lint, exactly the consumer-face verification this card needed. - Reverse verification isolated the fix (schema reverted, new tests kept): 2 spec pins + 1 lint pin red with precisely the issue's error strings; patch-file restore; both suites green after.
- Changeset minor, correctly classified — pure widening; scope grep confirms no direct
PageHeaderPropsconsumer outside spec/lint. - Gates: spec 377/9885, lint 1905 passed, typechecks clean,
check:generated13/13 aftergen:docs, spec-scoped script gates + nul-bytes OK.
Landing plan: this PR regenerates
content/docs/references/ui/component.mdx⇒ joins the os-regen relay — position: after #7713 (whose sync lap is running now). Its sync lap fires when #7713 merges; flip after that lap's evidence + gate conclusions.
Generated by Claude Code
- Ruling lands exactly:
Pre-enqueue os-regen sync lap for PR #7756 — complete.
Merge:
git merge origin/main(no rebase, no force-push) intoclaude/issue-7702-pageheader-title-optional, commit53b6fd1(parents508ff52+8669e5d). Auto-merged clean, no conflicts (ortstrategy). Picked up #7710, #7713, #7760 and the rest of main's advance since my base. Pushed:508ff52..53b6fd1.os-regen paths: took
origin/main's copy of every path ingrep os-regen .gitattributes(spec-changes.json, liveness/state-counts.md, authorable-surface/, authorable-surface.base.json, authorable-defaults/, json-schema.manifest/, api-surface/, export-origins/, api-surface-signatures.json, docs/protocol-upgrade-guide.md, the strictness-ledger counts, content/docs/references/) — not in MERGE state when doing this (merge was already committed). Then ran the real gen pipeline (pnpm --filter '@objectstack/spec' build→ gen:schema + gen:openapi, thengen:docs) so the regenerated output is authoritative rather than interpolated. Result matched the already-committed merge tree exactly (git diff HEADempty after regen) — re-staged for cleanliness, nothing to re-commit.check:generated reading (post-merge, post-regen):
✓ check:migration-registry ✓ check:spec-changes ✓ check:upgrade-guide ✓ check:skill-docs ✓ check:skill-refs ✓ check:react-blocks ✓ check:authorable-surface ✓ check:api-surface ✓ check:export-origins ✓ check:docs ✓ check:strictness-ledger ✓ check:liveness ✓ check:test-typecheck ✓ All 13 generated artifacts are up to date.Survival spot-checks:
- Mine —
content/docs/references/ui/component.mdx:244:**title** | ... | optional | Page title. Omit to let the renderer derive the heading from the record (the default for record pages) — set explicitly on non-record pages (dashboard, landing) with no record to derive from. - Mine —
pnpm --filter '@objectstack/spec' exec vitest run src/ui/component.test.ts --maxWorkers=2→ 162 passed. - Sibling (fix(spec): remove FieldReferenceSchema from the $between endpoints and rule $in/$nin members out (#7596) #7713) —
content/docs/references/data/filter.mdx:122-123:**$in**/**$nin**rows present, both citing(#7596). - Sibling (feat(spec): rule and enforce submitBehavior.url — relative-only, declared-field interpolation, URL-escaped (#7496) #7657/spec: rule the submitBehavior.url semantics — objectui#4190 dead-ends on it #7496) —
content/docs/references/ui/view.mdx:286:**submitBehavior**row present with theredirectarm /(ruled 2026-08-11, #7496)citation.
Broader scoped re-verification (post-merge):
pnpm --filter '@objectstack/spec' test -- --maxWorkers=2→ 378 files / 9949 tests passed.pnpm --filter '@objectstack/lint' test -- --maxWorkers=2(after rebuilding its dep closure) → 70 files / 1906 passed, 4 skipped (pre-existing).node scripts/check-nul-bytes.mjs→ OK.Worktree torn down (
node_modulesdeleted,git worktree removeunforced, clean tree confirmed first). Not waiting on CI — handing back for the flip + enqueue.
Generated by Claude Code
Generated by Claude Code
- Mine —
Close-confirm — PR #7756 merged via the merge queue.
PageHeaderProps.titleis nowI18nLabelSchema.optional(), so the platform's own synthesized no-titlepage:headervalidates clean (option A of the two shapes on the card).Provenance: direction recommended in the seat-#6017 full-lane triage sweep and accepted by the maintainer in chat 2026-08-11 (「接受你的建议,开始加速处理。」); implemented by the dispatched dev, ACCEPT-reviewed at step 7. The PR survived two infrastructure incidents on the way in, neither related to its diff: a stuck-
in_progressCheck Changeset check run whose job had finished green (resolved by workflow rerun), and the 14:30–16:23Z main-red queue wedge (#7802, fixed by #7818), after which it was re-queued and merged.Spec-lane PM, session
session_01JY2Q5Xto1u8YHADgrZDTnk.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed by the objectui whole-repo PM seat (session
session_017Qqyix2QcnpUC9XeYVDzx3) relaying a measured finding from objectui#4232 / PR objectui#4290 (Studio page-create ADR-0089 D3a compliance). Unassigned — recording for this lane's triage; the fix, if any, is a spec-side contract decision the objectui seat does not own.Fact (measured on vendored
@objectstack/spec17.0.0-rc.6)ComponentPropsMap'sPageHeaderPropsdeclarestitleas required. But the page:header that objectui'sbuildDefaultPageSchemasynthesizes — the platform's own default header on every seeded record page — deliberately carries no title, because the record-page renderer derives the heading from the record at render time (a static authored title would be wrong on every record but one).Consequence:
PageHeaderProps.safeParse(node.properties)fails withtitle: Invalid inputon the platform's own default output.Why nothing breaks today — and why it's still worth ruling on
The page write path is unaffected:
PageComponentSchema.propertiesis an opaquez.record(z.string(), z.unknown()), so the strict D3a validator accepts the node (objectui PR #4290's pins prove the full seeded payload round-trips). The contradiction only surfaces wherevervalidateComponentProps(or any future props-level lint/designer validation) runs against the header node — the platform's default header is then flagged as invalid input by the platform's own schema.The contract question
Either:
titlebecomes optional onPageHeaderProps, with the docstring stating the renderer-derived default (matches the shipped renderer behavior and the synthesizer's emission), ortitleomitted ⇒ derived, documented), which is A with better prose, orA/B match shipped behavior; C seems strictly worse but is listed for completeness.
Refs objectui#4232, objectui PR #4290, objectstack#6776 (which added
PageHeaderProps), ADR-0089 D3a.