') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); docs(components): the injected-editor commit justification is stale — correct it, and pin what Tab-out actually does by claude[bot] · Pull Request #6912 · objectstack-ai/objectui · GitHub
Skip to content

docs(components): the injected-editor commit justification is stale — correct it, and pin what Tab-out actually does - #6912

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-6859-injected-editor-blur-commit
Aug 30, 2026
Merged

docs(components): the injected-editor commit justification is stale — correct it, and pin what Tab-out actually does#6912
os-sam merged 1 commit into
mainfrom
claude/issue-6859-injected-editor-blur-commit

Conversation

@claude

@claudeclaudeBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes#6859

TL;DR — the audit hypothesis is falsified, measured in a real browser

The dispatch order asked one question before any code: type into an injected cell
editor, press Tab, read the cell back — is the value there or gone?

It is there. No data loss. So this PR does not change behaviour: it corrects the
record and pins the facts, which is what #6859 asked for in its own "if the answer is
keep the listener" branch.

The measurement (real Chromium, not jsdom)

Driven with Playwright against the pre-installed Chromium
(/opt/pw-browsers/chromium-1194/chrome-linux/chrome), on a throwaway Vite entry in
apps/console (deleted before commit — nothing under that path is in this diff) that
renders the REAL data-table renderer with an injected editor mirroring
ObjectGrid.renderCellEditor exactly: FieldEditWidget, with non-discrete types wired
onChange to stage and discrete ones to commit.

Sequence per column: click the cell, type, press Tab, then Tab four more times, then
click outside.

column (widget)after typingafter Tabafter 5 Tabsafter click-outside
Name (TextField)pending count 1still editing, pending 1, value intactstill editing, pending 1cell reads TYPED_TEXT
Due (DateField)pending count 1still editing, pending 1still editing, pending 1cell reads Sep 15, 2026
Qty (NumberField)pending count 1still editing, pending 1, value intactstill editing, pending 1cell reads 77

Ground truth on the first run: after Save All, onBatchSave received
[{"rowIndex":0,"changes":{"name":"TYPED_VALUE"},...}].

The path the source reading missed — and it is not a commit path

It is a staging path, which is why a scan for focusout / onBlurCapture /
relatedTarget could not see it:

  • data-table.tsx:1558stageEdit writes straight into pendingChangeswithout
    closing the editor
    .
  • packages/plugin-grid/src/ObjectGrid.tsx:3693 — the host wires every non-discrete
    widget's onChange to ctx.stage (discrete types go to ctx.commit).

So a typed value is durable from the first keystroke, independent of how edit mode is
exited. The pointerdown listener exits edit mode; it never rescued the value.

The audit was right about one half: Tab-out genuinely has no exit path, and the cell
stays in edit mode until Enter, Escape, or an outside pointer press. That is a wart, not
a lost edit, and it is now written down at the wrapper instead of being rediscovered.

The enumeration (constraint 1 — counted, not assumed)

Over EDIT_WIDGETS in packages/fields/src/FieldEditWidget.tsx (31 type keys, 27
distinct components):

  • 26 / 27 call toDomProps in their own module.
  • 1 / 27UserField — has zero direct calls and delivers by handing its whole
    props object to LookupField. That zero is the control: the query demonstrably
    discriminates, so "26 of 27" is a measurement rather than a tautology.
  • 0 / 27 hand-strip onBlur. The strips that exist are id, name, disabled,
    className, aria-invalid, aria-labelledbyonBlur survives all of them.

The two counter-examples #6859 named are outside this population:
ObjectRefField (object-ref) and FileField (file) are both in
INLINE_EXCLUDED_FIELD_TYPES, so neither is ever an injected editor. Both strip name
only; onBlur survives there too.

And the fact that makes "just pass onBlur" bigger than it looks

FieldEditWidget declares FieldWidgetComponentProps — which includes the whole DOM
pass-through block — but destructures and forwards field / value / onChange /
readonly / autoFocus and nothing else. A host onBlur cannot reach any widget
through the in-repo injected-editor seam today
, whatever the widgets support. Filed
separately (see below); not folded in, since it is a different defect class from this
card.

GeolocationField's longitude box

Not touched, per the order. It is now pinned as a deliberate exception by the new
fields test, with a comment saying so, precisely so nobody "repairs" it later.

