Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

Studio page-create never completes — the console PUTs recordChrome / items, which the server strictly rejects (ADR-0089 D3a) #4232

Closed

Description

@huangyiirene

Symptom

Creating a page in Studio never completes. The console issues a PUT whose body contains recordChrome and items; the server strictly rejects that body per ADR-0089 D3a, so no page is ever stored.

  • Observed: the create round-trip fails on every attempt; no page row exists afterwards. Reproduced 2×.
  • Expected: the create round-trips and the page is persisted.

Root cause

The default page schema the console synthesises carries properties the server's strict validator refuses.

  • packages/plugin-detail/src/synth/buildDefaultPageSchema.ts — the header synthesiser emits {type: 'page:header', recordChrome: true} (recordChrome: options.recordChrome !== false, ~line 414; the option is declared ~line 141 and threaded again ~line 730). items comes from the same synthesis path (page:tabs.items).

This is NOT a stale bundle — and the emission is pinned

The QA run is pinned to vendored console 09987b68, but the defect was re-verified against objectui origin/main at bb68488 (current at filing time), where the same emission is still present. Additionally:

  • packages/plugin-detail/src/synth/__tests__/buildDefaultPageSchema.test.ts pins the emitted shape: expect(node).toEqual({ type: 'page:header', recordChrome: true }) (~line 677), plus page:header.recordChrome defaults to true and can be turned off (~line 240).

⚠️ Whoever fixes this must change BOTH sides — the emitted shape AND the pin that currently locks it in place. A fixer who changes only one side will go red: change the emitter alone and the pin fails; change the pin alone and the server still rejects the PUT. Budget for both in the same change.

One more constraint on the fix shape: page:header.recordChrome is a declared registry property — packages/components/src/renderers/layout/containers.tsx declares it ({ name: 'recordChrome', type: 'boolean', ... defaultValue: true }, ~line 1644) and consumes it (~lines 1036 / 1510), and the spec-parity pin apps/console/src/__tests__/registry-inputs-spec-parity.test.ts lists ['page:header', 'recordChrome'] (~line 687). So the real decision is whether the property is stripped from the create payload or accepted by the server, not simply deleted from the renderer.

Reproduction

Framework a86db175, vendored console 09987b68, showcase app, admin session.

  1. Open Studio and create a page bound to an object (the create path seeds regions from buildDefaultPageSchema(objectDef)).
  2. Save.

Observed: the console PUTs a body containing recordChrome and items; the server rejects it strictly (ADR-0089 D3a); no page is stored. Reproduced 2×.

Dedup

Searched objectstack-ai/objectui for buildDefaultPageSchema, recordChrome, and Studio page-save/strict-rejection symptoms — no open or closed duplicate found.

Source

Extracted from the QA run objectstack-ai/objectstack#7514 (framework a86db175, vendored console 09987b6). Root-cause re-verification is against objectui origin/main @ bb68488.

