This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
Commit 1212e3e
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
File tree
- .changeset
- content/docs
- api
- components
- feedback
- overlay
- core
- plugins
- examples/schema-catalog/src/schemas
- components-feedback-toast
- plugin-chatbot
- packages
- components/src/renderers
- feedback
- overlay
- plugin-chatbot/src
- __tests__
- plugin-report/src
- types/src
- __tests__
- zod
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
667 | 667 | | |
668 | 668 | | |
669 | 669 | | |
670 | | - | |
| 670 | + | |
671 | 671 | | |
672 | 672 | | |
673 | 673 | | |
| |||
689 | 689 | | |
690 | 690 | | |
691 | 691 | | |
692 | | - | |
| 692 | + | |
693 | 693 | | |
694 | 694 | | |
695 | 695 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
| 23 | + | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
67 | | - | |
68 | | - | |
| 67 | + | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
389 | 389 | | |
390 | 390 | | |
391 | 391 | | |
392 | | - | |
| 392 | + | |
393 | 393 | | |
394 | 394 | | |
395 | 395 | | |
396 | | - | |
| 396 | + | |
397 | 397 | | |
398 | 398 | | |
399 | 399 | | |
| |||
467 | 467 | | |
468 | 468 | | |
469 | 469 | | |
470 | | - | |
| 470 | + | |
471 | 471 | | |
472 | 472 | | |
473 | | - | |
| 473 | + | |
474 | 474 | | |
475 | 475 | | |
476 | 476 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
399 | 399 | | |
400 | 400 | | |
401 | 401 | | |
402 | | - | |
| 402 | + | |
403 | 403 | | |
404 | 404 | | |
405 | 405 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
182 | | - | |
| 182 | + | |
183 | 183 | | |
184 | 184 | | |
185 | 185 | | |
| |||
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
332 | | - | |
| 332 | + | |
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
| |||
554 | 554 | | |
555 | 555 | | |
556 | 556 | | |
557 | | - | |
| 557 | + | |
558 | 558 | | |
559 | 559 | | |
560 | 560 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
0 commit comments