Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .changeset/objectstack-family-rc5-refresh.md
Original file line number Diff line number Diff line change
@@ -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
`--<key>: <value>`, 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.
4 changes: 2 additions & 2 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions packages/app-shell/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/collaboration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
71 changes: 70 additions & 1 deletion packages/core/src/protocols/DndProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 47 additions & 6 deletions packages/core/src/protocols/KeyboardProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading