Skip to content

fix(spec): PageHeaderProps.title is optional — matches the platform's own synthesized header - #7756

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-7702-pageheader-title-optional
Aug 11, 2026
Merged

fix(spec): PageHeaderProps.title is optional — matches the platform's own synthesized header#7756
os-zhuang merged 2 commits into
mainfrom
claude/issue-7702-pageheader-title-optional

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7702

Problem

PageHeaderProps.title (packages/spec/src/ui/component.zod.ts) was declared required, but the platform's own synthesizer — objectui's buildDefaultHeader — emits every seeded page:header with no title at all: { type: 'page:header', recordChrome, ...(actions?) }. PageHeaderRenderer (containers.tsx:1013) reads schema?.title ?? schema?.properties?.title and, finding neither, falls through to the record chip's own record-derived heading — a static authored title would be wrong on every record but one.

Consequence: PageHeaderProps.safeParse(node.properties) rejected the platform's own canonical output with title: Invalid input. Invisible on the write path today (PageComponent.properties is an opaque z.record, so the strict D3a validator accepts the node regardless), but a standing contradiction that surfaces the moment any props-level validation runs against a header node — validateComponentProps (#5068) is exactly that consumer, and it flagged the synthesized header before this fix.

Ruling

Maintainer ruling recorded 2026-08-11 (「接受你的建议,开始加速处理」, accepting the spec lane's A/B recommendation on #7702; option C — a sentinel value — rejected):

Ruling: A/B merged. PageHeaderProps.title becomes OPTIONAL, and its describe states the sanctioned spelling: title omitted ⇒ the renderer derives the heading from the record (matching the shipped renderer and buildDefaultPageSchema'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.

Change

  • PageHeaderProps.title: I18nLabelSchema.describe(...)I18nLabelSchema.optional().describe(...), with a docstring recording the ruling and a describe stating the sanctioned spelling (title omitted ⇒ renderer derives the heading from the record).
  • packages/spec/src/ui/component.test.ts: replaced the old "should reject header without title" pin (which asserted the now-overturned behavior) with three pins — (a) parses without title using the synthesizer's real emission shape ({ recordChrome: true }, not a minimal stub), (b) parses with a fully empty object, (c) a present title still validates as I18nLabelSchema (rejects non-string/non-map, accepts a string).
  • packages/lint/src/validate-component-props.test.ts: added a positive pin against the actual named consumer — validateComponentProps no longer reports component-props-invalid for the synthesized title-less header.
  • content/docs/references/ui/component.mdx: regenerated (gen:docs) — the reference table now shows title as optional with the sanctioned-spelling description.
  • .changeset/pageheader-title-optional.md: @objectstack/spec minor — this widens the accepted-input surface (every payload that validated before still validates identically; the only newly-accepted shape is title omitted).

Reverse verification

Isolated the fix (git checkout origin/main -- packages/spec/src/ui/component.zod.ts, keeping the new tests) and re-ran both suites:

  • packages/spec/src/ui/component.test.ts2 failed on the new pins, with exactly the issue's error: title: Invalid input ... expected string, received undefined.
  • packages/lint/src/validate-component-props.test.ts1 failed: component-props-invalid on properties.title for the synthesized header, confirming validateComponentProps was the consumer surfacing the contradiction, as the issue described.

Restored the fix from a patch file (git apply --include=... ) — never a second hand-revert — and re-ran; both suites green again (162/162 and 27/27 respectively).

Tests

  • pnpm --filter '@objectstack/spec' exec vitest run src/ui/component.test.ts src/ui/page.test.ts --maxWorkers=2 → 237 passed
  • pnpm --filter '@objectstack/spec' test -- --maxWorkers=2 (full package suite) → 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 → clean
  • pnpm --filter '@objectstack/lint' typecheck → clean
  • pnpm --filter '@objectstack/spec' check:authorable-surface → green (base-anchor lag is informational, per AGENTS.md guidance — not an error)
  • pnpm --filter '@objectstack/spec' check:generated → 1 stale artifact found (content/docs/references/**), regenerated with gen:docs, re-ran → all 13 green
  • node scripts/check-adr-anchors.mjs --self-test && node scripts/check-adr-anchors.mjs → OK
  • node scripts/check-spec-parsed-alias.mjs --self-test && node scripts/check-spec-parsed-alias.mjs → OK
  • node scripts/git-merge-regen.mjs --self-test && node scripts/check-regen-pending.mjs --self-test → OK
  • node scripts/check-nul-bytes.mjs → OK

Scope note

No downstream in-repo consumer imports PageHeaderProps directly outside packages/spec and packages/lint (checked via repo-wide grep) — this is a pure widening (required → optional) so nothing that validated before this change can start failing after it.


Generated by Claude Code

…d header (#7702)

PageHeaderProps.title was required, but the platform's own synthesizer
(objectui buildDefaultHeader) emits every seeded page:header with no title
at all; the renderer falls through to the record-derived heading.
PageHeaderProps.safeParse rejected the platform's own canonical output.

Maintainer ruling 2026-08-11 (A/B accepted, sentinel option C rejected):
title becomes optional, and its describe states the sanctioned spelling —
title omitted => the renderer derives the heading from the record.

- title: I18nLabelSchema.optional(), describe updated
- pins: parses without title (synthesizer's real emission shape), a present
  title still validates as I18nLabelSchema, describe carries the sanctioned
  sentence
- packages/lint: validateComponentProps no longer flags the synthesized
  page:header as component-props-invalid
- regenerated content/docs/references/ui/component.mdx (check:generated)
- changeset: @objectstack/spec minor (accepted-input surface widens)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY2Q5Xto1u8YHADgrZDTnk
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 2:10pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 11, 2026 14:50
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31503813959 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core — 失败步骤: Verify test shard results(日志不可读,点进 job 看)

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 4 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31504932974 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 66d99ec Aug 11, 2026
30 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7702-pageheader-title-optional branch August 11, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tests tooling

Projects

None yet

2 participants