test(app-shell): measure and pin what a declared flow-node defaultValue renders (objectui#6830) - #8431
Merged
os-justin merged 3 commits intoSep 7, 2026
Conversation
…objectui#6830) Measures the rendered control rather than the declaration table: an unset key draws an unchecked box / a blank select whatever the field declares, on both writers of the property (the hand-written table and the engine-published configSchema). Pins the one read site that does reach the DOM — a declared default on a `showWhen` controller changes visibility, not value — and the non-regression half, so an inspector that renders nothing cannot pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…830) Keeps the new pin at zero lint warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-justin
marked this pull request as ready for review
September 7, 2026 23:22
os-justin
deleted the
claude/issue-6830-flownode-inspector-defaultvalue
branch
September 7, 2026 23:39
This was referenced Sep 8, 2026
os-justin
pushed a commit
that referenced
this pull request
Sep 8, 2026
…defaultValue
objectui#6830 arm A ("show, do not write", triage 2026-09-04), the boolean
half — objectui#8451. The select half stays untouched: it is blocked on
objectui#8450.
The control drew `value === true`, which flattened an ABSENT key and a stored
`false` onto one unchecked box although the runtime treats them oppositely on a
key the spec defaults to true. It now shows the value in effect — the stored
boolean, else the declared `defaultValue` in the same 'true'/'false' spelling
`controllerAdmits` already resolves an unset controller against. Nothing is
written; the first author edit still commits an explicit boolean.
`isUnsetFieldValue` is exported from `flow-node-config.ts` so the three sites
that have to answer "has the author stored anything here" (the re-show rule,
the `showWhen` resolver, and now the control) share one definition instead of
three copies of `=== undefined || === null || === ''`.
⛔ No caption naming the default is rendered, deliberately. Of the two boolean
fields the offline table declares a default for, `escalation.enabled` declares
the OPPOSITE of what installed `@objectstack/spec` 17.3.0 applies to the
omitted key (objectui#6620), so a "(default)" label would have shipped that
wrong claim to authors in words. The seed alone leaves that field rendering
byte-identically to before, which is pinned.
PR #8431's pin is turned green against the repaired behaviour rather than
weakened: its three leg-B rows now assert the new states, and the stored-`false`
rows are kept load bearing so an always-checked control — an implementation
strictly worse than the bug — still fails.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #6830 — this PR carries the measurement half only. No render-path
change is in it: the direction is a product call and it is named under
"What is still open".
Base
0203a29e9· head09c44a863· installed@objectstack/spec17.3.0.What was measured
The card's claim is a claim about a rendered document, so it was settled by
rendering
FlowNodeInspectorand reading the control the author sees — thecheckbox's
checked, the select trigger's text — not by grepping the source.defaultValueseed the control's value?value === trueand the selectvalue != null ? String(value) : ''; nothing else in the path reads the propertyjson-schema-to-fieldsconverts JSON-SchemadefaultintodefaultValue, and that field renders identically blankcontrollerAdmitsresolves an unsetshowWhencontroller through it, so a declared default changes which fields are ON SCREEN. That is a visibility effect, never a value effectfalse"?getFieldValuereturnsundefinedvsfalse, andvalue === trueflattens both to an identical DOM node (asserted byouterHTMLequality)Two smaller measurements fell out of the same run:
card and
InspectorSelectFieldboth assume. The field always passes acontrolled value (the
''-to-sentinel bridge), and Radix renders itsplaceholder only for an undefined one.
escalation.enabled: defaultValue 'false'is inert: anabsent default and a
'false'default both failequals: ['true'], so thegroup hides either way. The ablation confirms it — neutralising the read site
reds only the online case.
Three of the card's numbers moved; the headline did not
are verbatim as quoted; the reproduction still reproduces on 17.3.0.
assignmentcreateisscreen.mode("Form mode") —assignmentdeclares nothing. The measured specversion is 17.3.0, not 17.2.0. And
FlowNodeInspector.tsx:236(named in acard comment as the sole read site) reads flow variable defaults from
draft.variables, a different property on a different type; theshowWhenread is
flow-node-config.ts'scontrollerAdmits, as the card body itself says.The pin, and the proof it can fail
FlowNodeInspector.declaredDefault.test.tsx— 13 cases, all asserting renderedvalues. Three ablations, each mutating a READ SITE from a committed tree, each
proved to reach disk by
git hash-objectagainst the HEAD blob, each restoredby state (
git diff HEADempty and hashes equal):controllerAdmitsstops resolving throughcontroller.defaultValuea gate whose default is true reveals its siblings for a node that omits the key(1 failed / 12 passed)field.defaultValue— i.e. someone implements the repairboolean: an unset key draws an UNCHECKED box…,renders identically whether the key is missing or explicitly false,the boolean control exists, is a checkbox, and commits the author edit(3 failed / 10 passed)Leg C is the point of the non-regression describe: every negative in this file
("the control shows no default") is also satisfied by an inspector that renders
nothing, so the controls' existence, their stored-value behaviour, the offered
vocabulary and the deprecated-value fallback are pinned beside them. A feature
deletion cannot pass.
What is still open
Triage already ruled direction A ("show, do not write") on this card, and set
a precondition: establish whether the inspector can tell absent from stored-false
before designing the affordance. That precondition is now answered — the
distinction survives to the component boundary and is destroyed by
value === true,so A's boolean half is implementable and its cost is known.
Two things the implementer of A needs, neither of which this PR decides:
effective default as placeholder text does not work today:
InspectorSelectField'splaceholderprop is unreachable for every caller (measured — an explicitplaceholder renders as the empty string). That primitive has 61 call sites
across the designer, so the blast radius is wider than flow nodes.
ApprovalEscalationSchemaparses{ timeoutHours: 24 }toenabled: true, while the offline table declares'false'. Showing declared defaults would show a wrong one on that field.That divergence is flow-node inspector: escalation.enabled toggle defaults to 'false' while @objectstack/spec now defaults the key to true — omitted-key blocks render OFF but are ON #6620's subject and is not addressed here; flow-node inspector: escalation.enabled toggle defaults to 'false' while @objectstack/spec now defaults the key to true — omitted-key blocks render OFF but are ON #6620 remains
open on its own terms.
⛔ Retirement is not available as the other arm: the property has a live
reader (
controllerAdmits) and a second writer (the engine-publishedconfigSchema), so ADR-0049 enforce-or-remove resolves to enforce byelimination. Only the doc comment's "spec default" wording could be retired,
which is triage's arm B — already refused as insufficient.
Verification
pnpm exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.declaredDefault.test.tsx— 13 passed.pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build— exit 0 (closure built before type-checking).pnpm --filter @object-ui/app-shell type-check— exit 0 (tsc --noEmitandtsc -p tsconfig.test.json). The new file is in the test program:tsc -p tsconfig.test.json --listFileslists it once, with a lit control and a zero-hit negative control.pnpm --filter @object-ui/app-shell lint— exit 0, 0 errors; the new file contributes 0 warnings.check:vi-mock-specifiers,check:vi-mock-inherit,check:control-bytes— all✅ OK.check-governed-queue-guard --teston both changed paths —✅ NOT GOVERNED.check-changeset-presence—✅ 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)… Every one of them has an EMPTY frontmatter.check-changeset-no-major—✅.🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code