Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions .changeset/10392-registration-record-source-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ on the registrations disagreed with that rule in two ways:
(objectui#10392).
- `object-map`, `map` and `object-gantt` did not declare `data` or
`staticData`, so the validator reported a block authored on either as an
unknown prop. Both are now declared, on the schema's own arms: `data` is a
`{ provider, … }` data-source configuration (an object, so a bare array there
now draws a type diagnostic, matching the schema), and `staticData` is an
array of records. Each description says what the renderer does with the key,
including that the map does not implement the `api` provider
(objectui#10394).
unknown prop. At this change both were declared on all three, on the schema's
own arms: `data` is a `{ provider, … }` data-source configuration (an object,
so a bare array there draws a type diagnostic, matching the schema), and
`staticData` is an array of records. Each description says what the renderer
does with the key, including that the map does not implement the `api`
provider (objectui#10394).

⚠️ **Dated note, 2026-09-25 — the bare `map` registration has since been
retired — objectui#10393.** Later in this same release `@object-ui/plugin-map`
stopped registering the bare `map` key (and its namespaced twin `view:map`),
so `map` declares nothing: a node authored `"type": "map"` resolves no
renderer, and the html tier reports it as `unknown-component`. The
`object-map` and `object-gantt` registrations keep both inputs exactly as
described above. The rest of this entry is kept as the reading of this
change; the objectui#10393 entry states what ships.

The renderers' read order and the zod schemas are unchanged. The
`@object-ui/plugin-gantt` README sentence that listed the registration's inputs
Expand Down
62 changes: 62 additions & 0 deletions .changeset/10393-retire-bare-map-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
'@object-ui/plugin-map': minor
'@object-ui/core': patch
'@object-ui/cli': patch
'@object-ui/console': patch
---

**BREAKING (node type key):** `@object-ui/plugin-map` no longer registers the bare
`map` node type key, and its namespaced twin `view:map` goes with it. A node
authored `"type": "map"` no longer resolves a renderer. Write
`"type": "object-map"` — the one spelling the plugin serves (objectui#10393,
executing the objectui#8008 family ruling of 2026-09-09 that retired the bare
`gantt` and `kanban` keys).

**Why.** The registry mounted a `map` node while the published declaration refused
it: `ObjectMapSchema.type` is the literal `'object-map'` and `AnyComponentSchema`
has no `map` arm, so `safeValidateSchema` answered a `map` node with
`invalid_union` while the html tier accepted it. Two faces, opposite verdicts.
No schema face ever declared `map` as a component node type, so there is no arm
to turn into a named refusal: unregistering is the whole retirement, exactly as
it was for `gantt`. After it, the html tier (`validateTree` over the live
registry) reports a `map` node as `unknown-component`, matching the Zod face.

**⛔ No stored document moves.** The string `map` names two different things at
two different layers, and only one of them is retiring:

| layer | value | who writes it | retired? |
| --- | --- | --- | --- |
| stored `NamedListView.type` / `defaultViewType` | `"map"` | `CreateViewDialog`, persisted per tenant | **no — untouched** |
| node type key | `map`, `view:map` | hand-authored JSON | **yes** |

`ObjectView` and `ListView` map a stored `map` view onto the node type they
render, and both already emit `object-map`, so every map view created through
the console keeps rendering. Nothing in a tenant database changes, and ⛔ nothing
should be migrated there.

**What moved with it.**

- `@object-ui/console` drops its lazy `map` / `view:map` registration stub; the
lazy `object-map` stub is unchanged.
- `@object-ui/core`: `recordSourceDataArmForType` no longer lists the `map` and
`view:map` rows, since no block is registered under either key.
- `@object-ui/cli`: the generated known-type list that `objectui check` reads no
longer contains bare `map`, so the check now flags it. ⚠️ `view:map` stays on
that list, because the opt-in protocol placeholder (`registerPlaceholders()`
in `@object-ui/components`) registers it — in the console a `view:map` node
renders that placeholder panel, not a map, and `objectui check` does not flag
it. Search documents for `view:map` directly.

The `@object-ui/plugin-map` README now describes one registered type, and its
sentence claiming a bare array under `data` reaches the in-memory adapter is
corrected: a bare array under `data` is not a record source on the map
(objectui#8348), so inline rows belong under `staticData`. The same false claim
is corrected on the two docs pages that carried it, `plugins/plugin-map.mdx`
and `fields/location.mdx`.

**One pending entry in this same release is superseded in part.** The
objectui#10392 / #10394 entry (`10392-registration-record-source-inputs.md`)
says `object-map`, `map` and `object-gantt` gained `data` / `staticData`
inputs. It now reads as of its own change and carries a dated note naming this
card: `map` declares nothing after this retirement, while `object-map` and
`object-gantt` keep both inputs.
6 changes: 3 additions & 3 deletions .github/prompts/component.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ Responsible for rendering records. The specific `type` determines the Props cont
> ⚠️ **A `Keys` entry is a REGISTRY key; a `Required Types` entry is a spec `type` value. They are
> not the same vocabulary and they have diverged.** `{ "type": "kanban" }` inside a `ListView`
> config is spec-valid, but the board component is registered as `object-kanban` — the namespaced
> `view:kanban` and `view:gantt` spellings retired with the bare `kanban` / `gantt` registrations and
> now answer only the opt-in protocol PLACEHOLDER panel. A document naming one passes
> `view:kanban`, `view:gantt` and `view:map` spellings retired with the bare `kanban` / `gantt` / `map`
> registrations and now answer only the opt-in protocol PLACEHOLDER panel. A document naming one passes
> `objectui check` and then draws nothing. Write the key from the `Keys` bullet, and where a
> presentation is a config value rather than a component, write it as a prop. Enforced by
> `pnpm check:prompt-keys`.

#### 1. List Views (Collection)
* **Keys:** `view:grid`, `object-kanban`, `view:map`, `view:calendar`, `object-gantt`, etc.
* **Keys:** `view:grid`, `object-kanban`, `object-map`, `view:calendar`, `object-gantt`, etc.
* **Contract:** Must implement `ListViewComponentProps`.
```typescript
type ListViewComponentProps = {
Expand Down
9 changes: 5 additions & 4 deletions apps/console/src/register-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ ComponentRegistry.registerLazy('object-map', () => import('@object-ui/plugin-map
namespace: 'plugin-map',
category: 'view',
});
ComponentRegistry.registerLazy('map', () => import('@object-ui/plugin-map'), {
namespace: 'view',
category: 'view',
});
// ⛔ The bare `map` node type key is RETIRED (objectui#10393, executing the
// objectui#8008 family ruling of 2026-09-09) — `object-map` above is the
// surviving spelling. The STORED `NamedListView.type` value `map` is a
// different layer and is untouched: `ObjectView`'s `switch (viewType)` already
// emits `object-map` for it.

ComponentRegistry.registerLazy('object-tree', () => import('@object-ui/plugin-tree'), {
namespace: 'plugin-tree',
Expand Down
2 changes: 1 addition & 1 deletion content/docs/fields/location.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ the renderer:
}
```

**Note**: the map plots the records it fetches, so it takes an `objectName` (or an explicit `data` array) rather than a `bind` path, and it derives its markers from those records — a `markers` key on the node is not read. Every marker setting lives under the declared `map` input.
**Note**: the map plots the records it fetches, so it takes an `objectName` (or inline rows under `staticData`, or a `data: { provider: 'value', items }` configuration — a bare array under `data` is not a record source) rather than a `bind` path, and it derives its markers from those records — a `markers` key on the node is not read. Every marker setting lives under the declared `map` input.

## Validation

Expand Down
10 changes: 6 additions & 4 deletions content/docs/plugins/plugin-map.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,12 @@ const schema: ObjectMapSchema = {
```

`filter` and `sort` are not object-only keys (objectui#9061). They narrow and
order inline rows — `staticData`, a bare array under `data`, or
`data: { provider: 'value' }` — exactly as they narrow and order fetched ones,
and the platform row ceiling (2,000 plotted rows with a footnote naming both
numbers) applies to inline rows too. The ceiling is applied to the **filtered**
order inline rows — `staticData` or `data: { provider: 'value', items }` —
exactly as they narrow and order fetched ones. A bare array under `data` is not
a record source on the map (objectui#8348): the ladder falls through to
`staticData`, then `objectName`, so inline rows belong under `staticData`. The
platform row ceiling (2,000 plotted rows with a footnote naming both numbers)
applies to inline rows too. The ceiling is applied to the **filtered**
set, so a large inline array that a `filter` cuts below the ceiling plots every
matching row and shows no footnote. Inline rows reach the map as the in-memory
adapter's own deep copy, so they must be JSON-serializable and a record handed
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/utils/known-schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ export const KNOWN_SCHEMA_TYPES: readonly string[] = [
'location',
'lookup',
'main',
'map',
'mark',
'markdown',
'marketplace:installed-list',
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/utils/record-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ export function resolveRecordSourceObjectName(
* a block the ruling does not decide, and it is reported rather than guessed.
*
* The arm is passed BY THE CALL SITE rather than looked up from `schema.type`
* on purpose. Every one of these renderers is registered twice — `object-grid`
* on purpose. Most of these renderers are registered twice — `object-grid`
* and the `view:grid` alias `grid`, `object-calendar` and `calendar`, and so on
* (the bare `gantt` and `map` keys are retired, objectui#8008 / objectui#10393)
* — so a node reaches the same component under either spelling, and a table
* keyed by `type` would answer for one tag and silently miss the other. A
* REQUIRED parameter makes the arm a compile-time obligation at each of the
Expand Down Expand Up @@ -317,8 +318,8 @@ export function resolveRecordSourceConfig<Arm extends RecordSourceDataArm>(
* ## Why a type-keyed table exists beside the REQUIRED parameter
*
* {@link RecordSourceDataArm}'s docblock states, correctly, why
* {@link resolveRecordSourceConfig} takes the arm as a required PARAMETER: each
* of these renderers is registered under two spellings, a node reaches the same
* {@link resolveRecordSourceConfig} takes the arm as a required PARAMETER: most
* of these renderers are registered under two spellings, a node reaches the same
* component under either, and a table keyed by `type` would answer for one tag
* and silently miss the other. That argument is about a call site that already
* has one block in front of it — there a parameter is strictly better, and it
Expand All @@ -345,7 +346,7 @@ export function resolveRecordSourceConfig<Arm extends RecordSourceDataArm>(
* ⚠️ ONE `register()` CALL PRODUCES SEVERAL KEYS, and a row must be written for
* each of them — `SchemaRenderer` looks this table up with the raw
* `schema.type`, which is whatever spelling the author wrote. A registration
* with `namespace: 'view'` is reachable as `view:map` AND as bare `map`; one
* with `namespace: 'view'` is reachable as `view:calendar` AND as bare `calendar`; one
* with `skipFallback` is reachable ONLY under its namespaced key. MEASURED via
* `ComponentRegistry.getAllTypes()` with the five plugins loaded, grouped by
* the renderer each key resolves to.
Expand All @@ -366,7 +367,9 @@ export function resolveRecordSourceConfig<Arm extends RecordSourceDataArm>(
* it does not compute one.
*
* - `ObjectGrid.tsx`'s `getDataConfig` — `'view-data'`.
* - `ObjectMap.tsx`'s `getDataConfig` — `'view-data'`.
* - `ObjectMap.tsx`'s `getDataConfig` — `'view-data'`. One block spelling
* only: the bare `map` key (and with it `view:map`) is retired
* (objectui#10393).
* - `ObjectGantt.tsx`'s `rawDataConfig` — `'view-data'`. One block spelling
* only: the bare `gantt` key is retired (objectui#8008).
* - `ObjectCalendar.tsx`'s `dataConfig` — `'array'`.
Expand All @@ -383,8 +386,6 @@ const RECORD_SOURCE_DATA_ARM_BY_TYPE: Readonly<Record<string, RecordSourceDataAr
'view:grid': 'view-data',
'object-map': 'view-data',
'plugin-map:object-map': 'view-data',
'view:map': 'view-data',
map: 'view-data',
'object-gantt': 'view-data',
'plugin-gantt:object-gantt': 'view-data',
// — array arm: `z.array(...)`, pre-fetched records —
Expand Down
39 changes: 25 additions & 14 deletions packages/plugin-map/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@ marker comes from a record's own coordinate fields, and the first paint frames t
records that were fetched. It is a *view over data* — there is no authored marker
list, and no pin you place by hand.

Importing the package registers two component types on the `ComponentRegistry`,
both resolving to the same renderer:
Importing the package registers one component type on the `ComponentRegistry`:
`object-map`, the object-bound renderer. Inside an `ObjectView`, a stored `map`
view is compiled to an `object-map` node, so a saved map view ends at the same
component.

- `object-map` — the object-bound renderer
- `map` — the bare spec view-type name (`ViewTypeSchema`'s `'map'`), for a node
authored with it directly. Inside an `ObjectView`, a `map` view is compiled to
an `object-map` node, so both spellings end at the same component.
> **The bare `map` node type key is retired** (objectui#10393, following the
> objectui#8008 ruling that retired `gantt`). The registry used to accept a node
> authored `"type": "map"` (and its namespaced twin `view:map`) while the
> published declaration refused it — `ObjectMapSchema.type` is the literal
> `'object-map'` and no schema arm names `map` — so a validated document could not
> use the key the registry took. Write `"type": "object-map"`.
>
> ⚠️ The **stored view type** `"map"` — what a saved `listViews[].type` or
> `defaultViewType` holds — is a **different layer and is unchanged**. Do not
> rewrite it: `ObjectView` maps a stored `map` view onto the `object-map` node
> type, so no saved view moves.

## Installation

Expand Down Expand Up @@ -88,12 +97,14 @@ is honoured as well.

**The provider does not change which query keys apply** (objectui#9061, the port
of objectui#8769). An authored `filter` and `sort` narrow and order the rows on
**every** provider, inline ones included — `staticData`, a bare array under
`data`, and `data: { provider: 'value', items }` all reach the same in-memory
adapter the other providers go through, so `filter` is evaluated with the same
matcher. Before objectui#9061 the inline provider skipped that query and plotted
every authored row with an authored `filter` silently dropped. The platform row
ceiling (2,000 drawn rows, with a footnote naming both numbers — objectui#7210,
**every** provider, inline ones included — `staticData` and
`data: { provider: 'value', items }` both reach the same in-memory adapter the
other providers go through, so `filter` is evaluated with the same matcher. A
bare array under `data` is not a record source on this map (objectui#8348): the
ladder falls through to `staticData`, then `objectName`, so inline rows belong
under `staticData`. Before objectui#9061 the inline provider skipped that query
and plotted every authored row with an authored `filter` silently dropped. The
platform row ceiling (2,000 drawn rows, with a footnote naming both numbers — objectui#7210,
ruling a′) applies to inline rows too, and it is applied to the **filtered** set,
never to the raw one: a large inline array that a `filter` cuts below the ceiling
plots every matching row and shows no footnote.
Expand All @@ -113,7 +124,7 @@ The declared configuration input. Every key is optional:
| `latitudeField` | Record field holding the latitude. Needs `longitudeField` alongside it; both values must be numbers. |
| `longitudeField` | Record field holding the longitude. |
| `locationField` | Single field holding both coordinates — see the formats below. Used when the lat/lng pair yields nothing. |
| `titleField` | Field shown as the marker title. Omitted, the title is resolved by the object's own record-title precedence (`@object-ui/core`'s `getRecordDisplayName`, ADR-0079): the declared `nameField`, its deprecated `displayNameField` alias, the legacy `titleFormat` template, a type-aware pick from the object's fields, then name-ish keys read straight off the record — the rung that answers when no object definition reached the view, as `staticData` and an inline `data` array never fetch one. `Record #<id>` is the floor; `Marker` is reached only by a record carrying no id at all. |
| `titleField` | Field shown as the marker title. Omitted, the title is resolved by the object's own record-title precedence (`@object-ui/core`'s `getRecordDisplayName`, ADR-0079): the declared `nameField`, its deprecated `displayNameField` alias, the legacy `titleFormat` template, a type-aware pick from the object's fields, then name-ish keys read straight off the record — the rung that answers when no object definition reached the view, as `staticData` and an inline `data: { provider: 'value', items }` configuration never fetch one. `Record #<id>` is the floor; `Marker` is reached only by a record carrying no id at all. |
| `descriptionField` | Field shown under the title in the marker popup. |
| `zoom` | Zoom level. Declaring it opts this view out of the auto-fit (see below). |
| `center` | `[latitude, longitude]` — a two-number **tuple**, latitude first. Declaring it opts this view out of the auto-fit. |
Expand Down Expand Up @@ -212,7 +223,7 @@ declare const dataSource: ObjectMapProps['dataSource'];
| Prop | Description |
| --- | --- |
| `schema` | The map schema — the keys above. |
| `dataSource` | Resolves the `object` provider. Not needed for `staticData` or an inline `data` array. |
| `dataSource` | Resolves the `object` provider. Not needed for `staticData` or an inline `data: { provider: 'value', items }` configuration. |
| `className` | Classes for the wrapper around the map. |
| `data` | Records to render directly, bypassing the component's own fetch — the shape `ListView` passes when it already holds the rows. Tracked live: passing a new array after mount (e.g. once a host's own in-flight query resolves) updates the map. |
| `onMarkerClick` | Called with the clicked record. |
Expand Down
11 changes: 5 additions & 6 deletions packages/plugin-map/src/ObjectMap.schemaDataShorthand.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import { describe, it, expect, vi } from 'vitest';
import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react';
import { ComponentRegistry, recordSourceDataArmForType } from '@object-ui/core';
import { ObjectMap } from './ObjectMap';
// Registers `object-map` and its `view:map` alias — row 3 renders through it.
// Registers `object-map` — row 3 renders through it.
import './index';
import type { DataSource } from '@object-ui/types';

Expand Down Expand Up @@ -190,15 +190,14 @@ describe('ObjectMap — the bare-array `schema.data` shorthand is retired (objec
// namespaced key and a bare one, and `SchemaRenderer` looks the arm up with
// the raw `schema.type`. A key added without a row in
// `recordSourceDataArmForType` turns this red instead of silently answering
// `'undeclared'` and keeping the prop seat. Unlike `plugin-grid`, this
// plugin claims the bare `map` key too — no `skipFallback` here.
// `'undeclared'` and keeping the prop seat. The bare `map` key and its
// `view:map` twin are retired (objectui#10393), so the group is exactly the
// one registration's two keys.
const siblings = ComponentRegistry.getAllTypes().filter(
(type) => ComponentRegistry.get(type) === ComponentRegistry.get('object-map'),
);

expect(siblings).toEqual(
expect.arrayContaining(['object-map', 'plugin-map:object-map', 'view:map', 'map']),
);
expect([...siblings].sort()).toEqual(['object-map', 'plugin-map:object-map']);
for (const type of siblings) {
expect([type, recordSourceDataArmForType(type)]).toEqual([type, 'view-data']);
}
Expand Down
Loading
Loading