Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

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): carry the spec's optional location keys across a coordinate edit by os-sales · Pull Request #6713 · objectstack-ai/objectui · GitHub
Skip to content

fix(fields): carry the spec's optional location keys across a coordinate edit - #6713

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy
Aug 28, 2026
Merged

fix(fields): carry the spec's optional location keys across a coordinate edit#6713
os-sales merged 1 commit into
mainfrom
claude/issue-6664-location-field-preserve-altitude-accuracy

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#6664

LocationField edits a location's coordinate pair as one comma-separated text box, and
rebuilt its emission as a fresh object from the parsed text:

onChange({ lat, lng });

@objectstack/spec declares the stored shape as { lat, lng, altitude?, accuracy? }, so a
stored altitude / accuracy was gone the moment a user retyped the coordinates. Nothing
warned; the two keys simply were not in the object handed to onChange.

Why this is worth fixing even with no producer

Triage measured both questions the card left open: no producer writes either key — not
in objectui, not in objectstack. It queued the card anyway, on a third reading: both keys
are registered on the authorable surface
(objectstack packages/spec/authorable-surface.base.json:3438,3442), so the platform has
already promised customers they may author them. The population that decides this is "does
a customer write it", which neither repo can measure — hence a real defect at p2 rather
than an implementation-first key.

This predates #6272

The drop is not a regression from #6272. Before that flip the widget emitted
{ latitude, longitude } and discarded the rest identically. What #6272 changed is only
that the declared value type is now the spec's — so the type claimed four keys while the
write path handled two. Keeping this out of that card's atomic fence was correct: the A1
ruling covered a bare two-sided rename and nothing else. This PR closes the gap that flip
made visible.

The fix

A key-by-key pick of exactly the two spec-declared optional keys, out of a value that is
already a valid LocationValue:

functioncarryOptionalKeys(lat: number,lng: number,previous: unknown): LocationValue{constemitted: LocationValue={ lat, lng };if(!isLocationValue(previous))returnemitted;if(isFiniteNumber(previous.altitude))emitted.altitude=previous.altitude;if(isFiniteNumber(previous.accuracy))emitted.accuracy=previous.accuracy;returnemitted;}

No spread, and no Object.assign — deliberately. A stored record may still hold the
retired latitude / longitude spelling, and spreading the incoming value would carry
that dialect straight back into the emitted object and undo #6272's rename. The two ...
occurrences left in the file are the pre-existing ...props rest parameter and the
toDomProps DOM pass-through; the value itself is never spread.

Each optional key is taken only when it is a usable number. Measured against the spec:
z.number() rejects NaN, Infinity and a numeric string alike (invalid_type at
[altitude]), so carrying one of those forward would make this widget emit a value the
platform's own validator refuses. Leaving it behind narrows the emission — it emits
less than it was handed, never more — rather than widening what the widget tolerates.

The finite test was extracted into a named isFiniteNumber predicate so the coordinates
and the two optional keys are held to the same check instead of two copies that can drift.

Tests

packages/fields/src/__tests__/LocationField.optionalKeys.test.tsx — 12 pins.

Red before, green after. On the merge-base (5967be095), with the test file present
and the widget untouched, the five positive pins fail and the rest pass:

Tests 5 failed | 7 passed (12)
AssertionError: expected { lat: 31.2304, lng: 121.4737 } to deeply equal { lat: …, …(2) }
- Expected
+ Received
{
- "accuracy": 12,
- "altitude": 5,
"lat": 31.2304,
"lng": 121.4737,
}

After the fix, all 12 pass, and so do the 12 existing #6272 pins in
LocationField.specShape.test.tsx (24/24 across the two files).

Negative control — green both before and after, and that is the correct reading. Four
tests build an old-dialect input carrying latitude/longitude and assert the emitted
object does not contain them, plus the general form: no key outside the spec's own
declared set, read off LOCATION_SCHEMA.shape rather than copied into a literal. These
cannot be red today, because today's code emits a freshly built { lat, lng } that
trivially carries no dialect. They are not vacuous — they guard the future regression
the card's fence names: someone "simplifying" the key-by-key pick into
onChange({ ...value, lat, lng }). Under that rewrite the first of them goes red, because
its stored value carries both spellings. No positive pin can provide that guard.

One of them pins why those assertions read the emitted object's keys and not
safeParse: LocationValueSchema is a plain, non-strictz.object, so it accepts a
spread-polluted object and merely strips the unknown keys from its parsed output while the
value handed to onChange keeps them. The schema cannot be the fence's guard.

Deliberately not in scope

LocationCellRenderer and ObjectMap still read lat/lng only, so neither displays the
carried keys. Triage measured that and ruled it a feature needing its own card; it is
not written here.

Changeset

'@object-ui/fields': patch. This restores data the declared value type already promised —
no API added, no accepted input retired, and the anti-spread fence keeps the emitted key
set a subset of the spec's declared four. That matches how this repo scores a change to
what a surface writes (cf. view-overlay-write-patch-only-5233); minor here is
reserved for retiring an accepted read (cf. view-column-io-canonical-identity-5725).
Per AGENTS.md a changeset in this repo never declares major.

Verification

All at bcfdb556e, the final commit.

checkresult
pnpm exec vitest run packages/fields/ packages/plugin-detail/226 files, 2910 tests passed
pnpm --filter @object-ui/fields type-checkgreen (tsc --noEmit && tsc -p tsconfig.test.json)
npx eslint packages/fields194 files, 0 errors
node scripts/check-lint-coverage.mjs46/46 packages linted, 0 outstanding errors
node scripts/check-control-bytes.mjsOK, 5561 tracked text files
node scripts/check-spec-symbol-derivation.mjsOK
node scripts/check-changeset-presence.mjsOK, 1 changeset for 2 published source files
node scripts/check-changeset-no-major.mjs / -fixed / -overwriteOK
node scripts/check-vi-mock-specifiers.mjsOK
node scripts/check-package-self-import.mjsOK
node scripts/check-readme-exports.mjsOK, 0 unbuilt
node scripts/check-published-dist-tooling.mjsOK after a full 43-task build
pnpm exec vitest run scripts/85 files, 2389 tests passed

Both edited files were confirmed present in the tsconfig.test.json program via
tsc --listFiles, so the green type-check really covers the new test file rather than
skipping it.

The repo-wide pnpm lint was narrowed to packages/fields locally. That narrowing
excludes nothing: eslint.config.js enables no type-aware linting (no project or
projectService) and none of the 17 local rules in eslint-rules/ reads the filesystem,
so a verdict is a pure function of the single file being linted and this three-file diff
cannot move the verdict on a file it did not touch — and check-lint-coverage.mjs
independently reports 0 outstanding errors across all 46 packages.


Generated by Claude Code

…ate edit
`LocationField` edits `{ lat, lng }` as one comma-separated text box and rebuilt
its emission as a fresh object from the parsed text, so the two OPTIONAL keys
`@objectstack/spec` declares alongside them — `LocationValue` is
`{ lat, lng, altitude?, accuracy? }` — were dropped the moment a user retyped
the coordinates. Nothing warned. Both keys are registered on the platform's
authorable surface, so a customer may author them even though neither repo
produces them today.
The drop predates objectui#6272: before that flip the widget emitted
`{ latitude, longitude }` and discarded the rest identically. #6272 changed only
the declared value type, so the type claimed four keys while the write path
handled two.
The carry is a key-by-key pick of exactly those two keys out of an
already-valid `LocationValue` — deliberately not a spread of the incoming
value, which would carry a stored record's retired `latitude` / `longitude`
spelling back into the emission and undo #6272's rename. A negative control
pins that, and reads the emitted object's own keys rather than `safeParse`
because `LocationValueSchema` is non-strict and accepts a polluted object.
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.1 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DtXh3Z4b.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)509.32KB115.60KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.20KB60.12KB
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.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
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 drops the spec's optional altitude / accuracy when a user edits the coordinate pair

2 participants

@os-sales@claude