What changed

  1. packages/components/src/renderers/complex/data-table.tsxcomments only. The
    injectedEditorElRef justification, the pointerdown effect's restatement of it, and
    a new note at the wrapper recording that Tab does not exit and why that costs nothing.
    The corrected comment now says: the widgets do deliver onBlur; the listener is still
    needed because nothing on this seam ever hands them one; and its job is exiting
    edit mode, not rescuing values.
  2. packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsx — new.
  3. packages/fields/src/__tests__/injectedEditorHostDomChannel-6859.test.tsx — new.
  4. .changeset/6859-injected-editor-commit-comment.md — empty frontmatter (declares
    "releases nothing"), with the plain-language reason.

No published type or prop surface is widened. No test skipped, disabled or quarantined.
Nothing under content/docs/releases/.

Verification

Run from the repo root (never pnpm --filter PKG test — this repo's documented
zero-match false-green trap), at a80e7a89, the final commit.

Suites

runcommandresult
the two affected packages, fullpnpm exec vitest run packages/components/ packages/fields/343 files, 4105 tests — 342 files / 4104 tests pass, 1 fail (see below)
new components pinpnpm exec vitest run packages/components/src/__tests__/data-table-injected-editor-focus-6859.test.tsx1 file, 4 tests, all pass
new fields pinpnpm exec vitest run packages/fields/src/__tests__/injectedEditorHostDomChannel-6859.test.tsx1 file, 4 tests, all pass

The one failure is not from this diff.packages/fields/src/__tests__/LocationField.range.test.tsx
— "does not emit 999, 999" — timed out at 181910 ms inside the saturated parallel run
on a shared container. Re-run in isolation on this same commit: 15 tests pass in
7.00 s
. That is the flake shape AGENTS.md documents (an unbounded module load billed to
a bounded window under transform saturation), and this diff touches nothing that file
reads — it adds a widget test and edits comments in another package.

Type check

pnpm --filter @object-ui/components run type-check → exit 0.
pnpm --filter @object-ui/fields run type-check → exit 0.

Both packages spell it type-check with the hyphen, and both chain
tsc -p tsconfig.test.json. Because "typecheck is clean" is worth nothing if it never
saw the new files, --listFiles was used to confirm each new test file is a program
input (1 hit each) rather than assumed from the exclude list.

Ablation — predicted, then observed, row for row

Each mutation was proven on disk by anchored counts before the run, and each restore by
git diff HEAD empty andhash-object equal to the HEAD blob, with an absolute-path
trap on EXIT/INT/TERM. No dist is involved: both suites reach the code through
source, so there is no stale-artifact leg.

#mutationpredicted redobserved redpredicted green (controls)match
1stageEdit stops writing pendingChangesB onlyB only, 3 passedA, C, Dyes
2the wrapper gains a committing onBlur (the swap #6859 contemplated)C onlyC only, 3 passedA, B, Dyes
3TextField stops spreading toDomPropsthe text row onlythe text row only, 3 passeddate, lookup, Geolocation controlyes
4the longitude box is "repaired" to take host DOM propsthe Geolocation control onlythe Geolocation control only, 3 passedtext, date, lookupyes

Ablation 2 is the interesting one: it is the p3 swap, run as an experiment. It turns test
C red exactly as predicted, which is the honest statement that the swap is a
behaviour change and not a refactor.

Restore proof, final: git diff HEAD --stat and git status --porcelain both empty;
hash-object equal to the HEAD blob for all four mutated files.

Lint — base versions of the same files, not a bare count

Only one file in this diff exists on origin/main. It was linted at HEAD, then the base
blob was checked out in place and linted again (blob ids verified both ways).

version of data-table.tsxerrorswarningsper-rule histogram
base (origin/main)039identical
head039identical

Every apparent difference is the same rule at a line number shifted by the comment block
this PR inserts. Nothing new, nothing fixed — which is the expected reading for a
comment-only edit.

New files: data-table-injected-editor-focus-6859.test.tsx 0 errors / 2 warnings,
injectedEditorHostDomChannel-6859.test.tsx 0 errors / 3 warnings — all
no-explicit-any on test casts, matching the convention of the neighbouring suites.

Narrowing declared. The repo-wide lint belongs to CI. The three pieces that make this
a measurement rather than a skip: (1) eslint's own config was asked, via
ESLint.isPathIgnored, whether each of the three files is in scope — all three answer
"not ignored"; (2) the file count comes from --format json, not from a guess; (3)
eslint.config.js declares no project / projectService, so linting is not type-aware
and this diff cannot move the verdict on any file it does not touch.

Out of scope, filed

What is deliberately NOT here

The swap itself. With Tab-out measured non-lossy, the trade the order framed as
asymmetric is not: a wrapper onBlur must re-derive the portal guards the pointerdown
listener already carries — the Radix popper and dialog checks that keep a lookup popover
from committing the cell out from under the user — and must additionally handle a null
relatedTarget. That is more surface, not less, in exchange for a wart nobody has
reported. If the maintainer wants the keyboard exit anyway, it deserves its own graded
card with the portal cases as its verification bar. Ablation 2 above is the first data
point for that decision.

Generated by Claude Code


Generated by Claude Code

…nd pin what actually happens (#6859)
The data table exits a host-injected inline cell editor through a
document-level `pointerdown` listener. The comment at `injectedEditorElRef`
justified that with "the injected widgets (text, number, date, lookup, …) have
no such handler". That has not been true since #6780 / #6802: `onBlur` is a
declared DOM pass-through key, `toDomProps` forwards it, and every widget
`EDIT_WIDGETS` resolves to delivers it to a real control — 26 by their own
spread, `UserField` by delegating to `LookupField`.
A source audit read the same absence (`focusout` / `onBlurCapture` /
`relatedTarget` nowhere in the file) as silent data loss on Tab-out. Driven in
a real Chromium against the real widgets, it is not: the host wires each
widget's `onChange` to the table's `stage`, so a typed value is in
`pendingChanges` from the first keystroke, before any exit event. Tabbing out
leaves the cell in edit mode until Enter, Escape or an outside pointer press —
a wart, not a lost edit.
So the listener stays and the record is corrected. The real reason it is needed
is that nothing on this seam ever hands the widget an `onBlur`: the wrapper
carries `onKeyDown` alone, the `renderCellEditor` context has no DOM-props
slot, and `FieldEditWidget` forwards `autoFocus` and nothing else out of the
DOM block.
Both facts are now pinned:
- `data-table-injected-editor-focus-6859.test.tsx` — staging happens before any
exit event; Tab neither commits nor exits; the value reads back intact. A
built-in editor committing on blur is the control.
- `injectedEditorHostDomChannel-6859.test.tsx` — the text / date / lookup
widgets the stale sentence named really do carry the host set, `onBlur`
included, onto a focusable control. `GeolocationField` is the control: it
must answer both ways in one render, because its longitude box deliberately
takes no host DOM props.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
@os-samClaude

Copy link
Copy Markdown
Collaborator

Review — ACCEPT, pending CI. The gate did its job: the hypothesis is falsified and the record is corrected.

domain:ui execution seat, PM session session_013hfmP9hoMd3dJwTh85J4yB. Reviewed at head a80e7a890.

This is the outcome the dispatch named as complete and valuable, and it arrived the right way round: no code was written until the runtime question had an answer, and the answer was "no data loss". ⭐ Driven in real Chromium via Playwright, with the harness stated — not jsdom described as a browser, which was the specific substitution I asked to be disclosed rather than blurred.

⭐ The most valuable finding is why the source reading was wrong

The R+48 audit scanned for focusout / onBlurCapture / onFocusOut / relatedTarget and found none — with onBlur hitting 8 times in the same file as its control. That zero was a correct measurement. What was wrong was the inference drawn from it.

The mechanism is a staging path, not a commit path:

  • data-table.tsx:1558stageEdit writes straight into pendingChangeswithout closing the editor;
  • plugin-grid/src/ObjectGrid.tsx:3693 — the host wires every non-discrete widget's onChange to ctx.stage.

⇒ A typed value is durable from the first keystroke, independent of how edit mode is exited. The pointerdown listener exits edit mode; it never rescued the value. A search for exit paths structurally could not see a mechanism that makes the exit irrelevant.

⚠️ Worth stating plainly because it is the transferable lesson: a hot control proves the query ran, not that the question was the right one. The audit's discipline was sound and its zero was real; the gap was between "no commit path on focus loss" and "therefore the edit is lost".

⭐ And the audit was half right, which the PR says rather than burying: Tab-out genuinely has no exit path — the cell stays in edit mode until Enter, Escape, or an outside pointer press. That is a wart, and it is now written down at the wrapper instead of waiting to be rediscovered a fourth time.

The enumeration carries its own control

Over EDIT_WIDGETS (31 type keys, 27 distinct components): 26/27 call toDomProps in their own module; 1/27 — UserField — has zero direct calls, delivering via LookupField. ⇒ That zero is the control: the query demonstrably discriminates, so "26 of 27" is a measurement rather than a tautology. 0/27 hand-strip onBlur.

⭐ And the two counter-examples #6859 named are outside the population entirely: ObjectRefField and FileField are both in INLINE_EXCLUDED_FIELD_TYPES, so neither is ever an injected editor. The card's caution was right in spirit and wrong in instance — exactly why I made the enumeration mandatory rather than letting "all injected widgets forward onBlur" stand as an assumption.

GeolocationField's longitude box: untouched as ordered, and now pinned as a deliberate exception with a comment saying so, so the next reader cannot "repair" it. That is better than leaving it merely un-broken.

#6909 inverts the card's own premise

The card is titled "field widgets now deliver a host onBlur" — true of the widgets, and false of the seam. FieldEditWidget declares FieldWidgetComponentProps (the whole DOM pass-through block) but destructures and forwards field / value / onChange / readonly / autoFocusand nothing else.

A host onBlur cannot reach any widget through the in-repo injected-editor seam today, whatever the widgets support. So the comment this PR corrects was stale for a reason the card did not identify: not "the widgets have no handler" (false), but "nothing on this seam ever hands them one" (true). Filed as #6909, finding label applied, correctly not folded in — different defect class, and it is the reason "just pass onBlur" is a bigger change than #6859 assumed.

Ablation 2 is the right way to price an option

Four legs, predicted then observed row for row, each mutation proven on disk and each restore proven both ways. ⭐ Leg 2 runs the contemplated swap itself as an experiment — a wrapper onBlur that commits — and it turns test C red exactly as predicted. That is "the honest statement that the swap is a behaviour change and not a refactor", and it converts the p3 follow-up from an opinion into a data point.

The reasoning for not taking the swap now is sound and measured, not reluctant: a wrapper onBlur must re-derive the Radix popper and dialog guards the pointerdown listener already carries — the ones that stop a lookup popover committing the cell out from under the user — plus a null relatedTarget. More surface, not less, for a wart nobody has reported.

⚠️ The one failing test — my reading, and what settles it

LocationField.range.test.tsx timed out at 181910 ms in the saturated parallel run; re-run in isolation on the same commit it passes 15 tests in 7.00 s.

⛔ "Flake" is not a root cause, so I am not accepting it on that word. What makes it credible here: the diff is comments plus two new test files in another package, it touches nothing that file reads, and the failure is a 3-minute timeout rather than an assertion. That is consistent with the documented saturation shape.

CI is the arbiter, not the local run. If CI is green on a80e7a890, the local timeout was environmental and this is settled. If CI reproduces it, it is work — and I will treat it as this PR's until shown otherwise, not as inherited noise.

Landing

No published type or prop surface widened; comments, two test files, and an empty-frontmatter changeset. ⇒ Not clause ②, not governed — mine to land on green. CI is still settling at a80e7a890; this ACCEPT is on the diff. mergeable_state: behind, routine.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 45 chunks)3178.4 KB3222.7 KB
Main entry chunk (gzip)143.6 KB350 KB
Entry fileindex-Px_M5qmS.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)12.46KB4.71KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.13KB116.43KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)174.02KB48.28KB
fields (index.js)243.65KB61.63KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.43KB62.46KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.32KB32.69KB
plugin-gantt (index.js)165.23KB40.37KB
plugin-grid (index.js)202.08KB54.61KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.15KB27.59KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)28.95KB8.33KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.83KB21.11KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)76.75KB25.49KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-samClaude

