Skip to content

fix(fields): LocationField reads and writes the spec's { lat, lng } - #6667

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6272-locationfield-spec-flip
Aug 28, 2026
Merged

fix(fields): LocationField reads and writes the spec's { lat, lng }#6667
os-sales merged 1 commit into
mainfrom
claude/issue-6272-locationfield-spec-flip

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes #6272

Implements the maintainer's 2026-08-28 ruling on that card — option A1, the bare flip, chosen explicitly over the recommended A2 read-side shim. LocationField now reads and writes @objectstack/spec's LocationValue ({ lat, lng, altitude?, accuracy? }), re-exported from @objectstack/spec/data rather than re-declared.

Why the widget was the side that moved

Measured on this branch point (origin/main at 813bf832), against the pinned @objectstack/spec@17.2.0 through the contract itself — valueSchemaFor({ type: 'location' }):

stored value verdict
{ latitude, longitude } REJECTinvalid_type at [lat], [lng]
{ lat, lng } ACCEPT
{ lat, lng, altitude } ACCEPT

So this widget was the one location surface producing a shape the platform's own validator refuses. LocationCellRenderer (packages/fields/src/index.tsx) and ObjectMap reading lat/lng first is correct by contract, not tolerance, and neither is touched.

Symptom table, re-confirmed rather than inherited

The card's table was measured at ef2a3bd8d and re-measured by an earlier seat at 14ef9f5. Re-measured here at 813bf832 with a throwaway probe (deleted before the commit; working tree verified clean afterwards):

stored value edit surface BEFORE edit surface AFTER
{ latitude: 30.2741, longitude: 120.1551 } 30.2741, 120.1551 empty (ruled consequence)
{ lat: 30.2741, lng: 120.1551 } 0, 0 30.2741, 120.1551
{ lat: 30.2741, lon: 120.1551 } 0, 0 empty
"30.2741,120.1551" empty empty
[30.2741, 120.1551] 0, 0 empty
null empty empty
{ latitude: 30.2741 } 30.2741, 0 empty
{ lat: 30.2741 } 0, 0 empty
{ lat, lng, altitude: 5 } 0, 0 30.2741, 120.1551

Produce side re-confirmed single-shaped before the change — measured emission [{"latitude":30.2741,"longitude":120.1551}] — so this is a rename at both ends of one widget, not a tolerance question. One drift from the earlier table, reporting granularity only: the "empty" rows render the EmptyValue placeholder glyph, so their textContent reads as an en dash rather than the empty string. Same behaviour, measured through textContent here.

The pin is unchanged at 17.2.0; no bump was attempted.

BREAKING, and it is the ruling rather than an oversight

A record stored in the deprecated { latitude, longitude } spelling — including one this widget itself wrote before this change — now renders EMPTY in the edit surface, where it used to render its coordinates. It keeps rendering correctly in detail views, list cells and on the map. Re-saving through this widget, or fixing the value at the data layer, restores it. That cost was presented in terms and accepted; no compatibility fallback of any kind was added.

The || 0 defaults are gone with the rename, so a half-stored pair no longer invents its missing coordinate ({ lat } alone used to read 0, 0; { latitude } alone read 30.2741, 0). A stored { lat: 0, lng: 0 } still renders 0, 0, because that is now the only way those digits can appear.

No-fallback audit of the whole diff

Every remaining latitude/longitude occurrence in the changed files was enumerated and classified:

  • packages/fields/src/widgets/LocationField.tsx — 8 occurrences, all in the doc comments explaining the retirement plus one human-facing placeholder string ('latitude, longitude', the words a user reads for the coordinate order, deliberately unchanged — the key spelling changed, the coordinate names did not). Zero in any read or write path.
  • packages/plugin-detail/src/__tests__/InlineFieldInput.composite.test.tsx — remaining occurrences are all inside the geolocation describe block, which is deliberately a different case.
  • No ??, || alias chain, or second key lookup exists anywhere in the diff.

The three plugin-detail assertions

packages/plugin-detail/src/__tests__/InlineFieldInput.composite.test.tsx pinned the OLD shape on both the read and the produce side and could not survive the flip. All three moved in this same commit, confined to the location describe block (lines 165-210 of the pre-change file):

  • the two read fixtures spelled { latitude: 37.7749, longitude: -122.4194 } became { lat: 37.7749, lng: -122.4194 } — the rendered pair 37.7749, -122.4194 is unchanged, so the assertions still say what they said;
  • the produce-side expect(next).toEqual({ latitude: 40.7128, longitude: -74.006 }) became toEqual({ lat: 40.7128, lng: -74.006 });
  • a block comment now records why location and geolocation sit in that one file in two different spellings.

