From 7f08c442d087fa04bbd6426e4fcd7e272fe58479 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:39:41 +0000 Subject: [PATCH] chore(deps): track the @objectstack family at 17.0.0-rc.5 and restore green MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps all 37 `@objectstack/*` declarations across 30 package.json from `^17.0.0-rc.2` to `^17.0.0-rc.5` and refreshes pnpm-lock.yaml. The lockfile now resolves exactly one copy of each of the six family packages (spec / client / core / formula / lint / sdui-parser), all at rc.5 — no split brain. Bumping the pin and repairing the fallout cannot be split: the pin alone reddens CI, the code alone targets a shape that is not yet in effect. Green-restoring riders only; every one repairs a compile or runtime break the bump lands. No feature-class adaptation is carried. - data-objectstack: `result.deleted` -> `result.success` (objectstack#5638, objectui#3412). The client declared a key no schema declares and no server path returns, so the guard read `undefined`: a successful single delete emitted NO MutationEvent and `delete()` resolved `undefined` from a `Promise`. Its fixture had invented the same key, which is why the test was green. Fixture replaced with the wire shape, plus the reverse pin #3412 asks for (a `deleted: true` response must emit nothing). - types/react/core/mobile: the five `spec/ui` interaction-config modules were deleted whole (objectstack#4988, PR #5321). The 32 re-exports go; each vocabulary is declared verbatim by its only real consumer, as the spec's own retirement ledger prescribes. Note DndProtocol/KeyboardProtocol DID couple to the spec (indirectly, via @object-ui/types) — objectui#3363 recorded them as uncoupled, corrected here. - types/core: `NotificationAction` / `EmbedConfig` retired (objectstack#5015, PR #5300). Survivors (the three notification enums, SharingConfig) kept. - core/types: theme `animation` / `zIndex` and five typography groups are tombstones (objectstack#5021, PR #5289), so ThemeEngine's emission for nine CSS variable groups was structurally dead; removed. Live emission untouched. - types: `HttpMethodSchema` -> spec's `HttpMethodSubsetSchema`, `HttpMethod` -> `HttpMethodSubset` (objectstack#5832, objectui#3499). Runtime domain and this repo's exported names unchanged; deliberately NOT the 7-value `HttpMethod`. - Fixtures re-judged individually: dashboard widget `actionUrl`/`actionType`/ `actionIcon`/`aria` are now refused not stripped (objectstack#5010), and `config.data.pageSize` is refused by the strict `object` arm. Parity tests whose spec anchor vanished are re-pointed at the vocabulary's new owner where that still asserts something, and removed where it would only compare a set against itself. The `page-nav-misc` tripwire INVERTED as designed: the spec has vacated GestureType/GestureConfig/OfflineConfig, so those rows now assert absence and name objectui#3363's reclaim as the follow-up. Out of scope, filed: objectui#3567 (`$icontains` is new in FieldOperatorsSchema and no builder operator can author it — needs a locale key in ten packs, so it is feature work). Recorded as a ratcheted exclusion, not silently dropped. Fixes objectstack-ai/objectui#3560 Fixes objectstack-ai/objectui#3412 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01BickTBKm2JYSNnrtPT8ysa --- .changeset/objectstack-family-rc5-refresh.md | 105 ++++++++ apps/console/package.json | 4 +- apps/site/package.json | 2 +- package.json | 2 +- packages/app-shell/package.json | 6 +- .../runtime-metadata-persistence.test.ts | 8 + ...-metadata-persistence.viewItemSpec.test.ts | 24 +- packages/auth/package.json | 2 +- packages/collaboration/package.json | 2 +- packages/components/package.json | 2 +- packages/core/package.json | 4 +- packages/core/src/protocols/DndProtocol.ts | 71 ++++- .../core/src/protocols/KeyboardProtocol.ts | 53 +++- .../core/src/protocols/SharingProtocol.ts | 29 +- packages/core/src/theme/ThemeEngine.ts | 132 ++-------- .../src/theme/__tests__/ThemeEngine.test.ts | 248 +++++++----------- packages/core/src/theme/index.ts | 7 +- packages/data-objectstack/package.json | 4 +- packages/data-objectstack/src/index.ts | 15 +- .../data-objectstack/src/onMutation.test.ts | 31 ++- packages/fields/package.json | 2 +- .../FilterConditionField.operators.test.ts | 26 +- packages/layout/package.json | 2 +- packages/mobile/package.json | 2 +- .../__tests__/gesture-spec-parity.test.tsx | 48 ++-- packages/plugin-charts/package.json | 2 +- packages/plugin-chatbot/package.json | 2 +- packages/plugin-dashboard/package.json | 2 +- packages/plugin-detail/package.json | 2 +- packages/plugin-form/package.json | 2 +- packages/plugin-gantt/package.json | 2 +- packages/plugin-grid/package.json | 4 +- packages/plugin-list/package.json | 4 +- packages/plugin-map/package.json | 2 +- packages/plugin-report/package.json | 2 +- packages/plugin-timeline/package.json | 2 +- packages/plugin-tree/package.json | 2 +- packages/plugin-view/package.json | 2 +- packages/providers/package.json | 2 +- packages/react/package.json | 2 +- ...nimation-notification-spec-parity.test.tsx | 79 +++--- ...ffline-nav-performance-spec-parity.test.ts | 146 +++++++---- packages/react/src/hooks/useOffline.ts | 137 ++++++---- packages/runner/package.json | 2 +- packages/sdui-parser/package.json | 2 +- packages/types/package.json | 2 +- .../src/__tests__/p1-spec-alignment.test.ts | 20 +- .../src/__tests__/p2-spec-exports.test.ts | 51 ++-- .../page-nav-misc-spec-parity.test.ts | 33 ++- .../src/__tests__/phase2-schemas.test.ts | 6 +- .../report-chart-query-spec-parity.test.ts | 40 ++- .../__tests__/spec-subschema-parity.test.ts | 21 +- packages/types/src/index.ts | 122 ++++----- packages/types/src/mobile.ts | 104 ++++++++ packages/types/src/objectql.ts | 23 +- packages/types/src/theme.ts | 23 +- packages/types/src/zod/index.zod.ts | 2 - packages/types/src/zod/objectql.zod.ts | 10 +- packages/types/src/zod/theme.zod.ts | 21 +- pnpm-lock.yaml | 193 +++++++------- 60 files changed, 1161 insertions(+), 741 deletions(-) create mode 100644 .changeset/objectstack-family-rc5-refresh.md diff --git a/.changeset/objectstack-family-rc5-refresh.md b/.changeset/objectstack-family-rc5-refresh.md new file mode 100644 index 0000000000..a8f04af1bf --- /dev/null +++ b/.changeset/objectstack-family-rc5-refresh.md @@ -0,0 +1,105 @@ +--- +"@object-ui/types": minor +"@object-ui/core": minor +"@object-ui/react": minor +"@object-ui/mobile": minor +"@object-ui/data-objectstack": minor +--- + +Track the `@objectstack` family at `17.0.0-rc.5` (objectui#3560). + +The pin moves from `^17.0.0-rc.2` to `^17.0.0-rc.5` across all 37 declarations in +30 `package.json` files, and the sibling `@objectstack/*` packages (`client` / +`formula` / `lint`) move with it — they pin `@objectstack/spec` **exactly**, so +leaving them behind would keep a second copy of the spec in the tree and have +`@objectstack/lint` validating against schemas that still accept the keys rc.3–rc.5 +retire. `pnpm-lock.yaml` now resolves one copy of each of the six family packages +(`spec` / `client` / `core` / `formula` / `lint` / `sdui-parser`), all at rc.5. + +Bumping the pin and repairing the fallout cannot be split: the pin alone reddens +CI, and the code alone targets a shape that is not in effect yet. + +## A live bug this upgrade fixes + +**`ObjectStackDataSource.delete()` never emitted its mutation event, and resolved +`undefined` instead of a boolean.** `@objectstack/client`'s `DeleteDataResult` +declared a key called `deleted` — a key no schema has ever declared and no server +path has ever returned on `DELETE /data/:object/:id`. So `result.deleted` +compiled and read `undefined` at runtime: the guard never fired, a successful +delete notified no subscriber, and every consumer's cache stayed stale. +objectstack#5638 corrected the interface to the schema's `success`; following the +rename is what restores both behaviours. Nothing in this repo had to change shape +for it — the code was already asking the right question of the wrong key. + +## Breaking, in FROM → TO form + +- **The five `@objectstack/spec/ui` interaction-config modules are gone** — + touch / dnd / keyboard / animation / offline, 32 defs and 64 exports + (objectstack#4988, PR objectstack#5321). None of them had an authoring door: no + metadata document could ever carry one of these blocks, so a stack that parsed + before the retirement parses byte-for-byte the same after it. `@object-ui/types` + drops the 32 `export type` re-exports. The vocabulary each one's only real + consumer needs is now declared by that consumer, which is the remedy the spec's + own retirement ledger prescribes ("declare that union locally — it is your + client's policy, not the platform's"): + - `@object-ui/react`'s `useOffline` owns `OfflineStrategy`, `ConflictResolution`, + `PersistStorageType`, `EvictionPolicyType`, `OfflineConfig`, + `OfflineCacheConfig`, `OfflineSyncConfig`; + - `@object-ui/core`'s `DndProtocol` / `KeyboardProtocol` own `DndConfig`, + `DragItem`, `DropZone`, `DragConstraint`, `DragHandle`, `DropEffect`, + `KeyboardNavigationConfig`, `KeyboardShortcut`, `FocusManagement`, + `FocusTrapConfig`; + - `@object-ui/types`' `mobile` module owns `SpecGestureConfig`, + `SwipeGestureConfig`, `PinchGestureConfig`, `LongPressGestureConfig`, + `TouchTargetConfig`, `TouchInteraction` (plus a new `SPEC_GESTURE_TYPES` + runtime tuple), so `@object-ui/mobile`'s import paths are unchanged. + + Every shape is moved verbatim — same keys, same members, same optionality — so + no hook or bridge changes behaviour. Consumers importing these names from + `@object-ui/types` must import them from the owning package instead. Note the + spec's *surviving* `ConnectorConflictResolution` (`/integration`, connector sync) + and `ConflictResolutionStrategy` (`/api`, route merge policy) are **different + concepts** — do not re-point at them. +- **`@object-ui/types` no longer re-exports `NotificationAction` or `EmbedConfig`** + (objectstack#5015, PR objectstack#5300). Both were published `ui` vocabulary with + no authoring door; no notification action was ever parsed from metadata and no + iframe route ever read an embed config. The presentation enums + (`NotificationType` / `NotificationSeverity` / `NotificationPosition`) and + `SharingConfig` **survive** and are untouched — public form sharing still gates + the anonymous endpoints on `allowAnonymous` + `publicLink`. + `@object-ui/core`'s `SharingProtocol` keeps `resolveEmbedConfig` / + `generateEmbedCode` against a locally declared `EmbedConfig`, so its surface is + unchanged. +- **`ThemeEngine` stops emitting nine retired CSS variable groups** + (objectstack#5021 option 2, PR objectstack#5289). `theme.animation`, + `theme.zIndex` and five typography groups (`fontSize` / `fontWeight` / + `lineHeight` / `letterSpacing`, plus `fontFamily.heading` / `fontFamily.mono`) + are tombstones the schema now rejects by name, so `--duration-*`, `--timing-*`, + `--z-*`, `--font-size-*`, `--font-weight-*`, `--line-height-*`, + `--letter-spacing-*`, `--font-heading` and `--font-mono` had become structurally + dead code — no author could produce the input that reached them. + `generateAnimationVars` and `generateZIndexVars` are removed from + `@object-ui/core`, and `@object-ui/types` drops `Animation` / `ZIndex` / + `AnimationSchema` / `ZIndexSchema`. **`theme.customVars` is the declared — and + since #5021 the only — door**: each entry is emitted verbatim as + `--: `, so a `--z-modal` or a `--duration-fast` goes there now. + LIVE emission is untouched byte for byte: `colors`, `borderRadius`, `shadows`, + `typography.fontFamily.base` (→ `--font-sans`) and `customVars`. +- **`@object-ui/types`' `HttpMethodSchema` now binds the spec's + `HttpMethodSubsetSchema`, and `HttpMethod` binds `HttpMethodSubset`** + (objectstack#5832, PR objectstack#5976 — objectui#3499). The spec renamed its + 5-value UI subset because `schemaNameFromExportKey` strips the `Schema` suffix, + so the 5-value and 7-value enums both published as `shared/HttpMethod` and the + later write won — the emitted JSON Schema and reference page described only one + of them. **The runtime domain is unchanged and this repo's exported names are + unchanged**; this follows the rename without touching cross-package semantics. + Deliberately NOT re-pointed at the spec's bare `HttpMethod`: that is the 7-value + enum, and widening to it would let `method: 'HEAD'` compile and then throw in + `HttpRequestSchema.parse()`. +- **`dashboard.widgets[].actionUrl` / `actionType` / `actionIcon` / `aria` are + refused, not stripped** (objectstack#5010, ADR-0049 enforce-or-remove). A + dashboard widget has no action button and never had one — every action the + dashboard dispatches comes from `header.actions[]` — and no renderer ever applied + the widget `aria`, so it promised accessibility compliance it did not deliver. + A stale dashboard now gets a named error telling it where the affordance moved, + instead of silently losing it. Run `os migrate meta --from 16` to rewrite. diff --git a/apps/console/package.json b/apps/console/package.json index c1d0d65600..e7b4920828 100644 --- a/apps/console/package.json +++ b/apps/console/package.json @@ -89,8 +89,8 @@ "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/client": "^17.0.0-rc.2", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/client": "^17.0.0-rc.5", + "@objectstack/spec": "^17.0.0-rc.5", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/typography": "^0.5.20", "@testing-library/jest-dom": "^7.0.0", diff --git a/apps/site/package.json b/apps/site/package.json index 7483675e53..84b7e66eba 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -32,7 +32,7 @@ "@object-ui/plugin-view": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "fumadocs-core": "16.14.0", "fumadocs-mdx": "15.2.1", "fumadocs-ui": "16.14.0", diff --git a/package.json b/package.json index 8eea5de70d..fa361f9060 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "devDependencies": { "@changesets/cli": "^2.31.1", "@eslint/js": "^10.0.1", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@playwright/test": "^1.62.1", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.0", diff --git a/packages/app-shell/package.json b/packages/app-shell/package.json index 9f16b5bf33..23cf997d84 100644 --- a/packages/app-shell/package.json +++ b/packages/app-shell/package.json @@ -49,9 +49,9 @@ "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/formula": "^17.0.0-rc.2", - "@objectstack/lint": "^17.0.0-rc.2", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/formula": "^17.0.0-rc.5", + "@objectstack/lint": "^17.0.0-rc.5", + "@objectstack/spec": "^17.0.0-rc.5", "@sentry/react": "^10.69.0", "jsonc-parser": "^3.3.1", "lucide-react": "^1.28.0", diff --git a/packages/app-shell/src/views/runtime-metadata-persistence.test.ts b/packages/app-shell/src/views/runtime-metadata-persistence.test.ts index 9a0bbe39bc..6262e0bed2 100644 --- a/packages/app-shell/src/views/runtime-metadata-persistence.test.ts +++ b/packages/app-shell/src/views/runtime-metadata-persistence.test.ts @@ -211,6 +211,14 @@ describe('runtime-metadata-persistence seam (ADR-0034)', () => { expect(env.config).not.toHaveProperty('label'); }); + // `pageSize` is deliberately NOT a spec key — the `object` arm of + // `ViewDataSchema` declares only `provider` + `object`, and is strict since + // objectstack#4001. It is used here precisely because it is undeclared: this + // asserts `viewEnvelope`'s pass-through MECHANICS, i.e. that it spreads + // whatever it is handed rather than whitelisting keys. Do not copy it as an + // example of a valid binding — the spec-validity contract is pinned in + // `runtime-metadata-persistence.viewItemSpec.test.ts`, where a fixture + // carrying this key is (correctly) refused by name. it('viewEnvelope preserves an existing config.data while stamping the object', () => { const env = viewEnvelope( 'acct', diff --git a/packages/app-shell/src/views/runtime-metadata-persistence.viewItemSpec.test.ts b/packages/app-shell/src/views/runtime-metadata-persistence.viewItemSpec.test.ts index 2929b70972..7452124c68 100644 --- a/packages/app-shell/src/views/runtime-metadata-persistence.viewItemSpec.test.ts +++ b/packages/app-shell/src/views/runtime-metadata-persistence.viewItemSpec.test.ts @@ -118,13 +118,31 @@ describe('viewEnvelope output conforms to the spec ViewItem gate (objectui#3375) expectSpecValid(env); }); - it('stamps a spec-valid config.data binding while preserving caller data keys', () => { + it('stamps a spec-valid config.data binding', () => { + // The `object` arm of `ViewDataSchema` declares exactly `provider` + `object` + // and is strict since objectstack#4001, so this fixture no longer carries the + // `pageSize: 25` it used to. That key was never part of the contract — it was + // being DROPPED silently, and the assertion that it survived `viewEnvelope` + // was pinning the leniency rather than a behaviour: the view rendered without + // whatever the key was meant to configure, and nothing said so. + // + // `viewEnvelope` still spreads caller `data` keys through, and that is + // deliberately left alone — no production caller supplies an extra one, and + // the spec refusing an undeclared key by name is the loud failure the strict + // arm exists to produce. What is pinned here is the part that IS the + // function's job: stamping `provider` and `object` onto whatever it is given. const env = viewEnvelope( 'acct', - { type: 'grid', columns: [], data: { pageSize: 25 } }, + { type: 'grid', columns: [], data: {} }, { name: 'big', label: 'Big' }, ); - expect(env.config.data).toEqual({ provider: 'object', pageSize: 25, object: 'acct' }); + expect(env.config.data).toEqual({ provider: 'object', object: 'acct' }); + expectSpecValid(env); + }); + + it('stamps the binding even when the caller passes no data at all', () => { + const env = viewEnvelope('acct', { type: 'grid', columns: [] }, { name: 'nodata', label: 'No data' }); + expect(env.config.data).toEqual({ provider: 'object', object: 'acct' }); expectSpecValid(env); }); diff --git a/packages/auth/package.json b/packages/auth/package.json index 003562487a..a679d174e0 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "better-auth": "^1.6.25" }, "devDependencies": { diff --git a/packages/collaboration/package.json b/packages/collaboration/package.json index cc21f5fd92..f9e8dec4a5 100644 --- a/packages/collaboration/package.json +++ b/packages/collaboration/package.json @@ -41,7 +41,7 @@ "@object-ui/types": "workspace:*" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@types/react": "19.2.18", "react": "19.2.8", "typescript": "^6.0.3", diff --git a/packages/components/package.json b/packages/components/package.json index 4efc0e1329..04ac9534cd 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,7 +40,7 @@ "@object-ui/react-runtime": "workspace:*", "@object-ui/sdui-parser": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@radix-ui/react-accordion": "^1.2.20", "@radix-ui/react-alert-dialog": "^1.1.23", "@radix-ui/react-aspect-ratio": "^1.1.15", diff --git a/packages/core/package.json b/packages/core/package.json index 30dfa03890..a43723a62c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,8 +32,8 @@ }, "dependencies": { "@object-ui/types": "workspace:*", - "@objectstack/formula": "^17.0.0-rc.2", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/formula": "^17.0.0-rc.5", + "@objectstack/spec": "^17.0.0-rc.5", "lodash": "^4.18.1", "zod": "^4.4.3" }, diff --git a/packages/core/src/protocols/DndProtocol.ts b/packages/core/src/protocols/DndProtocol.ts index 2ce94f26c6..c0bfed34c8 100644 --- a/packages/core/src/protocols/DndProtocol.ts +++ b/packages/core/src/protocols/DndProtocol.ts @@ -16,7 +16,76 @@ * @packageDocumentation */ -import type { DndConfig, DragItem, DropZone, DragConstraint } from '@object-ui/types'; +// ============================================================================ +// DnD Vocabulary (formerly `@objectstack/spec/ui`) +// ============================================================================ +// `@objectstack/spec` 17.0.0-rc.3 deleted the whole `ui/dnd` module along with +// the four other interaction-config modules (objectstack#4988, PR +// objectstack#5321): none of them had an authoring door, so no metadata +// document could ever carry a dnd block and the platform stopped publishing +// vocabulary nothing could author. +// +// The declarations below are that vocabulary moved here verbatim — same keys, +// same members, same optionality as the retired `z.infer` types this module +// used to import through `@object-ui/types`. This bridge is the only consumer +// of the semantics, so it is now their owner, which is the remediation the +// spec's retirement ledger prescribes for a client that consumed these as +// types. Nothing about the resolver behaviour changes. +// +// NOTE for objectui#3363: that card recorded `DndProtocol.ts` / +// `KeyboardProtocol.ts` as same-name LOCAL declarations that never imported the +// spec ("命名巧合,不是耦合,不动"). That measurement was taken against direct +// `@objectstack/spec` imports only — both files reached the very same spec types +// INDIRECTLY, through `@object-ui/types`' re-export block, and so went red on +// this upgrade. Corrected here rather than left for the card. + +/** How a drag is initiated. */ +export type DragHandle = 'element' | 'handle' | 'grip_icon'; + +/** The `dropEffect` advertised to assistive technology and DnD libraries. */ +export type DropEffect = 'none' | 'link' | 'move' | 'copy'; + +/** Movement constraints applied to a drag. */ +export interface DragConstraint { + axis: 'x' | 'y' | 'both'; + bounds: 'none' | 'parent' | 'viewport'; + grid?: [number, number]; +} + +/** A draggable item's declaration. */ +export interface DragItem { + type: string; + label?: string; + handle: DragHandle; + constraint?: DragConstraint; + preview: 'custom' | 'none' | 'element'; + disabled: boolean; + ariaLabel?: string; + ariaDescribedBy?: string; + role?: string; +} + +/** A drop target's declaration. */ +export interface DropZone { + label?: string; + accept: string[]; + maxItems?: number; + highlightOnDragOver: boolean; + dropEffect: DropEffect; + ariaLabel?: string; + ariaDescribedBy?: string; + role?: string; +} + +/** Top-level drag-and-drop configuration. */ +export interface DndConfig { + enabled: boolean; + dragItem?: DragItem; + dropZone?: DropZone; + sortable: boolean; + autoScroll: boolean; + touchDelay: number; +} // ============================================================================ // Resolved Types diff --git a/packages/core/src/protocols/KeyboardProtocol.ts b/packages/core/src/protocols/KeyboardProtocol.ts index a784425b27..0af044cff6 100644 --- a/packages/core/src/protocols/KeyboardProtocol.ts +++ b/packages/core/src/protocols/KeyboardProtocol.ts @@ -17,12 +17,53 @@ * @packageDocumentation */ -import type { - KeyboardNavigationConfig, - KeyboardShortcut, - FocusManagement, - FocusTrapConfig, -} from '@object-ui/types'; +// ============================================================================ +// Keyboard / Focus Vocabulary (formerly `@objectstack/spec/ui`) +// ============================================================================ +// `@objectstack/spec` 17.0.0-rc.3 deleted the whole `ui/keyboard` module with +// the four other interaction-config modules (objectstack#4988, PR +// objectstack#5321) — none had an authoring door, so no metadata document could +// ever carry a keyboard block. The declarations below are that vocabulary moved +// here verbatim (same keys, same members, same optionality as the retired +// `z.infer` types this module imported through `@object-ui/types`). This bridge +// is the only consumer of the semantics and is now their owner; resolver +// behaviour is unchanged. See `DndProtocol.ts` for the objectui#3363 premise +// correction that applies to this file too. + +/** A single keyboard shortcut binding. */ +export interface KeyboardShortcut { + key: string; + action: string; + description?: string; + scope: 'global' | 'form' | 'modal' | 'view' | 'list'; +} + +/** Focus trap declaration for a modal-like region. */ +export interface FocusTrapConfig { + enabled: boolean; + initialFocus?: string; + returnFocus: boolean; + escapeDeactivates: boolean; +} + +/** Focus management declaration. */ +export interface FocusManagement { + tabOrder: 'manual' | 'auto'; + skipLinks: boolean; + focusVisible: boolean; + focusTrap?: FocusTrapConfig; + arrowNavigation: boolean; +} + +/** Top-level keyboard navigation configuration. */ +export interface KeyboardNavigationConfig { + shortcuts?: KeyboardShortcut[]; + focusManagement?: FocusManagement; + rovingTabindex: boolean; + ariaLabel?: string; + ariaDescribedBy?: string; + role?: string; +} // ============================================================================ // Resolved Types diff --git a/packages/core/src/protocols/SharingProtocol.ts b/packages/core/src/protocols/SharingProtocol.ts index 9a13938bf4..0f5fd2c332 100644 --- a/packages/core/src/protocols/SharingProtocol.ts +++ b/packages/core/src/protocols/SharingProtocol.ts @@ -17,7 +17,34 @@ * @packageDocumentation */ -import type { SharingConfig, EmbedConfig } from '@object-ui/types'; +import type { SharingConfig } from '@object-ui/types'; + +// ============================================================================ +// Embed Vocabulary (formerly `@objectstack/spec/ui`) +// ============================================================================ +// `EmbedConfig` / `EmbedConfigSchema` were RETIRED in `@objectstack/spec` +// 17.0.0-rc.3 (objectstack#5015, PR objectstack#5300): they were published +// `ui` vocabulary with NO AUTHORING DOOR, and no iframe route ever read an +// embed config, so nothing ran to regress. `SharingConfig` is the SURVIVOR and +// is still the spec's — public form sharing is unaffected and `FormView.sharing` +// still gates the anonymous endpoints on `allowAnonymous` + `publicLink`. +// +// The declaration below is the retired shape moved here verbatim, so that +// `resolveEmbedConfig` / `generateEmbedCode` keep the exact input contract they +// had. Whether this bridge should exist at all now that the platform has +// withdrawn the vocabulary is a product question for objectui#3362, not a +// green-restoring one — this change deliberately keeps the surface intact. + +/** An iframe embed declaration. */ +export interface EmbedConfig { + enabled: boolean; + allowedOrigins?: string[]; + width?: string; + height?: string; + showHeader?: boolean; + showNavigation?: boolean; + responsive?: boolean; +} // ============================================================================ // Resolved Types diff --git a/packages/core/src/theme/ThemeEngine.ts b/packages/core/src/theme/ThemeEngine.ts index e9b70d75a7..7261ec0c67 100644 --- a/packages/core/src/theme/ThemeEngine.ts +++ b/packages/core/src/theme/ThemeEngine.ts @@ -135,42 +135,35 @@ export function generateColorVars(colors: ColorPalette): Record /** * Generate CSS custom properties from a Theme's typography config. */ +// ---------------------------------------------------------------------------- +// RETIRED THEME BLOCKS — @objectstack/spec 17.0.0-rc.3 (objectstack#5021 option +// 2, PR objectstack#5289), objectui#3361. +// +// `theme.animation`, `theme.zIndex` and five typography groups +// (`fontSize` / `fontWeight` / `lineHeight` / `letterSpacing`, plus +// `fontFamily.heading` / `fontFamily.mono`) became TOMBSTONES: the schema now +// rejects them by name and their prescription points at `theme.customVars`, the +// declared — and since #5021 the only — door for a custom property. A +// `--z-modal` or a `--duration-fast` is authored there now, emitted verbatim as +// `--: `. +// +// The emission code below them was therefore structurally dead: no author can +// produce the input that would reach it. It is removed rather than left behind +// a cast, because the tombstoned keys type as `never` and any cast that made +// them compile would fossilize a shape the contract has withdrawn (AGENTS.md +// #0.1). LIVE emission — `colors`, `borderRadius`, `shadows`, +// `typography.fontFamily.base` (→ `--font-sans`) and `customVars` — is +// untouched, byte for byte. +// ---------------------------------------------------------------------------- + export function generateTypographyVars(typography: NonNullable): Record { const vars: Record = {}; + // `fontFamily.base` is the only surviving typography input — see the RETIRED + // THEME BLOCKS note above for where the other five groups went. if (typography.fontFamily?.base) { vars['--font-sans'] = typography.fontFamily.base; } - if (typography.fontFamily?.heading) { - vars['--font-heading'] = typography.fontFamily.heading; - } - if (typography.fontFamily?.mono) { - vars['--font-mono'] = typography.fontFamily.mono; - } - - if (typography.fontSize) { - for (const [key, value] of Object.entries(typography.fontSize)) { - if (value) vars[`--font-size-${key}`] = value; - } - } - - if (typography.fontWeight) { - for (const [key, value] of Object.entries(typography.fontWeight)) { - if (value != null) vars[`--font-weight-${key}`] = String(value); - } - } - - if (typography.lineHeight) { - for (const [key, value] of Object.entries(typography.lineHeight)) { - if (value) vars[`--line-height-${key}`] = value; - } - } - - if (typography.letterSpacing) { - for (const [key, value] of Object.entries(typography.letterSpacing)) { - if (value) vars[`--letter-spacing-${key}`] = value; - } - } return vars; } @@ -223,40 +216,6 @@ export function generateShadowVars(shadows: NonNullable): Reco return vars; } -/** - * Generate CSS custom properties from a Theme's animation config. - */ -export function generateAnimationVars(animation: NonNullable): Record { - const vars: Record = {}; - - if (animation.duration) { - for (const [key, value] of Object.entries(animation.duration)) { - if (value) vars[`--duration-${key}`] = value; - } - } - - if (animation.timing) { - for (const [key, value] of Object.entries(animation.timing)) { - if (value) vars[`--timing-${key}`] = value; - } - } - - return vars; -} - -/** - * Generate CSS custom properties from a Theme's z-index config. - */ -export function generateZIndexVars(zIndex: NonNullable): Record { - const vars: Record = {}; - - for (const [key, value] of Object.entries(zIndex)) { - if (value != null) vars[`--z-${key}`] = String(value); - } - - return vars; -} - /** * Generate ALL CSS custom properties from a complete Theme. * This is the main entry point for theme → CSS conversion. @@ -282,16 +241,6 @@ export function generateThemeVars(theme: Theme): Record { Object.assign(vars, generateShadowVars(theme.shadows)); } - // Animation - if (theme.animation) { - Object.assign(vars, generateAnimationVars(theme.animation)); - } - - // Z-Index - if (theme.zIndex) { - Object.assign(vars, generateZIndexVars(theme.zIndex)); - } - // Custom CSS variables (passthrough) if (theme.customVars) { for (const [key, value] of Object.entries(theme.customVars)) { @@ -330,22 +279,6 @@ export function mergeThemes(parent: Theme, child: Partial): Theme { ...parent.typography?.fontFamily, ...child.typography?.fontFamily, }, - fontSize: { - ...parent.typography?.fontSize, - ...child.typography?.fontSize, - }, - fontWeight: { - ...parent.typography?.fontWeight, - ...child.typography?.fontWeight, - }, - lineHeight: { - ...parent.typography?.lineHeight, - ...child.typography?.lineHeight, - }, - letterSpacing: { - ...parent.typography?.letterSpacing, - ...child.typography?.letterSpacing, - }, } : undefined, // Deep-merge border radius @@ -356,25 +289,6 @@ export function mergeThemes(parent: Theme, child: Partial): Theme { shadows: child.shadows || parent.shadows ? { ...parent.shadows, ...child.shadows } : undefined, - // Deep-merge animation - animation: child.animation || parent.animation - ? { - ...parent.animation, - ...child.animation, - duration: { - ...parent.animation?.duration, - ...child.animation?.duration, - }, - timing: { - ...parent.animation?.timing, - ...child.animation?.timing, - }, - } - : undefined, - // Deep-merge zIndex - zIndex: child.zIndex || parent.zIndex - ? { ...parent.zIndex, ...child.zIndex } - : undefined, // Deep-merge customVars customVars: child.customVars || parent.customVars ? { ...parent.customVars, ...child.customVars } diff --git a/packages/core/src/theme/__tests__/ThemeEngine.test.ts b/packages/core/src/theme/__tests__/ThemeEngine.test.ts index 648483d552..b3ea7bd160 100644 --- a/packages/core/src/theme/__tests__/ThemeEngine.test.ts +++ b/packages/core/src/theme/__tests__/ThemeEngine.test.ts @@ -15,8 +15,6 @@ import { generateTypographyVars, generateBorderRadiusVars, generateShadowVars, - generateAnimationVars, - generateZIndexVars, generateThemeVars, mergeThemes, resolveThemeInheritance, @@ -48,36 +46,14 @@ const baseTheme: Theme = { border: '#E2E8F0', disabled: '#94A3B8', }, + // Only `fontFamily.base` survives. `fontFamily.heading` / `.mono`, `fontSize`, + // `fontWeight`, `lineHeight` and `letterSpacing` were retired in + // @objectstack/spec 17.0.0-rc.3 (objectstack#5021) — the schema rejects them + // by name now, so a fixture carrying them would be asserting against metadata + // no author can write. `customVars` below is the declared door they point at. typography: { fontFamily: { base: 'Inter, sans-serif', - heading: 'Inter, sans-serif', - mono: 'JetBrains Mono, monospace', - }, - fontSize: { - xs: '0.75rem', - sm: '0.875rem', - base: '1rem', - lg: '1.125rem', - xl: '1.25rem', - '2xl': '1.5rem', - }, - fontWeight: { - light: 300, - normal: 400, - medium: 500, - semibold: 600, - bold: 700, - }, - lineHeight: { - tight: '1.25', - normal: '1.5', - relaxed: '1.75', - }, - letterSpacing: { - tight: '-0.025em', - normal: '0', - wide: '0.025em', }, }, borderRadius: { @@ -99,20 +75,6 @@ const baseTheme: Theme = { xl: '0 20px 25px -5px rgb(0 0 0 / 0.1)', inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)', }, - animation: { - duration: { fast: '150ms', base: '300ms', slow: '500ms' }, - timing: { ease: 'cubic-bezier(0.4, 0, 0.2, 1)', linear: 'linear' }, - }, - zIndex: { - base: 0, - dropdown: 1000, - sticky: 1100, - fixed: 1200, - modalBackdrop: 1300, - modal: 1400, - popover: 1500, - tooltip: 1600, - }, }; // ============================================================================ @@ -226,52 +188,31 @@ describe('generateTypographyVars', () => { expect(vars['--font-sans']).toBe('Inter'); }); - it('should generate --font-heading', () => { - const vars = generateTypographyVars({ fontFamily: { heading: 'Georgia' } }); - expect(vars['--font-heading']).toBe('Georgia'); - }); - - it('should generate --font-mono', () => { - const vars = generateTypographyVars({ fontFamily: { mono: 'Fira Code' } }); - expect(vars['--font-mono']).toBe('Fira Code'); - }); - - it('should generate font size vars', () => { - const vars = generateTypographyVars({ - fontSize: { xs: '0.75rem', base: '1rem', '2xl': '1.5rem' }, - }); - expect(vars['--font-size-xs']).toBe('0.75rem'); - expect(vars['--font-size-base']).toBe('1rem'); - expect(vars['--font-size-2xl']).toBe('1.5rem'); - }); - - it('should generate font weight vars as strings', () => { - const vars = generateTypographyVars({ - fontWeight: { bold: 700, normal: 400 }, - }); - expect(vars['--font-weight-bold']).toBe('700'); - expect(vars['--font-weight-normal']).toBe('400'); - }); - - it('should generate line height vars', () => { - const vars = generateTypographyVars({ - lineHeight: { tight: '1.25', normal: '1.5' }, - }); - expect(vars['--line-height-tight']).toBe('1.25'); - expect(vars['--line-height-normal']).toBe('1.5'); - }); - - it('should generate letter spacing vars', () => { - const vars = generateTypographyVars({ - letterSpacing: { tight: '-0.025em', wide: '0.025em' }, - }); - expect(vars['--letter-spacing-tight']).toBe('-0.025em'); - expect(vars['--letter-spacing-wide']).toBe('0.025em'); - }); - it('should handle complete typography config', () => { const vars = generateTypographyVars(baseTheme.typography!); - expect(Object.keys(vars).length).toBeGreaterThanOrEqual(10); + expect(vars).toEqual({ '--font-sans': 'Inter, sans-serif' }); + }); + + // NEGATIVE CONTROL for objectstack#5021 / objectui#3361. The six retired + // typography groups must not be emitted even when a stale theme still carries + // them — an author cannot write them any more, and an engine that kept + // emitting them would be serving a contract the platform has withdrawn. Fed + // through a cast because the type no longer admits these keys at all, which + // is the point: this asserts the RUNTIME behaviour behind the type. + it('emits nothing for the retired typography groups (objectstack#5021)', () => { + const stale = { + fontFamily: { base: 'Inter', heading: 'Georgia', mono: 'Fira Code' }, + fontSize: { xs: '0.75rem', base: '1rem' }, + fontWeight: { bold: 700 }, + lineHeight: { tight: '1.25' }, + letterSpacing: { wide: '0.025em' }, + } as unknown as NonNullable; + const vars = generateTypographyVars(stale); + + // Positive control in the same run: the surviving key still emits, so an + // accidental gutting of this function cannot pass as a clean retirement. + expect(vars['--font-sans']).toBe('Inter'); + expect(vars).toEqual({ '--font-sans': 'Inter' }); }); }); @@ -318,51 +259,6 @@ describe('generateShadowVars', () => { }); }); -// ============================================================================ -// generateAnimationVars -// ============================================================================ - -describe('generateAnimationVars', () => { - it('should generate duration vars', () => { - const vars = generateAnimationVars({ - duration: { fast: '150ms', base: '300ms', slow: '500ms' }, - }); - expect(vars['--duration-fast']).toBe('150ms'); - expect(vars['--duration-base']).toBe('300ms'); - expect(vars['--duration-slow']).toBe('500ms'); - }); - - it('should generate timing vars', () => { - const vars = generateAnimationVars({ - timing: { ease: 'cubic-bezier(0.4, 0, 0.2, 1)', linear: 'linear' }, - }); - expect(vars['--timing-ease']).toBe('cubic-bezier(0.4, 0, 0.2, 1)'); - expect(vars['--timing-linear']).toBe('linear'); - }); -}); - -// ============================================================================ -// generateZIndexVars -// ============================================================================ - -describe('generateZIndexVars', () => { - it('should generate z-index vars as strings', () => { - const vars = generateZIndexVars({ - base: 0, - modal: 1400, - tooltip: 1600, - }); - expect(vars['--z-base']).toBe('0'); - expect(vars['--z-modal']).toBe('1400'); - expect(vars['--z-tooltip']).toBe('1600'); - }); - - it('should handle all z-index keys', () => { - const vars = generateZIndexVars(baseTheme.zIndex!); - expect(Object.keys(vars)).toHaveLength(8); - }); -}); - // ============================================================================ // generateThemeVars (integration) // ============================================================================ @@ -381,8 +277,50 @@ describe('generateThemeVars', () => { it('should generate vars from a complete theme', () => { const vars = generateThemeVars(baseTheme); - // Colors + Typography + BorderRadius + Shadows + Animation + ZIndex - expect(Object.keys(vars).length).toBeGreaterThan(40); + // Colors + Typography(fontFamily.base) + BorderRadius + Shadows. + // Animation and ZIndex are gone with objectstack#5021 — see the RETIRED + // THEME BLOCKS note in ThemeEngine.ts. + expect(Object.keys(vars).length).toBeGreaterThan(20); + }); + + // NEGATIVE CONTROL for objectstack#5021 / objectui#3361: a stale theme that + // still carries `animation` / `zIndex` must contribute NO variable. Before + // the retirement these produced `--duration-*`, `--timing-*` and `--z-*`. + it('emits no --duration-*, --timing-* or --z-* for a stale theme', () => { + const stale = { + name: 'stale', + label: 'Stale', + colors: { primary: '#000' }, + animation: { + duration: { fast: '150ms' }, + timing: { ease: 'linear' }, + }, + zIndex: { base: 0, modal: 1400 }, + } as unknown as Theme; + const vars = generateThemeVars(stale); + + const retired = Object.keys(vars).filter( + (k) => k.startsWith('--duration-') || k.startsWith('--timing-') || k.startsWith('--z-'), + ); + expect(retired).toEqual([]); + + // Positive control in the same run: the live half still emits, so this + // cannot pass merely because nothing was generated at all. + expect(vars['--primary']).toBeTruthy(); + }); + + // The retirement's own prescription: `customVars` is the declared door for a + // `--z-modal` or a `--duration-fast` now, and it still works. + it('customVars carries what the retired blocks used to (the prescribed door)', () => { + const theme = { + name: 'via_custom_vars', + label: 'Via customVars', + colors: { primary: '#000' }, + customVars: { 'z-modal': '1400', '--duration-fast': '150ms' }, + } as Theme; + const vars = generateThemeVars(theme); + expect(vars['--z-modal']).toBe('1400'); + expect(vars['--duration-fast']).toBe('150ms'); }); it('should include customVars with -- prefix', () => { @@ -412,10 +350,8 @@ describe('mergeThemes', () => { colors: { primary: '#000', secondary: '#111' }, typography: { fontFamily: { base: 'Arial' }, - fontSize: { base: '1rem', lg: '1.125rem' }, }, borderRadius: { sm: '2px', lg: '8px' }, - zIndex: { base: 0, modal: 1400 }, }; it('should override top-level scalar fields', () => { @@ -434,27 +370,26 @@ describe('mergeThemes', () => { expect(merged.colors.accent).toBe('#F00'); // from child }); + // `fontFamily.heading` was the child override here until objectstack#5021 + // retired it; `base` is the surviving key, so the deep-merge is exercised + // through an override of it instead of through a key no author can write. it('should deep-merge typography.fontFamily', () => { const merged = mergeThemes(parent, { typography: { - fontFamily: { heading: 'Georgia' }, + fontFamily: { base: 'Georgia' }, }, } as Partial); - expect(merged.typography?.fontFamily?.base).toBe('Arial'); // from parent - expect(merged.typography?.fontFamily?.heading).toBe('Georgia'); // from child + expect(merged.typography?.fontFamily?.base).toBe('Georgia'); // from child }); - it('should deep-merge typography.fontSize', () => { - const merged = mergeThemes(parent, { - typography: { - fontSize: { base: '1.125rem', xl: '1.5rem' }, - }, - } as Partial); - expect(merged.typography?.fontSize?.base).toBe('1.125rem'); // overridden - expect(merged.typography?.fontSize?.lg).toBe('1.125rem'); // from parent - expect(merged.typography?.fontSize?.xl).toBe('1.5rem'); // from child + it('should preserve the parent fontFamily when the child omits typography', () => { + const merged = mergeThemes(parent, { colors: { primary: '#FFF' } } as Partial); + expect(merged.typography?.fontFamily?.base).toBe('Arial'); }); + // `should deep-merge typography.fontSize` REMOVED with the `fontSize` group + // (objectstack#5021). `mergeThemes` no longer has a `fontSize` limb to merge. + it('should deep-merge borderRadius', () => { const merged = mergeThemes(parent, { borderRadius: { sm: '4px', xl: '16px' }, @@ -464,13 +399,18 @@ describe('mergeThemes', () => { expect(merged.borderRadius?.xl).toBe('16px'); // new }); - it('should deep-merge zIndex', () => { - const merged = mergeThemes(parent, { - zIndex: { tooltip: 9999 }, + // `should deep-merge zIndex` REMOVED with the `zIndex` block + // (objectstack#5021). `mergeThemes` no longer carries a `zIndex` limb, and a + // theme that still declares one is rejected by the schema before it could + // reach a merge. `customVars` merges in its place and is covered below. + it('should deep-merge customVars — the door the retired blocks point at', () => { + const withVars = { ...parent, customVars: { 'z-base': '0', 'z-modal': '1400' } } as Theme; + const merged = mergeThemes(withVars, { + customVars: { 'z-tooltip': '9999', 'z-modal': '1500' }, } as Partial); - expect(merged.zIndex?.base).toBe(0); - expect(merged.zIndex?.modal).toBe(1400); - expect(merged.zIndex?.tooltip).toBe(9999); + expect(merged.customVars?.['z-base']).toBe('0'); // from parent + expect(merged.customVars?.['z-modal']).toBe('1500'); // overridden + expect(merged.customVars?.['z-tooltip']).toBe('9999'); // from child }); it('should preserve parent when child has no field', () => { diff --git a/packages/core/src/theme/index.ts b/packages/core/src/theme/index.ts index 4fa7bfb761..058f49dc9b 100644 --- a/packages/core/src/theme/index.ts +++ b/packages/core/src/theme/index.ts @@ -13,8 +13,11 @@ export { generateTypographyVars, generateBorderRadiusVars, generateShadowVars, - generateAnimationVars, - generateZIndexVars, + // `generateAnimationVars` / `generateZIndexVars` removed with the + // `theme.animation` / `theme.zIndex` tombstones in @objectstack/spec + // 17.0.0-rc.3 (objectstack#5021) — see ThemeEngine's RETIRED THEME BLOCKS + // note. `theme.customVars` is the declared door for a `--duration-*` or + // `--z-*` now. generateThemeVars, mergeThemes, resolveThemeInheritance, diff --git a/packages/data-objectstack/package.json b/packages/data-objectstack/package.json index 6bdbb86e82..bd6d51fc3c 100644 --- a/packages/data-objectstack/package.json +++ b/packages/data-objectstack/package.json @@ -32,8 +32,8 @@ "dependencies": { "@object-ui/core": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/client": "^17.0.0-rc.2", - "@objectstack/spec": "^17.0.0-rc.2" + "@objectstack/client": "^17.0.0-rc.5", + "@objectstack/spec": "^17.0.0-rc.5" }, "devDependencies": { "tsup": "^8.5.1", diff --git a/packages/data-objectstack/src/index.ts b/packages/data-objectstack/src/index.ts index cf38ca1534..f716ce278f 100644 --- a/packages/data-objectstack/src/index.ts +++ b/packages/data-objectstack/src/index.ts @@ -1518,10 +1518,21 @@ export class ObjectStackAdapter implements DataSource { String(id), opts?.ifMatch ? { ifMatch: opts.ifMatch } : undefined, ); - if (result.deleted) { + // `success`, not `deleted` (objectstack#5638). `DeleteDataResult.deleted` + // was a key no schema ever declared and no server path ever returned on + // `DELETE /data/:object/:id` — the client's interface was a wrong CLAIM + // about the response body, and `@objectstack/client` 17.0.0-rc.5 + // corrected it to the schema's `success`. + // + // This was live here, not cosmetic: `result.deleted` compiled and read + // `undefined` at runtime, so the guard below never fired — a successful + // delete emitted NO mutation event, leaving every subscriber's cache + // stale — and this method, declared `Promise`, actually resolved + // `undefined`. Following the rename is what restores both. + if (result.success) { this.emitMutation({ type: 'delete', resource, id }); } - return result.deleted; + return result.success; } catch (err) { throw normaliseClientError(err); } diff --git a/packages/data-objectstack/src/onMutation.test.ts b/packages/data-objectstack/src/onMutation.test.ts index 991ed172be..6502ecd490 100644 --- a/packages/data-objectstack/src/onMutation.test.ts +++ b/packages/data-objectstack/src/onMutation.test.ts @@ -78,10 +78,18 @@ describe('ObjectStackAdapter.onMutation', () => { }); it('emits a delete event only when the server confirms deletion', async () => { + // `success`, not `deleted` (objectstack#5638). This mock is why the bug it + // fixed was invisible here: `DeleteDataResult` DECLARED a `deleted` key that + // no schema has ever declared and no server path has ever returned, and this + // fixture faithfully reproduced the declaration rather than the wire. So the + // adapter read `result.deleted`, got `undefined` against a real server — + // emitting no event and resolving `undefined` from a `Promise` — + // while this test stayed green against the fiction. Pinning the shape the + // server actually sends is what makes the assertion mean anything. const del = vi .fn() - .mockResolvedValueOnce({ deleted: true }) - .mockResolvedValueOnce({ deleted: false }); + .mockResolvedValueOnce({ object: 'showcase_project', id: 'r1', success: true }) + .mockResolvedValueOnce({ object: 'showcase_project', id: 'r2', success: false }); const ds = makeDS({ delete: del }); const events: DataSourceMutationEvent[] = []; ds.onMutation((e: DataSourceMutationEvent) => events.push(e)); @@ -94,6 +102,25 @@ describe('ObjectStackAdapter.onMutation', () => { ]); }); + // REVERSE PIN for objectui#3412 / objectstack#5638. The bug was not that the + // guard was missing — it was that the guard read a key the wire never carries, + // so it was dead while looking alive. A response spelling the old `deleted` + // must therefore emit NOTHING: if someone re-introduces a tolerant + // `result.success ?? result.deleted` read, this goes red. Reading both keys is + // exactly what contract-first forbids (AGENTS.md #0.1) and what objectstack#5638's + // own triage ruled out — the producer has one shape, so the consumer gets one. + it('emits nothing for a response carrying the retired `deleted` key', async () => { + const del = vi.fn().mockResolvedValue({ object: 'showcase_project', id: 'r1', deleted: true }); + const ds = makeDS({ delete: del }); + const events: DataSourceMutationEvent[] = []; + ds.onMutation((e: DataSourceMutationEvent) => events.push(e)); + + const returned = await ds.delete('showcase_project', 'r1'); + + expect(events).toEqual([]); + expect(returned).toBeUndefined(); + }); + it('emits a single bulk event per bulkUpdate call (not per id)', async () => { const updateMany = vi.fn().mockResolvedValue({ succeeded: 3, failed: 0, results: [] }); const ds = makeDS({ updateMany }); diff --git a/packages/fields/package.json b/packages/fields/package.json index b3b64d076e..ee28a03ff0 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -37,7 +37,7 @@ "@object-ui/providers": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "clsx": "^2.1.1", "lucide-react": "^1.28.0", "react-markdown": "^10.1.0", diff --git a/packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts b/packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts index df9ddaf617..c01655bf66 100644 --- a/packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts +++ b/packages/fields/src/widgets/__tests__/FilterConditionField.operators.test.ts @@ -93,14 +93,38 @@ describe('every spec field operator is reachable from the builder (#2942)', () = // emits the bare `{ field: value }` shape, never an explicit `$eq`. // `$between` stays covered by the `$gte`+`$lte` pair `between` emits // (kvToCondition reads that pair back as `between`). + // + // `$icontains` is a GENUINE GAP, not a modelling nuance, and is excluded + // here rather than silently dropped from the vocabulary. `FieldOperatorsSchema` + // gained it between @objectstack/spec 17.0.0-rc.2 and rc.5 (it folds ASCII + // case, i.e. case-insensitive contains): the server accepts the token, and no + // builder operator can author it, so the capability is unreachable from the + // filter UI. Closing it needs a new builder operator with a user-visible + // label — a new key in all ten locale packs — which is feature work and + // deliberately NOT carried by the dependency bump that surfaced it + // (objectui#3560). Tracked as objectui#3567; delete this exclusion when it + // lands, and the assertion below will hold it honest. + const KNOWN_UNREACHABLE = new Set(['$eq', '$between', '$icontains']); const unreachable = [...SPEC_OPERATORS].filter( - (op) => op !== '$eq' && op !== '$between' && !emitted.has(op), + (op) => !KNOWN_UNREACHABLE.has(op) && !emitted.has(op), ); expect( unreachable, 'FieldOperatorsSchema accepts these but no builder operator can author them', ).toEqual([]); }); + + it('every KNOWN_UNREACHABLE token is still a spec operator (the exclusion ratchet)', () => { + // A stale exclusion is how a parity test rots into a tautology: if the spec + // ever drops one of these, the entry must go too rather than sit there + // excusing a token nobody ships. `$eq` / `$between` / `$icontains` are all + // real `FieldOperatorsSchema` keys today. + for (const op of ['$eq', '$between', '$icontains']) { + expect(SPEC_OPERATORS.has(op), `'${op}' is excluded but no longer a spec operator`).toBe( + true, + ); + } + }); }); describe('kvToCondition round-trips what condToMongo writes', () => { diff --git a/packages/layout/package.json b/packages/layout/package.json index 24619796c7..f68fca7ca5 100644 --- a/packages/layout/package.json +++ b/packages/layout/package.json @@ -38,7 +38,7 @@ "react-router-dom": "^6.0.0 || ^7.0.0" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@vitejs/plugin-react": "^6.0.5", "react-router-dom": "^7.18.2", "vite": "^8.2.0", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 718a3fdb3e..da4cf9b6df 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -40,7 +40,7 @@ "@object-ui/types": "workspace:*" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@types/react": "19.2.18", "react": "19.2.8", "typescript": "^6.0.3", diff --git a/packages/mobile/src/__tests__/gesture-spec-parity.test.tsx b/packages/mobile/src/__tests__/gesture-spec-parity.test.tsx index 787d74e821..405d693f6d 100644 --- a/packages/mobile/src/__tests__/gesture-spec-parity.test.tsx +++ b/packages/mobile/src/__tests__/gesture-spec-parity.test.tsx @@ -7,39 +7,53 @@ */ /** - * Gesture type ↔ spec vocabulary parity + dispatch (#2942). + * Gesture type ↔ gesture vocabulary parity + dispatch (#2942). * * `useSpecGesture` never read `config.type` — it branched on which - * sub-object was present, so the spec's `pan` / `drag` / `rotate` / - * `double_tap` (types with no sub-object) all kept the `'tap'` initializer - * and fired on a tap. + * sub-object was present, so `pan` / `drag` / `rotate` / `double_tap` (types + * with no sub-object) all kept the `'tap'` initializer and fired on a tap. + * + * The vocabulary this pins USED to be `@objectstack/spec/ui`'s + * `GestureTypeSchema`. `@objectstack/spec` 17.0.0-rc.3 deleted the whole + * `ui/touch` module (objectstack#4988, PR objectstack#5321) because none of it + * had an authoring door, and objectui#3363 recorded this file as one of the + * three that would go red on the dependency refresh that brought it in. + * + * The pin is RE-POINTED rather than deleted: `SPEC_GESTURE_TYPES` in + * `@object-ui/types` is the same vocabulary under its new owner, and both + * directions still say something real — every declared gesture type must reach + * a recogniser, and `SPEC_GESTURE_TYPE_MAP` must not grow an entry the + * vocabulary does not declare. Deleting it instead would have removed live + * coverage of the very bug #2942 fixed, on the strength of an upstream change + * that did not touch this repo's behaviour. */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { renderHook } from '@testing-library/react'; -import { GestureTypeSchema } from '@objectstack/spec/ui'; +import { SPEC_GESTURE_TYPES } from '@object-ui/types'; import { useSpecGesture, SPEC_GESTURE_TYPE_MAP } from '../useSpecGesture'; import { useGesture } from '../useGesture'; vi.mock('../useGesture', () => ({ useGesture: vi.fn(() => ({ current: null })) })); -const specNames: string[] = (() => { - const raw = (GestureTypeSchema as unknown as { options?: readonly string[] }).options; - return Array.isArray(raw) ? [...raw] : []; -})(); +const gestureNames: string[] = [...SPEC_GESTURE_TYPES]; -describe('useSpecGesture covers the spec gesture vocabulary', () => { - it('reads a non-empty enum from the spec', () => { - expect(specNames, 'could not read GestureTypeSchema.options from the spec').not.toEqual([]); +describe('useSpecGesture covers the declared gesture vocabulary', () => { + it('reads a non-empty vocabulary', () => { + expect(gestureNames, 'could not read SPEC_GESTURE_TYPES from @object-ui/types').not.toEqual( + [], + ); }); - it('maps every spec gesture type onto a recognizer', () => { - const unmapped = specNames.filter((name) => !(name in SPEC_GESTURE_TYPE_MAP)); + it('maps every declared gesture type onto a recognizer', () => { + const unmapped = gestureNames.filter((name) => !(name in SPEC_GESTURE_TYPE_MAP)); expect(unmapped, 'these validate and then recognize as a plain tap').toEqual([]); }); - it('maps only spec gesture types', () => { - const extra = Object.keys(SPEC_GESTURE_TYPE_MAP).filter((name) => !specNames.includes(name)); - expect(extra, 'renderer-local gesture dialect — promote into @objectstack/spec instead').toEqual([]); + it('maps only declared gesture types', () => { + const extra = Object.keys(SPEC_GESTURE_TYPE_MAP).filter( + (name) => !gestureNames.includes(name), + ); + expect(extra, 'undeclared gesture dialect — add it to SPEC_GESTURE_TYPES first').toEqual([]); }); }); diff --git a/packages/plugin-charts/package.json b/packages/plugin-charts/package.json index bd91da39af..df6721edf7 100644 --- a/packages/plugin-charts/package.json +++ b/packages/plugin-charts/package.json @@ -44,7 +44,7 @@ "react-dom": "^18.0.0 || ^19.0.0" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", "@vitejs/plugin-react": "^6.0.5", diff --git a/packages/plugin-chatbot/package.json b/packages/plugin-chatbot/package.json index ecc1cff870..d47da0bf3f 100644 --- a/packages/plugin-chatbot/package.json +++ b/packages/plugin-chatbot/package.json @@ -38,7 +38,7 @@ "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", "@radix-ui/react-slot": "^1.3.3", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@radix-ui/react-use-controllable-state": "^1.2.6", "ai": "^7.0.44", "class-variance-authority": "^0.7.1", diff --git a/packages/plugin-dashboard/package.json b/packages/plugin-dashboard/package.json index 71f4514b47..53f5b0e479 100644 --- a/packages/plugin-dashboard/package.json +++ b/packages/plugin-dashboard/package.json @@ -42,7 +42,7 @@ "react-grid-layout": "^2.2.0 || ^1.4.0" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@types/react-grid-layout": "^2.1.0", "@vitejs/plugin-react": "^6.0.5", "react-grid-layout": "^2.2.4", diff --git a/packages/plugin-detail/package.json b/packages/plugin-detail/package.json index 715e35e115..72eef6b263 100644 --- a/packages/plugin-detail/package.json +++ b/packages/plugin-detail/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@object-ui/i18n": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "lucide-react": "^1.28.0" }, "peerDependencies": { diff --git a/packages/plugin-form/package.json b/packages/plugin-form/package.json index ffb5f81e33..a697bbbbe0 100644 --- a/packages/plugin-form/package.json +++ b/packages/plugin-form/package.json @@ -28,7 +28,7 @@ "@object-ui/permissions": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "lucide-react": "^1.28.0" }, "peerDependencies": { diff --git a/packages/plugin-gantt/package.json b/packages/plugin-gantt/package.json index 5eef8d0fd2..d806bc51bd 100644 --- a/packages/plugin-gantt/package.json +++ b/packages/plugin-gantt/package.json @@ -38,7 +38,7 @@ "@object-ui/plugin-detail": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "lucide-react": "^1.28.0", "sonner": "^2.0.7" }, diff --git a/packages/plugin-grid/package.json b/packages/plugin-grid/package.json index ced17ff98a..5bf3bd34b6 100644 --- a/packages/plugin-grid/package.json +++ b/packages/plugin-grid/package.json @@ -29,7 +29,7 @@ "@object-ui/permissions": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@tanstack/react-virtual": "^3.14.9", "exceljs": "^4.4.0", "lucide-react": "^1.28.0" @@ -40,7 +40,7 @@ }, "devDependencies": { "@object-ui/data-objectstack": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@vitejs/plugin-react": "^6.0.5", "msw": "^2.15.0", "typescript": "^6.0.3", diff --git a/packages/plugin-list/package.json b/packages/plugin-list/package.json index 6b39ed6387..88ae55040b 100644 --- a/packages/plugin-list/package.json +++ b/packages/plugin-list/package.json @@ -42,7 +42,7 @@ "@object-ui/permissions": "workspace:^", "@object-ui/react": "workspace:^", "@object-ui/types": "workspace:^", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, @@ -55,7 +55,7 @@ "@object-ui/permissions": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", "@vitejs/plugin-react": "^6.0.5", diff --git a/packages/plugin-map/package.json b/packages/plugin-map/package.json index 088edc0807..675a2d94df 100644 --- a/packages/plugin-map/package.json +++ b/packages/plugin-map/package.json @@ -35,7 +35,7 @@ "@object-ui/core": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "lucide-react": "^1.28.0", "maplibre-gl": "^6.1.0", "react-map-gl": "^8.1.2", diff --git a/packages/plugin-report/package.json b/packages/plugin-report/package.json index 12eea9e070..b85fe62fac 100644 --- a/packages/plugin-report/package.json +++ b/packages/plugin-report/package.json @@ -43,7 +43,7 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@types/node": "^26.1.2", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", diff --git a/packages/plugin-timeline/package.json b/packages/plugin-timeline/package.json index 44ae16e82d..bbce3f96bc 100644 --- a/packages/plugin-timeline/package.json +++ b/packages/plugin-timeline/package.json @@ -37,7 +37,7 @@ "@object-ui/mobile": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "class-variance-authority": "^0.7.1", "zod": "^4.4.3" }, diff --git a/packages/plugin-tree/package.json b/packages/plugin-tree/package.json index 933124b0ec..a23d75d0a1 100644 --- a/packages/plugin-tree/package.json +++ b/packages/plugin-tree/package.json @@ -36,7 +36,7 @@ "@object-ui/i18n": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "lucide-react": "^1.28.0" }, "peerDependencies": { diff --git a/packages/plugin-view/package.json b/packages/plugin-view/package.json index 8d9e93026c..9c24817feb 100644 --- a/packages/plugin-view/package.json +++ b/packages/plugin-view/package.json @@ -31,7 +31,7 @@ "@object-ui/plugin-grid": "workspace:*", "@object-ui/react": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "class-variance-authority": "^0.7.1", "lucide-react": "^1.28.0" }, diff --git a/packages/providers/package.json b/packages/providers/package.json index 912207f1a0..76a2c48d47 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2" + "@objectstack/spec": "^17.0.0-rc.5" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", diff --git a/packages/react/package.json b/packages/react/package.json index 6111f051b0..23d161ac12 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -34,7 +34,7 @@ "@object-ui/data-objectstack": "workspace:*", "@object-ui/i18n": "workspace:*", "@object-ui/types": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "react-hook-form": "^7.83.0" }, "peerDependencies": { diff --git a/packages/react/src/hooks/__tests__/animation-notification-spec-parity.test.tsx b/packages/react/src/hooks/__tests__/animation-notification-spec-parity.test.tsx index 555acd6749..576942f5c2 100644 --- a/packages/react/src/hooks/__tests__/animation-notification-spec-parity.test.tsx +++ b/packages/react/src/hooks/__tests__/animation-notification-spec-parity.test.tsx @@ -21,16 +21,28 @@ * and keyed positions in hyphens — all under comments claiming alignment; * - `useNavigationOverlay` read only the deprecated `width`, so an authored * `size` bucket was ignored by every host except app-shell. + * + * ## Three of the five spec anchors are gone (17.0.0-rc.3) + * + * `TransitionPresetSchema` and `EasingFunctionSchema` left with the whole + * `ui/animation` module (objectstack#4988, PR objectstack#5321), and + * `NotificationActionSchema` with objectstack#5015 (PR objectstack#5300) — + * published `ui` vocabulary with NO AUTHORING DOOR in every case. objectui#3363 + * and objectui#3362 both pre-declared this file as one that would go red on the + * dependency refresh that brought them in. + * + * The two-way parity assertions for those three cannot survive: parity needs + * two sides. What CAN survive — and is what actually protected the #2942 bugs — + * is the render half: every supported preset must produce classes, and every + * supported easing must produce valid CSS rather than leaking a raw underscore + * token. Those loops are re-pointed at the local `SUPPORTED_*` sets, which are + * the vocabulary's owner now. The two notification enums the spec KEPT + * (`NotificationTypeSchema` / `NotificationPositionSchema`) still carry their + * full two-way parity, and that half is deliberately untouched. */ import { describe, it, expect } from 'vitest'; import { renderHook } from '@testing-library/react'; -import { - TransitionPresetSchema, - EasingFunctionSchema, - NotificationActionSchema, - NotificationTypeSchema, - NotificationPositionSchema, -} from '@objectstack/spec/ui'; +import { NotificationTypeSchema, NotificationPositionSchema } from '@objectstack/spec/ui'; import { useAnimation, SUPPORTED_TRANSITION_PRESETS, @@ -61,12 +73,19 @@ function assertParity(specNames: string[], implemented: ReadonlySet, wha } describe('react hooks cover the spec animation/notification vocabularies', () => { - it('transition presets match TransitionPresetSchema both ways', () => { - assertParity(options(TransitionPresetSchema), SUPPORTED_TRANSITION_PRESETS, 'transition preset'); - }); - - it('easing functions match EasingFunctionSchema both ways', () => { - assertParity(options(EasingFunctionSchema), SUPPORTED_EASING_FUNCTIONS, 'easing'); + // The `TransitionPresetSchema` / `EasingFunctionSchema` two-way parity tests + // are REMOVED, not re-pointed at the local set: comparing `SUPPORTED_*` + // against itself is a tautology that would report green forever. The vocabulary + // is pinned by content instead, so it cannot drift silently now that no schema + // enumerates it, and the render loops below are what keep it honest. + it('the animation vocabulary is pinned by content (objectstack#4988)', () => { + expect([...SUPPORTED_TRANSITION_PRESETS].sort()).toEqual([ + 'fade', 'flip', 'none', 'rotate', 'scale', + 'slide_down', 'slide_left', 'slide_right', 'slide_up', + ]); + expect([...SUPPORTED_EASING_FUNCTIONS].sort()).toEqual([ + 'ease', 'ease_in', 'ease_in_out', 'ease_out', 'linear', 'spring', + ]); }); it('notification display types match NotificationTypeSchema both ways', () => { @@ -80,25 +99,23 @@ describe('react hooks cover the spec animation/notification vocabularies', () => // `NotificationActionButton.variant` was the shadcn Button vocabulary // (`default | destructive | outline`) under a spec-shaped name — a fork of // `NotificationActionSchema.variant`, and the one notification vocabulary - // this guard did not cover. - it('notification action variants match NotificationActionSchema both ways', () => { - // `variant` carries `.default('primary')`, so the enum sits one wrapper - // down: reading `.options` off the field itself returns nothing, which - // would make this guard pass by finding no spec values at all (the empty - // assertion in assertParity is what catches that). - const field = (NotificationActionSchema as { shape?: Record }) - .shape?.variant as { def?: { innerType?: unknown } } | undefined; - assertParity( - options(field?.def?.innerType), - SUPPORTED_NOTIFICATION_ACTION_VARIANTS, - 'notification action variant', - ); + // this guard did not cover. objectstack#5015 (PR objectstack#5300) then + // retired `NotificationActionSchema` outright: no notification action was + // ever parsed from metadata, so nothing ran to regress. + // + // `NotificationActionButton` is objectui's OWN interface and still exists, so + // its vocabulary is pinned by content here — the same disposition as the two + // animation enums above, and for the same reason (objectui#3362). + it('notification action variants are pinned by content (objectstack#5015)', () => { + expect([...SUPPORTED_NOTIFICATION_ACTION_VARIANTS].sort()).toEqual([ + 'link', 'primary', 'secondary', + ]); }); }); -describe('useAnimation renders every spec preset and easing', () => { - it('every spec preset except none resolves to non-empty classes', () => { - for (const preset of options(TransitionPresetSchema)) { +describe('useAnimation renders every supported preset and easing', () => { + it('every supported preset except none resolves to non-empty classes', () => { + for (const preset of SUPPORTED_TRANSITION_PRESETS) { const { result } = renderHook(() => useAnimation({ preset: preset as never })); if (preset === 'none') { expect(result.current.className).toBe(''); @@ -108,8 +125,8 @@ describe('useAnimation renders every spec preset and easing', () => { } }); - it('every spec easing resolves to valid CSS (never the raw underscore token)', () => { - for (const easing of options(EasingFunctionSchema)) { + it('every supported easing resolves to valid CSS (never the raw underscore token)', () => { + for (const easing of SUPPORTED_EASING_FUNCTIONS) { const { result } = renderHook(() => useAnimation({ preset: 'fade', easing: easing as never })); const value = result.current.style.animationTimingFunction; expect(value, `easing '${easing}' must map to CSS`).toBeTruthy(); diff --git a/packages/react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts b/packages/react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts index ef037dc8a0..5a99206023 100644 --- a/packages/react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts +++ b/packages/react/src/hooks/__tests__/offline-nav-performance-spec-parity.test.ts @@ -50,15 +50,7 @@ import type { } from '../useOffline'; import type { NavigationConfig } from '../useNavigationOverlay'; import type { SpecAuthoredInput } from '../../spec-input'; -import type { - OfflineStrategy as SpecOfflineStrategy, - ConflictResolution as SpecConflictResolution, - OfflineCacheConfig as SpecOfflineCacheConfig, - OfflineCacheConfigSchema, - OfflineConfigSchema, - NavigationConfigSchema, - SyncConfigSchema, -} from '@objectstack/spec/ui'; +import type { NavigationConfigSchema } from '@objectstack/spec/ui'; import type { ConflictResolutionStrategy as SpecMergeConflictStrategy } from '@objectstack/spec/api'; /** Every name `@objectstack/spec` exports from any subpath — types AND values. */ @@ -106,20 +98,42 @@ describe('the spec export-name probe itself works', () => { }); it('sees TYPE-only exports, not just runtime values', () => { - // `OfflineCacheConfig` is a type alias — invisible to a runtime `import()`. - expect(SPEC_NAMES.has('OfflineCacheConfig')).toBe(true); + // `SharingConfig` is a type alias — invisible to a runtime `import()`. It + // replaced `OfflineCacheConfig` as the witness here when objectstack#4988 + // retired the whole `ui/offline` module; the probe must still prove it can + // see erased exports, or every `.has(...)` assertion below would pass + // vacuously for the wrong reason. + expect(SPEC_NAMES.has('SharingConfig')).toBe(true); }); }); /** - * The rename here is unusual: the local symbol did not move to a LOCAL dialect - * name, it moved to the spec's own name for the union it always was. So the - * ratchet is the other way round — `ConflictResolution` must stay a spec export - * (it is a re-export), while the name it left must stay taken by something else. + * This pin's direction INVERTED at `@objectstack/spec` 17.0.0-rc.3, and the + * inversion is the honest result rather than a failure to adapt. + * + * It used to read: the local symbol did not move to a LOCAL dialect name, it + * moved to the spec's own name for the union it always was — so + * `ConflictResolution` must STAY a spec export, because this package merely + * re-exported it. objectstack#4988 (PR objectstack#5321) then deleted the whole + * `ui/offline` module, and with it that name. The spec's own retirement ledger + * prescribes the remedy by name: "If you consumed the bare `ConflictResolution` + * from `@objectstack/spec/ui` as a TYPE for your own offline code, declare that + * union locally — it is your client's policy, not the platform's." + * + * So `useOffline` now DECLARES it, and the first assertion below flips from + * `true` to `false`. What does NOT change is the half that made the rename + * load-bearing in the first place: `ConflictResolutionStrategy` is still a spec + * export meaning something else entirely, so the old name is still not free to + * take back. */ describe('the ConflictResolutionStrategy rename is load-bearing', () => { - it('the spec owns `ConflictResolution` — this package re-exports it', () => { - expect(SPEC_NAMES.has('ConflictResolution')).toBe(true); + it('the spec has VACATED `ConflictResolution` — this package declares it', () => { + expect( + SPEC_NAMES.has('ConflictResolution'), + '@objectstack/spec exports `ConflictResolution` again. `useOffline` declares its ' + + 'own, which is a fork the moment the spec owns the name. Re-triage it ' + + '(objectstack#4115) — derive, rename, or ALLOW with a reason.', + ).toBe(false); }); it('`ConflictResolutionStrategy` is still a DIFFERENT spec export', () => { @@ -146,26 +160,39 @@ type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? ? true : false; -describe('the offline unions ARE the spec bindings', () => { +describe('the offline unions keep the members the hook dispatches on', () => { it('is pinned at compile time', () => { - type _StrategyNotAny = Assert, false>>; - type _ResolutionNotAny = Assert, false>>; - - type _StrategyIsSpec = Assert>; - type _ResolutionIsSpec = Assert>; - - // The union this hook uses is NOT the one the old name pointed at. If these - // ever became the same, the rename would no longer be load-bearing. + type _StrategyNotAny = Assert, false>>; + type _ResolutionNotAny = Assert, false>>; + + // The spec-identity halves (`OfflineStrategy` / `ConflictResolution` ARE the + // spec's bindings) are gone with the `ui/offline` module (objectstack#4988). + // What survives is the distinction that made the original rename + // load-bearing, and it is still checkable because the OTHER side of it — + // `@objectstack/spec/api`'s `ConflictResolutionStrategy`, the metadata-MERGE + // policy — was NOT retired. If these ever became the same union, a reader + // would again have a symbol whose name points at one policy and whose value + // is another, which is the whole reason this hook does not use that name. type _NotTheMergePolicy = Assert< - Equal, false> + Equal, false> >; type _NoOverlapAtAll = Assert< - Equal, never> + Equal, never> >; expect(true).toBe(true); }); + it('still carries the four conflict policies, and only those', () => { + const all: ConflictResolution[] = [ + 'manual', + 'client_wins', + 'server_wins', + 'last_write_wins', + ]; + expect(all).toHaveLength(4); + }); + it('still carries the five strategies the hook dispatches on', () => { const all: OfflineStrategy[] = [ 'cache_first', @@ -178,26 +205,25 @@ describe('the offline unions ARE the spec bindings', () => { }); }); -describe('OfflineCacheConfig derives from the schema INPUT side', () => { - it('keeps the defaulted keys authorable (the z.input vs z.infer trap)', () => { - type _SpecNotAny = Assert, false>>; - - // `persistStorage` / `evictionPolicy` carry `.default()`, so the OUTPUT type - // makes them required. Deriving from `z.infer` would forbid omitting them — - // i.e. forbid using the defaults at all. - type _OutputRequiresThem = Assert< - Equal - >; +describe('OfflineCacheConfig keeps its defaulted keys authorable', () => { + it('preserves the z.input side the retired schema had', () => { + // The spec-side halves are gone with the `ui/offline` module + // (objectstack#4988), but the property they protected is the hook's own and + // is still worth pinning: `persistStorage` / `evictionPolicy` carried + // `.default()`, so a `z.infer`-shaped copy would have made them REQUIRED and + // forbidden the omissions the defaults exist for. The local declaration + // deliberately keeps them optional; this is the check that it stays that way. type _WeKeepThemOptional = Assert< Equal >; - - // …while still being the spec's vocabulary, not a copy of the members. - type _SameMembers = Assert< - Equal, SpecOfflineCacheConfig['persistStorage']> + type _EvictionOptionalToo = Assert< + Equal >; - type _IsTheSchemaInput = Assert< - Equal> + + // The members themselves, pinned so the storage backends the hook documents + // cannot drift silently now that no schema enumerates them. + type _StorageMembers = Assert< + Equal, 'sqlite' | 'indexeddb' | 'localstorage'> >; expect(true).toBe(true); @@ -277,19 +303,27 @@ describe('PerformanceConfig no longer collides — the spec retired the name', ( * This hook's config IS the spec's concept, key for key, so it takes the spec's * binding — a plain derivation, no dialect. */ -describe('OfflineConfig and OfflineSyncConfig derive from the schema INPUT side', () => { +describe('OfflineConfig and OfflineSyncConfig keep the retired schema shape', () => { it('is pinned at compile time', () => { - type SpecOfflineInput = SpecAuthoredInput; - type SpecSyncInput = SpecAuthoredInput; - type _SpecNotAny = Assert, false>>; - type _SpecNotUnknown = Assert, false>>; - - type _IsTheSchemaInput = Assert>; - type _SyncIsTheSchemaInput = Assert>; - - // Nothing invented, nothing dropped. - type _NoLocalOnlyKeys = Assert, never>>; - type _NoMissingKeys = Assert, never>>; + type _NotAny = Assert, false>>; + type _NotUnknown = Assert, false>>; + + // The `Equal>` pins are gone with + // the schemas themselves (objectstack#4988). The key inventory they + // protected is pinned directly instead, so the local declaration cannot + // quietly gain or lose a key now that no schema enumerates them. + type _OfflineKeys = Assert< + Equal< + keyof OfflineConfig, + 'enabled' | 'strategy' | 'cache' | 'sync' | 'offlineIndicator' | 'offlineMessage' | 'queueMaxSize' + > + >; + type _SyncKeys = Assert< + Equal< + keyof OfflineSyncConfig, + 'strategy' | 'conflictResolution' | 'retryInterval' | 'maxRetries' | 'batchSize' + > + >; expect(true).toBe(true); }); diff --git a/packages/react/src/hooks/useOffline.ts b/packages/react/src/hooks/useOffline.ts index 01a07eed1e..aac3c772b1 100644 --- a/packages/react/src/hooks/useOffline.ts +++ b/packages/react/src/hooks/useOffline.ts @@ -8,45 +8,52 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'; -import type { - OfflineStrategy, - ConflictResolution, - PersistStorage, - EvictionPolicy, - OfflineCacheConfigSchema, - OfflineConfigSchema, - SyncConfigSchema, -} from '@objectstack/spec/ui'; -import type { SpecAuthoredInput } from '../spec-input'; - // --------------------------------------------------------------------------- -// The offline vocabulary is the spec's (`OfflineConfigSchema` and friends in -// `@objectstack/spec/ui`), so these are its bindings rather than the hand -// copies that used to sit here under the same names (objectstack#4115). The -// header comment claimed alignment with "spec v2.0.7"; the installed spec is -// 17.0.0-rc.1, and a claim that stale is exactly what an import replaces. +// The offline vocabulary used to be the spec's (`OfflineConfigSchema` and +// friends in `@objectstack/spec/ui`), and these were its bindings rather than +// hand copies (objectstack#4115). `@objectstack/spec` 17.0.0-rc.3 DELETED the +// whole `ui/offline` module (objectstack#4988, PR objectstack#5321): none of it +// had an authoring door — no metadata document could ever carry an offline +// block — so the platform stopped publishing vocabulary nothing could author. +// +// The declarations below are that vocabulary, moved here verbatim (same keys, +// same members, same optionality). This is the remediation the spec's own +// retirement ledger prescribes by name: "If you consumed the bare +// `ConflictResolution` from `@objectstack/spec/ui` as a TYPE for your own +// offline code, declare that union locally — it is your client's policy, not +// the platform's." This hook IS that client, and it is the only implementation +// of these semantics in the repo. Nothing about `useOffline`'s behaviour +// changes; only the provenance of its types (objectui#3363). +// +// Do NOT re-point any of these at a same-named spec export that survived: +// `@objectstack/spec/integration`'s `ConnectorConflictResolution` (connector +// sync) and `@objectstack/spec/api`'s `ConflictResolutionStrategy` (route merge +// policy) are DIFFERENT concepts with different members. // --------------------------------------------------------------------------- /** Offline strategy determines how data is fetched when connectivity is limited. */ -export type { OfflineStrategy }; +export type OfflineStrategy = + | 'cache_first' + | 'network_first' + | 'stale_while_revalidate' + | 'network_only' + | 'cache_only'; /** * Conflict resolution strategy for sync operations. * - * Renamed from `ConflictResolutionStrategy` — that name belongs to a DIFFERENT - * spec export (`@objectstack/spec/api`: `error | priority | first-wins | - * last-wins`, the metadata-merge policy). The spec's name for the union this - * hook actually uses is `ConflictResolution`, so the fix was to take the spec's - * own name and its binding at once, the same move `FieldGroup` → - * `ObjectFieldGroup` made in objectui#3169. + * Deliberately NOT named `ConflictResolutionStrategy` — that name belongs to a + * different contract (`@objectstack/spec/api`: `error | priority | first-wins | + * last-wins`, the metadata-merge policy), and the confusion between the two is + * what objectui#3169 / objectstack#4115 cost. */ -export type { ConflictResolution }; +export type ConflictResolution = 'manual' | 'client_wins' | 'server_wins' | 'last_write_wins'; /** Persist storage backend. */ -export type PersistStorageType = PersistStorage; +export type PersistStorageType = 'sqlite' | 'indexeddb' | 'localstorage'; /** Eviction policy for cache management. */ -export type EvictionPolicyType = EvictionPolicy; +export type EvictionPolicyType = 'lru' | 'lfu' | 'fifo'; /** Sync state of the offline system. */ export type SyncState = 'idle' | 'syncing' | 'error' | 'offline'; @@ -61,39 +68,69 @@ export interface QueuedMutation { } /** - * Cache configuration — the AUTHORING side of the spec's - * `OfflineCacheConfigSchema`. + * Cache configuration. * - * `persistStorage` and `evictionPolicy` carry `.default()`s, so they are - * optional to write and present after a parse. This hook is handed what an app - * author wrote, hence the input side; see {@link SpecAuthoredInput}. + * `persistStorage` and `evictionPolicy` used to carry schema `.default()`s, so + * they stay optional to WRITE — this hook is handed what an app author wrote, + * which is why every key here is optional (the former `SpecAuthoredInput` / + * input-side reading of the retired schema). */ -export type OfflineCacheConfig = SpecAuthoredInput; +export interface OfflineCacheConfig { + /** Maximum cache size. */ + maxSize?: number; + /** Time-to-live for cached entries. */ + ttl?: number; + /** Storage backend. Defaults to `indexeddb` when a cache is used. */ + persistStorage?: PersistStorageType; + /** Eviction policy once `maxSize` is reached. Defaults to `lru`. */ + evictionPolicy?: EvictionPolicyType; +} -/** - * Sync configuration — the AUTHORING side of the spec's `SyncConfigSchema`. - * - * `strategy` and `conflictResolution` carry `.default()`s, so the output type - * makes them required; this hook is handed what an app author wrote. - */ -export type OfflineSyncConfig = SpecAuthoredInput; +/** Sync configuration — controls how queued mutations reach the server. */ +export interface OfflineSyncConfig { + /** Fetch strategy for sync requests. */ + strategy?: OfflineStrategy; + /** How to resolve a server/client conflict. */ + conflictResolution?: ConflictResolution; + /** Delay between retry attempts, in ms. */ + retryInterval?: number; + /** Maximum retry attempts before giving up. */ + maxRetries?: number; + /** Mutations flushed per sync batch. */ + batchSize?: number; +} /** - * Top-level offline configuration — the AUTHORING side of the spec's - * `OfflineConfigSchema`. + * Top-level offline configuration. * * This name stays with `@object-ui/react` (objectui#3156 / objectui#3159): the * `OfflineConfig` that used to sit in `@object-ui/types` was a service-worker * ROUTE cache and has been renamed `PWAOfflineConfig`, so there is no - * cross-package clash left — this hook's config is the spec's concept, key for - * key, and takes the spec's binding. + * cross-package clash. * - * Input side, not `z.infer`, and here the reason is visible in the signature: - * `useOffline(config: OfflineConfig = {})` defaults to the empty object, which - * the output type — where `enabled`, `strategy` and `offlineIndicator` are all - * required once their `.default()`s have run — would reject outright. + * Every key is optional, and that is load-bearing rather than incidental: + * `useOffline(config: OfflineConfig = {})` defaults to the empty object, so a + * shape that made `enabled` / `strategy` / `offlineIndicator` required — as the + * retired schema's OUTPUT side did, once its `.default()`s had run — would + * reject the hook's own default argument. {@link DEFAULTS} below applies the + * same values the schema defaults used to. */ -export type OfflineConfig = SpecAuthoredInput; +export interface OfflineConfig { + /** Whether offline mode is active. Defaults to `true`. */ + enabled?: boolean; + /** Fetch strategy. Defaults to `network_first`. */ + strategy?: OfflineStrategy; + /** Client-side cache configuration. */ + cache?: OfflineCacheConfig; + /** Sync behaviour for queued mutations. */ + sync?: OfflineSyncConfig; + /** Whether to surface the offline indicator UI. Defaults to `true`. */ + offlineIndicator?: boolean; + /** Message shown while offline. */ + offlineMessage?: string; + /** Maximum queued mutations retained. Defaults to `100`. */ + queueMaxSize?: number; +} /** Result returned by the useOffline hook. */ export interface OfflineResult { @@ -188,8 +225,8 @@ const DEFAULTS: Required< /** * Hook for offline mode detection and sync queue management. Its config types - * ARE the spec's `OfflineConfigSchema` / `SyncConfigSchema` / `ConflictResolution` - * (authoring side) — see {@link OfflineConfig}. + * are declared locally — see {@link OfflineConfig} for why they stopped being + * the spec's. * * @example * ```tsx diff --git a/packages/runner/package.json b/packages/runner/package.json index 75947b952d..fa8f7b91f3 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -37,7 +37,7 @@ "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "@tailwindcss/postcss": "^4.3.3", "@types/react": "19.2.18", "@types/react-dom": "19.2.4", diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index 142e64c200..53310fc385 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -33,6 +33,6 @@ "devDependencies": { "@object-ui/core": "workspace:*", "@object-ui/react": "workspace:*", - "@objectstack/spec": "^17.0.0-rc.2" + "@objectstack/spec": "^17.0.0-rc.5" } } diff --git a/packages/types/package.json b/packages/types/package.json index f6d4806c7d..9c41ca2198 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -90,7 +90,7 @@ "directory": "packages/types" }, "dependencies": { - "@objectstack/spec": "^17.0.0-rc.2", + "@objectstack/spec": "^17.0.0-rc.5", "zod": "^4.4.3" }, "devDependencies": { diff --git a/packages/types/src/__tests__/p1-spec-alignment.test.ts b/packages/types/src/__tests__/p1-spec-alignment.test.ts index 9d4b36551d..31c98dfc71 100644 --- a/packages/types/src/__tests__/p1-spec-alignment.test.ts +++ b/packages/types/src/__tests__/p1-spec-alignment.test.ts @@ -394,17 +394,15 @@ describe('P1.3 Dashboard Spec Alignment', () => { expect(dashboard.header?.actions).toHaveLength(2); }); - it('should accept widget ARIA properties', () => { - const widget: DashboardWidgetSchema = { - type: 'metric', - title: 'Revenue', - aria: { - ariaLabel: 'Total Revenue Widget', - role: 'figure', - }, - }; - expect(widget.aria?.ariaLabel).toBe('Total Revenue Widget'); - }); + // `should accept widget ARIA properties` REMOVED: `dashboard.widgets[].aria` + // was retired in @objectstack/spec 17.0.0-rc.3 (objectstack#5010, ADR-0049 + // D2). No renderer ever applied it, so ARIA attributes declared on a widget + // silently did not reach the DOM — the key promised accessibility compliance + // it did not deliver, which is exactly why it was removed rather than wired + // up. This is the same removal the dashboard-level `aria` got at #3896. The + // dashboard renderer emits its own `aria-*` attributes for the widget grid, + // so nothing regresses; asserting the key is ACCEPTED would now assert the + // opposite of the contract. }); // ============================================================================ diff --git a/packages/types/src/__tests__/p2-spec-exports.test.ts b/packages/types/src/__tests__/p2-spec-exports.test.ts index 827009f8d2..cb2724f277 100644 --- a/packages/types/src/__tests__/p2-spec-exports.test.ts +++ b/packages/types/src/__tests__/p2-spec-exports.test.ts @@ -35,7 +35,6 @@ import type { z } from 'zod'; // prescribes. import type { SharingConfig, - EmbedConfig, AddRecordConfig, AppearanceConfig, UserActionsConfig, @@ -52,7 +51,6 @@ import type { */ type _ReexportedValueTypes = [ SharingConfig, - EmbedConfig, AddRecordConfig, AppearanceConfig, UserActionsConfig, @@ -64,7 +62,6 @@ void 0 as unknown as _ReexportedValueTypes; // Runtime Zod schemas are imported directly from the spec package import { SharingConfigSchema as SharingConfigZod, - EmbedConfigSchema as EmbedConfigZod, AddRecordConfigSchema as AddRecordConfigZod, AppearanceConfigSchema as AppearanceConfigZod, UserActionsConfigSchema as UserActionsConfigZod, @@ -104,33 +101,19 @@ describe('P2.3 Spec Protocol Type Re-exports — Sharing & Embedding', () => { }); }); - describe('EmbedConfigSchema', () => { - it('should be a valid Zod schema with parse method', () => { - expect(EmbedConfigZod).toBeDefined(); - expect(typeof EmbedConfigZod.parse).toBe('function'); - expect(typeof EmbedConfigZod.safeParse).toBe('function'); - }); - - it('should validate a minimal EmbedConfig', () => { - const config: z.input = { enabled: true }; - const result = EmbedConfigZod.safeParse(config); - expect(result.success).toBe(true); - }); - - it('should validate a full EmbedConfig', () => { - const config: EmbedConfig = { - enabled: true, - allowedOrigins: ['https://example.com'], - width: '100%', - height: '600px', - showHeader: false, - showNavigation: false, - responsive: true, - }; - const result = EmbedConfigZod.safeParse(config); - expect(result.success).toBe(true); - }); - }); + // `EmbedConfigSchema` block REMOVED: `EmbedConfig` / `EmbedConfigSchema` were + // retired in @objectstack/spec 17.0.0-rc.3 (objectstack#5015, PR + // objectstack#5300) — published `ui` vocabulary with NO AUTHORING DOOR, and + // no iframe route ever read an embed config, so nothing ran to regress. + // objectui#3362 pre-declared this file as one of the three that would go red + // on the dependency refresh that brought the retirement in. + // + // The SURVIVOR half above is the load-bearing one and is deliberately kept: + // `SharingConfigSchema` must still be exported and must still parse, because + // a retirement that deleted the whole `ui/sharing` module would satisfy the + // absence half of this contract while destroying working surface. Public form + // sharing is unaffected — `FormView.sharing` still gates the anonymous + // endpoints on `allowAnonymous` + `publicLink`. }); // ============================================================================ @@ -285,12 +268,12 @@ describe('Type re-exports from @object-ui/types index', () => { expect(typeof types.defineStack).toBe('function'); }); - it('should allow type annotations with P2.3 Sharing & Embedding types', () => { - // Compile-time check: these lines would fail to compile if types were not re-exported + it('should allow type annotations with P2.3 Sharing types', () => { + // Compile-time check: this line would fail to compile if the type were not + // re-exported. The `EmbedConfigZod` half is gone with objectstack#5015 — + // see the `EmbedConfigSchema` note above. const sharing: z.input = { enabled: true }; - const embed: z.input = { enabled: false }; expect(sharing.enabled).toBe(true); - expect(embed.enabled).toBe(false); }); it('should allow type annotations with P2.4 View Configuration types', () => { diff --git a/packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts b/packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts index d899a602a3..a5cbacccb7 100644 --- a/packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts +++ b/packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts @@ -551,9 +551,6 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 it.each([ ['FileMetadata', 'UploadedFileMetadata'], - ['GestureType', 'TouchGestureType'], - ['GestureConfig', 'TouchGestureConfig'], - ['OfflineConfig', 'PWAOfflineConfig'], ['PageRegion', 'PageNodeRegion'], ['PageRegionSchema', 'PageNodeRegionSchema'], ['ResponsiveConfig', 'MobileResponsiveConfig'], @@ -566,6 +563,36 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 expect(names, `spec no longer owns '${owned}' — re-run the triage`).toContain(owned); }); + /** + * THE TRIPWIRE FIRED, exactly as designed. `@objectstack/spec` 17.0.0-rc.3 + * deleted the whole `ui/touch` and `ui/offline` modules (objectstack#4988, + * PR objectstack#5321), so `GestureType`, `GestureConfig` and `OfflineConfig` + * moved from the first list to this one: the spec no longer owns them, and + * the three local dialects are free to take their natural names back. + * + * The rename itself is objectui#3363's unlock item and is deliberately NOT + * done here — this is a dependency bump, and `TouchGestureType` → + * `GestureType` is a public-surface rename across `@object-ui/types`, + * `@object-ui/mobile` and every consumer, which deserves its own PR and its + * own changeset. What this block does is record that the reason for the + * workaround has expired, so the next reader finds the unlock rather than a + * deleted assertion. + */ + it.each([ + ['GestureType', 'TouchGestureType'], + ['GestureConfig', 'TouchGestureConfig'], + ['OfflineConfig', 'PWAOfflineConfig'], + ])( + 'the spec has VACATED `%s` — `%s` may reclaim it (objectui#3363)', + (vacated) => { + expect( + names, + `spec owns '${vacated}' again — the local dialect rename is load-bearing once more, ` + + `move this row back to the list above and close objectui#3363's unlock item.`, + ).not.toContain(vacated); + }, + ); + it.each([ ['UploadedFileMetadata', 'file-field VALUE payload, not the storage file record'], ['TouchGestureType', 'direction-fused recognizer vocabulary (`swipe-left`, …)'], diff --git a/packages/types/src/__tests__/phase2-schemas.test.ts b/packages/types/src/__tests__/phase2-schemas.test.ts index 22aff8617c..abb4d0f63c 100644 --- a/packages/types/src/__tests__/phase2-schemas.test.ts +++ b/packages/types/src/__tests__/phase2-schemas.test.ts @@ -128,10 +128,12 @@ describe('Phase 2: ThemeComponentSchema Zod Validation', () => { background: '#ffffff', text: '#0f172a', }, + // `fontSize` / `lineHeight` dropped: retired in @objectstack/spec + // 17.0.0-rc.3 (objectstack#5021), so a theme declaring them is now + // REFUSED rather than accepted-and-stripped. `fontFamily.base` is the + // surviving typography key; `customVars` is the door for the rest. typography: { fontFamily: { base: 'Inter, sans-serif' }, - fontSize: { base: '1rem' }, - lineHeight: { normal: '1.5' }, }, borderRadius: { base: '0.5rem', diff --git a/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts b/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts index b00e1ecdf1..fbca9a317c 100644 --- a/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts +++ b/packages/types/src/__tests__/report-chart-query-spec-parity.test.ts @@ -179,10 +179,19 @@ describe('DashboardWidgetSchema derives from the spec', () => { expect(localKeys.filter((k) => !specKeys.includes(k))).toEqual(['component']); }); - it('stops stripping the twelve spec keys the hand copy dropped', () => { + it('stops stripping the spec keys the hand copy dropped', () => { // Every one of these survived `objectui validate` with its value deleted // before the derivation — including the capability gates, which the // dashboard renderer honours at runtime. + // + // Four of the original twelve are gone: `actionUrl` / `actionType` / + // `actionIcon` and `aria` were retired in @objectstack/spec 17.0.0-rc.3 + // (objectstack#5010, ADR-0049 enforce-or-remove). A dashboard widget has no + // action button and never had one — every action the dashboard dispatches + // comes from `header.actions[]` — and no renderer ever applied the widget + // `aria`, so it promised accessibility compliance it did not deliver. They + // move to the negative-control block below: this fixture would now be + // REFUSED rather than parsed, which is the opposite of what it asserts. const parsed = DashboardWidgetSchema.parse({ id: 'w1', type: 'bar', @@ -190,11 +199,7 @@ describe('DashboardWidgetSchema derives from the spec', () => { colorVariant: 'blue', requiresObject: 'opportunity', requiresService: 'analytics', - actionUrl: '/opportunities', - actionType: 'url', - actionIcon: 'chart', suppressWarnings: ['no-data'], - aria: { ariaLabel: 'Pipeline' }, dataset: 'pipeline', dimensions: ['stage'], values: ['amount'], @@ -205,14 +210,31 @@ describe('DashboardWidgetSchema derives from the spec', () => { expect(parsed.colorVariant).toBe('blue'); expect(parsed.requiresObject).toBe('opportunity'); expect(parsed.requiresService).toBe('analytics'); - expect(parsed.actionUrl).toBe('/opportunities'); - expect(parsed.actionType).toBe('url'); - expect(parsed.actionIcon).toBe('chart'); expect(parsed.suppressWarnings).toEqual(['no-data']); - expect(parsed.aria).toEqual({ ariaLabel: 'Pipeline' }); expect(parsed.layout).toEqual({ x: 0, y: 0, w: 6, h: 4 }); expect(parsed.filterBindings).toEqual({ dateRange: 'closed_at' }); }); + + it('REFUSES the four keys objectstack#5010 retired, by name', () => { + // Not "strips" — refuses. That distinction is the whole point of + // ADR-0049 enforce-or-remove: a stale dashboard carrying `actionUrl` gets + // told where the affordance moved (`header.actions[]`) instead of silently + // losing it, which is what the pre-derivation hand copy did. + for (const key of ['actionUrl', 'actionType', 'actionIcon', 'aria'] as const) { + const result = DashboardWidgetSchema.safeParse({ + id: 'w1', + type: 'bar', + dataset: 'pipeline', + dimensions: ['stage'], + values: ['amount'], + [key]: key === 'aria' ? { ariaLabel: 'Pipeline' } : 'x', + }); + expect(result.success, `'${key}' must be refused, not accepted`).toBe(false); + if (!result.success) { + expect(result.error.issues.some((i) => i.path[0] === key)).toBe(true); + } + } + }); }); describe('DashboardWidgetSchema pinned divergences', () => { diff --git a/packages/types/src/__tests__/spec-subschema-parity.test.ts b/packages/types/src/__tests__/spec-subschema-parity.test.ts index b79f717a07..1e0e793acf 100644 --- a/packages/types/src/__tests__/spec-subschema-parity.test.ts +++ b/packages/types/src/__tests__/spec-subschema-parity.test.ts @@ -27,7 +27,7 @@ */ import { describe, it, expect } from 'vitest'; import { - HttpMethodSchema as SpecHttpMethodSchema, + HttpMethodSubsetSchema as SpecHttpMethodSubsetSchema, HttpRequestSchema as SpecHttpRequestSchema, ViewDataSchema as SpecViewDataSchema, ListColumnSchema as SpecListColumnSchema, @@ -38,8 +38,6 @@ import { TypographySchema as SpecTypographySchema, BorderRadiusSchema as SpecBorderRadiusSchema, ShadowSchema as SpecShadowSchema, - AnimationSchema as SpecAnimationSchema, - ZIndexSchema as SpecZIndexSchema, ThemeModeSchema as SpecThemeModeSchema, ChartTypeSchema as SpecChartTypeSchema, PageTypeSchema as SpecPageTypeSchema, @@ -58,8 +56,6 @@ import { TypographySchema, BorderRadiusSchema, ShadowSchema, - AnimationSchema, - ZIndexSchema, ThemeModeSchema, ThemeDefinitionSchema, } from '../zod/theme.zod.js'; @@ -68,7 +64,13 @@ import { PageTypeSchema } from '../zod/layout.zod.js'; describe('spec sub-schema re-exports are the spec objects (by reference)', () => { const pairs: Array<[string, unknown, unknown]> = [ - ['HttpMethodSchema', HttpMethodSchema, SpecHttpMethodSchema], + // The spec renamed the 5-value subset to `HttpMethodSubsetSchema` in + // 17.0.0-rc.5 (objectstack#5832) so it stops colliding with the 7-value + // `HttpMethod` in the published JSON Schema. This repo still exports it as + // `HttpMethodSchema`; the pin is what proves the two are still the SAME + // object, i.e. that following the rename did not quietly widen the type to + // the 7-value enum (objectui#3499). + ['HttpMethodSchema', HttpMethodSchema, SpecHttpMethodSubsetSchema], ['HttpRequestSchema', HttpRequestSchema, SpecHttpRequestSchema], ['ViewDataSchema', ViewDataSchema, SpecViewDataSchema], ['SelectionConfigSchema', SelectionConfigSchema, SpecSelectionConfigSchema], @@ -77,8 +79,11 @@ describe('spec sub-schema re-exports are the spec objects (by reference)', () => ['TypographySchema', TypographySchema, SpecTypographySchema], ['BorderRadiusSchema', BorderRadiusSchema, SpecBorderRadiusSchema], ['ShadowSchema', ShadowSchema, SpecShadowSchema], - ['AnimationSchema', AnimationSchema, SpecAnimationSchema], - ['ZIndexSchema', ZIndexSchema, SpecZIndexSchema], + // `AnimationSchema` / `ZIndexSchema` pairs REMOVED, not re-pointed: the + // spec deleted both value schemas outright with the `theme.animation` / + // `theme.zIndex` tombstones (objectstack#5021, PR objectstack#5289), and + // this package's re-exports went with them. There is no longer a pair to + // compare on either side — `theme.customVars` is the declared door now. ['ThemeModeSchema', ThemeModeSchema, SpecThemeModeSchema], ['ThemeDefinitionSchema', ThemeDefinitionSchema, SpecThemeSchema], // #2944 — these two were forks that had already drifted, re-exported under diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index e0ef445882..2c732f7c2f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -566,8 +566,25 @@ export type { TouchGestureConfig, GestureContext, MobileComponentConfig, + // The retired `@objectstack/spec/ui` touch vocabulary, now owned here — + // see the "Spec Touch Vocabulary" note in `./mobile` (objectstack#4988). + // `SPEC_GESTURE_TYPES` is its runtime witness and is exported as a VALUE + // below, outside this `export type` block. + SpecGestureType, + SpecSwipeDirection, + SpecGestureConfig, + SwipeGestureConfig, + PinchGestureConfig, + LongPressGestureConfig, + TouchTargetConfig, + TouchInteraction, } from './mobile'; +// Runtime witness for the retired touch vocabulary — a VALUE, so it must not +// sit inside the `export type` block above (#2561: inside one it is +// value-erased and resolves to `undefined` at runtime). +export { SPEC_GESTURE_TYPES } from './mobile'; + // ============================================================================ // Visual Designer Types (Q2 2026) // ============================================================================ @@ -662,8 +679,8 @@ export type { Typography, BorderRadius, Shadow, - Animation, - ZIndex, + // `Animation` / `ZIndex` retired with `theme.animation` / `theme.zIndex` in + // @objectstack/spec 17.0.0-rc.3 (objectstack#5021) — see `./theme`. ThemeSwitcherSchema, ThemePreviewSchema, // Legacy aliases @@ -984,38 +1001,19 @@ export type { // ---------------------------------------------------------------------------- // ============================================================================ -// v2.0.7 Spec UI Types — Drag and Drop -// ============================================================================ -export type { - DndConfig, - DragItem, - DropZone, - DragConstraint, - DragHandle, - DropEffect, -} from '@objectstack/spec/ui'; - +// v2.0.7 Spec UI Types — Drag and Drop / Focus & Keyboard / Animation & Motion // ============================================================================ -// v2.0.7 Spec UI Types — Focus & Keyboard Navigation -// ============================================================================ -export type { - FocusManagement, - FocusTrapConfig, - KeyboardNavigationConfig, - KeyboardShortcut, -} from '@objectstack/spec/ui'; - -// ============================================================================ -// v2.0.7 Spec UI Types — Animation & Motion -// ============================================================================ -export type { - ComponentAnimation, - AnimationTrigger, - MotionConfig, - TransitionConfig, - TransitionPreset, - EasingFunction, -} from '@objectstack/spec/ui'; +// RETIRED in @objectstack/spec 17.0.0-rc.3 (objectstack#4988, PR objectstack#5321): +// the five `ui/` interaction-config modules (touch / dnd / keyboard / animation / +// offline) were deleted whole — 32 defs, 64 exports. None of these blocks had an +// authoring door: no metadata document could ever carry one, so a stack that +// parsed before the retirement parses byte-for-byte the same after it, and the +// re-exports below were type-only surface with no runtime half to lose. +// +// The `DndConfig` / `KeyboardShortcut` names still live in this repo as LOCAL +// declarations in `@object-ui/core`'s `DndProtocol.ts` / `KeyboardProtocol.ts`. +// Those never imported the spec — the collision is a naming coincidence, not a +// coupling, and they are deliberately untouched (objectui#3363). // ============================================================================ // v2.0.7 Spec UI Types — Notifications @@ -1036,40 +1034,30 @@ export type { * The live objectui equivalent of the removed config is * `NotificationSystemConfig` in `@object-ui/react`'s `NotificationContext`, * which is declared locally and is what every surface actually reads. + * + * `NotificationAction` left the same way in 17.0.0-rc.3 (objectstack#5015, PR + * objectstack#5300): no notification action was ever parsed from metadata, so + * nothing regressed at runtime. The three PRESENTATION enums below survive and + * are the toaster vocabulary this repo actually reads. */ export type { - NotificationAction, NotificationPosition, NotificationSeverity, NotificationType, } from '@objectstack/spec/ui'; // ============================================================================ -// v2.0.7 Spec UI Types — Gestures & Touch -// ============================================================================ -export type { - GestureConfig as SpecGestureConfig, - GestureType as SpecGestureType, - SwipeGestureConfig, - SwipeDirection, - PinchGestureConfig, - LongPressGestureConfig, - TouchInteraction, - TouchTargetConfig, -} from '@objectstack/spec/ui'; - +// v2.0.7 Spec UI Types — Gestures & Touch / Offline & Sync // ============================================================================ -// v2.0.7 Spec UI Types — Offline & Sync -// ============================================================================ -export type { - OfflineConfig as SpecOfflineConfig, - OfflineCacheConfig, - OfflineStrategy, - SyncConfig, - ConflictResolution, - PersistStorage, - EvictionPolicy, -} from '@objectstack/spec/ui'; +// RETIRED with the same objectstack#4988 / PR objectstack#5321 deletion as the +// interaction blocks above. `@object-ui/react`'s `useOffline` is the real owner +// of the offline semantics and now declares its own types locally, which is the +// remediation the spec's own retirement ledger prescribes for a client that +// consumed these as types: "it is your client's policy, not the platform's". +// +// Note `@objectstack/spec/integration`'s `ConnectorConflictResolution` and +// `@objectstack/spec/api`'s `ConflictResolutionStrategy` are DIFFERENT concepts +// and are untouched — do not re-point the deleted `ConflictResolution` at them. // ============================================================================ // v2.0.7 Spec UI Types — View Enhancements @@ -1103,10 +1091,12 @@ export type { // ============================================================================ // v3.0.8 Spec UI Types — Sharing & Embedding (P2.3) // ============================================================================ -export type { - SharingConfig, - EmbedConfig, -} from '@objectstack/spec/ui'; +// `EmbedConfig` RETIRED in 17.0.0-rc.3 (objectstack#5015, PR objectstack#5300): +// no iframe route ever read an embed config, so nothing ran to regress. +// `SharingConfig` is the SURVIVOR and stays — public form sharing is unaffected, +// `FormView.sharing` still gates the anonymous endpoints on `allowAnonymous` + +// `publicLink`. +export type { SharingConfig } from '@objectstack/spec/ui'; // ============================================================================ // v3.0.8 Spec UI Types — View Configuration (P2.4) @@ -1179,10 +1169,10 @@ export type { // key, and its value schema had no other consumer, so it went with it. Nothing // in this repo bound to the spec type — `@object-ui/react`'s `usePerformance` // declares its own `PerformanceConfig` interface and is untouched. -export type { - PageTransition, - PageComponentType, -} from '@objectstack/spec/ui'; +// +// `PageTransition` went the same way one rc later, as part of the `ui/animation` +// module deletion (objectstack#4988, PR objectstack#5321). +export type { PageComponentType } from '@objectstack/spec/ui'; // ============================================================================ // v2.0.7 Spec UI Types — Accessibility diff --git a/packages/types/src/mobile.ts b/packages/types/src/mobile.ts index b707bfc898..0c512afe8c 100644 --- a/packages/types/src/mobile.ts +++ b/packages/types/src/mobile.ts @@ -272,3 +272,107 @@ export interface MobileComponentConfig { loadMore?: string; }; } + +// ============================================================================ +// Spec Touch Vocabulary (formerly `@objectstack/spec/ui`) +// ============================================================================ +// `@objectstack/spec` 17.0.0-rc.3 deleted the whole `ui/touch` module along +// with the four other interaction-config modules (objectstack#4988, PR +// objectstack#5321). None of them had an authoring door — no metadata document +// could ever carry a touch block — so the platform stopped publishing +// vocabulary nothing could author, and a stack that parsed before the +// retirement parses byte-for-byte the same after it. +// +// The declarations below are that vocabulary moved here verbatim: same keys, +// same members, same optionality as the retired `z.infer` types this file's +// consumers used to reach through the `@objectstack/spec/ui` re-export block in +// `index.ts`. `@object-ui/mobile`'s `useSpecGesture` / `useTouchTarget` are the +// only implementations of these semantics in the repo, so this package is now +// their owner. Nothing about either hook's behaviour changes. +// +// The `Spec…` prefix on {@link SpecGestureConfig} is kept deliberately. It +// still distinguishes this shape from the sibling {@link TouchGestureConfig} +// dialect above — a DIFFERENT contract with different members (`swipe-left` vs +// `swipe` + a direction array). objectui#3363 records reclaiming the natural +// names as an unlock now that the spec has vacated them; that is a rename with +// its own blast radius and stays on that card, not on a dependency bump. + +/** + * Gesture kinds the retired `ui/touch` vocabulary recognised. + * + * Declared as a runtime `as const` tuple, not a bare union, and that is + * deliberate. `@object-ui/mobile`'s `gesture-spec-parity.test.tsx` pinned + * `SPEC_GESTURE_TYPE_MAP` against `GestureTypeSchema.options` — a RUNTIME read + * of the spec's enum — in both directions: every declared type maps to a + * recogniser, and no renderer-local dialect sneaks in. A type-only union would + * have left that pin with nothing to read and it would have had to be deleted, + * which is how a retirement quietly takes working coverage with it. The tuple + * keeps the pin executable against the vocabulary's new owner. + */ +export const SPEC_GESTURE_TYPES = [ + 'swipe', + 'pinch', + 'long_press', + 'double_tap', + 'drag', + 'rotate', + 'pan', +] as const; + +/** Gesture kinds the retired `ui/touch` vocabulary recognised. */ +export type SpecGestureType = (typeof SPEC_GESTURE_TYPES)[number]; + +/** Swipe direction. */ +export type SpecSwipeDirection = 'left' | 'right' | 'up' | 'down'; + +/** Swipe recogniser tuning. */ +export interface SwipeGestureConfig { + direction: SpecSwipeDirection[]; + threshold?: number; + velocity?: number; +} + +/** Pinch recogniser bounds. */ +export interface PinchGestureConfig { + minScale?: number; + maxScale?: number; +} + +/** Long-press recogniser tuning. */ +export interface LongPressGestureConfig { + duration: number; + moveTolerance?: number; +} + +/** A single gesture declaration. */ +export interface SpecGestureConfig { + type: SpecGestureType; + label?: string; + enabled: boolean; + swipe?: SwipeGestureConfig; + pinch?: PinchGestureConfig; + longPress?: LongPressGestureConfig; +} + +/** Minimum touch target sizing (WCAG 2.5.5). */ +export interface TouchTargetConfig { + minWidth: number; + minHeight: number; + padding?: number; + hitSlop?: { + top?: number; + right?: number; + bottom?: number; + left?: number; + }; +} + +/** A component's whole touch-interaction declaration. */ +export interface TouchInteraction { + gestures?: SpecGestureConfig[]; + touchTarget?: TouchTargetConfig; + hapticFeedback?: boolean; + ariaLabel?: string; + ariaDescribedBy?: string; + role?: string; +} diff --git a/packages/types/src/objectql.ts b/packages/types/src/objectql.ts index fa03069eee..6c12008f47 100644 --- a/packages/types/src/objectql.ts +++ b/packages/types/src/objectql.ts @@ -33,15 +33,22 @@ import type { ListViewInferred } from './zod/objectql.zod.js'; * HTTP Method for API requests * Canonical definition from @objectstack/spec/ui. * - * The spec renamed this export to `HttpMethodType` in 17.0.0 (objectstack#4691): - * `HttpMethod` used to name two DIFFERENT types depending on the entry point — - * the 7-value enum on `./shared` / `./api` (which adds `HEAD` / `OPTIONS`) and - * the 5-value UI subset on `./ui`. We alias the `./ui` one back to `HttpMethod` - * so `@object-ui/types`' public surface is unchanged and the shape stays - * verbatim identical. Do NOT re-point this at `./shared`: that is the 7-value - * enum, and `ApiDataSource` means the 5-value one. + * The spec renamed this export twice. `HttpMethod` used to name two DIFFERENT + * types depending on the entry point — the 7-value enum on `./shared` / `./api` + * (which adds `HEAD` / `OPTIONS`) and the 5-value UI subset on `./ui`. 17.0.0 + * split them as `HttpMethodType` (objectstack#4691); 17.0.0-rc.5 renamed that + * again to `HttpMethodSubset` (objectstack#5832, PR objectstack#5976), because + * `schemaNameFromExportKey` strips the `Schema` suffix and both enums published + * as `shared/HttpMethod` — the later write won, so the emitted JSON Schema and + * reference page described only the 5-value one. + * + * The 5-value RUNTIME domain is unchanged by either rename; we alias it back to + * `HttpMethod` so `@object-ui/types`' public surface stays verbatim identical. + * Do NOT re-point this at the spec's bare `HttpMethod`: that is the 7-value + * enum, and `ApiDataSource` means the 5-value one. Widening it would let + * `method: 'HEAD'` compile and then throw in `HttpRequestSchema.parse()`. */ -export type { HttpMethodType as HttpMethod } from '@objectstack/spec/ui'; +export type { HttpMethodSubset as HttpMethod } from '@objectstack/spec/ui'; /** * HTTP Request Configuration for API Provider diff --git a/packages/types/src/theme.ts b/packages/types/src/theme.ts index 5b54245b09..7acb5e5682 100644 --- a/packages/types/src/theme.ts +++ b/packages/types/src/theme.ts @@ -48,17 +48,18 @@ export type { BorderRadius } from '@objectstack/spec/ui'; */ export type { Shadow } from '@objectstack/spec/ui'; -/** - * Animation Configuration - * Canonical definition from @objectstack/spec/ui. - */ -export type { Animation } from '@objectstack/spec/ui'; - -/** - * Z-Index Layer Configuration - * Canonical definition from @objectstack/spec/ui. - */ -export type { ZIndex } from '@objectstack/spec/ui'; +// `Animation` / `ZIndex` RETIRED in @objectstack/spec 17.0.0-rc.3 +// (objectstack#5021 option 2, PR objectstack#5289). `ThemeSchema.animation` and +// `ThemeSchema.zIndex` became tombstones whose prescription points at +// `customVars`, and the spec DELETED the two value schemas outright rather than +// leave them standing beside the tombstones — an exported value schema with no +// consumer reads as a capability to whoever finds it. A `--z-modal` or a +// `--duration-fast` is authored through `theme.customVars` now. +// +// Removing the DEAD EMISSION side in `ThemeEngine.generateThemeVars()` is +// objectui#3361 and deliberately NOT done here; this is only the type surface +// that stopped compiling, which that card pre-declared would ride with the +// dependency refresh. /** * Theme Mode diff --git a/packages/types/src/zod/index.zod.ts b/packages/types/src/zod/index.zod.ts index 02192b2ead..e7cfd066f9 100644 --- a/packages/types/src/zod/index.zod.ts +++ b/packages/types/src/zod/index.zod.ts @@ -286,8 +286,6 @@ export { TypographySchema, BorderRadiusSchema, ShadowSchema, - AnimationSchema, - ZIndexSchema, ThemeModeSchema, ThemeDefinitionSchema, ThemeComponentSchema, diff --git a/packages/types/src/zod/objectql.zod.ts b/packages/types/src/zod/objectql.zod.ts index abdd13010c..a4a56627bd 100644 --- a/packages/types/src/zod/objectql.zod.ts +++ b/packages/types/src/zod/objectql.zod.ts @@ -23,7 +23,7 @@ import { CalendarConfigSchema as SpecCalendarConfigSchema, GalleryConfigSchema as SpecGalleryConfigSchema, TimelineConfigSchema as SpecTimelineConfigSchema, - HttpMethodSchema as SpecHttpMethodSchema, + HttpMethodSubsetSchema as SpecHttpMethodSubsetSchema, HttpRequestSchema as SpecHttpRequestSchema, ViewDataSchema as SpecViewDataSchema, ListColumnSchema as SpecListColumnSchema, @@ -37,8 +37,14 @@ import { BaseSchema, specFieldsExcept } from './base.zod.js'; /** * HTTP Method Schema — `@objectstack/spec/ui` schema re-exported by reference * (issue #2231; formerly a hand-written mirror). + * + * The spec renamed the 5-value subset to `HttpMethodSubsetSchema` in + * 17.0.0-rc.5 (objectstack#5832) to stop it colliding with the 7-value + * `HttpMethod` in the published JSON Schema. The runtime domain is unchanged, + * so this repo keeps exporting it under the `HttpMethodSchema` name — following + * the rename WITHOUT changing cross-package semantics (objectui#3499). */ -export const HttpMethodSchema = SpecHttpMethodSchema; +export const HttpMethodSchema = SpecHttpMethodSubsetSchema; /** * HTTP Request Schema — `@objectstack/spec/ui` schema re-exported by reference diff --git a/packages/types/src/zod/theme.zod.ts b/packages/types/src/zod/theme.zod.ts index 689c66c6ba..5b979363eb 100644 --- a/packages/types/src/zod/theme.zod.ts +++ b/packages/types/src/zod/theme.zod.ts @@ -22,8 +22,6 @@ import { TypographySchema as SpecTypographySchema, BorderRadiusSchema as SpecBorderRadiusSchema, ShadowSchema as SpecShadowSchema, - AnimationSchema as SpecAnimationSchema, - ZIndexSchema as SpecZIndexSchema, ThemeModeSchema as SpecThemeModeSchema, ThemeSchema as SpecThemeSchema, } from '@objectstack/spec/ui'; @@ -53,19 +51,10 @@ export const BorderRadiusSchema = SpecBorderRadiusSchema; */ export const ShadowSchema = SpecShadowSchema; -/** - * Animation Schema — `@objectstack/spec/ui` schema re-exported by reference - * (issue #2231; formerly a hand-written mirror). The mirror's `timing` keys had - * drifted to camelCase (`easeIn`); the spec — and the runtime consumer - * (`usePageTransition`) — use snake_case (`ease_in`), which now applies here too. - */ -export const AnimationSchema = SpecAnimationSchema; - -/** - * Z-Index Schema — `@objectstack/spec/ui` schema re-exported by reference - * (issue #2231; formerly a hand-written mirror). - */ -export const ZIndexSchema = SpecZIndexSchema; +// `AnimationSchema` / `ZIndexSchema` RETIRED in @objectstack/spec 17.0.0-rc.3 +// (objectstack#5021 option 2, PR objectstack#5289). `theme.animation` and +// `theme.zIndex` are tombstones now and the spec deleted both value schemas +// outright; `theme.customVars` is the declared door. See `../theme.ts`. /** * Theme Mode Schema — `@objectstack/spec/ui` schema re-exported by reference @@ -139,8 +128,6 @@ export type ColorPaletteSchemaType = z.infer; export type TypographySchemaType = z.infer; export type BorderRadiusSchemaType = z.infer; export type ShadowSchemaType = z.infer; -export type AnimationSchemaType = z.infer; -export type ZIndexSchemaType = z.infer; export type ThemeModeSchemaType = z.infer; export type ThemeDefinitionSchemaType = z.infer; export type ThemeComponentSchemaType = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5347fb6865..93df51335e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,8 +35,8 @@ importers: specifier: ^10.0.1 version: 10.0.1(eslint@10.8.0(jiti@2.7.0)) '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@playwright/test': specifier: ^1.62.1 version: 1.62.1 @@ -270,11 +270,11 @@ importers: specifier: workspace:* version: link:../../packages/types '@objectstack/client': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@tailwindcss/postcss': specifier: ^4.3.3 version: 4.3.3 @@ -405,8 +405,8 @@ importers: specifier: workspace:* version: link:../../packages/types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) fumadocs-core: specifier: 16.14.0 version: 16.14.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.28.0(react@19.2.8))(next@16.2.12(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)(zod@4.4.3) @@ -694,14 +694,14 @@ importers: specifier: workspace:* version: link:../types '@objectstack/formula': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@objectstack/lint': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@sentry/react': specifier: ^10.69.0 version: 10.69.0(react@19.2.8) @@ -791,8 +791,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) better-auth: specifier: ^1.6.25 version: 1.6.25(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(mongodb@7.2.0)(next@16.2.12(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) @@ -878,8 +878,8 @@ importers: version: link:../types devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@types/react': specifier: 19.2.18 version: 19.2.18 @@ -914,8 +914,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@radix-ui/react-accordion': specifier: ^1.2.20 version: 1.2.20(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -1098,11 +1098,11 @@ importers: specifier: workspace:* version: link:../types '@objectstack/formula': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) lodash: specifier: ^4.18.1 version: 4.18.1 @@ -1160,11 +1160,11 @@ importers: specifier: workspace:* version: link:../types '@objectstack/client': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) devDependencies: tsup: specifier: ^8.5.1 @@ -1197,8 +1197,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1305,8 +1305,8 @@ importers: version: 3.6.0 devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@vitejs/plugin-react': specifier: ^6.0.5 version: 6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) @@ -1330,8 +1330,8 @@ importers: version: link:../types devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@types/react': specifier: 19.2.18 version: 19.2.18 @@ -1505,8 +1505,8 @@ importers: version: 3.10.1(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@19.2.6)(react@19.2.8)(redux@5.0.1) devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@types/react': specifier: 19.2.18 version: 19.2.18 @@ -1547,8 +1547,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@radix-ui/react-slot': specifier: ^1.3.3 version: 1.3.3(@types/react@19.2.18)(react@19.2.8) @@ -1663,8 +1663,8 @@ importers: version: 3.6.0 devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@types/react-grid-layout': specifier: ^2.1.0 version: 2.1.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -1772,8 +1772,8 @@ importers: specifier: workspace:* version: link:../i18n '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) lucide-react: specifier: ^1.28.0 version: 1.28.0(react@19.2.8) @@ -1891,8 +1891,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) lucide-react: specifier: ^1.28.0 version: 1.28.0(react@19.2.8) @@ -1946,8 +1946,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) lucide-react: specifier: ^1.28.0 version: 1.28.0(react@19.2.8) @@ -2007,8 +2007,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@tanstack/react-virtual': specifier: ^3.14.9 version: 3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -2148,8 +2148,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@types/react': specifier: 19.2.18 version: 19.2.18 @@ -2187,8 +2187,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) lucide-react: specifier: ^1.28.0 version: 1.28.0(react@19.2.8) @@ -2337,8 +2337,8 @@ importers: version: 3.6.0 devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@types/node': specifier: ^26.1.2 version: 26.1.2 @@ -2382,8 +2382,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -2437,8 +2437,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) lucide-react: specifier: ^1.28.0 version: 1.28.0(react@19.2.8) @@ -2501,8 +2501,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -2535,8 +2535,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) react: specifier: 19.2.8 version: 19.2.8 @@ -2569,8 +2569,8 @@ importers: specifier: workspace:* version: link:../types '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) react: specifier: 19.2.8 version: 19.2.8 @@ -2643,8 +2643,8 @@ importers: version: 1.0.7(tailwindcss@4.3.3) devDependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) '@tailwindcss/postcss': specifier: ^4.3.3 version: 4.3.3 @@ -2682,14 +2682,14 @@ importers: specifier: workspace:* version: link:../react '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) packages/types: dependencies: '@objectstack/spec': - specifier: ^17.0.0-rc.2 - version: 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + specifier: ^17.0.0-rc.5 + version: 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) zod: specifier: ^4.4.3 version: 4.4.3 @@ -4036,26 +4036,26 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@objectstack/client@17.0.0-rc.2': - resolution: {integrity: sha512-tM4jm2R6UnR2+ymA94BSefco9DbrgeZFytL6b/mTVaUv7uy/joSZKWWrcEG5FiBzcyxJCevDvQixRHuiYzcHHg==} + '@objectstack/client@17.0.0-rc.5': + resolution: {integrity: sha512-E1wiOK90d6CbiYtbQ0157oA8BKTBnf6JfizZ0vcH7gh89rrCCeL6dIsmo4SQyOYh+ZWtBRJMttrhLCLm35LBXQ==} engines: {node: '>=22.0.0'} - '@objectstack/core@17.0.0-rc.2': - resolution: {integrity: sha512-5hAtKmjBbzEWA7qWXCN8WB12+BhGZ8jZYg7Y5r8PHYH+jOf5ZLX4KkD3bQw6oTUBpBhWclLVREKSlah9truGRA==} + '@objectstack/core@17.0.0-rc.5': + resolution: {integrity: sha512-kw8A9ERMscZwEnOXGDFmnUW0QdYcEhvsiHW6H4HgBQYJEqkbWXjBaS0ujbWxbn/hT5A+pEAiVQc2RcDVx/yHYA==} engines: {node: '>=22.0.0'} - '@objectstack/formula@17.0.0-rc.2': - resolution: {integrity: sha512-+4H3nESU3fH+pjjV4b1P8llvSPff/vQF0Th5/FdbZrYz0fKxoNNL+MtLBAzpu+ME3ZLFMwwsKRH7obPyCS9wmQ==} + '@objectstack/formula@17.0.0-rc.5': + resolution: {integrity: sha512-50DI3S/A8cyR4amwROxOcYD1r4om7bo/3Gf6MugEfWXfejzLpc9EUS3maIlYiW0orAmo20SHA81Q4cB+qGUxGw==} - '@objectstack/lint@17.0.0-rc.2': - resolution: {integrity: sha512-S571+BtsXhyBpfZSNh9Ic0ljH8cVKyhgaVbQjqPc0ICgzvwh9/b4PEB7ElbgWUvTNje2KOriLgCjgNGGnTGfuw==} + '@objectstack/lint@17.0.0-rc.5': + resolution: {integrity: sha512-0huaGN11ub7iqjuRzmiyUoh0Sju1U8tpVkp062ilOOF2bf0xzxgSVUdQrJlFNjV4+gzLJnkbqha1/4Y8Tr4dNA==} engines: {node: '>=22.0.0'} - '@objectstack/sdui-parser@17.0.0-rc.2': - resolution: {integrity: sha512-Xc2ftxDFaDWyKmPWh+nAhgrMXmakKEXACdU/0fTrWertmfv9vRSrKomdCgFSvLksMxS/JcDT2O9EkMxGMQbj2g==} + '@objectstack/sdui-parser@17.0.0-rc.5': + resolution: {integrity: sha512-gi61YonxZnXQ5gPMa7CShz8d3Yg1ZGYC24jbDtOerbgOlBowc36Qrp3UKIL2JjW6PBQJJxUY+Y0h4lDwAEs/BA==} - '@objectstack/spec@17.0.0-rc.2': - resolution: {integrity: sha512-gvybztXuGTaqAlUzSOP4xeTpjl44ty2RZ67JQfiCma/engMG8pKqfv4/18nYitFktiWUVLsr2U6AquZAidmcRQ==} + '@objectstack/spec@17.0.0-rc.5': + resolution: {integrity: sha512-9AFBwdwCaFPUFxGAShRMvW3UQAW+Gz0ET1CEJEHL5f2Qh/Bu7hGMBT7NY8fhYqfajAroe08jZGx3JMOFgS2v5Q==} engines: {node: '>=22.0.0'} peerDependencies: ai: ^7.0.0 @@ -12616,41 +12616,42 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@objectstack/client@17.0.0-rc.2(ai@7.0.44(zod@4.4.3))': + '@objectstack/client@17.0.0-rc.5(ai@7.0.44(zod@4.4.3))': dependencies: - '@objectstack/core': 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) - '@objectstack/spec': 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + '@objectstack/core': 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) + '@objectstack/spec': 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) transitivePeerDependencies: - ai - '@objectstack/core@17.0.0-rc.2(ai@7.0.44(zod@4.4.3))': + '@objectstack/core@17.0.0-rc.5(ai@7.0.44(zod@4.4.3))': dependencies: - '@objectstack/spec': 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + '@objectstack/spec': 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) zod: 4.4.3 transitivePeerDependencies: - ai - '@objectstack/formula@17.0.0-rc.2(ai@7.0.44(zod@4.4.3))': + '@objectstack/formula@17.0.0-rc.5(ai@7.0.44(zod@4.4.3))': dependencies: '@marcbachmann/cel-js': 8.0.0 - '@objectstack/spec': 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + '@objectstack/spec': 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) transitivePeerDependencies: - ai - '@objectstack/lint@17.0.0-rc.2(ai@7.0.44(zod@4.4.3))': + '@objectstack/lint@17.0.0-rc.5(ai@7.0.44(zod@4.4.3))': dependencies: - '@marcbachmann/cel-js': 8.0.0 - '@objectstack/formula': 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) - '@objectstack/sdui-parser': 17.0.0-rc.2 - '@objectstack/spec': 17.0.0-rc.2(ai@7.0.44(zod@4.4.3)) + '@objectstack/formula': 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) + '@objectstack/sdui-parser': 17.0.0-rc.5 + '@objectstack/spec': 17.0.0-rc.5(ai@7.0.44(zod@4.4.3)) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) sucrase: 3.35.1 typescript: 6.0.3 transitivePeerDependencies: - ai - '@objectstack/sdui-parser@17.0.0-rc.2': {} + '@objectstack/sdui-parser@17.0.0-rc.5': {} - '@objectstack/spec@17.0.0-rc.2(ai@7.0.44(zod@4.4.3))': + '@objectstack/spec@17.0.0-rc.5(ai@7.0.44(zod@4.4.3))': dependencies: zod: 4.4.3 optionalDependencies: