Skip to content

feat(cli): objectui check refuses a ${…} on a text key its node never evaluates (objectui#4795) - #11126

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-4795-check-bindable-text-keys
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-4795-check-bindable-text-keys

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4795
Clause-②: yes — objectui check newly refuses ${…} in the four closed keys where expressionBindableTextKeysFor(type) excludes the key, as ruled (5406135987, 5479466110). The PR waits as a draft for the director seat's contract review.
Regen-provenance: 5903644098 · c985c4d → 4f29cc3 · git merge origin/main (81f8498) → pnpm install (pnpm-lock.yaml regenerated) — non-lockfile git patch-id --stable unchanged (fddcc0fbfcd30e517fa521b4af5cf528c5b4cf55); details in comment 5903953260.

Ruling item 2 of #4795 (maintainer, 2026-08-31, comment 5479466110), built in objectui check as ruled. Draft for the director seat's contract review.

The repository's own pnpm check: red in round 1, resolved by the seat's answer A

.github/workflows/lint.yml runs pnpm check on this repository (step "Verify the CLI's own check command passes on this repository"). Measured with the CLI built from each tree:

tree pnpm check exit refusals warnings
main d1e683fa1 (no gate) 0 0 0
round 1, ed32e7c6b 1 1 0
this branch, c985c4d7d 0 0 0

The round-1 refusal. It was examples/schema-catalog/src/schemas/plugin-editor/javascript-editor.json: a root code-editor whose value was a JavaScript sample using template literals. That is literal content, not a misplaced expression, and it had no mechanical channel move.

The fix. The seat chose A under ruling 5479466110: 「no-row types included」, and 「A row can always be added later with a named need + its own measurement」. The sample now builds the same strings by concatenation. Run, it still logs Hello, Developer! and returns Welcome, Developer.

Nothing pinned the old content. No test, snapshot or docs fence reproduces it:

  • the catalog registers the file by import;
  • safe-validate-corpus-6318.test.ts asserts only that it validates;
  • catalog-gallery-render.test.tsx skips it, because Monaco cannot load under happy-dom;
  • plugin-editor.mdx embeds it by id through SchemaExample.

The run's closing tally is the same as on main: 265 validated, 167 recognised but not validated, 1 did not validate.

What it does

findUnbindableTextExpressions (new, packages/cli/src/utils/unbindable-text-expressions.ts), called by check on every recognised file, from either recognition arm:

  • The rule. Each of the four keys EXPRESSION_BINDABLE_TEXT_KEYS on a component node is judged against expressionBindableTextKeysFor(type). Both are imported from @objectstack/spec/ui: the vocabulary and the carriage map are consumed, not copied.
  • Component nodes (sub-rule i). The document root, plus every node children holds (an array or a single node), recursively. A root of type page is not judged itself, because its title is a page key; its children still are.
  • Expression. The evaluator's own interpolation pattern, as ExpressionEvaluator.evaluate matches it. A lone ${ or an empty ${} is not an expression there, so it is not one here.
  • Refusal vs warning (sub-rule ii). A registered type (the isKnownSchemaType universe) is refused: a red x Unevaluated expression in FILE at PATH: … line plus a channel line, and it counts toward the run's Found N errors, so the exit code is 1. A type no registered component answers to gets a yellow ⚠️ Expression not judged in … line, and the run still passes.
  • Message. It follows the describeFirstIssue shape objectui#11007 / PR fix(cli): objectui check prints the refused key and path, and never calls an unvalidated file passed (objectui#11007) #11053 gave check. The path is spelled by formatIssuePath, the formatter objectui validate prints with. It names the key, the path, the type, the keys that type does evaluate, and the channels SchemaRenderer evaluates: the type's own evaluated keys, content, properties.KEY (evaluated, then hoisted onto the node), or resolving the value in the host.

@objectstack/spec becomes a declared dependency of @object-ui/cli at ^17.1.0. That is the range @object-ui/react declares, and @object-ui/react is the runtime consumer of the same lookup. The lockfile gains only the importer entry, which resolves the installed version.

Docs: content/docs/utilities/cli.mdx gains the rule, the two sub-rules and the real output. It also says a refused expression now fails the run, where the page used to say only unreadable JSON did, and that a literal ${…} has no escape spelling (the changeset says so too). Changeset: @object-ui/cli minor.

Premises re-derived on main d1e683fa1

  1. Spec floor and carriage map. The lockfile carries a single @objectstack/spec, and the installed node_modules/@objectstack/spec/package.json reads 17.4.0. It exports both symbols. Its answers: statistic gives label / value / description; card gives title / description; button gives label; text, action:button, ui:card and page give the empty set. check:spec-floors fetched the declared-floor tarball and passed: the CLI artifact's two ./ui symbols are present at the floor. Nothing here depends on 17.5.0.
  2. Type keying. The evaluation memo in SchemaRenderer calls expressionBindableTextKeysFor(typeof newSchema.type === 'string' ? newSchema.type : ''), verbatim, with no prefix stripping. The comment "keys on the bare registry name" describes the spec map's keys; it does not describe a normalization. The gate passes node.type verbatim.
    • Measured once through the real SchemaRenderer (at e2f8e48d9; the gate's code is unchanged since): every one of the 649 registered types was rendered with the four keys set to ${data.total}. On every type, the keys it left unevaluated equalled the gate's refusals, 0 disagreements. The types it evaluated anything on were exactly those with a row.
    • Not committed, for one reason: a CLI test importing @object-ui/react turns the CLI's generated-source allowance for that dependency stale in check:unused-deps (DECLARED_WITHOUT_IMPORT). That ledger is outside this claim's file surface.
    • What is committed instead: the unit-tier table. It pins the gate against the verbatim lookup on every registered type, and derives the namespaced spellings a prefix-stripping gate would get wrong. The runtime half stays pinned by the @object-ui/react suite SchemaRenderer.bindableTextKeys.test.tsx ("a namespaced spelling is not silently normalized").
  3. Sub-rule (i) premise: holds.
    • SchemaRenderer does not recurse on its own. Each renderer hands keys back through renderChildren / renderNodeSlot / a direct SchemaRenderer.
    • A form's fields[] entries are rendered by the form's renderFieldComponent (field widgets under field:*), never through SchemaRenderer.
    • check judged the root only before this change.
    • The walk follows children alone. That is the one composition key BaseSchema declares, and the same single spelling core's validateChildren and the SDUI parser's CHILD_LIST_KEY follow.
    • Census of the recognised corpus: objects with a string type that are not nodes sit under form fields[], filter-builder fields[], grid columns[], dashboard widgets[] / globalFilters[], and even object-grid's columns[].summary ({ "type": "count_unique" }) and selection ({ "type": "multiple" }). A walk over every typed object would judge all of them.
  4. Corpus. See the table above: one offender in round 1, not mechanical. It was resolved in round 2 by rewriting the sample, the seat's answer A.
  5. Sub-rule (ii). Pinned: stat-card warns and the run passes. text.value, action:button.label, card.label and ui:card.title are refused. statistic.value, card.title and button.label pass.
  6. Message format. See above.
  7. PR feat(cli): objectui validate and objectui check judge through the strict authoring face (objectui#5250, slice A) #11069. Not read from and not edited toward. The second of the two to land merges main.

Tests

  • packages/cli/src/__tests__/check-unbindable-text-expression-4795.test.ts (unit tier, 28 tests). Expected text is derived from the command's own formatters, fed from the gate's real findings. It covers:
    • the refusals, with the key, path, type, channels and exit code;
    • the passes, including a lone ${ and an empty ${};
    • the walk through children (array and single node) and multiple refusals per file;
    • the page-root title exclusion, with the page's children still judged;
    • the form-field label non-refusal, with a control: the same object under children IS refused;
    • the stat-card warning;
    • validity-arm files judged and unrecognised files not;
    • the verbatim-lookup agreement table over every registered type.
  • Whole package, pnpm exec vitest run packages/cli/ at c985c4d7d, under the verify lock: Test Files 23 passed (23), Tests 330 passed (330).
  • The catalog the rewritten sample lives in, pnpm exec vitest run examples/schema-catalog/ at c985c4d7d, under the verify lock: Test Files 34 passed (34), Tests 2199 passed (2199). Within it, safe-validate-corpus-6318.test.ts passes plugin-editor/javascript-editor validates unchanged.

Ablations

Each direction was predicted before the run. Mutations went through ablation-replace.mjs: in each run the anchor went 1 to 0, the marker 0 to 1, and the restore proved blob == HEAD with git diff HEAD empty. All three were re-run on ed32e7c6b. Round 2 touched neither the gate nor its tests, so these readings stand.

  1. Refusal removed (severity forced to warning). Predicted: the refusal pins go red. Observed: 15 failed | 13 passed. The passes, the page-root exclusion, the stat-card warning and the unrecognised-file pins stayed green.
  2. Walk widened to every object with a string type. Predicted: only the form-field pin goes red. Observed: exactly that, 1 failed | 27 passed, and the failure prints the refusal at fields → 0 → label.
  3. Namespace stripped before the lookup. Predicted: the two agreement cases and ui:card.title go red. Observed: 5 red, those 3 plus action:button.label and "refuses every offending node". action:button strips to button, which has a label row. The direction matched the prediction, with two more reds than predicted, both explained.

Gates (exit codes; at c985c4d7d unless noted)

Heavy runs (builds, test suites, pnpm check, the doc compile gates, check:node-esm-load) went through the shared verify lock in round 2.

  • CLI package: type-check 0 · lint 0 (0 errors; 5 warnings, all in files this PR does not touch) · tests 0 (above).
  • Repo check: pnpm check 0: 0 refusals, 0 warnings.
  • Changesets: check-changeset-presence 0 · check-changeset-no-major 0 · check-changeset-overwrite 0 · check-changeset-fixed 0 · check:changeset-claims 0 (report-only; it names the same five pending changesets as round 1, and none is falsified by this diff) · check:pending-changeset-literals 0.
  • Bytes and citations: check:control-bytes 0 · check:new-line-citations 0.
  • Dependencies and lockfile: check:phantom-deps 0 · check:unused-deps 0 · check:lockfile-integrity 0 · check:lockfile-dedupe 0. At ed32e7c6b (round 2 moved no manifest and no CLI source): check:spec-floors 0 · check:installed-pin-claims 0 · check:esm-specifiers 0 · check:spec-symbols 0 · check:self-import 0.
  • Docs: check:doc-snippets 0 (681 of 681 blocks) · check:doc-examples 0 · check-doc-component-types 0 · check-prompt-component-keys 0 · check-doc-example-ids 0 · check-doc-fence-languages 0 · check-doc-links 0 · check-doc-expression-carriage 0 (report-only) · toc-anchor-parity.test.tsx 15 passed.
  • Test shape and dist, at ed32e7c6b (round 2 touched no source or test file): check-lint-coverage 0 · check:test-path-roots 0 · check:vi-mock-* (three) 0 · type-check:coverage 0 · check:unreferenced-sources 0 · check:dist-completeness 0 · check:published-dist 0 · check:shell-escape-residue 0.
  • Governed surface: check-governed-queue-guard --test over the eight paths: NOT GOVERNED · check:governed-queue-guard self-test 0 (at ed32e7c6b).
  • check:node-esm-load: 1, left to CI. Under the lock at c985c4d7d, it again refused @object-ui/auth and @object-ui/react-runtime on provenance: the turbo cache is shared across worktrees and replayed another tree's build. The @object-ui/cli entry was among the 32 graded loads. CI builds fresh.

Acceptance notes

  • Known limitation: no escape spelling.
    • A literal ${…} in a closed key of a type with no carriage row is refused, and nothing lets it through. That covers a code sample in a code-editor value and shell ${VAR} text.
    • A carve-out for literal content is a spec-first decision, reopened on the first named user need. None is built here, and there is no local key list.
    • The docs page and the changeset say this. This repository's own sample was the only measured instance, and the runtime dev diagnostic already reported it.
  • Walk boundary. Nodes a renderer reaches through a key of its own are not walked: trigger, header, footer, a page's regions[].components[], a tab's items[].content, panels[].content and carousel items. No declaration of per-renderer node slots exists to derive them from. The boundary fails quiet in the safe direction: it never produces a false refusal.
  • Keys outside the four (e.g. badge.text) are not judged. 5479466110 set item 2's scope to the carriage map, which narrows 5406135987's "any key outside that closed list". The props-envelope blank render is outside that scope too.
  • check reads .json only. .yaml / .yml are globbed and never read, as check.ts already stated.
  • Two rulings meet in check.ts. The objectui#11007 triage ruling declined a nested validation walk in check. Its pin "no nested walk of its own" stays green, because this walk validates nothing and reads only the four keys. Named here so the reviewer sees both rulings.
  • The channel line says "evaluated", not "read back". It mirrors the runtime diagnostic's vocabulary (objectui#7849). For text.value, which TextSchema retired and whose renderer reads content alone, properties.value is evaluated and hoisted but not rendered. The seat kept this wording (answer A, one vocabulary with the runtime diagnostic); the contract review may still choose B, which would be a one-line change in workingChannels.
  • Runtime agreement. The real SchemaRenderer was measured once over all 649 registered types, with 0 disagreements. The committed pins are the two verbatim-lookup halves. The seat chose not to add the DOM suite (answer A), so the CLI's @object-ui/react allowance in check:unused-deps is untouched.

Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm


Generated by Claude Code

… evaluates (objectui#4795)

Ruling item 2: a `${…}` in `title` / `label` / `value` / `description` on a
component node whose `expressionBindableTextKeysFor(type)` answer excludes
that key is refused, no-row types included. The walk follows the root and
`children` only (sub-rule i; the page document root's own keys are page
keys), and a type no registered component answers to warns instead
(sub-rule ii).

The vocabulary and the carriage map are imported from `@objectstack/spec`,
now a declared dependency of the CLI, and the type string is passed
verbatim, as SchemaRenderer's evaluation memo passes it. A runtime-agreement
suite renders every registered type through the real SchemaRenderer and
compares what it evaluated with what the gate refuses.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
…hangeset (objectui#4795)

The `objectui check` page said only unreadable JSON fails the run; a refused
`${…}` on a text key its node never evaluates now does too. The page names
the rule, the two sub-rules and the real output, and the changeset declares
the new refusal as a minor for `@object-ui/cli`.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
…in the unit tier (objectui#4795)

The DOM suite that rendered every registered type through SchemaRenderer
imported `@object-ui/react` from a CLI test, which turns the CLI's
generated-source allowance for that dependency stale in
`check:unused-deps`. The agreement is now pinned in two halves against the
same verbatim `expressionBindableTextKeysFor(type)`: the runtime's by the
`@object-ui/react` suite, and the gate's here over every registered type,
with the namespaced spellings a prefix-stripping gate would get wrong
derived and asserted non-empty.

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

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 5 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/5793-spec-range-floors.md

  • names pnpm-lock.yaml → pnpm-lock.yaml — edited by this change

    Nothing a consumer installs today changes: normal resolution already picks the newest 17.x, and pnpm-lock.yaml still resolves 17.2.0 on this edge after the bump. The change is to the declared floor only, which is why it is scored patch rather than minor — the same reasoning objectui#5753 used for the other direction on this dependency.

.changeset/6320-check-nested-dist-ignore.md

  • names packages/cli/src/commands/check.ts → packages/cli/src/commands/check.ts — edited by this change

    packages/cli/src/commands/check.ts passed ignore: ['node_modules/**', 'dist/**', '.git/**'] to globSync. glob matches ignore patterns against the path relative to cwd, so an unanchored dist/** / node_modules/** excludes only a directory of that name at the scan root — every nested packages/ANGLE-BRACKETS(name)/dist/, examples/ANGLE-BRACKETS(name)/dist/, apps/ANGLE-BRACKETS(name)/dist/ (and their node_modules/) was still scanned. In a built workspace this means objectui check re-reads the author's own schemas a second time from build output, roughly doubling every count it reports (measured on this repository: 617 → 1047 files globbed after a full build) with nothing in the output explaining why.

.changeset/6361-spec-floor-17-2-0.md

  • names pnpm-lock.yaml → pnpm-lock.yaml — edited by this change

    Nothing a consumer installs today changes: normal resolution already picks the newest 17.x, and pnpm-lock.yaml still resolves 17.2.0 on both edges after the bump — only the recorded specifier: moves. No source and no behaviour changes, which is why this is scored patch, on the reasoning 111741454 used for the same remediation on @object-ui/plugin-detail.

.changeset/7122-objectstack-family-17-3-0.md

  • names pnpm-lock.yaml → pnpm-lock.yaml — edited by this change

    @objectstack/client, core, formula and lint each pin @objectstack/spec EXACTLY, so resolving the spec alone to 17.3.0 left the console bundling TWO copies of it. Moving the family with it in pnpm-lock.yaml collapses the duplicate; every declared range already admitted 17.3.0, so no manifest moved.

.changeset/7658-toc-code-span-anchor-parity.md

  • names content/docs/utilities/cli.mdx → content/docs/utilities/cli.mdx — edited by this change

    Seven live headings in this repo's own docs were affected (content/docs/utilities/cli.mdx, content/docs/utilities/runner.mdx, packages/cli/README.md). Pinned against the real render pipeline rather than a second derivation of the slug rules: the new test renders each heading through MarkdownImpl and compares extractToc's id to the id attribute rehype-slug actually emitted.

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 81f849852 (merge-base with origin/main): 7 file(s) changed outside .changeset/, read against 1784 pending declaration(s) that publish a body (2391 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3110.2 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-ufgaPvvd.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
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.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 558.84KB 133.94KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.99KB 63.22KB
fields (index.js) 261.11KB 66.33KB
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.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 234.38KB 62.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 169.69KB 43.37KB
plugin-gantt (index.js) 171.88KB 42.61KB
plugin-grid (index.js) 229.01KB 62.89KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 31.93KB 9.33KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 19.30KB 6.99KB
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

…bjectui check passes on this repository (objectui#4795)

Its `value` used template literals, which the new `objectui check` refusal
reads as unevaluated expressions: `code-editor` has no carriage row, and
ruling 5479466110 includes no-row types. The sample now builds the same
strings by concatenation and logs and returns exactly what it did before.

The docs page and the changeset state the limitation this exposes: a
`${…}` meant as literal text on one of the four keys is refused, with no
escape spelling.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3110.2 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-ufgaPvvd.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
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.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 558.84KB 133.94KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.99KB 63.22KB
fields (index.js) 261.11KB 66.33KB
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.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 234.38KB 62.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 169.69KB 43.37KB
plugin-gantt (index.js) 171.88KB 42.61KB
plugin-grid (index.js) 229.01KB 62.89KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 31.93KB 9.33KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 19.30KB 6.99KB
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: c985c4d7d3e246219bd667c847e7fd820f7fcb6a
Local-runs: none

Director seat's at-tier review (objectstack#12708, on the maintainer's 「项目总监契约复审」, 2026-09-30) — the review this PR was drafted for. Inputs: card objectui#4795 (body; the maintainer's rulings 5406135987 and 5479466110; the claim 5893308301; the two os-dev-reports 5894488882 / 5894716866 and the seat's ACCEPT 5894769255, read as claims to test, not as findings); PR #11126 (body, 8 files, +654/−8, the net diff against objectui main c2a8d23c67 at merge base d1e683fa1d); the trees at origin/main of objectui and of objectstack through git show / git grep; the head's check-runs. Nothing built, run or re-run. merge-tree of the head against main: clean.

Check-runs on the head, read 2026-09-30T03:2xZ: 45 runs — 42 success, 3 skipped (the coverage-shard matrix and dependabot), 0 in progress, 0 failure. Lint carries this repository's own pnpm check step, so the corpus is green under the new refusal on this head.

① Derived judgments

Against ruling 5479466110 (item 2's scope = the carriage map; sub-rules (i) and (ii); location objectui check; sequenced after #7015, closed 2026-09-01) and 5406135987 (the closed four-key list; loud rejection outside it):

  • The rule — RIGHT. findUnbindableTextExpressions judges exactly EXPRESSION_BINDABLE_TEXT_KEYS (title, label, value, description) on a node against expressionBindableTextKeysFor(node.type), both imported from @objectstack/spec/ui, not copied. At objectstack origin/main (packages/spec/src/ui/expression-bindable-text-keys.zod.ts:111-170) the list is those four; the map has three rows — statistic → label / value / description, card → title / description, button → label; an unlisted type answers the frozen empty set through an own-property check. So text.value, action:button.label and ui:card.title are refused ("no-row types included"), and statistic.value, card.title, button.label pass. The map is identical between spec 17.4.0 (what main's lockfile resolves) and 17.5.0 (PR chore(deps): resolve @objectstack/* 17.5.0, and the zod 4.6.5 it requires, in pnpm-lock.yaml (objectui#11073) #11086's bump): the tag diff is two comment lines, so the gate's answers do not move with the bump.
  • Verbatim type — RIGHT. The runtime memo (packages/react/src/SchemaRenderer.tsx:1694) calls expressionBindableTextKeysFor(typeof newSchema.type === 'string' ? newSchema.type : '') with no prefix stripping, and its comment gives the reason (a stripped ui:statistic would re-manufacture the evaluated-but-not-read-back half). The gate passes node.type verbatim. Each half is pinned in its own package: the runtime's by SchemaRenderer.bindableTextKeys.test.tsx, the gate's by the agreement table over every registered type; ablation 3 (stripping the namespace) goes red.
  • What an expression is — RIGHT. The gate's /\$\{[^}]+\}/ is the evaluator's own interpolation pattern (packages/core/src/evaluator/ExpressionEvaluator.ts:240, replace(/\$\{([^}]+)\}/g, …), and the single-template match above it). A lone ${ or an empty ${} is returned unchanged by the evaluator and is not refused by the gate; an expression inside a surrounding string is interpolated by both.
  • Sub-rule (i) — RIGHT, with its reach stated. The walk starts at the document root and follows children (an array or a single node), the one composition key BaseSchema declares (packages/types/src/base.ts:349). A root of type page keeps its own title (a page key) while its children are judged. A form's fields[] entries are not walked: they are rendered by the form's field widgets, never through SchemaRenderer, so an expression on a field's label is not this rule's; the pin has a control (the same object under children IS refused), and ablation 2 (widening the walk to every typed object) reddens exactly that pin. Nodes a renderer reaches through keys of its own (trigger, header, footer, a page's regions[].components[], a tab's items[].content, carousel items) are NOT walked — an under-reach, stated in the docs page and the PR, that never produces a false refusal; see ③.
  • Sub-rule (ii) — RIGHT. isKnownSchemaType (the registration-derived set, packages/cli/src/utils/known-schema-types.ts:694) decides refusal vs warning; stat-card warns and the run passes (pinned).
  • Where it runs — RIGHT. Inside check's existing recognition branch, after recogniseObjectUiSchemaFile, on both arms (validated, and recognised but not validated), .json only as before. A refusal counts into errors, so the run exits 1 (the ruling's "loud"); the closing tally prints only when nothing was refused. The message follows the describeFirstIssue shape and spells the path with formatIssuePath.
  • The corpus — RIGHT. The one measured offender (examples/schema-catalog/src/schemas/plugin-editor/javascript-editor.json, a code-editor whose value held JS template literals) is rewritten to concatenation; nothing pinned its text (the catalog imports it, safe-validate-corpus-6318 asserts validity only, the gallery render skips it, plugin-editor.mdx embeds it by id). No carve-out and no local key list built, as 5479466110 requires ("a row can always be added later with a named need").
  • Public surface. @object-ui/cli: the check command's accept set narrows (a recognised file carrying a refused expression now fails the run); no export, option or glob changes (packages/cli/src/index.ts untouched). @objectstack/spec enters the CLI's runtime dependencies at ^17.1.0 — the range @object-ui/react declares; both symbols exist at the @objectstack/spec@17.1.0 tag and are exported from the ./ui subpath; the lockfile gains one importer entry resolving 17.4.0. Docs: content/docs/utilities/cli.mdx states the rule, both sub-rules, the verbatim match, the no-escape limitation and the new exit-code sentence, and its sample output line is the one describeUnbindableTextExpression + workingChannels print.
  • Statements tested. The changeset and the docs page are true on main the moment this lands. The PR body's runtime-agreement measurement (649 registered types, 0 disagreements) is the dev's and was not re-run; the committed pins cover the verbatim lookup over every registered type, the half this gate owns.

② Semver level

Clause-②: yes — as the maintainer ruled on 5406135987 ("both halves change accept/reject behaviour"): objectui check's accept set moves, and the CLI gains a runtime dependency. .changeset/4795-check-bindable-text-keys.md: @object-ui/cli minor, with the break (a passing run now exits non-zero on a refused expression; no escape spelling) spelled in the body — this repository's rule (AGENTS.md §版本号策略: objectui's own breaking changes ship as minor with the break in the body; check-changeset-no-major refuses major). One package named, the one that publishes. RIGHT.

③ Boundary flags

  • No escape spelling (Acceptance note 1): answered — RIGHT under the ruling. A literal ${…} on a no-row type's closed key is refused; a carve-out is a spec-first decision, reopened on a named need. Repository instances: one, rewritten. Not blocking.
  • Walk boundary (renderer-owned slots): answered — accepted as a stated under-reach; carrier filed. The ruling's scope is "component nodes"; nodes under a tab's items[].content, a page:card's body / footer or a page's regions[].components[] are component nodes the gate does not reach, so a ${…} there still reaches the user unrefused. No declaration of per-renderer node slots exists to walk from, and a hand-kept slot list in the CLI would be a second copy of what the protocol does not declare (objectstack's conversions walker declares COMPONENT_CHILD_KEYS for the page family — prior art, not a consumer-side list). The safe direction holds. Filed as objectui#11170 (pm:queue, p3): declare the slot keys once, consume them in the gate, core's validateChildren and the SDUI parser. Not blocking this PR.
  • Channel line wording, "evaluated" (the seat's answer A): answered — A accepted. The line names evaluated channels and says so; read-back is the renderer's, which this gate cannot see. One vocabulary with the runtime diagnostic (objectui#7849). No head move.
  • Runtime DOM suite not committed (answer A): answered — RIGHT. A CLI test importing @object-ui/react would stale the CLI's check:unused-deps allowance, a ledger outside the claim; the two verbatim-lookup halves are pinned in their own packages.
  • Keys outside the four, and the props-envelope blank render: outside item 2 as 5479466110 scoped it; noted, not this PR's.
  • PR feat(cli): objectui validate and objectui check judge through the strict authoring face (objectui#5250, slice A) #11069 (a parked draft that also edits check.ts): not a single-claim path; the second to land merges main.
  • Dev-report deviations (round-1 single-file runs outside the verify lock; check:node-esm-load refused locally on shared-turbo-cache provenance): CI on the head is green; the local refusal was a cache artefact, not a property of the diff. Answered.

Implemented-by: claude/issue-4795-check-bindable-text-keys
Reviewed-by: session_01AsCNgFBs8HCjwhyHQsFbx3

VERDICT: PASS

State: needs:contract-review comes off PR #11126 in the same stroke (this PASS at this head); the PR stays a draft until its seat lands it through the queue; the walk-boundary follow-up is objectui#11170.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 03:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 30, 2026
…jectui#4795)

Brings in PR #11086 (objectui#11073), which moves the workspace to
@objectstack/spec 17.5.0 and zod 4.6.5. The textual merge is clean; the
CLI importer's lockfile entry still names the 17.4.0 snapshot main no
longer carries, and the next commit regenerates it.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
…he 17.5.0 snapshot (objectui#4795)

`pnpm install` after merging origin/main: the `packages/cli` importer's
`@objectstack/spec` (`^17.1.0`) now resolves the same
`17.5.0(ai@7.0.65(zod@4.6.5))` snapshot as the rest of the workspace. The
17.4.0 snapshot it named is gone from main since PR #11086, which is what
broke `pnpm install --frozen-lockfile` in the merge group. Generated, not
hand-edited; no manifest moved.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3569.3 KB 3607.4 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-DL-zMq85.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
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.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 559.68KB 134.21KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.08KB 62.96KB
fields (index.js) 261.19KB 66.27KB
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.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 234.78KB 62.36KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 229.83KB 63.15KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 22.76KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 19.82KB 7.15KB
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

Regen-provenance: 5903644098 · c985c4d → 4f29cc3 · git merge origin/main (81f8498) → pnpm install (pnpm-lock.yaml regenerated)

From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm, 2026-09-30T04:18Z. The director's PASS 5903644098 at c985c4d7d carries to 4f29cc319 under the pure-regeneration rule. The seat re-derived this itself, not from the dev's report 5903934309.

  • Why the head moved. The merge group ca091aa75 failed pnpm install --frozen-lockfile with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY for @objectstack/spec@17.4.0(ai@7.0.65(zod@4.4.3)). PR chore(deps): resolve @objectstack/* 17.5.0, and the zod 4.6.5 it requires, in pnpm-lock.yaml (objectui#11073) #11086 (17.5.0, zod 4.6.5), ahead of it in the queue, had replaced that snapshot. This PR's CLI importer entry was textually clean but named a snapshot main no longer carried.
  • Non-generated delta unchanged. git diff <merge-base> <head> -- . ':!pnpm-lock.yaml' | git patch-id --stable reads fddcc0fbfcd30e517fa521b4af5cf528c5b4cf55 both for c985c4d7d against d1e683fa1 and for 4f29cc319 against 81f849852.
  • Generated delta. The lockfile adds only the packages/cli importer entry @objectstack/spec · specifier: ^17.1.0 · version: 17.5.0(ai@7.0.65(zod@4.6.5)). That is the snapshot the rest of the workspace resolves, and it was produced by pnpm install, not by hand. A clean pnpm install --frozen-lockfile exits 0 on the new head; the pre-regen lockfile reproduces the merge group's exact error.
  • 17.5.0 changes nothing the review judged. The carriage map read off the installed 17.5.0 equals the 17.4.0 reading the review checked. The agreement-table pin passes 28/28, and the repo's own pnpm check is 0 refusals.

The PR is re-enqueued once CI on 4f29cc319 is green.

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 33da643 Sep 30, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-4795-check-bindable-text-keys branch September 30, 2026 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

除 content 外没有任何文本键既被求值又被读回 —— statistic.value / card.title / button.label 无法绑定表达式

2 participants