Repository navigation
feat(app-shell): the screen-flow dialog renders a screen field's declared bound, help text and lookup target (objectui#9248) - #11188
Conversation
…ared bound, help text and lookup target (objectui#9248) The spec's screen field carries `min` / `max`, `inlineHelpText` and `reference` (object-field spellings). `ScreenView` now puts the bound on the numeric input as native attributes, draws the help text under the control and names it in `aria-describedby`, and resolves a `type: 'lookup'` field's picker from `reference` through the shared `LookupField` widget fed the screen's data source. `FlowRunner`'s submit check refuses an out-of-bound value naming the field, with the engine's resume comparison (`screenFieldBoundViolations`). The Studio preview's `buildScreenSpec` projection carries the same four keys, so the preview draws them as the runtime does. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…ectui#9248) `react-hooks/static-components` refuses a component created during render; the shared registry caches one lazy `LookupField` per type, so the module-scope constant is the same component `ActionParamDialog` renders. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
Contract reviewServed-tier: Inputs: card #9248 (body and its five comments: unlock ① Derived judgments
② Semver levelChangeset ③ Boundary flagsDeviations named in dev report
Out-of-scope findings:
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #9248
Clause-②: no
The three screen-field keys that objectstack-ai/objectstack#17306 (ruling A, 「声明即强制」) added now render in the console's screen-flow dialog. They are read straight off the installed
@objectstack/spec17.5.0ScreenFieldSpec, with the spec's own spellings:min/max,inlineHelpText,reference. There is no local spelling and no alias. That card holds itself open until these keys render, so this PR supplies the rendering half.Implemented by the
domain:uiseat-2 dispatch of sessionhttps://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec, branchclaude/issue-9248-screen-field-bound-help-reference.What changed
ScreenView/ScreenFieldInputnumberorcurrency) getsmin/maxas native attributes. They are never written onto a date or text input.inlineHelpTextis drawn as its own element under the control. Every control arm names it inaria-describedby: text, number, textarea, checkbox, select and lookup. The placeholder is left alone, because the browser clears it on the first keystroke.type: 'lookup'field with a non-emptyreferencerenders the object form's ownLookupField, fed the screen'sdataSource. It is reached throughgetLazyFieldWidget('lookup')from@object-ui/fields, the same seamActionParamDialoguses, so no new picker is written. It has its own Suspense boundary, because the Studio preview renders this body with none around it.referencekeeps today's plain input. The spec'sScreenFieldSpec.referencedoc names that fallback for a run suspended before the key existed.screenFieldBoundViolations(screen, values).FlowRunner: the submit check refuses an out-of-bound value beside therequiredcheck and names the field. The message uses the existing pack keysvalidation.min/validation.max/validation.formInvalidJoiner, so no i18n key is added. The header's "deliberately not localized" list gainsinlineHelpText(premise 6).metadata-admin/previews/screen-spec.ts(toScreenFields). It now carries the same four keys into the Studio preview'sScreenSpec, each only when it holds the spec's declared type. Premise 5 below explains why.'@object-ui/app-shell': patch.Premises re-measured
Measured at objectui BASE
3ab51503b, against the installed@objectstack/spec17.5.0 and objectstack tag commit0f6dcac5e9.ScreenFieldConfigSchemaparses{type:'number',min:1,max:10},{inlineHelpText}and{type:'lookup',reference:'account'}. It refuses a lookup with noreference(a custom issue on pathreference).helpandhelpTextare refusals carrying a rename prescription (unrecognized_keys, "Did you meanhelpText→inlineHelpText"), not accepted aliases. objectui reads none of them.ScreenFieldSpecin@objectstack/spec/contractsdeclaresmin,max,inlineHelpTextandreference, all optional. They are read off the verbatim re-export, with no local widening.FlowRunnerhosts passdataSource:useConsoleActionRuntime,RecordDetailView, and the console'sFlowRunsPage, which passes its adapter. The Studio preview passes its adapter too. So the lookup reaches a data source throughScreenView's existing prop, and the FlowRunner lookup pin runs through that path.validateScreenInputsinservice-automation'sscreen-input-contract.tsat0f6dcac5e9. The bound is compared only when the value is a present finite number, whatever the field'stype. A value belowminor abovemaxis refused, so the bounds themselves pass (inclusive). A field hidden byvisibleWhenis skipped.screenFieldBoundViolationsapplies exactly this comparison, and a pin covers each clause.ScreenView, butbuildScreenSpecprojects each authored field down toname/label/type/required/visibleWhenintoScreenFields. The new keys therefore never reached the preview's renderer. The evidence is thepreview-projectionablation leg below: with the BASE projection restored, the three preview pins go red. Following the ruling's intent that the keys ship with their rendering, the projection now carries the four keys. This is the only file outside the dispatch's surface.inlineHelpTextis not overlaid.FLOW_SCREEN_FIELD_COPY_KEYSis['label', 'placeholder']at 17.5.0 and on objectstack main. The spec's translation face also refuses aninlineHelpTextentry loudly (unrecognized_keysonflows.F.screens.S.fields.Q), and the spec documents this as a not-yet that needs a ruled widening of that face. The runner walks the constant, so it will follow with no edit here.Tests
All of these ran on HEAD
7d9c24c28, from the worktree root.ScreenView.boundHelpReference-9248.test.tsx,__tests__/FlowRunner.bounds-9248.test.tsxandmetadata-admin/previews/ScreenPreview.boundHelpReference-9248.test.tsx. Together they hold 24 tests, one lit leg per key, each with its control:min: 1, max: 10puts both native bounds on the input. Submitting 11 or 0 is refused naming the field, and no resume request is sent. Submitting 10 passes and resumes with{qty: 10}. An empty optional field is not refused and resumes with{}. CONTROL: the same field without the keys resumes with 11.inlineHelpText: 'Shown below'renders below the input (document order), is linked byaria-describedby, becomes the accessible description, and stays on screen while the input holds a value. This holds on every control arm.type: 'lookup', reference: 'account': the picker listsaccountrecords from the data source. The pin asserts the queried object name, and it renders with noSchemaRendererProvider, so onlyScreenView'sdataSourceprop can feed it. The same test runs throughFlowRunner's hostdataSourceand in the Studio preview. CONTROL: a lookup withoutreferencestays a text box.aria-describedby, no help element and no picker.ScreenView/FlowRunner/screen-spec/buildScreenSpec/ScreenPreview, enumerated withgit grep -l.Test Files 31 passed (31),Tests 378 passed (378).RecordDetailViewsuites):Test Files 37 passed (37),Tests 228 passed (228).pnpm --filter @object-ui/app-shell type-check, run after the closure buildturbo run build --filter='@object-ui/app-shell^...'(28 of 28 tasks), exits 0. Bothtsc --noEmitandtsc -p tsconfig.test.jsonran, and--listFilesOnlyshows all three new pins in the test program.scripts/__tests__/(run once because the diff adds a markdown changeset):Test Files 177 passed | 2 skipped (179),Tests 5370 passed | 2 skipped.Ablations
These were one-shot runs; no permanent file came from them. Each leg went through objectstack's
scripts/ablation-replace.mjsin WRAP mode: the anchor had to hit exactly once, the mutation was proven on disk by a blob change, the three pin files were run, and the restore was proven (blob equals HEAD andgit diff HEADis empty). An outertraprestored the tree on EXIT, INT and TERM. No dist is involved: the pins import the subject by relative path fromsrc.min=/max=props on the Inputif (outOfBounds.length)becomes always-falsehelpIdbecomes alwaysundefinedtoScreenFields(the BASE projection)Every leg ended with
ok restoredand a clean tree.The control, measured byte-for-byte. A throwaway probe rendered key-less fields covering every arm, with empty and with filled values, plus the whole
FlowRunnerdialog. It ran against HEAD and against BASE'sScreenView.tsx/FlowRunner.tsx, with the blob verified equal to3ab51503bbefore the run. Result:cmpbyte-identical, 21501 bytes each. The files were restored to their HEAD blobs, andgit diff HEADcame back empty.Gates
.changesetmarkdown, answers "File ignored because no matching configuration was supplied."--format jsonresult length.--print-configshows noparserOptions.project/projectService, so linting is not type-aware. No rule undereslint-rules/reads the filesystem. So this diff cannot move any untouched file's verdict.node scripts/check-changeset-presence.mjsexits 0 ("6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)").check:control-bytesexits 0.check:new-line-citationsexits 0 (0 new citation(s)).check:i18n-keysexits 0.check:changeset-claimsexits 0 and is report-only. It names two pending changesets that mentionscreen-spec.ts. Both paragraphs are about that file's spec imports, which this change leaves alone, so both still hold.check:pending-changeset-literals,markdown-test-inputs.mjs --auditandcheck-changeset-no-majorexit 0.check-governed-queue-guard --testover the changed paths answersNOT GOVERNED.check:designer-field-key-parityexits 0.check:eager-closure, because it needs a full console build, and CI owns it.ScreenView's one new static import is@object-ui/fields, which both runtime hosts already import statically throughActionParamDialog.LookupFielditself stays behind the registry's lazy chunk.Acceptance notes
toScreenFieldsalso dropsoptions,placeholderanddefaultValue, all keys thatScreenViewrenders.ScreenPreviewgave, for aselectfield withoptionsand a text field withplaceholder: previewcombobox 0 | textbox 1 | placeholder 0against runtimecombobox 1 | textbox 0 | placeholder 1.examples/app-todoflowtask.flow.ts, screen nodescreen_1, fieldspriority(selectwithoptionsanddefaultValue: 'normal') andcategory.defaultValuecarries the engine's{token}interpolation, which makes the class fix non-mechanical. It also predates this change: the byte-identical control above covers the key-less path.inlineHelpTextis drawn untranslated until the spec's per-field translation face lists it (premise 6). The spec owns that step. Noted, not filed.ScreenView.tsxeslint warnings go from 12 to 13: one morereact-refresh/only-export-components, for the new exported helper. The file's existing exported helpers already trip the same rule, andlint.ymlsets no--max-warnings.Generated by Claude Code