Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', '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('^' + ".*" + ' fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, '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" + ' fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, '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('^' + ".*" + ' fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, '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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(fields): refuse location coordinates the spec rejects by claude[bot] · Pull Request #6717 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): refuse location coordinates the spec rejects - #6717

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal
Aug 29, 2026
Merged

fix(fields): refuse location coordinates the spec rejects#6717
os-sales merged 1 commit into
mainfrom
claude/issue-6714-location-range-refusal

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#6714

LocationField accepted any pair of finite numbers as a coordinate, while @objectstack/spec's LocationValueSchema also constrains their range (lat -90..90, lng -180..180). Typing 999, 999 emitted {"lat":999,"lng":999} — a value valueSchemaFor({ type: 'location' }) refuses with too_big at both keys. Producer direction of the contract-first failure class (AGENTS.md #0.1), open to every user who edits a location field, since typing the coordinates is this field's only interaction.

The pre-measurement triage required, and what it decided

The ruling made one reading a precondition of choosing the arm: does anything downstream reject or repair the value before storage? It was measured by driving a REAL ObjectForm (create mode, a type: 'location' field, a fake DataSource) and typing the card's 999, 999:

create payload {"title":"HQ","place":{"lat":999,"lng":999}}
spec verdict on it REJECT too_big@[lat], too_big@[lng]
aria-invalid on control "false"
visible field error text "Place" (the label — no error rendered)
create call count 1

Neither. Nothing rejects it and nothing repairs it: sanitizeFormData filters KEYS (server-managed, computed, read-only) and never inspects a value; buildValidationRules has no location branch, so its min/max rules only ever carry an author-declared bound on a scalar; and valueSchemaFor has no runtime call site anywhere in the repo — it appears only in tests and comments. The payload goes straight to dataSource.create.

Per the ruling's own branch — 若下游不拒 ⇒ 越界坐标会落库,那么「拒绝发射」是唯一能防住脏数据的那条 — that pushes to refuse the emission, and this PR takes that arm. It extends a rule this widget already applies to text that isn't a coordinate pair from format to range: the typed pair is simply not written and the prior value stands. Same branch, same comment, no new UI and no new mechanism.

The measurement is kept as a pin (packages/plugin-form/src/ObjectForm.locationRange.test.tsx) rather than discarded, so the fact that made the arm correct is the thing that fails if it ever stops being true.

The bounds are not restated in the widget

A hand-copied -90..90 in the widget would be a second contract free to drift from the spec — the shape #0.1 bans — so the emission is put to LocationValueSchema itself. @objectstack/spec is already a runtime dependency of @object-ui/fields (file-value.ts imports isFileIdToken from the same subpath), and the schema is a memoized lazy schema, so this costs one safeParse of a 2-4 key object.

Two consequences of asking the schema rather than testing two bounds by hand, both deliberate:

Per the dispatch's pointer, the new predicate sits beside isFiniteNumber rather than in a parallel validator, and isFiniteNumber keeps its job unchanged.

Reading is deliberately unchanged.isLocationValue is the READ guard and stays range-free, so a record that already holds an out-of-range pair still renders in the box — blanking it would hide the dirty data from the only person who can correct it. #6272's empty render was for a value whose SHAPE this widget cannot read; this shape is readable, it is only not writable.

Anti-vacuity: red before, green after

Reverse-verified from the committed state, by restoring the merge-base widget (98188c284) into the tree and re-running. The mutation was proven on disk before anything was measured — guard-marker count 2 to 0, and git hash-object equal to the merge-base blob aac3b3cf and different from the HEAD blob 28f88fa2; a trap ... EXIT INT TERM with absolute paths held the restore leg. The tests resolve packages/fields/src through the root vitest alias table (not dist), so no rebuild is involved and the source mutation is what ran.

RED (merge-base widget) Test Files 2 failed | 2 passed (4)
Tests 11 failed | 31 passed (42)
GREEN (this branch) Test Files 4 passed (4)
Tests 42 passed (42)

The 11 failures are exactly the two new suites; LocationField.optionalKeys and LocationField.specShape stayed green under the ablation, so the pin is targeted rather than a blanket breakage. Restoration proven afterwards: git diff HEAD empty, on-disk hash back to 28f88fa2, marker count back to 2.

Every case is judged by the spec's own refusal, never by a range copied into the test — including the inclusive bounds 90, 180 and -90, -180, which are real places an off-by-one would have made untypable.

Verification

All on 232813b45 (the commit this PR ships), run after the final commit.

checkverdict line
the four suites aboveTest Files 4 passed (4) / Tests 42 passed (42)
type-check (fields + plugin-form)both echoed tsc --noEmit && tsc -p tsconfig.test.json, Done
check:changeset-presence3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
check:control-bytesOK (scanned 5573 tracked text file(s); skipped 85 binary)
check:spec-symbolsexit 0
check:phantom-deps / check:self-import / check:esm-specifiers / check:vi-mock-specifiersexit 0
type-check:coverage45/46 via type-check + 41/41 packages compile their tests
lint:coverage46/46 packages linted, 0 with outstanding errors

Both new test files were confirmed present in their package's tsconfig.test.json program via tsc --listFiles (1 hit each) — the type-check pass really does cover them.

Lint was narrowed, and here is the evidence it measured what it claims.eslint --no-inline-config on the three changed files: filesLinted=3, errors=0, warnings=8, all @typescript-eslint/no-explicit-any — the same class and convention as the pre-existing sibling LocationField.optionalKeys.test.tsx (0 errors, 4 warnings). The file count is read from --format json, not counted by hand. The narrowing cannot have hidden anything in untouched files because type-aware linting is not enabled in eslint.config.js (no projectService, no parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint is CI's run.

Two gates read NOT MEASURED locally, and neither is a verdict on this diff.check:spec-floors and check:readme-exports both fail on an unbuilt workspace and say so in their own output (produced no build output to judge / the population COLLAPSED, both naming pnpm build as the remedy); their 12 and 7 findings name only packages and READMEs this diff never touches. check:spec-floors is nonetheless the gate most implicated here, since this PR adds a runtime spec import, so its substantive question was answered directly instead: @objectstack/spec@17.0.0 does carry LocationValueSchema (verified by unpacking 17.0.0 — 1 reference in dist/data/index.d.ts, 3 in dist/data/index.mjs), so @object-ui/fields' declared ^17.0.0 floor is honest and no floor bump is needed. CI builds the workspace and will run both gates for real.

Changeset

.changeset/6714-location-range-refusal.md, scored patch on @object-ui/fields. Reasoning: user-visible behaviour changes (an input previously accepted is now refused), so the empty-frontmatter "declares no release" form would be wrong — but nothing is added to the public surface. No new export, no new prop, no new option; the widget narrows what it writes to what the platform already required, and a value it now declines to emit is one storage would have been wrong to hold. @object-ui/plugin-form gains only a test file and rides the fixed group.

Out-of-scope findings, filed not fixed

The parseFloat leniency on these same two lines was fenced out by both the card and the ruling and is not touched here. It and one neighbouring gap are recorded as their own cards:

Dedup before filing: repo-scoped REST list of open issues updated since 2026-08-18 (239 issues), grepped for parseFloat / LocationField / coordinate / latitude / aria-invalid / silent-refusal wordings. Only #6714 matched, where both appear as excluded halves.


Generated by Claude Code

`LocationField` accepted any pair of FINITE numbers as a coordinate, while
`@objectstack/spec`'s `LocationValueSchema` also constrains their range
(`lat` -90..90, `lng` -180..180). Typing `999, 999` emitted
`{ lat: 999, lng: 999 }`, which `valueSchemaFor({ type: 'location' })`
refuses with `too_big` at both keys - the producer direction of the
contract-first failure class (AGENTS.md #0.1).
Measured before choosing the disposition: nothing downstream rejects or
repairs the value. A real `ObjectForm` submit handed
`place: { lat: 999, lng: 999 }` straight to `dataSource.create`, with
`aria-invalid="false"` and no error text anywhere. So the widget is the only
place a refusal can work, and the fix refuses the emission - extending the
rule this widget already applies to text that is not a coordinate pair from
format to range.
The bounds are not restated in the widget. A hand-copied `-90..90` would be a
second contract free to drift, so the emission is put to `LocationValueSchema`
itself. That also covers the whole emitted object (so `altitude`/`accuracy`
carried across an edit are held to the contract too) and refuses `Infinity`,
which the finiteness gate let through.
Reading is deliberately unchanged: a record already holding an out-of-range
pair still renders, so the person who can correct it can still see it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3232.8 KB3266.6 KB
Main entry chunk (gzip)157.3 KB350 KB
Entry fileindex-V6b0OF9m.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)11.89KB4.50KB
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)510.58KB116.01KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.31KB60.18KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.89KB12.91KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.44KB34.48KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.20KB40.37KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.17KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.00KB3.08KB
plugin-view (index.js)85.87KB21.12KB
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)67.73KB22.54KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
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

Sign up for freeto 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.

finding(fields): LocationField emits out-of-range coordinates the spec rejects

2 participants

@os-sales@claude