test(app-shell): measure the reachability of a dependsOn lookup action param (objectui#8672) - #8749
Conversation
…param (objectui#8672) Measurement only — no disposition is chosen. Three legs, each with a lit control: the dialog's permanent gate (current shape), `@objectstack/spec` already refusing `dependsOn` on an action param (contract, version-qualified), and the field-backed route reading the snake spelling the spec refuses (current shape). Also corrects a stale comment in paramToField.test.ts that described this seam backwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
✅ 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 review — accepted, flipped out of draft, auto-merge armed. Measurement-only, as dispatched: no arm chosen, no fix shipped, and the pins that record today's shape are labelled CURRENT SHAPE, NOT CONTRACT on the legs that a disposition will redden. That labelling is the difference between a pin that helps the next implementer and one that fights them. Q2 came back decisive and it is not what either the card or I expected.
The query half exists and is wired into three picker surfaces, and your ABL-1 measured the gate lifting. The card's "different consumers, different remedies" is contradicted the same way — one consumer, one gate, one One thing I am not adopting, because you could not check it and neither could I. You report objectui#7154 as closed and fixed by objectui#7165. #7165 is not reachable in this checkout's history, and Both dedup channels dark and you filed nothing — REST Noted on the push-order question: your standing contract (push the empty branch first as a write-route probe) beat my "push nothing if no pin is possible", it cost nothing because pins were possible, and flagging rather than silently choosing was the right call. I will settle it in the dispatch template rather than leave two rules pointing different ways. Generated by Claude Code |
Part of #8672 — measurement only. ⛔ No disposition is chosen here, and the card stays open: arms 1 (wire it) and 2 (refuse it) are both still on the table and choosing between them is the maintainer's, not this PR's.
What this measures
The card asks three questions. All three now have readings, each with a lit control.
Q1 — how many writers are there?
Zero lookup action params declare
dependsOnin this repository, and zero action params declare it at any type. Sweeps run asgit grep -l … -- .over 7,031 tracked files, with controls on the same command shape and population:dependsOn(any use)'lookup'lookupActionParamDefdependsOnEvery co-occurrence of a lookup and
dependsOnin the tree is field metadata inside test files, never an action param. Three framework code sites carry the key (paramToField's emit, the resolver'sRESOLVED_ONLY_PARAM_KEYSmessage, the resolver's snake read) — none is an authored document.git grep -lE '"params"' -- '*.json'and the same for"actions"both return 0: this repository authors no action metadata as data at all, so the denominator is zero rather than large./api/v1/meta/objectdocuments the resolver actually reads, and any host app's action definitions — never appears in this tree. The honest reading is "zero writers in this repository", not "zero writers".Q2 — where is the authoring surface? ⭐
@objectstack/spec(17.3.0, frompnpm-lock.yaml), and it already refuses the key. Measured by parsing, not by reading declarations:ActionParamSchema.safeParsereferenceunrecognized_keysdependsOnunrecognized_keys: ['dependsOn']selectparam +dependsOn@object-ui/types'ActionParamextendsOmit<z.input<typeof ActionParamSchema>, 'type'>, so it declares nodependsOneither andtscrefuses it; the resolver names it a third time inRESOLVED_ONLY_PARAM_KEYS.⇒ Arm 2's refusal already exists, upstream, mirrored objectui-side by three independent mechanisms. There is nothing to build here and nothing to request upstream.
Q3 — is the gate reachable outside a probe?
Not from any spec-valid authored metadata. Inline authoring is refused (Q2). The one route the repo itself points authors to —
RESOLVED_ONLY_PARAM_KEYS.dependsOn: "make the param field-backed … to pick it up" — readsfield.depends_on, the snake spellingFieldSchemarefuses by name (Did you mean depends_on → dependsOn?), while the cameldependsOnit accepts is never read:FieldSchemadependsOn(spec's spelling)undefineddepends_on⇒ The two spellings are disjoint: the one the spec admits is not read, and the one that is read the spec refuses.
Pins shipped
New
packages/app-shell/src/views/ActionParamDialog.lookupDependsOnReach-8672.test.tsx, 10 assertions in three legs. Legs A and C are labelled CURRENT SHAPE, NOT CONTRACT in the file docblock and on their describe blocks — whoever implements a disposition should expect them red and should rewrite them, not trust them. Leg B is labelled a contract pin, version-qualified.Also corrects a stale comment in
utils/paramToField.test.tsthat described this seam backwards (the folded-in finding on the card).Verification — every assertion observed red
Seven ablations, each from the committed tree, each with the mutation proved on disk (
git hash-objectvs the HEAD blob, anchor counts, a line-total gate) and the restore proved by state (git diff HEAD --quiet), undertrap … EXIT INT TERMwith absolute paths. Per-test outcomes from the JSON reporter.'lookup'toCASCADE_OPTION_WIDGET_TYPESLookupFieldgate forced falsefield.dependsOninstead offield.depends_ondependsOnselectdocument losesdependsOnAll 10 assertions were observed red at least once. ⛔ None of these mutations is committed; every restore is proved by state above.
Green runs: 10/10 on the pin file; 141/141 across the blast radius (
paramToField, all threeresolveActionParamssuites, all threeActionParamDialogsuites).pnpm --filter @object-ui/app-shell type-checkexits 0 after building the dependency closure, andtsc -p tsconfig.test.json --listFilesconfirms both changed files are inside that program. Targetedeslinton both files: 0 errors, 0 warnings.Gate verdict lines, quoted:
✅ 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 — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.✅ No changeset declares a 'major' bump.✅ check-control-bytes: OK (scanned 6946 tracked text file(s); skipped 85 binary).✅ NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched.1. The PM triage's argument for arm 1 being a feature. The triage says a lookup "has no options list; it has a query. Filtering candidate records by a dependency's value is a different mechanism that does not exist here … the gate would lift while the picker still showed every record." That mechanism does exist and is shipped.⚠️ This is not an argument for arm 1: which route should supply the record is still open (
LookupFieldbuildsdependentFilterfrom the samedependsOnchain and merges it intopopoverFilter, which feedsuseRecordQueryfor all three candidate surfaces (popover, Level-2 picker, PeoplePicker). The#7165changeset states it directly: "every picker takes thedependsOnchain as a hardbaseFilter. The second half is host-independent and was already live." ABL-1 measured the consequence — withlookupin the set, the gate did lift on the keystroke.CASCADE_OPTION_WIDGET_TYPESis documented as an options-list allow-list, and the lookup-family boundary is objectui#4771). Only the stated reason is contradicted.2. The card's "different consumers" claim about the grid twin. The card says the two surfaces "have different consumers and different remedies." They share one final consumer —
LookupField, onedependenciesMissinggate, onedependentFilter. What differs is the host that supplies the record. And objectui#7154 is CLOSED, itsdependsOncase rewritten by objectui#7165, which fixed the grid withdependentValues={ctx.pendingRow ?? ctx.row}— so the grid twin is not an open twin but a landed precedent for the remedy. The genuinely open siblings are #7190 (detail page, pinned not fixed) and #7206 (the unsettable context tail), bothpm:blocked.🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code