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
144 changes: 144 additions & 0 deletions .changeset/8802-8257-8008-kanban-gantt-family-retirement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
'@object-ui/types': minor
'@object-ui/plugin-kanban': minor
'@object-ui/plugin-gantt': minor
'@object-ui/components': patch
'@object-ui/cli': patch
'@object-ui/console': patch
'@object-ui/runner': patch
'@object-ui/sdui-parser': minor
---

Four node type keys retire, and the kanban and gantt families converge on their
`object-*` spellings: `kanban` (objectui#8802), `kanban-ui` and `kanban-enhanced`
(objectui#8257), and `gantt` (objectui#8008). All four were ruled by the
maintainer in one batch on 2026-09-09.

**⛔ No stored document moves.** The strings `kanban` and `gantt` name two
different things at two different layers, and only one of them is retiring:

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

`ObjectView`'s `switch (viewType)` maps a stored view type onto the node type it
renders, and it already emitted `object-kanban` and `object-gantt` — as it does
for all twelve stored view types. So every kanban and gantt view any user ever
created through the console already renders through the surviving spelling.
Nothing in a tenant database changes, and ⛔ nothing should be migrated there.

**What each retirement was, measured.** Three of the four were
registration-only: no schema face in `@object-ui/types` ever declared
`kanban-ui`, `kanban-enhanced` or `gantt` as a component node type, so
unregistering is the whole retirement. The bare `kanban` key was the exception —
it had a declared arm on both faces (`KanbanSchema` in `complex.ts` and its Zod
mirror), and a plain deletion there would have been the objectui#7664 failure:
`BaseSchema` is `.passthrough()`, so a document naming a dropped key validates
green and renders nothing. It therefore retires as a **named refusal**: the Zod
union keeps an arm claiming the literal and answers a `{ "type": "kanban" }`
document with a message naming `object-kanban` as the remedy, while the
TypeScript half is the absence of the arm from `ComplexSchema` and of the key
from `SchemaRegistry`, so `tsc` refuses it at the authoring site.

**⭐ This closes objectui#8818's `objectFields` hole — for that ENTRY, not for
the class.** `SchemaRenderer` strips a fixed enumerated metadata list and
spreads the rest as React props; `objectFields` is not on that list, and
`KanbanRenderer` — the component the `kanban-ui` key resolved to — declares
`objectFields` as a real prop, so an authored value reached the predicate layer
with no schema face judging it. With the registration gone, no authored node
reaches that component through the registry. ⚠️ The **class** is still open: the
hole returns the moment another registered renderer declares an `objectFields`
prop. objectui#8818's option (a) — stripping at the `SchemaRenderer` boundary —
is what would close the class.

**⚠️ What the `kanban` arm took with it, stated because it is the cost of this
change.** That arm was the only schema face that ever declared `columns`,
`cardTitle`, `swimlaneField`, `grouping` and `navigation`, the only one that
refused `allowCollapse` / `cardTemplates` / `columnWidths` / `titleField` /
`draggable` / `onColumnAdd` / `onCardAdd` by name, and — through
`columns: KanbanColumn[]` — the only one that judged a lane's `cards`
(objectui#6939). The surviving `ObjectKanbanSchema` face declares none of them.
⛔ Nothing about an `object-kanban` document changes: it was never judged by the
`kanban` arm, so all of those keys have always ridden `BaseSchema`'s index
signature there. What is gone is the `kanban` document that had them. Declaring
them on `ObjectKanbanSchema` would WIDEN a published accept set, which is a
maintainer ruling and not part of this one; every one of these readings is
pinned where it can be seen rather than left to be rediscovered.

**Migrating.** Replace `"type": "kanban"` with `"type": "object-kanban"` and
`"type": "gantt"` with `"type": "object-gantt"` in hand-authored documents. The
`object-kanban` face requires `groupBy` and one of `bind` / `data` /
`objectName`; a purely static board (lanes carrying their own cards, no record
source) adds `"groupBy"` and `"data": []`. `kanban-ui` and `kanban-enhanced`
have no authored documents anywhere in this repository to migrate.

**⚠️ The namespaced spellings retire with the registrations — `view:kanban` and
`view:gantt` are the same two keys.** `ComponentRegistry.register(type, C,
{ namespace })` stores BOTH `namespace:type` and a bare-`type` fallback, so
every one of these keys had a namespaced twin that goes with it:

| retired spelling | namespaced twin | author instead |
| --- | --- | --- |
| `kanban` | `view:kanban` | `object-kanban` |
| `gantt` | `view:gantt` | `object-gantt` |
| `kanban-ui` | `plugin-kanban:kanban-ui` | `object-kanban` |
| `kanban-enhanced` | `plugin-kanban:kanban-enhanced` | `object-kanban` |

Both spellings are pinned as gone, each against a firing control on the
surviving key, in `plugin-kanban/src/__tests__/kanban-family-registry-keys-retired-8257.test.ts`
and `plugin-gantt/src/__tests__/bare-gantt-node-key-retired-8008.test.ts`.

**What an unmigrated `view:kanban` / `view:gantt` node now renders depends on
the host.** In `apps/console` it renders the protocol **placeholder** panel, not
the OBJUI-001 "Unknown component type" error: the console calls the opt-in
`registerPlaceholders()` (`@object-ui/components`, `renderers/placeholders.tsx`)
*after* its plugin registrations, `view:kanban` and `view:gantt` are both in
that file's `PROTOCOL_COMPONENTS` list, and the placeholder only claims a key
nothing else has taken — which, until this change, `@object-ui/plugin-kanban`
and `@object-ui/plugin-gantt` had. In every other host, which does not call that
bootstrap, the same node renders OBJUI-001.

**⚠️ `objectui check` will NOT flag either namespaced spelling.** The CLI's
`known-schema-types.ts` is generated from the repository's real registration
calls, and the placeholder registration is a real one — so `view:kanban` and
`view:gantt` are still on that list and still validate green, while the node
renders a placeholder rather than a board. The bare `kanban` / `gantt` entries
DID leave the generated list; only the namespaced pair survives, and only
because of the placeholder. Grep your documents for the namespaced spellings
directly; do not rely on `objectui check` to find them.

`KanbanRenderer` is still exported from this package's entry point
(`@object-ui/plugin-kanban`); only its registry key is gone. ⚠️ `KanbanEnhanced`
is a different case, and the earlier draft of this note stated it wrongly: this
package's `exports` map has exactly two entries — `.` and `./style.css` — and
the barrel never re-exported the component, so
`@object-ui/plugin-kanban/KanbanEnhanced` has never been a resolvable specifier
for a consumer. With `kanban-enhanced` unregistered, `KanbanEnhanced.tsx` has
zero non-test importers. ⛔ The file is deliberately left in place: deleting
published-but-unreachable source is a further narrowing and needs its own
maintainer ruling, which this change does not have.

**⚠️ `@object-ui/sdui-parser`: `QUICK_ADD_HOST_TYPES` loses `kanban` with the
registration.** The `inert-quick-add` diagnostic (objectui#8285) named the two
tags `ObjectKanbanRenderer` answered to; one of them retires here, so the set is
now `{ 'object-kanban' }`. ⛔ Nothing is silently dropped by that narrowing, and
this is measured rather than argued: `checkKanbanQuickAdd` has exactly one call
site — `validate.ts`'s per-prop walk — and that walk runs only in the branch
where the manifest RESOLVED the tag. A tag no registration produces is answered
one level up by `unknown-component`, an **error**, and its props are never
walked, so on a manifest built from the live registry a `<kanban quickAdd>` node
draws `error/unknown-component` and nothing else, against a firing control on
`<object-kanban quickAdd>` that still draws `warning/inert-quick-add`. Keeping
`kanban` in the set would have been reachable only through a hand-built manifest
declaring a component of that name — which, after this retirement, is somebody
else's component, and the message asserts things about `ObjectKanban` that would
be false of it. This supersedes the `kanban` half of the objectui#8285 entry.

**The diagnostic's remedy text moves from a tag to a component.** It used to end
"render `<kanban-ui>` from a React host that passes `onQuickAdd`". That sentence
is falsified by this change: `kanban-ui` is no longer a node type key, so a page
written to the old advice draws `unknown-component`. It now names
`KanbanRenderer` from `@object-ui/plugin-kanban` — still exported, still
forwarding both halves by identity — which is the surviving way to get the pair.
`content/docs/plugins/plugin-kanban.mdx` says the same thing the same way.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export default App

```json
{
"type": "kanban",
"type": "object-kanban",
"objectName": "tasks",
"groupBy": "status",
"titleField": "title",
Expand Down
24 changes: 10 additions & 14 deletions apps/console/src/register-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@ ComponentRegistry.registerLazy('object-gantt', () => import('@object-ui/plugin-g
namespace: 'plugin-gantt',
category: 'view',
});
ComponentRegistry.registerLazy('gantt', () => import('@object-ui/plugin-gantt'), {
namespace: 'view',
category: 'view',
});
// ⛔ The bare `gantt` node type key is RETIRED (objectui#8008, maintainer
// ruling 2026-09-09, route 3) — `object-gantt` above is the surviving spelling.
// The STORED `NamedListView.type` value `gantt` is a different layer and is
// untouched: `ObjectView`'s `switch (viewType)` already emits `object-gantt`
// for it.

ComponentRegistry.registerLazy('markdown', () => import('@object-ui/plugin-markdown'), {
namespace: 'plugin-markdown',
Expand Down Expand Up @@ -121,16 +122,11 @@ ComponentRegistry.registerLazy('object-kanban', () => import('@object-ui/plugin-
namespace: 'plugin-kanban',
category: 'view',
});
ComponentRegistry.registerLazy('kanban', () => import('@object-ui/plugin-kanban'), {
namespace: 'view',
category: 'view',
});
for (const variant of ['kanban-ui', 'kanban-enhanced']) {
ComponentRegistry.registerLazy(variant, () => import('@object-ui/plugin-kanban'), {
namespace: 'plugin-kanban',
category: 'view',
});
}
// ⛔ The bare `kanban` key (objectui#8802) and the `kanban-ui` /
// `kanban-enhanced` variants (objectui#8257) are RETIRED — maintainer rulings
// 2026-09-09. `object-kanban` above is the surviving spelling. The STORED
// `NamedListView.type` value `kanban` is a different layer and is untouched:
// `ObjectView`'s `switch (viewType)` already emits `object-kanban` for it.

ComponentRegistry.registerLazy('report', () => import('@object-ui/plugin-report'), {
namespace: 'plugin-report',
Expand Down
77 changes: 21 additions & 56 deletions content/docs/api/schema-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -910,79 +910,43 @@ A complete object management interface combining grid, form, search, filters, an

## Complex Schemas

### KanbanSchema
### ObjectKanbanSchema

A drag-and-drop Kanban board. The `kanban` type key validates the shape the registered renderer (`@object-ui/plugin-kanban`) reads: bind the board to an object with `objectName` + `groupBy` (the lanes come from the group field's options), or author it statically with `columns`, each carrying its `cards`.
A drag-and-drop Kanban board. The `object-kanban` type key validates the shape the registered renderer (`@object-ui/plugin-kanban`) reads: bind the board to an object with `objectName` + `groupBy` — the lanes come from the group field's options — or hand it rows on `data`.

> **The bare `kanban` node type key is retired** (objectui#8802, ruled 2026-09-09). It published two faces that disagreed with each other, and `object-kanban` is now the one spelling. A `{ "type": "kanban" }` document is refused by name and told to write `object-kanban`.
>
> ⚠️ The **stored view type** `"kanban"` — what `listViews[].type` and `defaultViewType` hold — is a **different layer and is unchanged**. Do not rewrite it: a saved kanban view already renders through the `object-kanban` node type, because `ObjectView` maps the stored type onto it.

```json
{
"type": "kanban",
"type": "object-kanban",
"objectName": "tasks",
"groupBy": "status",
"cardTitle": "title",
"titleField": "title",
"cardFields": ["assignee", "due_date"],
"quickAdd": true
}
```

A static board carries its cards inline:

```json
{
"type": "kanban",
"columns": [
{
"id": "todo",
"title": "To Do",
"cards": [
{
"id": "task-1",
"title": "Design mockups",
"description": "Create wireframes for new feature",
"badges": [{ "label": "High", "variant": "destructive" }]
},
{ "id": "task-2", "title": "Write tests", "description": "Unit tests for auth module" }
]
},
{
"id": "in-progress",
"title": "In Progress",
"limit": 3,
"cards": [
{ "id": "task-3", "title": "API integration", "description": "Connect to payment gateway" }
]
},
{
"id": "done",
"title": "Done",
"cards": []
}
]
}
```

| Property | Type | Description |
|----------|------|-------------|
| `objectName` | `string` | Object to fetch records from. |
| `groupBy` | `string` | Field whose values become the lanes (maps to column ids). |
| `swimlaneField` | `string` | Field for swimlane rows (2D grouping). |
| `cardTitle` | `string` | Field used as the card title. |
| `groupBy` | `string` | **Required.** Field whose values become the lanes (maps to column ids). |
| `titleField` | `string` | Field used as the card title. |
| `cardFields` | `string[]` | Fields rendered on each card. |
| `data` | `any[]` | Inline records, bucketed into lanes by `groupBy`. |
| `filter` | `any[]` | Query filter, forwarded verbatim as `$filter`. |
| `limit` | `number` | Fetch window for the board (default 100). |
| `columns` | `KanbanColumn[]` | Lanes, each with `id`, `title`, `cards`, and optional `limit` / `className` / `collapsed`. A card has `id`, `title`, optional `description` and `badges`. |
| `quickAdd` | `boolean` | Show a Quick Add button at the bottom of each column. |
| `coverImageField` | `string` | Field whose URL renders as the card cover image. |
| `allowCollapse` | `boolean` | Allow lanes to collapse and expand. |
| `conditionalFormatting` | `KanbanConditionalFormattingRule[]` | Card colouring rules — native `{ field, operator, value }` or spec `{ condition, style }`. |
| `grouping` | `GroupingConfig` | ListView grouping config; its first field is the swimlane fallback. |
| `navigation` | `ViewNavigationConfig` | Record navigation behaviour when a card is clicked (drawer / dialog / page). Defaults to an inline right-side drawer. |
| `onCardMove` | `function` | Runtime slot supplied by a React host, `(cardId, fromColumnId, toColumnId, newIndex)`; not authorable in JSON. |
| `onCardClick` | `function` | Runtime slot supplied by a React host, `(card, event?)`; not authorable in JSON. On the object-bound board the host's handler runs alongside the record-detail overlay. |
| `onQuickAdd` | `function` | Runtime slot supplied by a React host, `(columnId, title)`; not authorable in JSON. |

> Four spellings the `kanban` arm once accepted are now refused by name (objectui#7742, ADR-0049). `allowCollapse`, `cardTemplates` and `columnWidths` were declared and read by no registered board — collapse a lane with `columns[].collapsed`; card templates and column widths reach the board through a component prop and a hook option, not through the node. `titleField` is the legacy spelling of `cardTitle` and is retired on this arm only: write `cardTitle`. An `object-kanban` node still accepts `titleField`.
> `groupField` is refused by name (objectui#7322): the renderer reads `groupBy`.

> The retired `kanban` arm declared `columns`, `cardTitle`, `swimlaneField`, `grouping` and `navigation`; the `object-kanban` face never did, and it is unchanged. The renderer still reads those keys, so a board may carry them — they are simply not judged. The board's React host supplies `onCardMove` / `onCardClick` / `onQuickAdd` as props; none of the three is authorable in JSON.

> The former `@object-ui/types` kanban dialect — `DeclarativeKanbanSchema`, with a board-level `draggable`, a column `color` and card `labels` / `priority` — was retired in objectui#7664: no registered renderer read it, so a board written that way validated and rendered empty. `draggable` and a column `color` are now refused by name; a static board written with `columns[].cards[]` as above is the same document in both dialects and renders every card.
> `data` and `bind` are [`BaseSchema`](#baseschema) members, not narrowed here, but this face requires **one of** `bind`, `data`, `objectName` — the renderer's own record-source ladder (an external `data` prop → `bind` via `useDataScope` → this schema's own `data` → a fetch keyed by `objectName`). A purely static board (lanes carrying their own cards, no record source) authors `"groupBy"` and `"data": []`.

**Related:** [ObjectViewSchema](#objectviewschema), [ObjectGridSchema](#objectgridschema)

Expand Down Expand Up @@ -1237,8 +1201,9 @@ A toggle control that switches between different view types (list, grid, kanban,
"label": "Kanban",
"icon": "Kanban",
"schema": {
"type": "kanban",
"columns": []
"type": "object-kanban",
"objectName": "tasks",
"groupBy": "status"
}
}
]
Expand All @@ -1256,7 +1221,7 @@ A toggle control that switches between different view types (list, grid, kanban,
| `storageKey` | `string` | Storage key for persisting the preference. |
| `onViewChange` | `string` | Expression or callback invoked on view change. |

**Related:** [ObjectViewSchema](#objectviewschema), [KanbanSchema](#kanbanschema), [CalendarViewSchema](#calendarviewschema)
**Related:** [ObjectViewSchema](#objectviewschema), [ObjectKanbanSchema](#objectkanbanschema), [CalendarViewSchema](#calendarviewschema)

---

Expand Down Expand Up @@ -1331,7 +1296,7 @@ import type { ActionSchema, DetailSchema } from '@object-ui/types';
import type { ObjectGridSchema, ObjectFormSchema, ObjectViewSchema } from '@object-ui/types';

// Complex
import type { KanbanSchema, DashboardComponentSchema, CalendarViewSchema } from '@object-ui/types';
import type { DashboardComponentSchema, CalendarViewSchema } from '@object-ui/types';

// Views
import type { DetailViewSchema, ViewSwitcherSchema } from '@object-ui/types';
Expand Down
10 changes: 5 additions & 5 deletions content/docs/guide/architecture-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ Plugins are self-contained packages that register heavy or complex views (grids,
PluginScope.registerComponent(type, Component, meta)
│ └─ Auto-prefixes with plugin namespace
ComponentRegistry.register('kanban-ui', KanbanRenderer, {
ComponentRegistry.register('object-kanban', ObjectKanbanRenderer, {
namespace: 'plugin-kanban',
category: 'plugin'
category: 'view'
})
```

Expand Down Expand Up @@ -164,10 +164,10 @@ const KanbanRenderer: React.FC<Props> = ({ schema }) => (
);

// 3. Register in the global registry
ComponentRegistry.register('kanban-ui', KanbanRenderer, {
ComponentRegistry.register('object-kanban', KanbanRenderer, {
namespace: 'plugin-kanban',
label: 'Kanban Board',
category: 'plugin',
label: 'Object Kanban',
category: 'view',
inputs: [/* schema config */]
});
```
Expand Down
2 changes: 1 addition & 1 deletion content/docs/guide/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This guide covers the most common issues you may encounter when working with Obj

## 1. "Component type X not found"

**Symptom:** The `SchemaRenderer` renders nothing or shows a fallback, and the console logs `component type "kanban-ui" not found in registry`.
**Symptom:** The `SchemaRenderer` renders nothing or shows a fallback, and the console logs `component type "object-kanban" not found in registry`.

**Cause:** The plugin that provides the component type has not been imported, so it never registered itself with the `ComponentRegistry`.

Expand Down
Loading
Loading