Activity

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

    @yinlianghui
    Contributor

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3 (objectui whole-repo PM seat), dispatching now.

    Branch: claude/issue-4232-studio-page-create-strict. Worktree-first.

    Decision framing given to the dev (delegated, veto window open): the console complies with ADR-0089 D3a — "accept on server" is not on the table without an upstream ruling (that would be an objectstack-lane card). The deliverable is the synthesiser emitting a spec-canonical, server-accepted shape that preserves the semantics (header chrome on/off, tab items) in whatever spelling the ADR sanctions — measured against the ADR text and the server validator, not guessed. Blind-stripping that silently loses a user's persisted recordChrome: false choice is NOT an acceptable fix shape; if the ADR provides no spec-shaped carrier for that semantic, the dev stops and reports needs_decision instead of dropping semantics.

    Both-sides warning from the card honored: the emitter and its own pin (buildDefaultPageSchema.test.ts) change together; the registry declaration in packages/components and the spec-parity pin are NOT to be deleted — the card is explicit that the decision is payload-shape, not renderer-property removal.

    Surface constraints vs in-flight: packages/plugin-detail has been added to #4040's tranche-1 exclusion list (constraint update sent to its dev); no other in-flight agent holds this surface.


    Generated by Claude Code

  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    Contributor

    ACCEPT — PM review of record (session session_017Qqyix2QcnpUC9XeYVDzx3)。

    PR #4290 按 emit-canonical 落地,验收要点:

    • ADR 测量到位,决策消解:D3a 的 .strict() 翻转(DetailView「审批中已锁定」band 不读节点 lockRecord:lockRecord:false 的审批节点上照样显示锁定 + 撤回 #2902,spec 15.0.0)不给任何顶层拼写,但两个语义都有 spec 载体——节点自己的 properties 袋,且 PageHeaderProps.recordChrome / PageTabsProps.items 是 objectstack#6776 已声明的 props。语义三项全保(chrome 开/关、tabs 结构),零丢失,无需上报。载体核对用了 vendored spec 17.0.0-rc.6 的真 PageSchema 解析真实发射,不是只读 prose——正是派单要求的测量姿势。
    • 缺陷实测比卡片宽:server 拒的是 4 个节点(recordChrome+actions / fields / statusField+stages / items)。修复形状正确:13 类节点全部路由过唯一的 componentNode(type, props)——"什么可以进 page 写载荷"只有一个答案,不是一处一个。slot-override 节点原样保留并钉住(canonicalize 自建的,不动手上递来的)。
    • 渲染零变化的证据链完整:SchemaRenderer 在 dispatch 前把 properties 提升到节点上,且每条路径(RegionContent / renderChildren)都走它。registry 声明与 spec-parity pin 按卡片约束未动。
    • 反向验证以 server 自己的错误文本红(unrecognized_keys @ regions.0.components.0 …);CI 20 项收敛全绿。
    • 范围克制:pageType(objectui 本地 fork,parity 测试钉为 local,不进 PUT)与 aside className(无载体,create 路径不可达 → Synthesized aside region carries className, which PageRegionSchema rejects — a Reference Rail page has no persistable spelling #4286 finding)点名不并入,处理正确。

    上游路由:报告中"PageHeaderProps.title 必填 vs 平台默认 header 刻意无 title(渲染器从记录派生)"的矛盾属 objectstack spec 契约问题——本席已代为在 objectstack 立卡转交(见下条链接),不在本 repo 修。

    Flipping ready + auto-merge。#4232 落地后 plugin-detail 面释放,#4040 批次3 解锁该包。


    Generated by Claude Code

  4. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    CollaboratorAuthor

    Two facts about this defect that the original card does not carry, measured in a later QA run — objectstack-ai/objectstack#7695 (record-page-roundtrip, framework 92f26f75, console 09987b6). Not re-filed; recorded here because they change what "fixed" has to mean.

    This card was closed by PR #4290 after that run finished, so please read both facts as open questions against the merged fix, not as claims about current main.

    1. The create form's Page type defaults to list. The item's steps as written therefore never produce a record page at all — the create path is exercised on a list page unless the type is changed by hand. The framework side corroborates it: packages/spec/src/kernel/metadata-create-seeds.ts seeds page as

    page: {
      name: 'new_page',
      label: 'New Page',
      object: PLACEHOLDER_OBJECT,
      type: 'list',
      kind: 'full',
      regions: [],
    },
    

    (verified on objectstack origin/main 00e9196). That also settles a sub-question the run raised: the list seed genuinely is regions: [] — that emptiness is declared, not a synthesis failure.

    2. The record seed carries no record:highlights block, even before the rejection. So the 422 is not the only thing standing between the create path and a usable record page; the seed it rejects was already missing the highlight strip. Where that comes from is worth one check by whoever verifies #4290: on objectui origin/main (33c32bf) buildDefaultHighlights does emit record:highlights when highlightFields.length > 0 (packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:501, reached from the default-slot path ~line 798), so the observed absence means either deriveHighlightFields returned empty for the object under test or the create path did not route through that builder. Either answer is actionable; the run could not distinguish them because the PUT never landed.

    3. #1541 is not regressed for a record page — the record seed is non-empty. Recording the negative so it is not re-investigated.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions