Skip to content

fix(app-shell): the flow inspector's inline visibleWhen cell for a screen field reads the screen's declared fields, as the Problems panel and the Debug run do (objectui#10772) - #10792

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10772-inline-cell-screen-scope
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10772-inline-cell-screen-scope

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10772
Clause-②: no — a Studio authoring hint moves onto the already-ruled screen scope (ruling C on objectui#10743); no declared surface moves

Executes the triage direction on objectui#10772 (comment 5856191354, from ruling C 5853038135 on objectui#10743) under claim 5856807565. Branch claude/issue-10772-inline-cell-screen-scope, head b6e912aab4, base 29b45f6a0c. Dispatched by the domain:ui seat 2, session https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN.

What changed

  • inspectors/FlowObjectListField.tsx: the expression column branch asks screenVisibleWhenNode(context.node, fieldId, col.key). When that names a screen node, the picker gets screenScopeGroups(node): one "Screen fields" section holding exactly [...screenPredicateRoots(node)], which is the screen's declared fields plus record. FlowExprIssue gets screenNode. Every other expression column is unchanged and keeps scopeGroups, the flow scope. New optional prop fieldId: the list's descriptor id. It is never forwarded to a nested list, so a nested list can never qualify.
  • inspectors/FlowExprIssue.tsx: new optional prop screenNode. When it is set, the unknown-reference step is screenVisibleWhenScopeError(value, screenNode), and scopeGroups is not read. The brace/shape error still comes first (validateExpressionClient), in the Problems panel's order (checkScreenVisibleWhen). The docblock now says which scope each cell reads (direction item 2).
  • inspectors/FlowNodeConfigField.tsx (the plumbing): passes fieldId={field.id} to FlowObjectListField. One prop, plus a comment.
  • previews/flow-expr-problems.ts: isScreenVisibleWhenColumn is now exported, with its docblock saying why. The cell names the column with the same predicate the Problems panel uses, so the column's identity is not copied. ⚠ This file was not in the claimed file surface. It is declared here: one keyword, and no verdict moves.
  • inspectors/flow-scope.ts: ScopeGroupId gains 'screen_fields'. This follows the approval precedent: its own id, so the section can never leak into the flow picker. The change is type-only.
  • previews/screen-spec.ts: read-only. No export was needed; screenPredicateRoots and screenVisibleWhenScopeError were already exported.
  • .changeset/10743-studio-screen-visiblewhen-live.md: a dated note (objectui#10533 form) follows its "Still on the flow scope" paragraph. That paragraph is kept as the record of that change. Frontmatter md5 of lines 1–3 is 4e8ce55021fe3b14329c8b4cb3f9f354 before and after.
  • packages/app-shell/README.md: the flows paragraph said "the inline inspector's visibleWhen cell still reads the flow scope (a follow-up)". This change makes that sentence false, and the README is published (files lists README.md), so it now says the cell follows the same rule. Declared as a file-surface addition.
  • .changeset/10772-inline-screen-visiblewhen-cell.md: '@object-ui/app-shell': patch.

H1: reproduced on base 29b45f6a0c

Probe (not committed): the real FlowObjectListField with the screen descriptor's fields columns, context set to the draft and node, and the resolveFlowScope groups. The draft has a record-after-update start on crm_lead, a flow variable needsApproval, and a screen review. Readings, verbatim:

  • tokens offered: ["needsApproval","record","previous"]
  • note, discount > 0 (sibling): NOTES ["discount is not a reference in scope at this step."]
  • other, other_field == 'x': NOTES ["other_field is not a reference in scope at this step."]
  • reason, needsApproval == true (run variable): NOTES []
  • PICKER (option text is token then detail): ["needsApprovalvariable · boolean","recordtrigger record · crm_lead","previousrecord values before the change"]

The card's measurement holds on base.

H2: the seam

  • The cell is the col.kind === 'expression' branch of FlowObjectListField. Before this change it rendered VariableTextInput groups={scopeGroups ?? []} and FlowExprIssue scopeGroups={scopeGroups}, and FlowExprIssue ran findUnknownRefs(value, role, scopeRoots(groups)).
  • The node was already there: FlowNodeInspector passes context={{ draft, node }} through FlowNodeConfigField into FlowObjectListField. The field id was missing. The Problems panel names the column by isScreenVisibleWhenColumn(type, fieldId, colKey), so the one missing input is fieldId, set by FlowNodeConfigField from field.id.
  • The function is screenVisibleWhenNode(context.node, fieldId, col.key), which returns a ScreenPreviewNode or undefined. It feeds screenScopeGroups(node), which is screenPredicateRoots(node), for the picker, and FlowExprIssue.screenNode (screenVisibleWhenScopeError(value, node)) for the note. No third copy of the rule.
  • What screenVisibleWhenScopeError returns for needsApproval == true on this screen is the sentence "needsApproval is not a field on this screen". No nearest-field suffix is added, because no declared field is close. The Problems panel shows the same sentence prefixed by the row label ("Reason: …"). The inline cell sits in the row, so it shows the sentence without a prefix. The pin asserts equality with the panel's message instead of pinning the literal.

Pins and ablation

New file inspectors/FlowObjectListField.screenVisibleWhen-10772.test.tsx. It mounts through the real plumbing: FlowNodeConfigField with the real screen and decision descriptors, context set to the draft and node, and resolveFlowScope groups.

  1. A sibling-field predicate is silent: discount > 0 and other_field == 'x' (both declared on the screen) show no note and no alert.
  2. A run variable is flagged in the Problems panel's words. The needsApproval == true cell shows exactly one role="note" naming needsApproval. The panel's messages on node review equal ["Reason: " + that note], at warning.
  3. The picker offers the screen roots: the tokens equal [...screenPredicateRoots(review)] (discount, other_field, note, extra, reason, record), with no needsApproval and no previous.
  4. Control, a decision's conditions[].expression upstream of the screen: discount > 0 is flagged naming discount, needsApproval == true is clean, and the picker equals the resolveFlowScope tokens (needsApproval, record, previous).
  • Red leg: the five base sources were checked out from 29b45f6a0c under the head pin file, with blobs confirmed equal to base. The restore was trap-driven with git checkout HEAD --, every blob was re-checked equal to HEAD, and git diff HEAD was empty afterwards. Result: Tests 3 failed | 1 passed (4). Pins 1–3 are red; control 4 is green.
  • Green leg at b6e912aab4: Tests 4 passed (4).
  • Ablation (flow-scope fallback) ran through ablation-replace.mjs in wrap mode. It replaced const screenNode = screenVisibleWhenNode(context?.node, fieldId, col.key); with const screenNode: ScreenPreviewNode | undefined = undefined;. The anchor hit exactly once and went from 1 to 0 hits; the blob went from 599051a346e0 to da61329c536d. Run over the pin file plus the three existing FlowObjectListField.* suites: Test Files 1 failed | 3 passed (4), Tests 3 failed | 11 passed (14). Only the three screen pins are red; the control and every existing row stay green. The restore left the blob equal to HEAD 599051a346e0 and git diff HEAD empty, and afterwards the marker count on disk is 0 and the anchor count is 1.

Gates (at b6e912aab4, every run of the heavy ones under the verify lock; seconds are shared-box)

  • Dependency closure turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: Tasks: 28 successful, 28 total, VERDICT command-exit 0.
  • pnpm --filter @object-ui/app-shell type-check: the script echoed @object-ui/app-shell@17.6.0 type-check, VERDICT command-exit 0. tsc -p tsconfig.test.json --listFiles lists the new pin file (1 hit).
  • Tests, narrowed to the 24 test files that import a touched module (git grep over app-shell tests for imports of FlowExprIssue, FlowObjectListField, FlowNodeConfigField, FlowKeyValueField, FlowNodeInspector, flow-scope, useFlowScope, flow-expr-problems): Test Files 24 passed (24), Tests 299 passed (299). This narrowing is declared. Tests that reach these modules only transitively are not in that set; the full pnpm test is CI's run.
  • eslint on the six changed source and test files (--format json): 6 files, 0 errors, 1 warning. The warning is react-hooks/refs on the rows.map line of FlowObjectListField.tsx, and it is pre-existing: the base blob linted alone shows the same rule, 1 warning. eslint.config.js has no parserOptions.project, no projectService and no type-checked preset (0 hits), and no import-resolution plugin. So type-aware linting is off, and this diff cannot move a verdict on an untouched file. The repo-level pnpm lint is CI's run.
  • check-changeset-presence ✅ (9 files changed, 6 published source, 1 changeset added). check-control-bytes ✅. check-new-cross-file-line-citations: VERDICT … 0 new citation(s). check-pending-changeset-literals ✅. check-changeset-no-major ✅. check-changeset-fixed ✅. check-vi-mock-specifiers ✅. check-vi-mock-override-shape ✅. check-vi-mock-inherit ✅. check-test-path-roots ✅.
  • check-changeset-claims (report-only) names two pending changesets that cite flow-scope.ts: 6296-conditionbuilder-subject-vocabulary.md and flow-entry-condition-builder.md. Both paragraphs are still true. They describe fieldPrefix: onStart ? '' : 'record.' and includePrevious, and that text reads 1 and 5 occurrences at both base and head. This diff adds one union member and a comment to ScopeGroupId and nothing else.
  • check-changeset-overwrite (report-only) records the modification of 10743-studio-screen-visiblewhen-live.md, case 2: a deliberate prose correction with the declaration unchanged (@object-ui/app-shell: patch at base and now).
  • Governed predicate: NOT GOVERNED — 9 path(s).
  • NOT MEASURED: check-readme-exports, because app-shell's own dist is not built here (only its dependency closure). The README edit is prose inside an existing bullet and adds no fenced import block. CI's README Export Check covers it. Console Performance Budget (eager closure) is CI's.

Serial

The claim read open PRs at 14:25Z and none touched these files. Before the last push, origin/main was fetched into the private ref refs/issue-10772/main at bff63cc12e (3 commits and 47 files past base). git merge-tree --write-tree against HEAD exited 0 (tree 13994de4a0). The intersection of main's moved files with this PR's 9 files is empty, and none of main's moved files is under metadata-admin/inspectors/, metadata-admin/previews/ or ScreenView.

Acceptance notes

  • The screen roots are read from the committed node (context.node.config.fields), the same node the Problems panel judges. A field row renamed in the cell is picked up on that row's flush, on blur or Enter, not keystroke by keystroke.
  • The "Screen fields" section offers record as a bare token, because screenPredicateRoots admits it (record.discount resolves as discount does). The section carries no per-token detail, since the rule yields names only.
  • Carried over from the objectui#10743 review, not new and not addressed here: record.zzz > 0 is clean on every surface (the root record is admitted) while the runner faults on it. The inline cell now inherits that same reading. objectui#10743 remains closed as delivered.
  • "Screen fields" is an English literal, like the picker's other section labels in useFlowScope ("Flow variables", "Trigger record"). The screen sentence is the Problems panel's English literal, and the flow wording is localized, as recorded on objectui#10743.
  • The server half, where registerFlow and the resume door narrow to the declared scope, is finding(service-automation,lint): the resume door evaluates a screen field's visibleWhen over the run's variables, wider than the declared scope (the screen's own field names), and nothing refuses an undeclared name at authoring objectstack#20178. It is unchanged by this PR.

Generated by Claude Code

… declared fields

The flow inspector's inline expression cell for a screen node's
fields[].visibleWhen column read the FLOW scope: it flagged a
sibling-field predicate, said nothing on a run variable, and its picker
offered the flow scope. It now names the column by the Problems panel's
own isScreenVisibleWhenColumn (from the node in context and the list's
fieldId), offers screenPredicateRoots(node) in its picker, and judges its
inline note by screenVisibleWhenScopeError - the one rule the Problems
panel and the Debug run's screen step share. Every other expression
column keeps the flow scope.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…en visibleWhen cell

A new patch changeset for the cell; a dated note after the pending
10743 entry's "Still on the flow scope" paragraph (frontmatter
untouched); the README sentence that said the cell still reads the flow
scope now says it follows the screen rule.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests package: app-shell labels Sep 27, 2026
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 2 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6296-conditionbuilder-subject-vocabulary.md

  • names flow-scope.ts → packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts — edited by this change

    The builder built every row subject as record. + field name, plus a fixed record.id / user.* / org.* context list. That is correct for all five files that mount it today — six mount sites, since ActionDefaultInspector mounts it twice — because every one of them is a record-scoped site. It is wrong for a flattened-scoped site such as the flow designer's entry condition, where the trigger record's fields are the top-level evaluation context (bare status) and the prior values arrive as previous.FIELD. This repo's own flow-scope.ts already computes that distinction (fieldPrefix: onStart ? '' : 'record.', includePrevious), and objectstack's packages/formula/src/validate.ts defines the two scopes.

.changeset/flow-entry-condition-builder.md

  • names flow-scope.ts → packages/app-shell/src/views/metadata-admin/inspectors/flow-scope.ts — edited by this change

    The builder is given the trigger record's own vocabulary — bare field names and previous.ANGLE-BRACKETS(field), resolved by flow-scope.ts — rather than the record-scoped default its five other consumers use, so the subjects it offers are the spelling this surface evaluates and teaches. 15 of the 17 entry conditions shipped in the example apps now open as structured rows; the remaining two mix && with a parenthesised || group, which is a grammar limit of the row model and keeps them on raw mode by design.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with dac022c4e (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1590 pending declaration(s) that publish a body (2178 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3069.0 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-BSw8mRgB.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 554.95KB 132.67KB
core (index.js) 9.57KB 3.81KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 224.98KB 62.48KB
fields (index.js) 259.68KB 65.89KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 83.58KB 22.75KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.11KB 35.62KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 158.35KB 40.47KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b6e912aab4c576ee8fefa008c5ec9d4189a785f7

① Derived judgments

  • One rule, not three: holds.
    • The inline cell reaches the rule only through screenPredicateRoots(node), for the picker (screenScopeGroups spreads that Set as-is), and screenVisibleWhenScopeError(value, node), for the note. It re-derives no list. These are the same two functions the Problems panel's checkScreenVisibleWhen and the simulator's unevaluableVisibleWhen call.
    • The node is built the same way on both sides: { id, config }, with a non-object config coerced to {}. That matches the panel's asRecord(node.config).
    • isScreenVisibleWhenColumn is unchanged between base and head. The diff adds only the export keyword and a docblock. The body is still type === 'screen' && fieldId === 'fields' && colKey === 'visibleWhen'.
  • Column identity: holds.
    • FlowNodeInspector renders every descriptor field through FlowNodeConfigField with context={{ draft, node }}. The screen's fields descriptor id is 'fields', and FlowNodeConfigField passes fieldId={field.id}. So the product path is live.
    • The nested FlowObjectListField does not forward fieldId, so nothing leaks into a nested list.
    • Only the screen descriptor has a visibleWhen column. The other fields descriptors (create and update record) are keyValue, not objectList.
    • Non-blocking, and carried over from before this PR: the Studio alias user_task maps to 'screen' (TYPE_ALIASES), so it renders this column. The type check excludes it, so it stays on the flow scope in the inline cell, in the Problems panel, and in the Debug run's screen step (type === 'screen'). That is consistent across all three surfaces, and user_task is not a node type in the spec.
  • Wording parity: holds, checked in the source.
    • Brace step: both sides call validateExpressionClient('predicate', value) first.
    • Scope step: both sides take screenVisibleWhenScopeError verbatim, and both show it at warning level (the cell as an amber role="note").
    • The panel adds only rowLabel || col.label plus ": ". Pin 2 asserts ["Reason: " + note] against the live flowExpressionProblems.
    • Non-blocking, carried over: the cell coerces a stored non-string visibleWhen to '' and stays silent, while the panel sends the raw value to the shape refusal. The same coercion applies to every expression column.
  • Control and other columns: hold.
    • Decision conditions, keyValue values and edge guards still pass scopeGroups / flow roots.
    • Approver cells are a reference column with the expression discriminator. They still pass approvalScopeGroups to both the picker and FlowExprIssue.
    • The control is real. It mounts the real decision descriptor and asserts that discount is flagged and needsApproval is clean. Its picker check compares against the same flowGroupsAt('d') that feeds the props, but the explicit arrayContaining(['needsApproval','record','previous']) check makes it more than a self-comparison.
  • 'screen_fields': no gap.
    • ScopeGroupId is never used exhaustively. GROUP_LABELS is keyed on GROUP_ORDER only, not the full union, and there is no switch or i18n map.
    • VariableTextInput uses g.id only as a React key and renders g.label, so the heading is always "Screen fields".
    • The label is an English literal, the same as the sibling groups ("Flow variables", "Trigger record").
  • Pins and ablation: sound, judged from the test source.
    • On base, all three screen pins fail for the intended reason. Pin 1: discount is flagged by the flow scope. Pin 2: the run variable gets no note, so toHaveLength(1) fails. Pin 3: the picker offers the flow tokens.
    • The ablation is the single screenNode line, which covers the picker and the note together.
    • The pins also separate the two halves: pins 1 and 2 cover the note, and pin 3 covers the picker.
  • The 10743 dated note: holds.
    • The frontmatter (lines 1–3) has md5 4e8ce55021fe3b14329c8b4cb3f9f354 at both base and head.
    • The heading ⚠️ **Dated note, 2026-09-27 — … — objectui#10772.** and the closing "kept as the reading of this change; the objectui#10772 entry states …" match the objectui#10533 form.
    • Every clause is true at head.
  • README and docblocks: one false published sentence.
    • The docblocks in FlowExprIssue, FlowObjectListField, flow-scope.ts and flow-expr-problems.ts are true at head.
    • packages/app-shell/README.md's flows paragraph adds "(objectui#10772; every other expression cell keeps the flow scope)". That is false for the approver expression cell. It renders an expression input over the approval roots and never over the flow scope (FlowObjectListField's approver branch: "never the regular flow scopeGroups").
    • This PR's own FlowExprIssue docblock says so too: "the flow scope at the node, or the approval roots on an approver".
    • The README ships in the package (files lists README.md), and the false parenthetical implies record.x is valid in an approver expression.
    • Replace "every other expression cell keeps the flow scope" with "every other expression column keeps the flow scope". That is the changeset's wording, and it is exact, because the approver is a reference column.

② Semver level

  • '@object-ui/app-shell': patch / Clause-②: no stands.
    • package.json exports has only "." (./dist/index.js) and "./styles.css".
    • src/index.ts has zero export * lines and no re-export of flow-expr-problems, FlowObjectListField, FlowExprIssue, flow-scope or ScopeGroupId.
    • So isScreenVisibleWhenColumn and 'screen_fields' stay internal.
  • Changeset, sentence by sentence: all true at head. That covers the column identity via fieldId, "exactly the roots screenPredicateRoots(node) admits", the note sentence, "brace error ahead of the scope", and "No evaluation moved".
  • PR body: accurate. Its base 29b45f6a0c is the merge-base.

③ Boundary flags

  • CI on the head: 43 check runs, 40 success and 3 skipped (dependabot, Test (coverage), Test (coverage shard …)). The successes include every Test shard from 1 to 8, Lint, Type Check, Build & E2E, README Export Check and Changeset Bump Policy. The combined status is success (Vercel).
  • git merge-tree --write-tree against current main dac022c4e4: exits 0 with tree a4b8427533. Main has moved 48 files since 29b45f6a0c, none of them in this PR's 9 paths.
  • Open PRs: none of the other 10 touches these paths.
  • Governed predicate: NOT governed (0 of 9 paths), with 364 changed lines, under the human-merge threshold.
  • PR state: draft is true, the assignee is os-elon-musk, and mergeable state is clean.

Implemented-by: claude/issue-10772-inline-cell-screen-scope
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: FAIL

One wording fix before landing. In packages/app-shell/README.md's flows paragraph, replace "every other expression cell keeps the flow scope" with "every other expression column keeps the flow scope". The approver expression cell reads the approval roots, not the flow scope, and this PR's own FlowExprIssue docblock says so. Everything else in ① to ③ holds.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3069.0 KB 3104.5 KB
Main entry chunk (gzip) 148.3 KB 350 KB
Entry file index-BSw8mRgB.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.57KB 6.15KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 554.95KB 132.67KB
core (index.js) 9.57KB 3.81KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 224.98KB 62.48KB
fields (index.js) 259.68KB 65.89KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.65KB 14.69KB
plugin-charts (index.js) 83.58KB 22.75KB
plugin-chatbot (index.js) 198.40KB 47.22KB
plugin-dashboard (index.js) 134.11KB 35.62KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 233.23KB 61.74KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 158.35KB 40.47KB
plugin-gantt (index.js) 169.75KB 41.96KB
plugin-grid (index.js) 218.12KB 59.72KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 114.70KB 28.42KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.91KB 9.05KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 87.83KB 22.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8e4e5982affb93c88532a829cb53ce64aaab8a23

A narrow round-2 review, run in-seat by the dispatching seat, of the delta b6e912aab4..8e4e5982af (one commit), against the round-1 FAIL record 5857262255. Every other judgment in that record stands: one rule (no third copy), column identity, wording parity, the control, 'screen_fields', pins and ablation, the 10743 dated note, patch / Clause-② no. No executable line moved.

① Derived judgments

  • The delta is one line of prose. packages/app-shell/README.md, +1 / −1, and no other file (compare API: 1 file, ahead 1, behind 0).
  • The FAIL item is resolved verbatim. "every other expression cell keeps the flow scope" now reads "every other expression column keeps the flow scope", the review's replacement text and the changeset's wording. It is exact: the approver cell is a reference column (with the expression discriminator) on the approval roots, so no expression column is left out.
  • The rest of the paragraph is unchanged and true at head (round 1 read it).

② Semver level

Unchanged: '@object-ui/app-shell': patch, Clause-② no (no package-entry export moves). There is no major.

③ Boundary flags

  • CI on 8e4e5982af: taken from the head's check-runs at landing; landing waits for green.
  • git merge-tree --write-tree against main dac022c4e4 is clean, tree a4b8427533, per the dev's reading. It is the same tree as round 1's, since only README prose moved.
  • NOT GOVERNED (round 1: 0 of 9 paths).
  • The PR is a draft, assigned to os-elon-musk.

Implemented-by: claude/issue-10772-inline-cell-screen-scope
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

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 package: app-shell tests

Projects

None yet

2 participants