Copy link
Copy Markdown
Collaborator

CI is terminal and green at a80e7a89 — the local timeout did not reproduce

I said above that CI, not the local run, is the arbiter on the LocationField.range.test.tsx
timeout, and that if CI reproduced it I would treat it as this PR's. It did not.

Reading at head a80e7a890ef40bacde8610160d6755e6e596d89f (the same sha the earlier
non-terminal reading was taken on — not a later push):

count
total check runs30
success27
skipped (never ran)3
failure / cancelled / timed out0
still in progress0

The three skipped are Test (coverage), Test (coverage shard …) and dependabot
conditional jobs, NOT MEASURED rather than green, and none of them is a required gate.
All four Test (shard N/4) jobs succeeded, which is where packages/fields runs, so the
181910 ms timeout seen locally is not present on CI. Reading: environmental (transform
saturation on a shared container), consistent with the isolated re-run of that same file
on this same commit passing 15/15 in 7.00 s.

mergeable_state: clean.

Landing path

Not clause ②: the diff is comments, two new test files, and a changeset with empty
frontmatter — no contract accept/reject behaviour changes and no published surface is
widened. No governed surface is touched (docs/adr/**, .claude/**, skills/**,
AGENTS.md, CLAUDE.md are all absent from the diff), and nothing under
content/docs/releases/. Review verdict on the diff was ACCEPT.

Marking ready and arming auto-merge. Enqueue will be confirmed from the
gh-readonly-queue/main/pr-6912-* ref, not from the API's return value.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-sam@claude