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
12 changes: 12 additions & 0 deletions .changeset/27130-gantt-api-source-not-taught.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@object-ui/plugin-gantt': patch
---

docs(plugin-gantt): the `object-gantt` registration no longer teaches the retired view-level `api` source

Clause-②: no
Metadata-protocol: none

- **Before.** The `data` input said `{ provider: 'api', read }` "reads through that request", and the `filter` input listed "an `api` request" among the record sources the gantt reads. The spec refuses that provider by name and the gantt has no reader for it: a gantt carrying it requests neither URL.
- **Now.** `data` names the two providers the gantt reads, `{ provider: 'value', items }` and `{ provider: 'object', object }`, and says that rows a server computes reach the gantt as an object whose plugin driver computes them, never as a request URL in the view. `filter` names the object and inline rows.
- **Unchanged.** Text only: no input, schema, renderer behaviour or export moves. The page element's `dataSource: { provider: 'api', read, path? }` binding is a different door and is untouched.
4 changes: 2 additions & 2 deletions content/docs/ui/components/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ Namespace `plugin-gantt`.
| :--- | :--- | :--- | :--- |
| `objectName` | `string` | | ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-gantt` schema refuses a block that declares none of them, unless the node's `dataSource.object` names the object, which then lands on this key. |
| `gantt` | `object` | | Gantt-timeline configuration, the author face — the same block `ListViewSchema.gantt` declares, and the one the renderer validates this node against. Taken WHOLE when present: the flat top-level spelling beside it is ignored. The spec requires `startDateField`, `endDateField` and `titleField`, which name the record fields a task's bar starts at, ends at and is titled by; the other members (`progressField`, `colorField`, `dependenciesField`, `parentField`, `viewMode`, `exportFileName`, …) are optional. |
| `data` | `object` | | A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a gantt carrying one never reaches `staticData` and never queries `objectName`. `{ provider: 'value', items }` charts those rows, `{ provider: 'object', object }` queries that object and `{ provider: 'api', read }` reads through that request. A bare array is not this key’s shape and is not a record source: the gantt falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`. |
| `data` | `object` | | A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a gantt carrying one never reaches `staticData`. The gantt reads two providers: `{ provider: 'value', items }` charts those rows and never queries `objectName`, and `{ provider: 'object', object }` queries that object in place of `objectName`. Rows a server computes reach the gantt as an object whose plugin driver computes them, never as a request URL in the view. A bare array is not this key’s shape and is not a record source: the gantt falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`. |
| `staticData` | `array` | | Inline records, read SECOND on the record-source ladder: a `data` configuration wins and this key is then never reached, while `objectName` is read AFTER it, so a gantt carrying both charts these rows and never queries that object. `filter` and `sort` narrow and order these rows exactly as they do fetched ones. |
| `filter` | `array` | | Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object, an `api` request, or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape. |
| `filter` | `array` | | Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape. |
| `sort` | `array` | | Task order in `[{ field, order }]` form, ordering the rows the gantt charts. Lowered to `$orderby` on the same query. |
| `navigation` | `object` | | What a task click opens — the `{ mode, size, openNewTab, preventNavigation }` block a list view declares. With the key ABSENT a click opens the task's record in a drawer, this renderer's own default. `mode` is `drawer` (the overlay), `new_window`, `page` or `none`, and a block written without `mode` takes the spec's `drawer` default. `drawer` opens the task's record in the drawer; `none` opens nothing. `page` opens the task's record page in the same tab and `new_window` opens it in a new tab. The gantt builds that address itself from the page it is on and does not use a record navigator the host publishes, so a host that publishes one changes nothing: on the object's own list or view route (`…/OBJECT/view/VIEW`) the address is the record page `…/OBJECT/record/ID`, while on any other page, such as a custom page, `/OBJECT/record/ID` is appended to the current address. The object is `data.object` when `data` is the object provider, else `objectName`; on inline rows that name neither there is no record to open, and every mode, the default drawer included, opens nothing. `preventNavigation: true` opens nothing whatever the mode, `openNewTab: true` opens the record page in a new tab and outranks every mode except `none`, and `size` sets the overlay width. |
| `label` | `string` \| `object` | | Gantt label — the second link of the exported PNG/PDF file-name chain, after `gantt.exportFileName` and before the bound object's own label. A string is used as written, and an inline locale map (`{ en, "zh-CN", … }`) gives the entry for the display locale. The chart draws it nowhere else. |
Expand Down
2 changes: 1 addition & 1 deletion scripts/sdui-manifest.record.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"source": "built-tree",
"generator": "scripts/gen-sdui-manifest-node.mjs",
"generatedAt": "2026-10-29",
"sha256": "2ec5933ffaca5ab9cd1767f6d53c0e7eddb3880627c00c3275f71ed03253f3b8",
"sha256": "776bef14f20193239ad50cb76769e3dc366456005e907593041ebb5266c9f444",
"components": 106,
"inTree": {
"modulesRoot": "ui/apps/console",
Expand Down
4 changes: 2 additions & 2 deletions sdui.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@
{
"name": "data",
"type": "object",
"description": "A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a gantt carrying one never reaches `staticData` and never queries `objectName`. `{ provider: 'value', items }` charts those rows, `{ provider: 'object', object }` queries that object and `{ provider: 'api', read }` reads through that request. A bare array is not this key’s shape and is not a record source: the gantt falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`."
"description": "A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a gantt carrying one never reaches `staticData`. The gantt reads two providers: `{ provider: 'value', items }` charts those rows and never queries `objectName`, and `{ provider: 'object', object }` queries that object in place of `objectName`. Rows a server computes reach the gantt as an object whose plugin driver computes them, never as a request URL in the view. A bare array is not this key’s shape and is not a record source: the gantt falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`."
},
{
"name": "staticData",
Expand All @@ -1014,7 +1014,7 @@
{
"name": "filter",
"type": "array",
"description": "Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object, an `api` request, or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape."
"description": "Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape."
},
{
"name": "sort",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* objectstack#27130 — the `object-gantt` registration teaches no view-level
* `api` record source.
*
* The view-level `api` data source is retired in `@objectstack/spec`
* (objectstack#23082): the spec refuses `data: { provider: 'api', … }` by name,
* and `resolveDataSource` has no `api` arm, so a gantt still carrying one never
* requests its `read` / `write` URLs (behaviour pinned by
* `recordSourceInputs-10394.test.tsx`, its "retired api block" row). The
* registration's `data` and `filter` descriptions still named it as a live
* record source, and that published text is what an author copied. Rows a
* server computes reach the gantt as an object behind a plugin driver.
*
* The pin reads the registered inputs, which feed `sdui.manifest.json` and the
* generated component reference, so one row covers all three.
*/

import { describe, it, expect } from 'vitest';
import { ComponentRegistry } from '@object-ui/core';
// Module scope, not a hook: this import IS the registration (AGENTS.md
// test-discipline section).
import '../index';

/** `provider: 'api'` in any quoting, or an `api` request/source named in code font. */
const TEACHES_API_SOURCE = /provider:\s*\\?['"]api\\?['"]|`api`/;

const descriptions = (): Array<[string, string]> =>
(((ComponentRegistry.getConfig('object-gantt', 'plugin-gantt') as any)?.inputs ?? []) as any[]).map(
(input) => [input.name, String(input.description ?? '')],
);

describe('objectstack#27130 — object-gantt teaches no retired api record source', () => {
it('control: the matcher fires on the text it replaced', () => {
expect("`{ provider: \\'api\\', read }` reads through that request").toMatch(TEACHES_API_SOURCE);
expect("`{ provider: 'api', read }` reads through that request").toMatch(TEACHES_API_SOURCE);
expect('the object, an `api` request, or inline rows').toMatch(TEACHES_API_SOURCE);
});

it('no input description names a provider: api record source', () => {
const rows = descriptions();
expect(rows.map(([name]) => name)).toEqual(expect.arrayContaining(['data', 'filter']));
expect(rows.filter(([, text]) => TEACHES_API_SOURCE.test(text)).map(([name]) => name)).toEqual([]);
});

it('positive control: data still names the object and value providers', () => {
const data = new Map(descriptions()).get('data') ?? '';
expect(data).toContain("`{ provider: 'object', object }`");
expect(data).toContain("`{ provider: 'value', items }`");
});
});
4 changes: 2 additions & 2 deletions ui/packages/plugin-gantt/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ ComponentRegistry.register('object-gantt', ObjectGanttRenderer, {
inputs: [
{ name: 'objectName', type: 'string', description: 'ObjectQL object name. The record source is one of `data`, `staticData` and `objectName`; the `object-gantt` schema refuses a block that declares none of them, unless the node\'s `dataSource.object` names the object, which then lands on this key.' },
{ name: 'gantt', type: 'object', description: 'Gantt-timeline configuration, the author face — the same block `ListViewSchema.gantt` declares, and the one the renderer validates this node against. Taken WHOLE when present: the flat top-level spelling beside it is ignored. The spec requires `startDateField`, `endDateField` and `titleField`, which name the record fields a task\'s bar starts at, ends at and is titled by; the other members (`progressField`, `colorField`, `dependenciesField`, `parentField`, `viewMode`, `exportFileName`, …) are optional.' },
{ name: 'data', type: 'object', description: 'A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a gantt carrying one never reaches `staticData` and never queries `objectName`. `{ provider: \'value\', items }` charts those rows, `{ provider: \'object\', object }` queries that object and `{ provider: \'api\', read }` reads through that request. A bare array is not this key’s shape and is not a record source: the gantt falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`.' },
{ name: 'data', type: 'object', description: 'A `{ provider, … }` data-source configuration, read FIRST on the record-source ladder: a gantt carrying one never reaches `staticData`. The gantt reads two providers: `{ provider: \'value\', items }` charts those rows and never queries `objectName`, and `{ provider: \'object\', object }` queries that object in place of `objectName`. Rows a server computes reach the gantt as an object whose plugin driver computes them, never as a request URL in the view. A bare array is not this key’s shape and is not a record source: the gantt falls through to `staticData`, then `objectName`, so inline rows belong under `staticData`.' },
{ name: 'staticData', type: 'array', description: 'Inline records, read SECOND on the record-source ladder: a `data` configuration wins and this key is then never reached, while `objectName` is read AFTER it, so a gantt carrying both charts these rows and never queries that object. `filter` and `sort` narrow and order these rows exactly as they do fetched ones.' },
{ name: 'filter', type: 'array', description: 'Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object, an `api` request, or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape.' },
{ name: 'filter', type: 'array', description: 'Base query filter in the rule-array form `[{ field, operator, value }, ...]`, narrowing the tasks the gantt charts — from every record source it reads: the object or inline rows. Context tokens such as `{current_user_id}` are resolved first, then the filter is lowered to `$filter` on the query. The MongoDB-style record form is not this key’s shape.' },
{ name: 'sort', type: 'array', description: 'Task order in `[{ field, order }]` form, ordering the rows the gantt charts. Lowered to `$orderby` on the same query.' },
{ name: 'navigation', type: 'object', description: OBJECT_GANTT_NAVIGATION_DESCRIPTION },
{ name: 'label', type: ['string', 'object'], description: 'Gantt label — the second link of the exported PNG/PDF file-name chain, after `gantt.exportFileName` and before the bound object\'s own label. A string is used as written, and an inline locale map (`{ en, "zh-CN", … }`) gives the entry for the display locale. The chart draws it nowhere else.' },
Expand Down
Loading