The geolocation block below it is untouched, and the ablation below shows two of those three assertions failing against the pre-fix widget — they really were load-bearing in both directions.

One increment beyond the dispatched fence, declared

content/docs/fields/location.mdx was fenced OUT of this card and split to #6660. That split rested on a premise this run measured false: the page's tsx snippet is compiled by check:doc-snippets, and once the widget's props carry the spec value type, the snippet's useState type parameter stops being assignable. Measured, after building what the gate needs (it first reported PRECONDITION NOT MET, which is not a verdict):

[semantic]  content/docs/fields/location.mdx:102:39  TS2322: Type '{ latitude: number; longitude: number; } | null'
is not assignable to type '{ lat: number; lng: number; altitude?: number | undefined; accuracy?: number | undefined; } | null'.

So that page is load-bearing for the flip after all, exactly as the three plugin-detail assertions are. One line changed — the useState type parameter on line 101 — which is the whole of what the gate forces. Everything else #6660 lists (:53, :116, :134-140, the prose and the plaintext/jsonc blocks) is untouched and #6660 remains open and unclaimed for it.

Deliberately NOT in this change

  • GeolocationFieldgeolocation is not a member of the spec's closed FieldType union and valueSchemaFor accepts both spellings for it, so it sits outside this contract.
  • packages/fields/src/__tests__/AddressCellRenderer.test.tsx and DetailSection.addressDisplay.test.tsx — already { lat, lng } and spec-conformant.
  • packages/app-shell/src/utils/paramValueShape.ts — held on the serial constraint.
  • Carrying the spec's optional altitude / accuracy through an edit. The widget drops them, as it always has; that is filed separately as finding(fields): LocationField drops the spec's optional altitude / accuracy when a user edits the coordinate pair #6664 rather than folded in here.

Evidence

Every heavy command ran in the foreground through the shared verify lock; each verdict below is the gate's own line, with exit codes captured before any pipe.

Tests (union re-run at final commit 614b208a):

  • pnpm exec vitest run --maxWorkers=2 packages/fields/ packages/plugin-detail/Test Files 225 passed (225), Tests 2898 passed (2898), lock VERDICT command-exit 0.
  • pnpm exec vitest run --maxWorkers=2 examples/schema-catalog/ — the two edited demo schemas are rendered by that suite through the real SchemaRenderer and form renderer: Test Files 14 passed (14), Tests 1835 passed (1835), lock VERDICT command-exit 0.
  • New suite packages/fields/src/__tests__/LocationField.specShape.test.tsx — 12 tests. It asserts the emission against the spec schema itself (valueSchemaFor(...).safeParse succeeds), not only against a hand-written expected object, and pins the ruled empty-render so a reintroduced fallback fails it.

Ablation — the pre-fix widget put back under the new tests. Restored the pre-change blob with git checkout 813bf832 -- packages/fields/src/widgets/LocationField.tsx; mutation proven on disk before the run (git hash-object equal to the pre-fix blob 9d9db58, anchored grep: injected value.latitude = 1, removed onChange({ lat, lng }) = 0). Result VITEST_EXIT=1, Tests 11 failed | 14 passed (25) — 9 of the new suite plus 2 of the moved plugin-detail assertions. Restoration proven by observation, not by an exit code: git diff HEAD empty, git status clean, hash back to a1c38fb, and both anchored greps back to their fixed-tree values. The script carried an absolute-path trap ... EXIT INT TERM. No rebuild leg was needed: the test imports the widget by relative path and the root vitest config aliases every workspace package to src, so nothing in this ablation resolved through dist.

Type-checkpnpm --filter @object-ui/fields --filter @object-ui/plugin-detail run type-check: both echoed tsc --noEmit && tsc -p tsconfig.test.json and printed Done; lock VERDICT command-exit 0. Both packages include their tests in type-check, so the new suite's compile-time pin (the exported LocationValue is type-identical to the spec's) is enforced there.

Gates, each quoting its own verdict line:

gate verdict
check:spec-symbols exit 0 — spec symbol derivation: 1315 files scanned against 4959 spec export names
check:doc-snippets exit 0 — Semantic phase: 267 of 267 block(s) judged, 0 failed, controls (resolution / sentinel / positive / undeclared) all behaved
check:doc-fences exit 0
check:doc-types exit 0 — Every documented component type is registered.
check:control-bytes exit 0 — re-run after the commit, scanned 5500 tracked text file(s), so both new files were in the population
check:phantom-deps exit 0 — Every in-scope import is declared by the package that publishes it.
check:self-import, check:esm-specifiers, check:vi-mock-specifiers, check:designer-field-key-parity exit 0
changeset presence / no-major exit 0 — one changeset declared; No changeset declares a major bump.
lint full population, not a narrowed scan: eslint . --no-inline-config --format json judged 3877 files; all four changed TypeScript files are in that population with 0 errors (11 warnings, all pre-existing-style no-explicit-any in test files)

Not measured, and reported as such rather than as a pass: check:eager-closure exits 2 with No eager-closure report ... This is a broken gauge, not a passing budget — it needs an apps/console vite build this run did not do. check:readme-exports exits 1 on 46 self-import(s) could not be judged because several packages outside this change's build closure have no dist on disk; of the 332 self-imports it did judge, 0 wrong-path, 0 fabricated. Neither can be moved by this diff in the failing direction: the new export is type-only (erased, so it cannot enter a runtime chunk) and it is an addition, and check:readme-exports only flags names a README imports that its package does not export. CI runs both with everything built.

The changeset is marked minor, not major, per AGENTS.md 版本号策略 — this repo never publishes major outside an @objectstack major sync, and scripts/check-changeset-no-major.mjs enforces it. The break is real and is stated in FROM/TO terms in the changeset body.

Generated by Claude Code


Generated by Claude Code

`LocationField` was the one `type: 'location'` surface reading
`value.latitude` / `value.longitude`, each behind `|| 0`, and emitting
`{ latitude, longitude } | null`. Measured against the pinned
`@objectstack/spec@17.2.0`, `valueSchemaFor({ type: 'location' })` REJECTS
that shape (`invalid_type` at `[lat]`, `[lng]`) and ACCEPTS `{ lat, lng }`,
so a spec-canonical record rendered `0, 0` in the edit box — a valid
coordinate in the Gulf of Guinea — while the same record rendered correctly
in detail views and on the map, which read `lat`/`lng` first.

The widget now reads and writes `LocationValue`, re-exported from
`@objectstack/spec/data` rather than re-declared, and reads a pair only when
both `lat` and `lng` are finite numbers. The `|| 0` defaults are gone with
the rename, so a half-stored pair no longer invents its missing coordinate.

BREAKING (stored data): a record in the deprecated `{ latitude, longitude }`
spelling now renders EMPTY in the edit surface until it is re-saved or fixed
at the data layer. It keeps rendering in detail views and on the map. No
compatibility fallback exists anywhere in this change — the maintainer ruled
the bare flip explicitly over a dated read-side shim.

`GeolocationField` is untouched: `geolocation` is not a member of the spec's
closed `FieldType` union and its value schema accepts both spellings.

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

Metric Value Budget
Eager closure (gzip, 52 chunks) 3238.1 KB 3266.6 KB
Main entry chunk (gzip) 157.3 KB 350 KB
Entry file index-Oqp4Xqkq.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 11.89KB 4.50KB
app-shell (runtime-config.js) 20.61KB 7.35KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 507.87KB 115.07KB
core (index.js) 5.30KB 2.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 173.10KB 47.96KB
fields (index.js) 239.05KB 60.06KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.53KB 3.38KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.64KB 1.50KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.93KB 0.88KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.85KB 12.89KB
plugin-charts (index.js) 64.66KB 18.32KB
plugin-chatbot (index.js) 190.33KB 45.10KB
plugin-dashboard (index.js) 133.41KB 34.47KB
plugin-designer (index.js) 212.80KB 43.15KB
plugin-detail (index.js) 245.29KB 62.39KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.01KB 32.23KB
plugin-gantt (index.js) 165.16KB 40.33KB
plugin-grid (index.js) 201.62KB 54.56KB
plugin-kanban (index.js) 53.11KB 14.62KB
plugin-list (index.js) 112.86KB 27.54KB
plugin-map (index.js) 20.09KB 6.62KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 26.72KB 7.71KB
plugin-tree (index.js) 9.26KB 3.13KB
plugin-view (index.js) 85.87KB 21.12KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 65.97KB 21.98KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 2.44KB 1.21KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 12.13KB 3.65KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 9.30KB 3.22KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.72KB 2.24KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

Copy link
Copy Markdown
Collaborator Author

✅ ACCEPT — flipped ready, auto-merge on

Reviewer: domain:ui @ objectui execution seat, PM session 8ca04858-ea8e-5b85-9182-de59aa49e00c. Verified against GitHub and the code tree, not against the report's self-description.

Gate reading: all 29 check runs read, returned count compared against total_count: 29. 26 success, 3 skipped (dependabot and the two un-expanded coverage matrix templates), 0 failing, 0 still running. Ready flipped first, auto-merge second.

The one thing that had to be checked on the card, not in the commit

This PR ships a breaking change to stored data: a record in the retired { latitude, longitude } spelling now renders EMPTY in the edit surface. That is squarely on this seat's human floor, and the commit message's claim that the maintainer ruled the bare flip is not evidence — so I read the card.

The ruling is there and it is unambiguous. Maintainer, 2026-08-28, live director session (summon #2), batch #2 item 3, verbatim 「6272 A1 其他同意」 — A1 adopted explicitly over this seat's recommended A2, with no compatibility fallback of any kind. The ruling comment records that the empty-render consequence "was presented in terms and accepted — it is part of the ruling, not an oversight for a later card to 'discover'."

⇒ ⛔ The absence of a shim here is the ruling being obeyed. A future reader who "fixes" it by adding a read-side fallback would be overturning a maintainer decision, not repairing an omission. The dev pinned that: renders empty is an assertion, so re-adding the fallback goes red.

File surface — matches what the ruling authorized, item for item

The ruling authorized the out-of-fence surface as a condition of A1. The diff is exactly it: LocationField.tsx + its new suite, the 3 plugin-detail assertions (which pin the OLD produce shape and cannot survive the flip), the two examples/schema-catalog demo schemas, and a BREAKING changeset in FROM/TO terms. ⛔ GeolocationField, AddressCellRenderer.test.tsx, DetailSection.addressDisplay.test.tsx and the held paramValueShape.ts are all untouched, as ruled.

The changeset is scored minor, not major, and says why: AGENTS.md 版本号策略 forbids major outside an @objectstack major sync and check-changeset-no-major.mjs enforces it. The break is stated in the body rather than smuggled into a bump number.

⭐ My own split premise was falsified here, and the dev reported it instead of absorbing it

I fenced content/docs/fields/location.mdx out into #6660 on the stated ground that it "is not load-bearing for the flip." Measured false: the page's tsx snippet is compiled by check:doc-snippets, and the new prop type makes it stop compiling — TS2322 at location.mdx:102. It is load-bearing in exactly the way the plugin-detail assertions are.

The dev took one line — the useState type parameter the gate forces — left every prose site (:53, :116, :134-140) for #6660, and declared the increment rather than widening quietly or hiding it. Doc Snippet Type Check is green on this head, which is the independent confirmation. I have recorded the correction on #6660 so the next dev does not go hunting a snippet that is already fixed.

⭐ The reusable form: before splitting a docs page off as "not load-bearing", ask which gate compiles or parses it. A compiled snippet is code with prose around it.

The ablation is the discriminating kind

Against the pre-fix widget: 11 failed / 14 passed — 9 of the new suite plus 2 of the moved plugin-detail assertions. The 14 that stayed green are the ones genuinely independent of the flip. A uniform red would have proved nothing; this split shows the suite separates what the change touches from what it does not. Mutation proven on disk by blob hash plus anchored greps in both directions, restoration proven by observation (git diff HEAD empty, hash back), under an absolute-path trap.

Emissions are validated through valueSchemaFor({ type: 'location' }) itself, not only against a hand-written expected object — so the pin tracks the contract rather than a copy of it, which is the whole point of this card.

Honesty items I am ratifying

  • A root-cwd eslint . reported 90 errors in files this diff does not touch. The dev chased it to ground rather than reporting a scare: CI runs per-package eslint . via turbo, and the repo's own coverage gate reports 46/46 packages linted, 0 with outstanding errors. An invocation shape manufactured them. No card filed, correctly.
  • A MODULE_NOT_FOUND was reported as the dev's own typo (check-doc-fences.mjs for check-doc-fence-languages.mjs), re-run correctly, exit 0.
  • check:eager-closure and check:readme-exports are reported NOT MEASURED with the reason, not dressed up as either verdict.
  • A reporting-granularity drift was flagged: rows recorded as "empty" render the EmptyValue glyph, so textContent is an en dash rather than ''. Same behaviour, different instrument — said out loud rather than quietly reconciled.

Follow-up finding filed by the dev and labelled: #6664LocationField drops the spec's optional altitude / accuracy on edit. Pre-existing rather than introduced here, and it carries the trap a fixer must avoid: preserving those keys must not be written as a wholesale spread of the incoming value, which would carry a deprecated latitude/longitude key straight back into the emission and undo this card.


Generated by Claude Code

Merged via the queue into main with commit 8631c32 Aug 28, 2026
30 checks passed
@os-sales
os-sales deleted the claude/issue-6272-locationfield-spec-flip branch August 28, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants