From 125643e5b77a4c156af864d8f6c5d859f4475724 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 02:06:58 +0000 Subject: [PATCH 1/2] feat(types,mobile)!: retire the MobileResponsiveConfig and GestureConfig published types Both were declared in packages/types/src/mobile.ts and published twice -- re-exported by the @object-ui/types root barrel and by @object-ui/mobile's -- and each had exactly one consumer: the `responsive` / `gestures` member of MobileComponentConfig, which objectui#5942 (PR #7526) retired. Re-measured on origin/main a472b071 before deleting: each name was a declaration plus two barrel re-exports and nothing else (read-shape probes over all tracked files, controls GestureType 36 / GestureContext 7 / ResponsiveValue 11 lit in the same run; objectstack sibling checkout 0 hits for both, control ComponentSchema 270; no package.json in that repo depends on @object-ui/*). Same route the container took: removed outright, not tombstoned. The whole interface goes, so there is no surviving object to hang a `?: never` key on; no documentation ever taught either; and there is no Zod mirror at all -- packages/types/src/zod/ has never had a mobile twin -- so the "deletion under a non-strict z.object is a silent strip" argument has no schema to apply to and the refusal is type-level only (TS2305 / TS2724 at the import), the objectui#7654 shape. Both barrels drop the names; a `//` RETIRED note replaces each declaration (line comments are stripped from declaration emit, the objectui#5942 contract-review lesson), and the surviving JSDoc on GestureType and the useSpecGesture hook no longer names the retired symbol, so both names occur zero times across every emitted .d.ts (rebuilt and measured; control GestureContext 2 / 3). The page-nav-misc-spec-parity rows that pinned the two names' spec non-ownership retire with the types: a name this package no longer exports cannot collide with anything, so a pin on it would guard nothing. The absence is pinned instead in mobile-residue-retired-7519.test.ts on the tsc channel (`@ts-expect-error` through the import('...') spelling, each paired with a live control through the same spelling) and by a source read of both barrels. Changeset: minor for @object-ui/types and @object-ui/mobile, graded on the published-surface change -- the in-repo consumer count is zero, consumers outside this repository are not visible from here. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 --- .../7519-retire-mobile-residue-types.md | 72 ++++++++ packages/mobile/src/index.ts | 7 +- packages/mobile/src/useSpecGesture.ts | 5 +- .../mobile-residue-retired-7519.test.ts | 134 ++++++++++++++ .../page-nav-misc-spec-parity.test.ts | 21 ++- packages/types/src/index.ts | 14 +- packages/types/src/mobile.ts | 164 +++++++++--------- 7 files changed, 320 insertions(+), 97 deletions(-) create mode 100644 .changeset/7519-retire-mobile-residue-types.md create mode 100644 packages/types/src/__tests__/mobile-residue-retired-7519.test.ts diff --git a/.changeset/7519-retire-mobile-residue-types.md b/.changeset/7519-retire-mobile-residue-types.md new file mode 100644 index 0000000000..d15c676837 --- /dev/null +++ b/.changeset/7519-retire-mobile-residue-types.md @@ -0,0 +1,72 @@ +--- +'@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, on this package's +own discriminator: a tombstone steers authors to a named live replacement key or +keeps loud a key the docs taught as working, and neither applies — whole +interfaces go, so there is no surviving object to hang a `never` key on, and no +documentation ever described either (`skills/objectui/guides/mobile.md` teaches +the hooks). Nor is there a Zod mirror to host a `retirementTombstone()`: the +mobile module has never had a `zod/` twin, so the "deletion under a non-strict +`z.object` is a silent strip" argument has no schema to apply to. 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..f9d44cb70d 100644 --- a/packages/types/src/mobile.ts +++ b/packages/types/src/mobile.ts @@ -71,53 +71,42 @@ 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, on the same discriminator the +// container used: a `?: never` tombstone steers authors to a named live +// replacement KEY or keeps loud a key the docs taught as working, and neither +// applies — the whole interface goes, so there is no surviving object to hang +// a `never` key on, and no documentation ever described it. Nor is there a Zod +// mirror to host a `retirementTombstone()`: this module has never had a `zod/` +// twin, so the refusal is type-level only (TS2305 / TS2724 at the import). +// +// 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 +249,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 +266,44 @@ 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, on the same discriminator the +// container used: the whole interface goes, so there is no surviving object to +// hang a `?: never` key on; no documentation ever taught 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 type-level only (TS2305 / TS2724 at the +// import). 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 +378,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. From 45df4a2ec73150e8216337ac361f20144a68fcbf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 02:14:39 +0000 Subject: [PATCH 2/2] docs(types): state the two-prong retire-vs-remove measurement per retired type The RETIRED notes and the changeset said the two-prong discriminator did not apply; the PM's mid-task correction (objectui#7678) asks for the measurement per key instead. Prong 1 (named live replacement key): none for either -- MobileResponsiveConfig's distinctive keys have zero readers outside the declaration, no dispatcher reads a gesture `action`, and TouchInteraction.gestures is SpecGestureConfig[], a different contract. Prong 2 (advertised as working): the only CHANGELOG lines are the objectstack#4115 rename-ledger rows (6 / 6, replicated per package) and, for GestureConfig, the objectui#3363 reclaim note (4 lines); none teaches a renderer or dispatcher reading them and no member carried a published @default. Controls in the same run: triggerIcon 1 (tombstoned on prong 2), striped 19; MobileComponentConfig / MobileOverrides 0 (removed outright). Whether a rename-ledger row or a reclaim note counts as "taught as working" is recorded on objectui#7519 for the docket, not decided in the comment. The objectui#4919 note (the one-line form under correction as objectui#7678) is untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 --- .../7519-retire-mobile-residue-types.md | 22 ++++---- packages/types/src/mobile.ts | 51 ++++++++++++++----- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/.changeset/7519-retire-mobile-residue-types.md b/.changeset/7519-retire-mobile-residue-types.md index d15c676837..4c5ad7afb2 100644 --- a/.changeset/7519-retire-mobile-residue-types.md +++ b/.changeset/7519-retire-mobile-residue-types.md @@ -20,15 +20,19 @@ 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, on this package's -own discriminator: a tombstone steers authors to a named live replacement key or -keeps loud a key the docs taught as working, and neither applies — whole -interfaces go, so there is no surviving object to hang a `never` key on, and no -documentation ever described either (`skills/objectui/guides/mobile.md` teaches -the hooks). Nor is there a Zod mirror to host a `retirementTombstone()`: the -mobile module has never had a `zod/` twin, so the "deletion under a non-strict -`z.object` is a silent strip" argument has no schema to apply to. The compiler -was the only channel these names ever had, and the refusal now lives there. +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 diff --git a/packages/types/src/mobile.ts b/packages/types/src/mobile.ts index f9d44cb70d..7f291a61b0 100644 --- a/packages/types/src/mobile.ts +++ b/packages/types/src/mobile.ts @@ -83,13 +83,24 @@ export type ResponsiveValue = T | Partial>; // zero authors. A value written against it could not reach a renderer by any // path. // -// Removed outright rather than tombstoned, on the same discriminator the -// container used: a `?: never` tombstone steers authors to a named live -// replacement KEY or keeps loud a key the docs taught as working, and neither -// applies — the whole interface goes, so there is no surviving object to hang -// a `never` key on, and no documentation ever described it. Nor is there a Zod -// mirror to host a `retirementTombstone()`: this module has never had a `zod/` -// twin, so the refusal is type-level only (TS2305 / TS2724 at the import). +// 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 @@ -278,13 +289,25 @@ export type GestureType ='tap' | 'double-tap' | 'long-press' | 'swipe-left' | 's // A binding written against it could not reach a handler by any path — // `action` was a string nothing dispatched. // -// Removed outright rather than tombstoned, on the same discriminator the -// container used: the whole interface goes, so there is no surviving object to -// hang a `?: never` key on; no documentation ever taught 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 type-level only (TS2305 / TS2724 at the -// import). The absence is pinned in `__tests__/mobile-residue-retired-7519.test.ts`. +// 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,