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
58 changes: 58 additions & 0 deletions .changeset/reclaim-natural-gesture-names-3363.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
"@object-ui/types": minor
"@object-ui/mobile": minor
---

Reclaim the natural names `GestureType` and `GestureConfig` (objectui#3363).

`@objectstack/spec` 17.0.0-rc.3 deleted the whole `ui/touch` module
(objectstack#4988, PR objectstack#5321), vacating three names objectui had
renamed **away from** in objectstack#4115 purely to avoid a collision. Two of
those workarounds have now outlived their reason and are undone.

## Breaking, in FROM → TO form

- `TouchGestureType` → **`GestureType`** — objectui's direction-fused recogniser
vocabulary (`tap`, `swipe-left`, `swipe-up`, …).
- `TouchGestureConfig` → **`GestureConfig`** — the flat gesture→`action` handler
binding.

Both are exported from `@object-ui/types` and re-exported by `@object-ui/mobile`.
Nothing about either shape changed: same members, same optionality. Consumers
import the new name; there is no other edit.

**The old names are gone, not deprecated.** This follows the precedent set by the
objectstack#4115 rename batch that introduced them, whose own migration note reads:
"an alias would preserve exactly the ambiguity being removed". A deprecated alias
would be worse here than in the general case, because the ambiguity these renames
exist to prevent is between two same-named types — leaving `TouchGestureType`
alive next to `GestureType` restores the two-spellings-one-concept problem while
claiming to retire it.

The retired spec vocabulary that used to hold these names still lives in
`@object-ui/types`' `mobile` module under its deliberate `Spec…` prefix
(`SpecGestureType`, `SpecGestureConfig`, `SwipeGestureConfig`, …), and that prefix
is untouched — it is now the only thing distinguishing the two contracts, so
`useSpecGesture` still maps one onto the other exactly as before.

## `PWAOfflineConfig` is deliberately NOT reclaimed

The spec vacated `OfflineConfig` in the same retirement, but the spec was never
its only claimant: that rename was a **cross-package arbitration between two
objectui packages**, and `@object-ui/react` won it. `useOffline`'s config is the
offline data/sync model key for key, so it holds the bare `OfflineConfig`, while
this package's service-worker route cache stays `PWAOfflineConfig`
(objectui#3156 / objectui#3159).

Before objectui#3560 that name reached `@object-ui/react` from the spec, so the
spec-side tripwire covered it by accident. Since the retirement it is declared
locally in `packages/react/src/hooks/useOffline.ts`, which means the spec's
vacancy no longer says anything about whether the name is free — it is not.
Reclaiming it would put two different `OfflineConfig` shapes on the public
surface of two packages that are routinely imported together, which is the exact
ambiguity objectstack#4115 renamed it away from.

`page-nav-misc-spec-parity.test.ts` now pins that reason directly instead of
leaving it as prose: it asserts `@object-ui/react` still declares
`OfflineConfig`, and its failure message tells the next reader that the reclaim
has become available if it ever stops.
4 changes: 2 additions & 2 deletions packages/mobile/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ export type {
FetchCacheStrategy,
PWAOfflineConfig,
OfflineRoute,
TouchGestureType,
TouchGestureConfig,
GestureType,
GestureConfig,
GestureContext,
MobileComponentConfig,
SpecGestureConfig,
Expand Down
4 changes: 2 additions & 2 deletions packages/mobile/src/useGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*/

import { useEffect, useRef, useCallback } from 'react';
import type { TouchGestureType, GestureContext } from '@object-ui/types';
import type { GestureType, GestureContext } from '@object-ui/types';

export interface UseGestureOptions {
/** Gesture type to detect */
type: TouchGestureType;
type: GestureType;
/** Callback when gesture is detected */
onGesture: (context: GestureContext) => void;
/** Minimum distance for swipe detection (pixels) */
Expand Down
20 changes: 12 additions & 8 deletions packages/mobile/src/useSpecGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { useGesture } from './useGesture';
import type { TouchGestureType, SpecGestureConfig } from '@object-ui/types';
import type { GestureType, SpecGestureConfig } from '@object-ui/types';

export interface UseSpecGestureOptions {
/** Spec gesture configuration */
Expand All @@ -28,16 +28,19 @@ export interface UseSpecGestureOptions {
onGesture?: (context: { type: string; direction?: string; scale?: number; rotation?: number }) => void;
}

const SWIPE_DIRECTION_MAP: Record<string, TouchGestureType> = {
const SWIPE_DIRECTION_MAP: Record<string, GestureType> = {
left: 'swipe-left',
right: 'swipe-right',
up: 'swipe-up',
down: 'swipe-down',
};

/**
* Spec `GestureTypeSchema` (`ui/touch.zod.ts`) → the recognizer type
* `useGesture` implements. The spec's `drag` and `pan` are one recognizer
* `SPEC_GESTURE_TYPES` (the retired `ui/touch` vocabulary, owned by
* `@object-ui/types` since objectstack#4988) → the recognizer
* {@link GestureType} `useGesture` implements. Note the two sides are
* different vocabularies, which is why this map exists at all: the retired
* spec's `drag` and `pan` are one recognizer
* (any-direction move past the threshold); `swipe` resolves per configured
* direction, so it maps through {@link SWIPE_DIRECTION_MAP} instead.
* Exported for the spec-parity test.
Expand All @@ -47,7 +50,7 @@ const SWIPE_DIRECTION_MAP: Record<string, TouchGestureType> = {
* `pan` / `drag` / `rotate` / `double_tap` (types with no sub-object) all
* fell through to the `'tap'` initializer and fired on a tap.
*/
export const SPEC_GESTURE_TYPE_MAP: Record<string, TouchGestureType> = {
export const SPEC_GESTURE_TYPE_MAP: Record<string, GestureType> = {
swipe: 'swipe-left', // per-direction; resolved via SWIPE_DIRECTION_MAP
pinch: 'pinch',
long_press: 'long-press',
Expand All @@ -58,8 +61,9 @@ export const SPEC_GESTURE_TYPE_MAP: Record<string, TouchGestureType> = {
};

/**
* Spec-aware gesture hook that maps an @objectstack/spec GestureConfig
* to the existing useGesture hook.
* Spec-aware gesture hook that maps a {@link SpecGestureConfig} — the retired
* `@objectstack/spec` `ui/touch` shape, not this package's own
* `GestureConfig` — onto the existing useGesture hook.
*
* @example
* ```tsx
Expand Down Expand Up @@ -90,7 +94,7 @@ export function useSpecGesture<T extends HTMLElement = HTMLElement>(
? 'pinch'
: undefined;

let gestureType: TouchGestureType = 'tap';
let gestureType: GestureType = 'tap';
let threshold: number | undefined;
let longPressDuration: number | undefined;
let onGesture: (ctx: { direction?: string; scale?: number; rotation?: number }) => void = () => {};
Expand Down
82 changes: 62 additions & 20 deletions packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
* spec's export set, and every OLD name asserted still present — a rename
* whose reason has evaporated should give the natural name back.
*
* Two of those nine have since been given back: `GestureType` and
* `GestureConfig` were reclaimed in objectui#3363 after objectstack#4988
* deleted `ui/touch`. A third, `OfflineConfig`, was vacated by the spec too but
* deliberately NOT reclaimed — `@object-ui/react` owns that name in-repo, and
* that reason is pinned below rather than left as prose.
*
* Type-level assertions here are real gates: `tsconfig.test.json` compiles this
* file, unlike the package build (see its header for why that distinction was
* itself a bug once).
Expand All @@ -36,6 +42,7 @@ import { describe, it, expect } from 'vitest';
import { createRequire } from 'node:module';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import ts from 'typescript';
import type { z } from 'zod';
import { NavigationAreaSchema as SpecNavigationAreaSchema } from '@objectstack/spec/ui';
Expand Down Expand Up @@ -564,39 +571,74 @@ describe('renamed local dialects do not collide with a spec export (objectui#307
});

/**
* 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 TRIPWIRE FIRED, exactly as designed, and objectui#3363 acted on it.
* `@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 stopped owning all three.
*
* Two of the three were then RECLAIMED — `TouchGestureType` → `GestureType`
* and `TouchGestureConfig` → `GestureConfig` now carry the natural names in
* `@object-ui/types`' `mobile` module, so a workaround does not outlive its
* reason (objectui#3169). These rows keep asserting the same thing they did
* before the reclaim, and that is the point: they are now what makes the
* reclaimed names SAFE, not merely available.
*
* 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.
* The third did not move — see the `OfflineConfig` block below.
*/
it.each([
['GestureType', 'TouchGestureType'],
['GestureConfig', 'TouchGestureConfig'],
['OfflineConfig', 'PWAOfflineConfig'],
])(
'the spec has VACATED `%s``%s` may reclaim it (objectui#3363)',
(vacated) => {
'the spec still does not own `%s`, reclaimed from `%s` (objectui#3363)',
(reclaimed) => {
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);
`spec owns '${reclaimed}' again, and @object-ui/types now exports that ` +
`exact name — this is a live collision, not a latent one. Re-triage ` +
`(objectstack#4115): derive from the spec, or rename the local dialect back.`,
).not.toContain(reclaimed);
},
);

/**
* `OfflineConfig` is the one the spec vacated that objectui did NOT reclaim,
* and the reason is worth pinning rather than remembering: **the spec was
* never the only claimant**. That rename was a cross-package arbitration
* between two objectui packages, and `@object-ui/react` won it — its
* `useOffline` config IS the offline data/sync model, key for key, so it
* holds the bare name while this package's service-worker route cache stays
* `PWAOfflineConfig` (objectui#3156 / objectui#3159).
*
* Before objectui#3560 that name reached `@object-ui/react` from the spec, so
* the spec-side assertion above covered it by accident. Since the retirement
* it is declared locally, which is exactly why it needs its own pin: the
* spec's vacancy no longer says anything about whether the name is free. If
* `@object-ui/react` ever drops or renames `OfflineConfig`, this goes red and
* the reclaim genuinely IS available — that is the unlock signal, and it now
* points at the right repository instead of at the spec.
*/
it('`@object-ui/react` still owns `OfflineConfig`, so `PWAOfflineConfig` keeps its prefix (objectui#3363)', () => {
// A source read, not an import: `@object-ui/types` has zero deps and must
// not take one on `@object-ui/react`. Same instrument the sibling
// `spec-ui-schema-reexports.test.ts` uses to inspect source it cannot load.
const useOfflinePath = resolve(
dirname(fileURLToPath(import.meta.url)),
'../../../react/src/hooks/useOffline.ts',
);
const src = readFileSync(useOfflinePath, 'utf8');
expect(
/^export interface OfflineConfig\b/m.test(src),
`${useOfflinePath} no longer declares 'export interface OfflineConfig'. If the ` +
`name was dropped or moved, the cross-package reason for 'PWAOfflineConfig' is ` +
`gone — the spec vacated 'OfflineConfig' back in objectstack#4988, so the ` +
`natural name is now free and @object-ui/types' mobile module may reclaim it ` +
`(objectui#3363 reclaimed 'GestureType'/'GestureConfig' the same way).`,
).toBe(true);
});

it.each([
['UploadedFileMetadata', 'file-field VALUE payload, not the storage file record'],
['TouchGestureType', 'direction-fused recognizer vocabulary (`swipe-left`, …)'],
['TouchGestureConfig', 'gesture→action binding, not the spec per-gesture tuning'],
['PWAOfflineConfig', 'service-worker route caching, not the offline data model'],
['PageNodeRegion', 'region of the objectui page NODE, holding renderer nodes'],
['PageNodeRegionSchema', 'zod twin of PageNodeRegion'],
Expand Down
10 changes: 8 additions & 2 deletions packages/types/src/__tests__/spec-ui-schema-reexports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ const DROPPED_SCHEMA_EXPORTS = [
// Notifications
'NotificationSchema',
'NotificationConfigSchema',
'NotificationActionSchema',
// `NotificationActionSchema` removed (objectui#3362 residue, closed out on
// objectui#3363): objectstack#5015 / PR objectstack#5300 RETIRED
// `NotificationAction` from the spec outright. A deny-list entry for a name
// the spec no longer publishes asserts nothing about this package's decision
// — nothing could re-export it — so the row passed as a tautology.
'NotificationPositionSchema',
'NotificationSeveritySchema',
'NotificationTypeSchema',
Expand Down Expand Up @@ -93,7 +97,9 @@ const DROPPED_SCHEMA_EXPORTS = [
'WidgetColorVariantSchema',
// Sharing & Embedding
'SharingConfigSchema',
'EmbedConfigSchema',
// `EmbedConfigSchema` removed for the same reason as
// `NotificationActionSchema` above — `EmbedConfig` was retired by
// objectstack#5015 / PR objectstack#5300.
// View Configuration
'AddRecordConfigSchema',
'AppearanceConfigSchema',
Expand Down
8 changes: 6 additions & 2 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,12 @@ export type {
FetchCacheStrategy,
PWAOfflineConfig,
OfflineRoute,
TouchGestureType,
TouchGestureConfig,
// `GestureType` / `GestureConfig` reclaimed their natural names in
// objectui#3363 once `@objectstack/spec` deleted `ui/touch`
// (objectstack#4988). `PWAOfflineConfig` above deliberately did NOT — see
// its note in `./mobile`; `@object-ui/react`'s `useOffline` owns that name.
GestureType,
GestureConfig,
GestureContext,
MobileComponentConfig,
// The retired `@objectstack/spec/ui` touch vocabulary, now owned here —
Expand Down
Loading
Loading