Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

Commit 1212e3e

Browse files
feat(types)!: nine duration keys carry their unit in their name, old spellings refused by name (objectstack#25762) (#13515)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao44byRSa5AQXv1uFcacVmaq)_ Part of objectstack#25762 (cutover prep E of the epic). Part of objectstack#25677. Clause-②: yes Metadata-protocol: added ## Why After objectui merges into objectstack as `ui/`, objectstack's spec gate `check:duration-unit-keys` walks `ui/packages/types/src/zod` too. It refuses a numeric key whose describe names a unit that the key name lacks. Nine keys here do that, so `timeout: 300` reads as either milliseconds or seconds. The maintainer chose to rename them (decision 17e = A, director thread 2026-10-25 09:06Z). Exempting `ui/` from the gate was the rejected option. The renames follow objectstack's own (objectstack#15679 / objectstack#15680): `…Ms` / `…Seconds`, the unit each describe already stated. ## The nine renames | Node / schema | FROM | TO | Unit (from the describe) | |---|---|---|---| | `carousel` (`CarouselSchema`) | `autoPlay` | `autoPlayMs` | ms | | `chatbot`, `chatbot-enhanced`, `chatbot-floating` | `autoResponseDelay` | `autoResponseDelayMs` | ms | | `ActionSchema` | `timeout` | `timeoutMs` | ms | | `ActionSchema.retry` | `delay` | `delayMs` | ms | | `toast` (`ToastSchema`) | `duration` | `durationMs` | ms | | `tooltip` (`TooltipSchema`) | `delayDuration` | `delayDurationMs` | ms | | `hover-card` (`HoverCardSchema`) | `openDelay` | `openDelayMs` | ms | | `hover-card` (`HoverCardSchema`) | `closeDelay` | `closeDelayMs` | ms | | `report` (`ReportComponentSchema`) | `refreshInterval` | `refreshIntervalSeconds` | seconds | Each name is the one the gate itself suggests in its finding line. ## Retirement mechanism objectui has no conversion registry. A grep for `retiredKey`, `UNKNOWN_KEY_GUIDANCE`, `spec-changes` and `conversions` under `packages/types` and `scripts` finds only comments that cite the spec's helper. So, as the card allows, the conversion is the refusal plus the changeset table: - **zod.** Each old key stays declared as a `retirementTombstone` (the objectui counterpart of the spec's `retiredKey`). The tombstone is fed by a new guidance builder, `durationKeyRenamedGuidance(retired, renamed, unit)` in `zod/tombstone.zod.ts`. An authored value is refused at the key's own path with code `invalid_type`, and the message names the new key: "RENAMED (objectstack#25762, ADR-0087) — `timeout` is now `timeoutMs` … the value (milliseconds) is unchanged." Without the tombstone, `.passthrough()` would keep the value unread. On the two chatbot twins, the tombstone is picked off `ChatbotSchema` the same way `maxToolRoundtrips` is. - **TypeScript.** Each old member is `?: never` with `@deprecated`, so a typed literal fails to compile. - **Changeset.** `.changeset/25762-duration-keys-carry-unit.md` declares `minor` on `@object-ui/types`, `components`, `plugin-chatbot` and `plugin-report`, and carries the FROM → TO table. ## Readers that follow the rename - `toast`, `tooltip` and `hover-card` renderers (`@object-ui/components`): the read, the registration `inputs` and `defaultProps`. - The three chatbot registrations (`@object-ui/plugin-chatbot`): the read, `inputs` and `defaultProps`. The `useObjectChat` hook option keeps the name `autoResponseDelay`. It is a programmatic API, not a schema key (the same pattern as `body: schema.requestBody`). - `ReportViewer` (`@object-ui/plugin-report`) reads `report.refreshIntervalSeconds`. - Docs snippets (`toast`, `tooltip`, `hover-card`, `plugin-chatbot`, `report-schema`, `enhanced-actions`), three schema-catalog examples, and the tests that authored the old keys. - New pin: `packages/types/src/__tests__/duration-unit-key-renames-25762.test.ts`. For each rename it checks that the new key accepts a number and refuses a string, and that the old key is refused at its own path naming the new key. It also has `@ts-expect-error` pins for every TypeScript twin. ## Verification Head `a63bbdf3fd`: the card's two commits (`117dea6` rename, `7396eb3` docs) plus a merge of objectui `main` `be64bf6`. That merge brought in #13512 and #13513 at the dispatcher's request, with no conflict. The only shared file was `content/docs/plugins/plugin-chatbot.mdx`, in a different section. The local runs below were on base `0063f22ce` (pin `43edf90b56`). CI on this head covers the merged base and the new pin. **Merged layout (the card's acceptance).** Verify head `9e37acd126`, plus `git diff 0063f22..117dea6` applied with `git apply --directory=ui` (the docs commit touches no `src/`), then `pnpm install --frozen-lockfile`, then `pnpm --filter @objectstack/spec check:duration-unit-keys`: | | before (verify head alone) | after (+ this diff) | |---|---|---| | exit | 1 | 0 | | `ui/` offenders | 9 (the nine keys above, `unit-in-prose-not-in-name`) | 0 | | objectstack-side offenders | 0 | 0 | | census | 210 unit-declaring keys / 4321 source files | 210 / 4321 | | self-test | 114 cases, 13 batteries pass | same | **objectui standalone.** | Gate | Exit | |---|---| | `type-check`: `@object-ui/types` (src, examples, tests), `components`, `plugin-chatbot`, `plugin-report` | 0 each | | vitest `packages/types/` | 389 of 390 files passed. `phase2-schemas` failed because it authored `timeout` / `retry.delay`; its fixture is renamed in this PR, and the file re-runs 35/35 | | vitest `packages/components/` + `examples/schema-catalog/` | 479 files / 6677 tests passed (24 skipped, pre-existing) | | vitest `packages/plugin-chatbot/` + `packages/plugin-report/` + the overlay / feedback / complex renderers | 142 files / 1272 tests passed | | vitest `apps/console/` | 245 files / 2478 tests passed | | `check:spec-symbols`, `check:doc-snippets`, `check:doc-examples`, `check:doc-json`, `check:doc-types`, `check:readme-exports`, `check:skill-examples` (after the scoped build each one asks for) | 0 each. `check:doc-json` first answered 1 on `schema-reference.md`'s `retry.delay`, which is fixed in `7396eb3` | | `check:component-surface-parity`, `check:handler-key-reads`, `check:prompt-keys`, `check:action-forward-parity`, `check:designer-field-key-parity`, `check:doc-example-readers`, `check:registry-bare-names` | 0 each | | `check:new-line-citations`, `check:control-bytes`, `check:changeset-claims`, `check:pending-changeset-literals` | 0 each | | changeset presence / no-major / fixed | 0 each | | ESLint on the changed `.ts` / `.tsx` files | 0 errors (warnings are the files' existing `no-explicit-any`) | | `check-governed-queue-guard --test` on the file list | NOT GOVERNED | **Ablation.** With the five `zod/*.zod.ts` files reverted to `0063f22ce`, the new pin file reads 22 failed / 13 passed. Restored, it reads 35/35. ## Deviations from the order 1. **Changeset level `minor`, not `major`.** The card asks for a `major` changeset. objectui's AGENTS.md version policy forbids `major` in the fixed group (objectui's major follows `@objectstack`'s), and `scripts/check-changeset-no-major.mjs` refuses it in CI. So the bump is `minor`, and the body states the breaking semantics and the FROM → TO table. 2. **Refusal by named tombstone, not `.strict()`.** These mirrors are `.passthrough()` by design. A `.strict()` refusal would refuse every undeclared key on nine node types, which is wider than the card. objectui's own counterpart of the spec's `retiredKey` is `retirementTombstone`, and it refuses exactly the old key with a message that names the new one. 3. **`useObjectChat`'s `autoResponseDelay` option is not renamed.** It is the hook's programmatic API, not a schema key, and the gate does not judge it. The renderer maps `schema.autoResponseDelayMs` onto it. 4. **One extra docs commit.** `7396eb3` is the `schema-reference.md` JSON fence that `check:doc-json` refused. **Residual, not widened (reported to the dispatcher).** `autoPlayMs` (carousel), `timeoutMs` and `retry.delayMs` / `retry.maxAttempts` (ActionSchema) have no renderer read, before or after this PR. They are declared but inert, and `enhanced-actions.mdx`'s "Retry Logic" section describes behaviour that nothing implements. The carousel registration spreads its props onto a `div`. An authored `autoPlay` used to land there as a valid `autoplay` attribute; an authored `autoPlayMs` now lands as an unknown one. Enforcing or removing these keys is a separate decision. - [x] Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019dczBdb6awqr3txksmEa9W --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent be64bf6 commit 1212e3e

32 files changed

Lines changed: 422 additions & 103 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@object-ui/types': minor
3+
'@object-ui/components': minor
4+
'@object-ui/plugin-chatbot': minor
5+
'@object-ui/plugin-report': minor
6+
---
7+
8+
Clause-②: yes
9+
Metadata-protocol: added
10+
11+
Nine duration keys now carry their unit in their name (objectstack#25762). Each one said its unit only in its description, so `timeout: 300` read as either milliseconds or seconds. After objectui merges into objectstack, `@objectstack/spec`'s `check:duration-unit-keys` judges these schemas too, and the maintainer chose to rename the keys rather than exempt them. The renames follow objectstack's own (`…Ms` / `…Seconds`, the unit each description already stated). The value and its unit do not change, only the key name. This is breaking for a document that still writes an old name; it is marked `minor` under this repository's version policy.
12+
13+
| Node / schema | FROM | TO | Unit |
14+
|---|---|---|---|
15+
| `carousel` (`CarouselSchema`) | `autoPlay` | `autoPlayMs` | milliseconds |
16+
| `chatbot`, `chatbot-enhanced`, `chatbot-floating` | `autoResponseDelay` | `autoResponseDelayMs` | milliseconds |
17+
| `ActionSchema` | `timeout` | `timeoutMs` | milliseconds |
18+
| `ActionSchema.retry` | `delay` | `delayMs` | milliseconds |
19+
| `toast` (`ToastSchema`) | `duration` | `durationMs` | milliseconds |
20+
| `tooltip` (`TooltipSchema`) | `delayDuration` | `delayDurationMs` | milliseconds |
21+
| `hover-card` (`HoverCardSchema`) | `openDelay` | `openDelayMs` | milliseconds |
22+
| `hover-card` (`HoverCardSchema`) | `closeDelay` | `closeDelayMs` | milliseconds |
23+
| `report` (`ReportComponentSchema`) | `refreshInterval` | `refreshIntervalSeconds` | seconds |
24+
25+
What an author sees with an old name:
26+
27+
- **zod (`@object-ui/types/zod`).** The old key is still declared, as a tombstone, so it is refused at its own path with code `invalid_type`. The message names the new key: "`timeout` is now `timeoutMs` … the value (milliseconds) is unchanged." Without the tombstone, `.passthrough()` would keep the value and nothing would read it.
28+
- **TypeScript.** The old member is `?: never` with `@deprecated`, so a typed literal that writes it fails to compile.
29+
- **Renderers.** `toast`, `tooltip`, `hover-card`, the three chatbot registrations and `ReportViewer` read the new key. Their registration `inputs` and `defaultProps` use the new name too. The `useObjectChat` hook option `autoResponseDelay` is a programmatic API, not a schema key, and keeps its name.
30+
31+
objectui has no conversion registry: ADR-0087's D2 table lives in `@objectstack/spec`. The conversion is this table plus the refusal message. To migrate, rename each key as the table shows and keep the value.

‎content/docs/api/schema-reference.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ A powerful action definition supporting API calls, confirmations, dialogs, chain
667667
"condition": "${data.items.length > 0}",
668668
"retry": {
669669
"maxAttempts": 3,
670-
"delay": 1000
670+
"delayMs": 1000
671671
}
672672
}
673673
```
@@ -689,7 +689,7 @@ A powerful action definition supporting API calls, confirmations, dialogs, chain
689689
| `successMessage` / `errorMessage` | `string` | Toast messages on success/failure. |
690690
| `reload` | `boolean` | Reload data after action completes. |
691691
| `redirect` | `string` | URL to navigate to after action. |
692-
| `retry` | `object` | Retry config with `maxAttempts` and `delay`. |
692+
| `retry` | `object` | Retry config with `maxAttempts` and `delayMs` (milliseconds; renamed from `delay`, objectstack#25762). |
693693

694694
**Related:** [DetailSchema](#detailschema), [ButtonSchema](#buttonschema)
695695

‎content/docs/components/feedback/toast.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The Toast component displays brief notifications to users that appear temporaril
1010
## Basic Usage
1111

1212
A `toast` node renders its own trigger button. Clicking the button raises the toast the
13-
node declares — the title, description, variant and duration are all read off the node
13+
node declares — the title, description, variant and `durationMs` are all read off the node
1414
itself, so the whole demo is one schema node with no handler to wire up.
1515

1616
<SchemaExample id="components-feedback-toast/basic-toast" />
@@ -38,7 +38,7 @@ interface ToastSchema {
3838
variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
3939

4040
// Timing
41-
duration?: number; // Auto-dismiss time in ms
41+
durationMs?: number; // Auto-dismiss time in ms
4242
}
4343
```
4444

‎content/docs/components/overlay/hover-card.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ interface HoverCardSchema {
2020
content: SchemaNode | SchemaNode[]; // Card content
2121
side?: 'top' | 'right' | 'bottom' | 'left';
2222
align?: 'start' | 'center' | 'end';
23-
openDelay?: number; // Delay before opening (ms)
24-
closeDelay?: number; // Delay before closing (ms)
23+
openDelayMs?: number; // Delay before opening (ms)
24+
closeDelayMs?: number; // Delay before closing (ms)
2525
className?: string;
2626
}
2727
```

‎content/docs/components/overlay/tooltip.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface TooltipSchema {
1919
children?: SchemaNode | SchemaNode[]; // Rich content — the fallback half
2020
side?: 'top' | 'right' | 'bottom' | 'left';
2121
align?: 'start' | 'center' | 'end';
22-
delayDuration?: number; // Delay before showing (ms)
22+
delayDurationMs?: number; // Delay before showing (ms)
2323
}
2424
```
2525

‎content/docs/core/enhanced-actions.mdx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ const ajaxAction: ActionSchema = {
6464
- `method` - HTTP method (GET, POST, PUT, DELETE, PATCH)
6565
- `data` - Request body/payload
6666
- `headers` - Custom HTTP headers
67-
- `timeout` - Request timeout in milliseconds
68-
- `retry` - Retry configuration
67+
- `timeoutMs` - Request timeout in milliseconds (renamed from `timeout`)
68+
- `retry` - Retry configuration (`maxAttempts`, `delayMs`)
6969

7070
### Confirm Actions
7171

@@ -389,11 +389,11 @@ const retryAction: ActionSchema = {
389389
api: '/api/submit',
390390
method: 'POST',
391391

392-
timeout: 30000, // 30 seconds
392+
timeoutMs: 30000, // 30 seconds
393393

394394
retry: {
395395
maxAttempts: 3,
396-
delay: 1000 // 1 second between retries
396+
delayMs: 1000 // 1 second between retries
397397
}
398398
};
399399
```
@@ -467,10 +467,10 @@ const complexAction: ActionSchema = {
467467
},
468468

469469
// Retry
470-
timeout: 60000,
470+
timeoutMs: 60000,
471471
retry: {
472472
maxAttempts: 3,
473-
delay: 2000
473+
delayMs: 2000
474474
},
475475

476476
// Post-action behavior

‎content/docs/core/report-schema.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ const comprehensiveReport: ReportComponentSchema = {
399399
showExportButtons: true,
400400
showPrintButton: true,
401401
showScheduleButton: true,
402-
refreshInterval: 300 // Auto-refresh every 5 minutes
402+
refreshIntervalSeconds: 300 // Auto-refresh every 5 minutes
403403
};
404404
```
405405

‎content/docs/plugins/plugin-chatbot.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const schema: ChatbotSchema = {
9898
maxHeight?: string,
9999
autoResponse?: boolean,
100100
autoResponseText?: string,
101-
autoResponseDelay?: number,
101+
autoResponseDelayMs?: number,
102102
onSend?: (content: string, messages: ObjectChatMessage[]) => void,
103103
className?: string,
104104
// AI / service-ai integration fields
@@ -179,7 +179,7 @@ to apply. They are documented in their own table after this one, under
179179
| `maxHeight` | string | `'500px'` | **`chatbot` and `chatbot-enhanced` only** (declared on `ChatbotSchema` and `ChatbotEnhancedSchema`; `ChatbotFloatingSchema` does not declare it). Maximum height of the chat message container, as a CSS length. `chatbot-floating` does not read it: its panel is sized by `floatingConfig.panelHeight` (a **number** of pixels, default `520`), and the panel pins its inner chat to `maxHeight: '100%'` so it fills that panel - a `maxHeight` authored on a floating node would be overridden even if it were forwarded. Size a floating chatbot with `floatingConfig.panelHeight` instead |
180180
| `autoResponse` | boolean | `false` | Enable auto-response (demo mode, ignored when `api` is set) |
181181
| `autoResponseText` | string | - | Text for auto-response |
182-
| `autoResponseDelay` | number | `1000` | Delay before auto-response (ms) |
182+
| `autoResponseDelayMs` | number | `1000` | Delay before auto-response, in milliseconds (renamed from `autoResponseDelay`, which is now refused with a message naming this key) |
183183
| `onSend` | function | - | Callback when message is sent |
184184
| `className` | string | `''` | Additional Tailwind CSS classes |
185185
| `api` | string | - | Backend SSE endpoint (e.g., `/api/v1/ai/chat`). Enables AI streaming mode |
@@ -329,7 +329,7 @@ const schema: ChatbotSchema = {
329329
messages: [], // seed with your own ChatMessage values
330330
autoResponse: true,
331331
autoResponseText: 'Thanks!',
332-
autoResponseDelay: 1000,
332+
autoResponseDelayMs: 1000,
333333
};
334334
```
335335

@@ -554,7 +554,7 @@ const salesBot: ChatbotSchema = {
554554
userAvatarFallback: 'You',
555555
autoResponse: true,
556556
autoResponseText: 'Great choice! Let me show you some options...',
557-
autoResponseDelay: 1200,
557+
autoResponseDelayMs: 1200,
558558
maxHeight: '500px',
559559
className: 'border-2 border-primary rounded-xl'
560560
};

‎examples/schema-catalog/src/schemas/components-feedback-toast/success-toast.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"variant": "success",
44
"title": "Saved Successfully",
55
"description": "Your changes have been saved.",
6-
"duration": 3000
6+
"durationMs": 3000
77
}

‎examples/schema-catalog/src/schemas/plugin-chatbot/basic-chatbot.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
"showTimestamp": false,
1212
"autoResponse": true,
1313
"autoResponseText": "Thank you for your message! This is an automated response.",
14-
"autoResponseDelay": 1000,
14+
"autoResponseDelayMs": 1000,
1515
"maxHeight": "400px"
1616
}

0 commit comments

Comments
 (0)