Skip to content

spec: declare placeholder on FieldSchema — four objectui render surfaces already honour the key the producer refuses (ruled Option C on objectui#4676) #9019

Description

@os-zhuang

Provenance

Filed by the PM session (session_01NYgmGheCzM6NrHZN436Cxf) recording a maintainer ruling of 2026-08-16 on objectui#4676. The maintainer reviewed the decision-inbox report and approved the contract-coherence group's recommendations verbatim: 「主线1 3 4:同意」— for objectui#4676 the approved recommendation was Option C: add placeholder to FieldSchema in objectstack (the producer moves).

Why (measured, not inferred)

objectui#4676's dispatched measurement (report comment 5301288148, probes with sensitivity controls, origin/main @ 9ce096fb0) found an object-field-level placeholder is read and applied at render time in four packages plus apps/console, none of them the designer preview:

  • plugin-form: ObjectForm.tsx:601, sectionFields.ts:122 (fromObjectSchema), ModalForm.tsx:425, DrawerForm.tsx:350
  • plugin-detail: RecordDetailDrawer.tsx:253 (inline edit)
  • app-shell: resolveActionParams.ts:512 — field-backed action params inherit placeholder, documented in the module header as intended behaviour
  • apps/console: FormPage.tsx:343 (override.placeholder ?? def.placeholder), rendered at :830
  • underneath, @object-ui/fields widgets read field.placeholder off the metadata carrier

Meanwhile FieldSchema rejects the key by name (unrecognized_keys, prescription text says "never a FieldSchema key. Author hint text through inlineHelpText or description"), so the ObjectStack path 422s on PUT /api/v1/meta/object/:name for a key the designer's own preview renders — the classic preview-renders/save-refuses trap, hitting mostly AI authors.

This is the #7176 doctrine satisfied from the consumer side: the consumers are written, shipped, and reachable; the declaration is what is missing.

The work

  1. Add placeholder as a declared optional string key on FieldSchema (packages/spec/src/data/field.zod.ts), with the semantics the renderers already implement (in-input placeholder text; distinct from inlineHelpText).
  2. Remove/replace the now-false "never a FieldSchema key" guidance prose and any alias-table entry steering authors away from it.
  3. Consumer-package fixture triage per standing rule: run the consumer packages' tests (rest/meta routes, objectql validation fixtures) — a spec accept-set change can flip sibling-package fixtures.
  4. Changeset: minor (accept-set expansion on a published surface).

Dispatch notes

  • model: claude-fable-5 mandatory — this changes contract accept behaviour (standing 2026-08-12 tiering rule).
  • Routes to the packages/spec semantic lane (accept-set change); domain:* left for the triage seat to stamp (single-producer rule).
  • Downstream: objectui#4676 carries Blocked-by: this card; once this lands and the objectui pin covers it, the designer trap closes with zero objectui code change (controls stay as they are).

Refs: objectui#4676 (ruling + measurement) · #7176 (doctrine) · #4001 (silent drop → loud rejection).

Activity

  1. os-project-manager commented on Aug 16, 2026

    @os-project-manager
    Collaborator

    Triage (triage seat, session session_01VUKiXCWoLo8Vnd26tHqXzj): lands in packages/spec/src/data/field.zod.ts (accept-set expansion) ⇒ domain:spec semantic face, type Feature. pm:blocking stamped: objectui#4676 is open and carries Blocked-by: objectstack-ai/objectstack#9019 (verified in its body just now), so this card has an open downstream dependent and sorts ahead in lane selection.

    Rationale: maintainer ruling of 2026-08-16 (Option C on objectui#4676, 「主线1 3 4:同意」) — direction is ruled, this is the destination-repo implementation card. The dispatch mandates in the body stand: model: claude-fable-5 (contract accept-behaviour change, standing 2026-08-12 tiering rule); consumer-package fixture triage with real per-package readings; changeset minor. The prescription prose "never a FieldSchema key" must flip with the schema in the same PR, or the error guidance contradicts the contract.


    Generated by Claude Code

  2. self-assigned this
    on Aug 16, 2026
  3. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 5
    Session: session_01225pUjnCKWqxcc1PeqKFUq
    Branch: claude/issue-9019-fieldschema-placeholder
    Worktree: objectstack-issue-9019
    Domain: domain:spec
    File surface: packages/spec/src/data/field.zod.ts (declare placeholder; flip the now-false "never a FieldSchema key" guidance prose in the same PR) + field.test.ts, one minor changeset, regenerated content/docs/references/** (field pages), consumer fixture triage per the card (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: fable — mandatory (contract accept-set expansion, 2026-08-12 semantic-lane clause; the card and triage both mandate it)
    Serial constraints cleared: in-flight devs are the #4001 terminal re-measure (docs/audits only — disjoint) — no other dev running. ⚠️ Same-file churn: #9088 (field.zod.ts currency guidance) is IN THE MERGE QUEUE right now — dev branches off current origin/main AND re-merges main after #9088 lands before opening/finalizing the PR; both edits are in the guidance-table region, so declare region-level care there. #9019's own regen surface (field reference pages) overlaps no in-flight card.


    Generated by Claude Code

  4. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    ContributorAuthor
    {
      "issue": 9019,
      "status": "done",
      "branch": "claude/issue-9019-fieldschema-placeholder",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9113",
      "premise_still_valid": true,
      "summary": "Ruled Option C implemented. Premise verified on origin/main @9bf110fb6 before editing: FieldSchema rejected `placeholder` with unrecognized_keys and the exact prescription ('never a FieldSchema key. Author hint text through `inlineHelpText` or `description`.'), while `inlineHelpText`/`description` parsed. One PM mechanism assumption corrected: the prescription prose does NOT live in field.zod.ts — it lives in FIELD_KEY_GUIDANCE in packages/spec/src/data/authoring-key-lint.ts and is folded into FieldSchema's strictObject guidance options; and NO existing test pinned the placeholder rejection anywhere in packages/spec or the consumer packages (quoted-exact grep), so there was nothing to flip — only accept-side pins to add. Changes: `placeholder` declared as optional string on FieldSchema next to inlineHelpText with .describe() prose carrying the three-way distinction (in-input placeholder vs always-visible inlineHelpText vs description tooltip); FIELD_KEY_GUIDANCE entry removed with a NOTE comment (the authoring-key-lint.test.ts no-rot pin 'no guidance entry names a key the schema now declares' makes the removal gate-enforced); object.form.ts tombstone comment replaced with the real form entry and field.form.ts (the full per-field editor) offers the key too (bounded same-class completion, named in the PR body); liveness/field.json gains a live row with cross-repo evidence citing the measured objectui read points @9ce096fb0 (the liveness gate demands a row for every new authorable property — a surface the card did not name); state-counts.md, authorable-surface/data.json (+data/Field:placeholder) and content/docs/references/data/field.mdx regenerated wholesale; minor changeset with ADR-0087 not-required annotation. #9088 landed mid-task exactly as the PM predicted; origin/main re-merged (merge commit 7cb089f37), clean, both semantics verified coexisting (currency guidance entry + placeholder declaration). FieldTranslation.placeholder was already declared, so the translation surface now has a declared base key.",
      "tests": "All at final HEAD 7cb089f37 (post-merge), union re-run after the last commit. Reverse verification, direction reject-flips-to-accept: pre-change probe on unmodified origin/main dist — control SUCCESS, +placeholder FAIL unrecognized_keys with the exact prescription, +inlineHelpText/+description SUCCESS; post-change rebuilt dist — +placeholder SUCCESS ('Enter nickname' round-trips), non-string placeholder still rejects (proves the rebuilt artifact was read). Spec full suite: 406 files / 10771 tests passed (309s, maxWorkers=2, under /tmp/os-heavy-verify.lock); spec typecheck EXIT=0. Consumer sweep, direction DOWNSTREAM consumers of @objectstack/spec, per-package real readings: rest 120 files/2001 tests ✓, objectql 212/3730 ✓, lint 73/2065 ✓, metadata 31/603 ✓, metadata-core 10/162 ✓, metadata-fs 6/51 ✓, metadata-protocol 112/1565 ✓ — zero fixture flips (no consumer fixture pinned the rejection). Gate union re-derived via dispatch-gates.mjs against the actual diff, all EXIT=0: changeset-gate-self-tests, cross-package-test-inputs (pnpm + node forms), docs-audit-scope, docs-redirects, merge-driver, objectui-changeset, quick-reference-counts, role-word, spec-parsed-alias, type-source-resolution, nul-bytes, adr-0087-registration, changeset-no-major, empty-changeset, dev-prereqs, doc-formula-expressions, query-options-erasure, engine-double-contract, where-matcher, type-check-coverage, type-check-debt ('33 ledger entries re-measured, none above recorded' — workspace closure built first via turbo 70/70), spec check:api-surface/authorable-surface (green, base-anchor lag is the allowed informational line)/docs/generated/liveness/export-origins. Examples validate: app-crm, app-showcase, app-todo all EXIT=0 (pre-existing draft-flow warnings only). Derivation additions beyond the prompt's gate list: the changeset family (gate-self-tests, objectui-changeset, adr-0087, no-major, empty) and docs family (audit-scope, redirects, quick-reference-counts, role-word) — all run. One mid-run interruption: a 10-minute foreground call was killed while queued behind another agent's lock hold (issue-9068, verified via fuser — a live short vitest run, normal queueing, not a stall); split into separate bounded calls and completed. No ablation was run (no dogfood/dist-ablation surface in this change), so no rebuild statement is owed.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  5. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    ContributorAuthor

    ACCEPT (content) — PR #9113 (claude/issue-9019-fieldschema-placeholder, reviewed at head 7cb089f, the post-#9088-merge commit). Verified against GitHub, not the report's self-description. Ready-flip waits only on CI convergence.

    • Ruling fidelity (Option C): placeholder declared as optional string on FieldSchema, .describe() carrying the three-way distinction (placeholder / inlineHelpText / description) so the reference page renders it. The now-false prescription REMOVED at its real address — the dev corrected the PM's assumption (FIELD_KEY_GUIDANCE in authoring-key-lint.ts, folded in via fieldKeyGuidanceAsStrictOptions(), not literally in field.zod.ts) and the removal is gate-enforced by the no-rot pin, not convention.
    • Reverse verification in the correct direction for an accept-set expansion: pre-change probe on unmodified main dist (control SUCCESS / +placeholder FAIL with the exact prescription), post-change rebuilt dist (+placeholder SUCCESS + round-trip; non-string still rejects — proving the rebuilt artifact was read).
    • docs(spec): FieldSchema points a bare currency key at currencyConfig (#8163) #9088 same-file churn handled exactly as dispatched: origin/main re-merged after it landed (7cb089f), both semantics verified coexisting (currency guidance entry survives; placeholder declaration stands); full suite re-run green post-merge.
    • Ecosystem sync complete in one PR: liveness ledger live row with cross-repo evidence (the measured objectui read points), authorable-surface + reference page regenerated, form entries (the tombstone comment's flip surface + the per-field editor row, validated by the reconciliation test), minor changeset with ADR-0087 not-required annotation. Consumer sweep across 7 downstream packages: zero fixture flips, searched not assumed.
    • Declared amendments accepted: both are the flip's own surface or its bounded same-class completion, named in the PR body — not scope creep.

    Landing: touches regenerated references (field.mdx) — if main moves on an os-regen surface before queueing, the relay applies; otherwise flip ready + auto-merge at gate green. Downstream: objectui#4676 unblocks on release + pin bump (its own card).


    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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions