diff --git a/.changeset/7519-retire-mobile-residue-types.md b/.changeset/7519-retire-mobile-residue-types.md new file mode 100644 index 0000000000..4c5ad7afb2 --- /dev/null +++ b/.changeset/7519-retire-mobile-residue-types.md @@ -0,0 +1,76 @@ +--- +'@object-ui/types': minor +'@object-ui/mobile': minor +--- + +**Removes two published exports.** Retire the `MobileResponsiveConfig` and +`GestureConfig` types (objectui#7519, ADR-0049 enforce-or-remove). Both names +are deleted from `@object-ui/types` and from `@object-ui/mobile`, which +re-exported them — after this release `import type { MobileResponsiveConfig }` +or `import type { GestureConfig }` from either package is a compile error, not a +deprecation warning. + +Each had exactly one consumer: the `responsive` and `gestures` members of +`MobileComponentConfig`, which objectui#5942 retired. Re-measured on current +`main` before anything was deleted, each was a declaration plus the two barrel +re-exports and nothing else — no type mounted either, nothing extended, +annotated, cast to or imported them outside the barrels, and the example apps +and the `objectstack` sibling checkout had zero authors. A value written against +either could not reach a renderer or a handler by any path. That is the same +declared-surface-with-no-consumption-path shape as `MobileComponentConfig` +itself and `MobileOverrides` (objectui#4919) before it, one level down. + +Removed outright rather than kept as `?: never` tombstones, measured against +this package's two-prong discriminator (a tombstone steers authors to a named +live replacement key, or keeps loud a key the docs taught as working). Prong 1: +neither has a replacement key — the behaviour they named lives in hooks, and +`SpecGestureConfig` is a different contract, not a successor. Prong 2: the only +release-note lines naming either are the objectstack#4115 rename-ledger rows +and, for `GestureConfig`, the objectui#3363 reclaim note; none taught a +renderer or dispatcher reading them, and no member carried a published +`@default` (contrast `triggerIcon`, tombstoned by objectui#7654 on exactly that +evidence). Structurally there is also no silent-strip hazard for a tombstone to +guard: whole interfaces go, nothing ever parsed them, and the mobile module has +never had a `zod/` twin to host a `retirementTombstone()`. The compiler was the +only channel these names ever had, and the refusal now lives there. + +## Upgrading + +**No behaviour changes and there is nothing to migrate at runtime.** An object +authored against either type did nothing before and does nothing now; what +changes is that the contract no longer claims otherwise, so the mistake surfaces +at authoring time instead of silently type-checking. + +- **You imported a type only** (the only thing that was possible — nothing + accepted either as a value): delete the import. If you kept a local object + annotated with it, drop the annotation; it was never passed anywhere that read + it. +- **You wanted per-breakpoint layout:** it exists and is not being retired — + `useResponsive` / `ResponsiveContainer` / `useBreakpoint` in + `@object-ui/mobile`. `ResponsiveValue` and `BreakpointName` stay exported from + both packages. +- **You wanted to bind a gesture to a handler:** `useGesture` in + `@object-ui/mobile` takes `{ type: GestureType, onGesture, threshold?, + longPressDuration?, enabled? }`. `GestureType` and `GestureContext` stay + exported from both packages. +- **You want a declarative mobile config surface:** that re-enters deliberately + as designed product surface on its own card, with the renderer that reads it + landing in the same change as the declaration — not by restoring these + declarations. + +**Do not follow the compiler's suggestion for `GestureConfig`.** Measured against +the built declarations: `import type { GestureConfig }` from either package now +fails as TS2724 with `Did you mean 'SpecGestureConfig'?`. That is a lexical +near-match, not a migration target. `SpecGestureConfig` is the retired +`@objectstack/spec` `ui/touch` **tuning** record (`{ type, label, enabled, +swipe, pinch, longPress }`) that `useSpecGesture` reads; it has no `action` +member and does not bind a gesture to anything. `MobileResponsiveConfig` fails +as a plain TS2305 with no suggestion from either package. + +Marked `minor`, not `major`, per this repo's version-alignment rule (AGENTS.md +版本号策略), which reserves `major` for following `@objectstack` across a major — +the same classification objectui#5942 and objectui#4919 used for identically +breaking type removals. **Breaking for TypeScript consumers of the two names +only.** The in-repo consumer count is zero; consumers outside this repository +that import either name from either package are not visible from here, which is +why this entry is graded on the published-surface change and not on that count. diff --git a/packages/mobile/src/index.ts b/packages/mobile/src/index.ts index d837454233..0f75b68efc 100644 --- a/packages/mobile/src/index.ts +++ b/packages/mobile/src/index.ts @@ -62,14 +62,17 @@ export { BREAKPOINTS, resolveResponsiveValue } from './breakpoints.js'; export type { BreakpointName, ResponsiveValue, - MobileResponsiveConfig, + // `MobileResponsiveConfig` and `GestureConfig` RETIRED outright by + // objectui#7519, from this barrel and from `@object-ui/types` in the same + // change — see the RETIRED notes in that package's `mobile.ts`. No behaviour + // went with them: the live per-breakpoint and gesture readers are the hooks + // exported above. PWAConfig, PWAIcon, FetchCacheStrategy, PWAOfflineConfig, OfflineRoute, GestureType, - GestureConfig, GestureContext, SpecGestureConfig, SwipeGestureConfig, diff --git a/packages/mobile/src/useSpecGesture.ts b/packages/mobile/src/useSpecGesture.ts index 5bf7fa9dbd..5d859d44a6 100644 --- a/packages/mobile/src/useSpecGesture.ts +++ b/packages/mobile/src/useSpecGesture.ts @@ -62,8 +62,9 @@ export const SPEC_GESTURE_TYPE_MAP: Record = { /** * 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. + * `@objectstack/spec` `ui/touch` tuning shape, a different contract from the + * direction-fused {@link GestureType} vocabulary `useGesture` speaks — onto + * the existing useGesture hook. * * @example * ```tsx diff --git a/packages/types/src/__tests__/mobile-residue-retired-7519.test.ts b/packages/types/src/__tests__/mobile-residue-retired-7519.test.ts new file mode 100644 index 0000000000..308a1a4bba --- /dev/null +++ b/packages/types/src/__tests__/mobile-residue-retired-7519.test.ts @@ -0,0 +1,134 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Retirement pin — `MobileResponsiveConfig` and `GestureConfig` (objectui#7519). + * + * Both were declared in `../mobile.ts` and published twice — re-exported by this + * package's root barrel AND by `@object-ui/mobile`'s — and each had exactly one + * consumer: the `responsive` / `gestures` member of `MobileComponentConfig`. + * objectui#5942 (PR #7526) retired that container, which left each of them as a + * declaration plus two barrel re-exports and nothing else. objectui#7519 removes + * the declarations and both re-exports — the route the container took. + * + * ## Why this pin is type-level ONLY, and why that is not a shortcut + * + * The sibling retirement pins in this directory have a second half: the Zod + * mirror refuses the retired key by name (`retirementTombstone()`), because under + * a non-strict `z.object` a deleted key is silently STRIPPED and only a tombstone + * turns that into a named refusal. ⛔ That half does not exist here and writing it + * would be a fabrication: `../mobile.ts` has never had a `zod/` twin — no mirror + * ever parsed either shape — so there is no parse verdict for this deletion to + * change. The only channel a consumer of either name ever had was the compiler, + * and that is the channel pinned: TS2305 / TS2724 at the import (TS2694 through + * the `import('…')` spelling below). Same shape as objectui#7654. + * + * ## How the `@ts-expect-error` lines stay honest + * + * Each directive sits on a line whose ONLY possible diagnostic is the missing + * export: the probe value is used, so no unused-local error can consume the + * directive by accident, and the literal would type-check cleanly if the + * declaration came back. Each retired-name probe is paired with a LIVE name + * reached through the identical `import('…')` spelling and no directive, so a + * broken specifier — which would satisfy the directive for the wrong reason + * (TS2307) — turns the control red instead. Real enforcement because + * `tsconfig.test.json` compiles this file under this package's `type-check` + * script (objectui#3009). Reverse-verified at the PR: with the declarations and + * barrel lines restored, `tsc -p tsconfig.test.json` reports TS2578 on exactly + * the four directive lines. + * + * The second `describe` reads the two barrels and the declaring file off disk — + * a source read, not an import, because `@object-ui/types` has zero deps and + * must not take one on `@object-ui/mobile` (the same instrument the + * `OfflineConfig` pin in `page-nav-misc-spec-parity.test.ts` uses). That leg is + * what keeps the `@object-ui/mobile` barrel honest, which the compiler leg + * cannot reach from this package. + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +const read = (rel: string): string => readFileSync(resolve(here, rel), 'utf8'); + +describe('objectui#7519 — the retired names are gone from the compile-time surface', () => { + it('`GestureConfig` no longer resolves from the root barrel or from `./mobile`', () => { + // @ts-expect-error RETIRED (objectui#7519): `@object-ui/types` no longer exports `GestureConfig` + const viaBarrel: import('../index.js').GestureConfig = { type: 'tap', action: 'noop' }; + // @ts-expect-error RETIRED (objectui#7519): `./mobile` no longer declares `GestureConfig` + const viaModule: import('../mobile.js').GestureConfig = { type: 'tap', action: 'noop' }; + expect(viaBarrel).toEqual(viaModule); + }); + + it('`MobileResponsiveConfig` no longer resolves from the root barrel or from `./mobile`', () => { + // @ts-expect-error RETIRED (objectui#7519): `@object-ui/types` no longer exports `MobileResponsiveConfig` + const viaBarrel: import('../index.js').MobileResponsiveConfig = { columns: 2 }; + // @ts-expect-error RETIRED (objectui#7519): `./mobile` no longer declares `MobileResponsiveConfig` + const viaModule: import('../mobile.js').MobileResponsiveConfig = { columns: 2 }; + expect(viaBarrel).toEqual(viaModule); + }); + + it('the neighbours the two stood beside still resolve through the same spelling (control)', () => { + // No directive on purpose: if `../index.js` or `../mobile.js` stopped + // resolving, the directives above would be satisfied by TS2307 for the wrong + // reason — these lines go red first. + const ctx: import('../index.js').GestureContext = { + type: 'tap', + startPosition: { x: 0, y: 0 }, + endPosition: { x: 0, y: 0 }, + distance: 0, + duration: 0, + velocity: 0, + }; + const value: import('../mobile.js').ResponsiveValue = { md: 2 }; + const kind: import('../index.js').GestureType = 'tap'; + expect(ctx.type).toBe(kind); + expect(value).toEqual({ md: 2 }); + }); +}); + +describe('objectui#7519 — the retired names are gone from both barrels and the declaring file', () => { + const typesBarrel = read('../index.ts'); + const mobileBarrel = read('../../../mobile/src/index.ts'); + const declaring = read('../mobile.ts'); + + // A re-export block entry: the bare name alone on its line, followed by a comma. + // `SpecGestureConfig,` cannot match `reexport('GestureConfig')` — the name must + // start right after the indent. + const reexport = (name: string): RegExp => new RegExp(`^\\s*${name},\\s*$`, 'm'); + const declaration = (name: string): RegExp => new RegExp(`^export (interface|type) ${name}\\b`, 'm'); + + it('`@object-ui/types` root barrel no longer re-exports either name (controls stay in)', () => { + expect(typesBarrel).not.toMatch(reexport('GestureConfig')); + expect(typesBarrel).not.toMatch(reexport('MobileResponsiveConfig')); + expect(typesBarrel).toMatch(reexport('GestureContext')); + expect(typesBarrel).toMatch(reexport('ResponsiveValue')); + }); + + it('`@object-ui/mobile` root barrel no longer re-exports either name (controls stay in)', () => { + expect(mobileBarrel).not.toMatch(reexport('GestureConfig')); + expect(mobileBarrel).not.toMatch(reexport('MobileResponsiveConfig')); + expect(mobileBarrel).toMatch(reexport('GestureContext')); + expect(mobileBarrel).toMatch(reexport('ResponsiveValue')); + }); + + it('`mobile.ts` no longer declares either name, and records why in a `//` note', () => { + expect(declaring).not.toMatch(declaration('GestureConfig')); + expect(declaring).not.toMatch(declaration('MobileResponsiveConfig')); + expect(declaring).toMatch(declaration('GestureContext')); + expect(declaring).toMatch(declaration('GestureType')); + // The retirement notes are `//` comments so declaration emit strips them — + // the objectui#5942 contract-review lesson: a JSDoc pointer into a `//` note + // survives into the published .d.ts while its target does not. They must + // still exist in SOURCE, one per retired name. + expect(declaring).toMatch(/^\/\/ RETIRED \(objectui#7519.*`MobileResponsiveConfig`/m); + expect(declaring).toMatch(/^\/\/ RETIRED \(objectui#7519.*`GestureConfig`/m); + }); +}); 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 541b2fb213..2f24edc3cf 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 @@ -38,6 +38,15 @@ * deliberately NOT reclaimed — `@object-ui/react` owns that name in-repo, and * that reason is pinned below rather than left as prose. * + * Two rows have since RETIRED WITH THEIR TYPE (objectui#7519): `GestureConfig` + * and `MobileResponsiveConfig` were each a declaration plus two barrel + * re-exports with no consumer once objectui#5942 removed their only mount + * point, and `@object-ui/types` no longer exports either name. A pin on a name + * this package does not hold guards nothing — the spec re-publishing + * `GestureConfig` or `ResponsiveConfig` would collide with no export here — so + * the rows went with the declarations rather than staying green as phantom + * checks. The absence itself is pinned in `mobile-residue-retired-7519.test.ts`. + * * 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). @@ -605,7 +614,8 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 ['FileMetadata', 'UploadedFileMetadata'], ['PageRegion', 'PageNodeRegion'], ['PageRegionSchema', 'PageNodeRegionSchema'], - ['ResponsiveConfig', 'MobileResponsiveConfig'], + // `['ResponsiveConfig', 'MobileResponsiveConfig']` left this list when the + // local dialect was RETIRED (objectui#7519) — see the header. // `WidgetManifest` / `WidgetSource` moved OUT of this list on the // 17.0.0-rc.6 bump — see the block below. The tripwire fired for the second // time, the same way objectui#3363 recorded the first. @@ -628,7 +638,8 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 * `@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. + * reclaimed names SAFE, not merely available. (`GestureConfig` has since been + * RETIRED outright — objectui#7519 — so only the `GestureType` row remains.) * * The third did not move — see the `OfflineConfig` block below. */ @@ -666,7 +677,7 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 it.each([ ['GestureType', 'TouchGestureType'], - ['GestureConfig', 'TouchGestureConfig'], + // `['GestureConfig', 'TouchGestureConfig']` retired with its type (objectui#7519). ])( 'the spec still does not own `%s`, reclaimed from `%s` (objectui#3363)', (reclaimed) => { @@ -737,7 +748,7 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 `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).`, + `(objectui#3363 reclaimed 'GestureType' the same way).`, ).toBe(true); }); @@ -746,7 +757,7 @@ describe('renamed local dialects do not collide with a spec export (objectui#307 ['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'], - ['MobileResponsiveConfig', 'mobile box config, not the spec SDUI grid contract'], + // `MobileResponsiveConfig` retired with its type (objectui#7519). ['RuntimeWidgetManifest', 'SDUI component manifest, not the field-widget plugin'], ['RuntimeWidgetSource', 'objectui module/inline/registry loader union'], ])('the spec does not own `%s` (%s)', (name) => { diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 1216ff51ca..e7b241ddc5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -616,18 +616,20 @@ export type { export type { BreakpointName, ResponsiveValue, - MobileResponsiveConfig, + // `MobileResponsiveConfig` RETIRED outright by objectui#7519 — see the + // RETIRED note in `./mobile`; its only consumer went with objectui#5942. PWAConfig, PWAIcon, FetchCacheStrategy, PWAOfflineConfig, OfflineRoute, - // `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` reclaimed its natural name in objectui#3363 once + // `@objectstack/spec` deleted `ui/touch` (objectstack#4988); `GestureConfig` + // took the same road and was then RETIRED outright by objectui#7519 — see + // the RETIRED note in `./mobile`. `PWAOfflineConfig` above deliberately did + // NOT reclaim — see its note in `./mobile`; `@object-ui/react`'s `useOffline` + // owns that name. GestureType, - GestureConfig, GestureContext, // The retired `@objectstack/spec/ui` touch vocabulary, now owned here — // see the "Spec Touch Vocabulary" note in `./mobile` (objectstack#4988). diff --git a/packages/types/src/mobile.ts b/packages/types/src/mobile.ts index 535a3f2179..7f291a61b0 100644 --- a/packages/types/src/mobile.ts +++ b/packages/types/src/mobile.ts @@ -71,53 +71,53 @@ export type BreakpointName = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; /** Responsive value - different values for different breakpoints */ export type ResponsiveValue = T | Partial>; -/** - * Responsive layout configuration for the mobile renderer's box layout. - * - * Renamed off the spec's `ResponsiveConfig` name (objectstack#4115): the two - * configure responsiveness through different vocabularies, so the bare name - * claimed an authority it did not have. - * - * ⚠️ The rename's second stated ground — that this package "already re-exports - * the spec's own under `SpecResponsiveConfig`" — expired at objectui#7580 and is - * recorded rather than silently dropped, because it is the kind of sentence a - * later reader mistakes for a live measurement. objectstack#11027 retired the - * spec `ResponsiveConfig` outright and that prefixed re-export went with its two - * readers. The rename still stands on the FIRST ground alone: the two - * vocabularies genuinely differ, as the paragraph below sets out key by key. - * - * The spec's `ResponsiveConfig` is the SDUI grid contract — - * `{ breakpoint, hiddenOn, columns: {xs..2xl}, order: {xs..2xl} }` — arranging a - * node within a grid. This one is the mobile renderer's box config: `columns` - * also accepts a bare number, plus `gap`, `padding`, `stackOnMobile` / - * `stackBreakpoint`, and `hidden`/`showOnly` in place of `hiddenOn`. - * - * It currently has NO consumer: its only one was retired by objectui#5942 (PR - * objectui#7526), so this type is still exported from `@object-ui/types` and - * `@object-ui/mobile` and is mounted nowhere. Whether it is retired in turn or - * given a renderer is open as objectui#7519 — a product call rather than a - * mechanical one, because the spec name-ownership tripwire named next outlives - * the type either way. - * - * Tripwire: `__tests__/page-nav-misc-spec-parity.test.ts` fails if the spec ever - * claims this name, so the alias cannot outlive its reason. - */ -export interface MobileResponsiveConfig { - /** Number of columns at each breakpoint */ - columns?: ResponsiveValue; - /** Whether to stack vertically on mobile */ - stackOnMobile?: boolean; - /** Breakpoint at which to stack */ - stackBreakpoint?: BreakpointName; - /** Gap between items at each breakpoint */ - gap?: ResponsiveValue; - /** Padding at each breakpoint */ - padding?: ResponsiveValue; - /** Whether to hide on specific breakpoints */ - hidden?: BreakpointName[]; - /** Whether to show only on specific breakpoints */ - showOnly?: BreakpointName[]; -} +// RETIRED (objectui#7519, ADR-0049 enforce-or-remove): `MobileResponsiveConfig` +// — the mobile renderer's box config (`columns` as a bare number or per +// breakpoint, `gap`, `padding`, `stackOnMobile` / `stackBreakpoint`, `hidden` / +// `showOnly`) — is gone, not narrowed. Its only consumer was the `responsive` +// member of `MobileComponentConfig`, retired below by objectui#5942 (PR +// objectui#7526); once that container went, the type was a declaration plus +// two barrel re-exports and nothing else. Re-measured before removal: no type +// mounted it, nothing extended, annotated, cast to or imported it outside the +// two barrels, and the example apps and the `objectstack` sibling checkout had +// zero authors. A value written against it could not reach a renderer by any +// path. +// +// Removed outright rather than tombstoned, measured against the two-prong +// discriminator the precedent changesets state (objectui#5941, #7526; the +// one-line form in the objectui#4919 note below is under correction as +// objectui#7678): a tombstone exists (1) to steer authors to a named live +// replacement KEY, or (2) to keep loud a key the docs taught as working. +// Prong 1: none — its distinctive keys (`stackOnMobile`, `showOnly`, +// `stackBreakpoint`) have zero readers outside this declaration, and no +// mounted type has carried a `responsive` member since objectui#5942. +// Prong 2: the only CHANGELOG lines naming it are the objectstack#4115 +// rename-ledger row (`ResponsiveConfig` -> `MobileResponsiveConfig`, "mobile +// box config"), replicated per package; no line taught a renderer reading it +// and no member carried a published `@default`. Whether a rename-ledger row +// counts as "taught as working" is recorded on objectui#7519 rather than +// decided here. Structurally there is no silent-strip hazard for prong 2 to +// guard: the whole interface goes, nothing ever parsed it, and there is no Zod +// mirror to host a `retirementTombstone()` — this module has never had a +// `zod/` twin — so the refusal is the compiler's own (TS2305 at the import), +// already loud. +// +// History kept because it explains the name: this was renamed off the spec's +// `ResponsiveConfig` in objectstack#4115 — the spec's is the SDUI grid contract +// (`{ breakpoint, hiddenOn, columns, order }`), a different vocabulary. The +// `__tests__/page-nav-misc-spec-parity.test.ts` rows that pinned the rename +// retired with the type: a name this package no longer exports cannot collide +// with anything, so a pin on it would guard nothing. The absence itself is +// pinned in `__tests__/mobile-residue-retired-7519.test.ts`. +// +// No BEHAVIOUR is retired here. Per-breakpoint layout lives in +// `@object-ui/mobile` as `useResponsive` / `ResponsiveContainer` / +// `useBreakpoint`, and `ResponsiveValue` above stays — `breakpoints.ts` and +// `useResponsive.ts` read it. +// +// Reopen condition: a declarative mobile box-layout surface re-enters as +// designed product surface on its own card, with the renderer that READS it +// landing in the same change as the declaration. // RETIRED (objectui#4919, maintainer ruling 2026-08-19, ADR-0049 // enforce-or-remove): the mobile component-override surface and its mount @@ -260,7 +260,8 @@ export interface OfflineRoute { * objectui#3363: `@objectstack/spec` owned `GestureType`, and the two unions * agree on only three members (the spec modelled gesture and direction * separately — `swipe | pinch | long_press | double_tap | drag | rotate | pan`, - * with direction inside its `GestureConfig.swipe.direction` — while objectui + * with direction inside its tuning record's `swipe.direction`, the shape now + * owned here as {@link SpecGestureConfig} — while objectui * folds direction into the name: `swipe-left`, `swipe-up`, …). Neither was a * subset of the other; objectui has `tap`, the spec had `drag`. * @@ -276,36 +277,56 @@ export interface OfflineRoute { */ export type GestureType ='tap' | 'double-tap' | 'long-press' | 'swipe-left' | 'swipe-right' | 'swipe-up' | 'swipe-down' | 'pinch' | 'rotate' | 'pan'; -/** - * Gesture handler configuration — binds one {@link GestureType} to an - * action name. - * - * Held the prefixed name `TouchGestureConfig` from objectstack#4115 until - * objectui#3363, for the same reason as its `type` field: the spec's - * `GestureConfig` was a per-gesture TUNING record (`{ type, label, enabled, - * swipe: { direction, threshold, velocity }, pinch: { minScale, maxScale }, - * longPress: { duration, moveTolerance } }`) with no notion of what the gesture - * DOES. This one is a handler binding: flat, and its whole point is `action`, - * which the spec's had no room for. That shape did not go away — it is - * {@link SpecGestureConfig} below, now owned by this package — but the spec no - * longer exports the bare name, so the dialect takes it back. - * - * Tripwire: `__tests__/page-nav-misc-spec-parity.test.ts`. - */ -export interface GestureConfig { - /** Gesture type */ - type: GestureType; - /** Action to execute */ - action: string; - /** Minimum distance for swipe gestures (pixels) */ - threshold?: number; - /** Duration for long-press (milliseconds) */ - duration?: number; - /** Whether to prevent default browser behavior */ - preventDefault?: boolean; - /** Whether gesture is enabled */ - enabled?: boolean; -} +// RETIRED (objectui#7519, ADR-0049 enforce-or-remove): `GestureConfig` — the +// flat handler binding `{ type: GestureType, action, threshold?, duration?, +// preventDefault?, enabled? }` — is gone, not narrowed. Its only consumer was +// the `gestures` member of `MobileComponentConfig`, retired below by +// objectui#5942 (PR objectui#7526); once that container went, the type was a +// declaration plus two barrel re-exports and nothing else. Re-measured before +// removal: `useGesture` reads `GestureType` and `GestureContext`, never this +// record, and nothing in this repo, the example apps or the `objectstack` +// sibling checkout annotated, cast to or imported it outside the two barrels. +// A binding written against it could not reach a handler by any path — +// `action` was a string nothing dispatched. +// +// Removed outright rather than tombstoned, measured against the two-prong +// discriminator the precedent changesets state (objectui#5941, #7526): a +// tombstone exists (1) to steer authors to a named live replacement KEY, or +// (2) to keep loud a key the docs taught as working. Prong 1: none — no +// dispatcher reads a gesture `action` (zero hits), and the only `gestures` key +// on any type is `TouchInteraction.gestures: SpecGestureConfig[]`, a different +// contract with no reader of its own. Prong 2: the CHANGELOG lines naming it +// are the objectstack#4115 rename-ledger row (`GestureConfig` -> +// `TouchGestureConfig`) and the objectui#3363 reclaim note ("the flat +// gesture->`action` handler binding ... nothing about either shape changed"); +// no line taught a dispatcher reading it, no `@example`, and no member carried +// a published `@default`. Whether a reclaim note counts as "taught as working" +// is recorded on objectui#7519 rather than decided here. Structurally there is +// no silent-strip hazard for prong 2 to guard: the whole interface goes, +// nothing ever parsed it (`skills/objectui/guides/mobile.md` teaches +// `useGesture`), and there is no Zod mirror to host a `retirementTombstone()` +// — this module has never had a `zod/` twin — so the refusal is the compiler's +// own (TS2724 at the import), already loud. The absence is pinned in +// `__tests__/mobile-residue-retired-7519.test.ts`. +// +// ⚠️ `SpecGestureConfig` below is NOT a successor. It is the retired +// `@objectstack/spec` `ui/touch` TUNING record (`{ type, label, enabled, swipe, +// pinch, longPress }`) that `useSpecGesture` reads, and it has no `action` +// member; a compiler "Did you mean" near-match on that name is lexical, not a +// migration target. What this type named — binding a gesture to a handler — +// lives in `@object-ui/mobile`'s `useGesture` options (`type` + `onGesture`). +// +// History kept because it explains the name: this held the prefixed name +// `TouchGestureConfig` from objectstack#4115 until objectui#3363 reclaimed the +// natural name once the spec vacated it (objectstack#4988). The +// `__tests__/page-nav-misc-spec-parity.test.ts` row that pinned the reclaim +// retired with the type: a name this package no longer exports cannot collide +// with anything, so a pin on it would guard nothing. `GestureType` keeps its +// row — it is live, read by `useGesture` and `useSpecGesture`. +// +// Reopen condition: a declarative gesture-binding surface re-enters as designed +// product surface on its own card, with the dispatcher that READS `action` +// landing in the same change as the declaration. /** Touch gesture context */ export interface GestureContext { @@ -380,10 +401,12 @@ export interface GestureContext { // The `Spec…` prefix on {@link SpecGestureConfig} is kept deliberately, and // objectui#3363 has now made it the ONLY thing carrying the distinction: the // sibling dialect above shed its own `Touch` prefix and is plain -// {@link GestureConfig} / {@link GestureType}. The two are still a DIFFERENT -// contract with different members (`swipe-left` vs `swipe` + a direction -// array), so both prefixed names below stay exactly as they are — dropping -// `Spec…` too would collapse the pair the rename just made legible. +// {@link GestureType}. (Its record half, `GestureConfig`, was retired outright +// by objectui#7519 — see the RETIRED note above. That does not free the +// prefix: `SpecGestureConfig` is still a DIFFERENT contract from the dialect's +// vocabulary — `swipe` + a direction array vs `swipe-left` — and dropping +// `Spec…` now would read as the retired name coming back under the spec's +// members.) Both prefixed names below stay exactly as they are. /** * Gesture kinds the retired `ui/touch` vocabulary recognised.