Skip to content

fix(metadata-protocol): honour hidden on getUiView's list priority pass - #13329

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13259-hidden-priority-fields
Aug 30, 2026
Merged

fix(metadata-protocol): honour hidden on getUiView's list priority pass#13329
os-zhuang merged 1 commit into
mainfrom
claude/issue-13259-hidden-priority-fields

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #13259

FieldSchema.hidden is declared "Hidden from default UI", and getUiView is the default UI — it is the producer behind GET /api/v1/ui/view/:object/:type. Its list branch chose columns in two passes and applied the visibility filter to the second one only, so a field declared hidden: true was withheld for eight of nine spellings and served, with its authored label, for the ninth: whenever the author happened to name it one of name, title, label, subject, email, status, type, category, created_at.

The form branch of the same function already filtered every hidden field uniformly, so two branches of one producer disagreed about what hidden means. This brings the priority pass to the side that already honoured the declaration.

The change

One line in packages/metadata-protocol/src/protocol.ts:

// before
let columns = fieldKeys.filter(k => priorityFields.includes(k));
// after
let columns = fieldKeys.filter(k => priorityFields.includes(k) && !fields[k].hidden);

plus the comment block explaining why the filter belongs on both passes. Per the triage ruling this is option 1 — restoring a stated invariant. ⛔ Option 2 ("make the priority pass respect a declared column list") is a new authoring capability, needs its own card and the maintainer's decision, and is not attempted here.

Because searchableFields is derived from columns, a hidden priority-named field also stops being offered as a search affordance. That is a transitive consequence of the same line and is pinned.

Clause-②: no

The path limb does not fire: the diff lands in packages/metadata-protocol/src/, not packages/spec/src/**, and touches no *.zod.ts contract schema or the error-code ledger. On the content limb both halves read no:

  • Accept/reject behaviour is untouched. getUiView is a pure derivation of a presentation payload from already-validated metadata. It parses nothing, validates nothing, rejects nothing. The set of authorable spellings is byte-identical either side of the change.
  • The public surface narrows, toward the declaration. Columns are removed, and precisely the ones the author already declared should not be there. No new key, no new option, no new accepted value, no widened route.

⚠️ Recorded as a reading, not a dispensation. The one thing that does change is a response shape, and I considered whether that alone reaches the content limb. It does not, for a reason specific to this card rather than general: the limb asks about the contract surface — what an author may write and what the platform will take — and a response that stops emitting a field the author declared hidden is the contract being honoured, not altered. Had the repair gone the other way it would have added an authoring capability and I would be declaring Clause-②: yes.

Verification

⚠️ The bar this card sets, and why. PR #13244 drove one hidden field, which happened not to be a priority name, saw it dropped, and reported "hidden is dropped by declaration" — true of the field it drove and false of the class. A false clearance. So every case here carries an arm that would have come out the other way.

New pin: packages/metadata-protocol/src/protocol.ui-view-hidden-columns.test.ts (7 cases).

Three arms in one case, and what each rules out:

arm field rules out
1 status — hidden and a priority name the defect itself; this is the arm #13244 never drove
2 beta_secret — hidden, not a priority name a repair that over-reaches and breaks the fill pass would read green without it
3 name — a priority name, not hidden the control. Without it, a producer that emits nothing at all satisfies arms 1 and 2 vacuously

Each case also asserts the name-agnostic form — no emitted column is declared hidden, computed from the fixture — so a tenth priority name added later without the filter fails even though the test's restated list would not know about it.

The class, not the field. A sweep drives all nine priority names hidden at once, plus one visible non-priority field, and pins the answer as exactly ['visible_note'] — a specific non-empty set rather than "empty".

Form-branch agreement, pinned two ways. One case drives both branches from the same fixture and asserts they withhold the same set. A second pins the form branch's exact output (['name', 'plain_note']) — if the repair had over-reached into the branch this card does not touch, that is what says so.

Reverse verification (ablation). Direction predicted in writing before the run, deliberately mixed so both halves are falsifiable. Mutation: revert protocol.ts to the merge base. Proven on disk before reading anything — injected filter grep count 0, pre-fix line count 1, on-disk blob 3e5f6838 differing from HEAD blob 71c528b6. Restore leg names HEAD explicitly (a bare git checkout -- path restores from the index, which the mutation had already written) and is verified by observed state, not an exit code: git diff HEAD empty, git status --porcelain empty, on-disk blob back to 71c528b6 exactly, both anchored greps back to the fixed state.

Predicted RED and observed RED — the three-arm case, the label case, the searchable case, the nine-name sweep, the agreement case:

× drops a hidden PRIORITY-named field, drops a hidden non-priority field, and still serves a visible priority field
    AssertionError: expected [ Array(4) ] to not include 'status'
× does not leak the LABEL of a hidden field either
    AssertionError: expected [ 'Account Name', 'Beta Status', …(2) ] to not include 'Beta Status'
× does not offer a hidden field as searchable
    AssertionError: expected [ 'name', 'status', 'created_at' ] to not include 'status'
× holds for ALL NINE priority names, not just the one the card drove
    AssertionError: expected [ 'name', 'title', 'label', …(6) ] to deeply equal [ 'visible_note' ]
× the list and form branches now agree about what `hidden` withholds
 Test Files  1 failed | 1 passed (2)
      Tests  5 failed | 8 passed (13)

Predicted GREEN and observed GREEN under the same ablation: the form-branch pin (it measures the branch this card does not touch), and the schema-conformance case. ⭐ That second one is a finding in its own right — the pre-fix body parsed GREEN against GetUiViewResponseSchema, so the existing conformance test could never have caught this defect. That is why a dedicated pin had to exist rather than leaning on conformance.

No dist leg is involved in the ablation: the test imports ./protocol.js, a relative specifier that vitest resolves to the source file, and the package declares no vitest config and no alias.

Checks — all run on the final commit 21aee57fa, quoting each gate's own verdict line

Repo-wide, not narrowed:

pnpm lint                        → clean, no output (89s, eslint . --no-inline-config)
pnpm --filter @objectstack/metadata-protocol build
                                 → check-dts-emitted: 1/1 declared declaration file(s) present.
pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2
                                 → Test Files  145 passed | 2 skipped (147)
                                   Tests  2017 passed | 10 skipped (2027)

Type-check ratchet (both halves, after building the closure exactly as lint.yml does):

check-type-check-coverage: OK — 66/78 workspace packages type-checked (plus the root),
  12 in the DEBT ledger (372 frozen raw errors), 1 exempt.
check-type-check-coverage --re-measure: OK — 30 ledger entr(ies) re-measured in 297.8s,
  1560 raw tsc error(s) total, none above its recorded number.

Measured directly as well, because @objectstack/metadata-protocol carries a shrink-only DEBT entry frozen at 63 and its tsconfig.json includes src/**/*, so a new test file is inside that program: tsc --noEmit -p tsconfig.json counts 63 — the frozen number exactly — with 0 errors in protocol.ts and 0 in the new test file. The per-file distribution matches the ledger note (27 in protocol.stored-migration.test.ts, 10 in seed-loader-multi-value-reference.test.ts), confirming the same population the ratchet freezes.

Gate family derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no hand-built diff list) — all green:

check:changeset-gate-self-tests · check:objectui-changeset · check:pm-half-states
check:published-files · check:slot-lookup · check:durability-log-level
check:filter-alias-parity · check:logger-receiver-detach · check:objectql-double-limit
check:page-declaration-shape · check:where-matcher · check:cross-package-test-inputs
check:test-source-alias · check:type-source-resolution · check:engine-double-contract
check:query-options-erasure · check:dispatcher-error-vocabulary · check:dual-build-cjs-loads
check-adr-0087-registration · check-changeset-no-major · check-empty-changeset
check-ci-filter-parity · check-comment-mask-adoption · check-keyed-text-bounds
check-plugin-teardown-shape · check-undeclared-dep-imports · check-shard-attestation
docs-audit/check-affected-docs · pm/release-rehearsal-clone --self-test

Two report NOT MEASURED, in their own words, and neither is a red:

  • scripts/check-test-completeness.mjs (exit 3) — "There is no local log to hand it, so the local reading for this gate is NOT MEASURED. ⛔ It is not a red." It needs a saved turbo run test log that only CI tees.
  • scripts/pm/check-half-states.mjs (exit 3) — "PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential." A board sweep, unrelated to this diff.

Changeset — minor, with the blast radius measured

.changeset/ui-view-hidden-priority-columns.md, graded minor and declared BREAKING.

Why minor and not patch. This repo uses exactly two grades — 341 patch, 257 minor, zero major — and 48 changesets state the convention verbatim: breaking changes ship as minor under the launch-window convention. The two patch precedents I read both justify themselves with "the set of accepted metadata is byte-identical", i.e. nothing a consumer observes moves. Here something a consumer observes does move: a column, its label and its search affordance disappear from a published route's body, and the consequence is a removal. Grading it patch would keep a behaviour removal out of the changelog an upgrading agent greps.

Why not major. Nothing authorable is removed or renamed, no stored metadata is invalidated, and there is no FROM/TO mapping to carry — the metadata was already correct and now simply takes effect. The ADR-0087 disposition is therefore not-required (no-migration-prescription), and the gate accepted it on its own terms: "✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition."

Blast radius, measured rather than assumed. Every hidden: true declaration site across all tracked files was resolved to the field key it attaches to. ⚠️ The first pass of this walk returned "(none)" with a failed control — it resolved zero keys, because it only knew key: { while this repo writes fields as key: Field.textarea({ … }). That zero was discarded, not reported. The corrected walk resolves 22 distinct keys including previous_password_hashes, token and key, so its zero is a reading:

  • 0 fields in packages/platform-objects, examples/** or packages/plugins/** are both hidden and priority-named. The three real hidden fields there are all non-priority names and were already dropped.
  • The only created_at + hidden pair in the tree is a @objectstack/objectql unit fixture that never calls getUiView (grep count 0).
  • Every other getUiView reference outside this package is a mock (vi.fn(), undefined as any, a thrower).

No in-repo consumer loses a column. ⚠️ That is a measurement of this repo, not of the class — a downstream app declaring status: { hidden: true } is exactly the ordinary shape this fixes, which is why it is declared rather than filed as invisible.

Serial constraints cleared: #13214

Verified disjoint rather than assumed. PR #13258 is confined to packages/rest and its own harness; this diff is confined to packages/metadata-protocol/src/ plus .changeset/. No shared file.

⚠️ The read-coupling runs the other way, and the PM should know before either lands. PR #13258's harness section 3 currently pins the pre-fix answer as a measurement — expect(columns).toContain('status') for a field declared hidden. When this PR lands, that assertion is what turns red, by design. It belongs to #13214/#13258 and is ⛔ deliberately not edited here; I read it only at that PR's head for reference. Whichever of the two lands second needs the sibling's expectation updated in its own PR. Nothing on main today asserts the pre-fix behaviour, so this branch's own CI is unaffected.

Out of scope, filed not fixed

#13328 — after this fix, a hidden created_at is correctly withheld from columns while sort still names it, because the sort directive is built from presence alone. Driven with a control arm (the sort block is byte-identical whether created_at is hidden or visible, while columns and searchableFields differ, so the probe can observe a difference where one exists). Left unfixed on purpose: whether hidden should govern an ordering directive is a judgment call, and #13259's triage ruled that this card restores a stated invariant and smuggles in nothing else. Filed unassigned with finding.

Generated by Claude Code


Generated by Claude Code

…pass

`FieldSchema.hidden` is declared "Hidden from default UI" and `getUiView` is
the default UI, but its list branch applied `!fields[k].hidden` to the fill
pass only. A field declared `hidden: true` was therefore withheld for eight of
nine spellings and served — with its authored label — for the ninth, whenever
the author happened to name it one of `name`, `title`, `label`, `subject`,
`email`, `status`, `type`, `category`, `created_at`. The `form` branch of the
same function already filtered hidden uniformly, so two branches of one
producer disagreed about what `hidden` means.

Bring the priority pass to the side that already honoured the declaration.
This restores a stated invariant; it does not redesign what `hidden` governs
and adds no way to declare a column list.

The pin drives three arms in one case — a hidden priority-named field, a
hidden non-priority field, and a visible priority field as the control that
stops "nothing is emitted" from passing vacuously — then sweeps all nine
priority names, and pins the form branch's exact output as unchanged.
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 4 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via getView (sdk), meta.getView (sdk))
  • content/docs/api/metadata-api.mdx (via /view/:object/:type (route))
  • content/docs/api/plugin-endpoints.mdx (via /view/:object/:type (route))
  • content/docs/kernel/contracts/metadata-service.mdx (via getView (sdk))
  • content/docs/kernel/services-checklist.mdx (via getUiView (symbol), getView (sdk), /view/:object/:type (route))
  • content/docs/protocol/objectui/concept.mdx (via /view/:object/:type (route))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via getView (sdk), meta.getView (sdk), /view/:object/:type (route))

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.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 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 0ae9e1e16e34db799cb717ad254165a2b4cef243packageMentionDocs.

Which tree this was computed on

This run read content/docs from 23ed64ff46d0552d4be93fb188eb382f9f92d999 — the merge of head 21aee57fa674e1abab115d0d18b2cf129de4848a into base 0ae9e1e16e34db799cb717ad254165a2b4cef243, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 23ed64ff46d0552d4be93fb188eb382f9f92d999 && git checkout 23ed64ff46d0552d4be93fb188eb382f9f92d999
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0ae9e1e16e34db799cb717ad254165a2b4cef243 21aee57fa674e1abab115d0d18b2cf129de4848a && git checkout -B drift-repro 0ae9e1e16e34db799cb717ad254165a2b4cef243 && git merge --no-ff 21aee57fa674e1abab115d0d18b2cf129de4848a

node scripts/docs-audit/affected-docs.mjs --json 0ae9e1e16e34db799cb717ad254165a2b4cef243

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 0ae9e1e16e34db799cb717ad254165a2b4cef243 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Contributor Author

PM review — accepted for landing (held in draft until CI is complete and green)

Reviewed at head 21aee57fa, three-dot against the branch's real merge base 3322527fa (⛔ not the PR's base.sha — this clone is shallow and a two-dot read renders post-cut commits as reverts).

Scope

Three files: the changeset, packages/metadata-protocol/src/protocol.ts, and the new pin. Zero hits for governed surfaces, content/docs/releases/**, and packages/spec/src/**; the same patterns matched 3/3 against a synthetic control list, so the zeros are real.

Clause-② does not bind, and it is worth being precise about why rather than resting on the narrative: the path limb is mechanical and does not fire (packages/metadata-protocol, not packages/spec/src/**), and the card carries no Clause-②: yes declaration. The "accept set is byte-identical / the response narrows toward the declaration" reasoning in the report is sound, but it is a justification, not the gate — the gate simply does not trigger here.

The fix

One line: fieldKeys.filter(k => priorityFields.includes(k))… && !fields[k].hidden. Option 1 per the triage ruling; option 2 (a declared column list) correctly not attempted — that is a new authoring capability, not this card.

I verified the load-bearing claim myself rather than accepting the report. The argument is "two branches of ONE producer disagreed about what hidden means", and it holds at the head:

  • :7563 — the fill pass already carried !fields[k].hidden
  • :7596 — the form branch already carried it (k !== 'id' && k !== 'created_at' && k !== 'updated_at' && !fields[k].hidden)
  • :7559 — the priority pass was the sole outlier

So this is restoring a stated invariant, exactly as the in-code comment claims, not a redesign of what hidden governs. The comment says so explicitly and I want that wording kept.

The test, and one finding inside it

The three-arm structure is right, and the third arm is the one that matters: name (priority-named, not hidden) is the control without which "nothing is emitted" would satisfy the other two arms vacuously. The name-agnostic invariant computed from the fixture — no emitted column is declared hidden — is better than the restated nine-name list, because a tenth priority name added later without the filter fails even though a hard-coded list would not notice. Good instinct.

The ablation produced a finding, not just a red. The direction was predicted in writing and deliberately mixed, and the two predicted-GREEN legs are the informative half: the form-branch pin stayed green (it measures the branch this card does not touch), and — the real one — the pre-fix body parsed GREEN against GetUiViewResponseSchema. The existing conformance test could never have caught this defect. That is why a dedicated pin had to exist, and it is the kind of thing that is only visible because someone predicted the green rather than only hunting the red.

Mutation proven on disk before any reading (grep counts plus blob 3e5f6838 ≠ HEAD blob 71c528b6), and the restore leg named HEAD explicitly — ⭐ correctly noting that a bare git checkout -- <path> restores from the index, which the mutation had already written. That is a trap that silently produces a fake-clean restore, and catching it is the difference between a verified restore and a hopeful one.

⭐ The discarded first measurement

The blast-radius sweep returned (none) on pass 1 with a failed control — it resolved zero field keys because it only recognised key: { while this repo writes key: Field.textarea({ … }). That zero was thrown away rather than reported. The corrected walk resolves 22 distinct keys and then reports zero affected in-repo consumers, which makes it a reading instead of a silence.

This is the single most important thing in the report. A zero from an instrument that was never shown to be able to produce a non-zero is not evidence, and reporting the first pass would have been a defensible-looking lie. ⚠️ The report is also correctly scoped: a measurement of THIS repo, not of the class.

⚠️ Cross-PR coupling — handled, and it is not this PR's to fix

The report flagged that PR #13258 (card #13214, os-trump's, draft) pins the pre-fix answer as a deliberate measurement — status, hidden and priority-named, served with its label — and that landing this PR turns that assertion red by design.

Verified: #13258's head ba06dabd sits on 74049254d (the pre-stall main), so this PR's CI is unaffected and nothing on main today asserts the pre-fix behaviour. There is no file-level overlap either — #13258's only file is under packages/rest/src/.

⛔ Leaving it untouched was the right call and I have not pushed anything to that branch: a larger ask on a PR this seat did not open goes to its author, never as a push. I have posted the sequencing note there instead, with an explicit offer to hold this PR if they would rather land first. Absent that, this lands first — #13214 is a maintainer decision card, so #13258 is not landing soon.

Changeset

minor, declared BREAKING, ADR-0087 disposition not-required — and check-adr-0087-registration is ✅ green on the head, so the disposition is accepted on the gate's own terms rather than by assertion. The grading reasoning is right: the patch precedents justify themselves with "the set of accepted metadata is byte-identical", and here a column, its label and its search affordance disappear from a published route's body. That is a removal a consumer observes.

Type-check

Not a NOT-MEASURED green: this package's tsconfig includes src/**/*, so the new test file is inside the program, and tsc --noEmit -p tsconfig.json counts 63 — the frozen ledger number exactly — with 0 errors in protocol.ts and 0 in the new file, with the per-file distribution matching the ledger note. Measuring that rather than inferring it from a green ratchet is the right standard.

Two gates read NOT MEASURED in their own refusal text (check-test-completeness.mjs, pm/check-half-states.mjs); neither is a red and neither is being counted as a pass.

Out-of-scope finding

#13328 (a hidden created_at is withheld from columns while the list body's sort still names it) — correctly filed, not fixed: whether hidden governs an ordering directive is a judgment call, not a mechanically-pinned shape, so the bounded in-place exemption does not apply. The probe carried a control arm showing the sort block is byte-identical either way while columns and searchableFields differ, so it can observe a difference where one exists.

Landing posture

Not enqueued. CI is early (29 checks reported, most in_progress) and total_count still grows as rollup rows appear. The bar is EVERY check completed and green. Holding in draft; I'll flip ready and arm auto-merge when it clears, then verify the landing by content on origin/main — ⛔ never by the merged boolean.

Nothing to change.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 30, 2026 03:57
@os-zhuang
os-zhuang enabled auto-merge August 30, 2026 03:57
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 30, 2026
Merged via the queue into main with commit 2a75270 Aug 30, 2026
34 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-13259-hidden-priority-fields branch August 30, 2026 04:23
os-trump pushed a commit that referenced this pull request Aug 30, 2026
The case asserted that `hidden` is NOT a uniform floor on the crossed path:
`beta_secret` (hidden, non-priority) was dropped from the list body while
`status` (hidden, priority-named) was served, carrying its authored label.

That reading was true when taken on 2026-08-29 and has since been made false
by a repair, not by an error in the measurement. `2a75270b1e` (#13329, merged
2026-08-30) put `!fields[k].hidden` on getUiView's list priority pass, where it
had sat on the fill pass alone, so the list branch now agrees with the form
branch it had disagreed with.

Re-driven on the repaired producer through the same crossed path — anonymous
request, `X-Environment-Id: env_beta`, unscoped mount, built `dist/` — both
hidden field kinds are now withheld, together with their labels and their
`searchableFields` entries:

    columns    = ["name", "created_at", "beta_only_field"]
    colLabels  = ["Beta Account Name", "created_at", "Beta Only"]
    searchable = ["name", "created_at", "beta_only_field"]
    formFields = ["name", "beta_only_field"]

The case is rewritten to pin that, keeps controls so an empty producer cannot
satisfy it vacuously, and records the 2026-08-29 -> 2026-08-30 sequence in
place so a reader does not conclude the original measurement was wrong. It is
kept rather than deleted because #13329's own pin calls `getUiView` directly
in-package from source; this one is the blast-radius reading for the CROSSED,
cross-environment path through the REST seam and the built artifact.

`rest-server.ts` remains byte-identical to base; no production code changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvqBFLRzXdSPcbusDoED9k
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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] getUiView list branch serves fields declared hidden when the field name is one of its nine priority names — declared, not enforced

1 participant