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
76 changes: 76 additions & 0 deletions .changeset/7519-retire-mobile-residue-types.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 5 additions & 2 deletions packages/mobile/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions packages/mobile/src/useSpecGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ export const SPEC_GESTURE_TYPE_MAP: Record<string, GestureType> = {

/**
* 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
Expand Down
134 changes: 134 additions & 0 deletions packages/types/src/__tests__/mobile-residue-retired-7519.test.ts
Original file line number Diff line number Diff line change
@@ -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<number> = { 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);
});
});
21 changes: 16 additions & 5 deletions packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
});

Expand All @@ -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) => {
Expand Down
14 changes: 8 additions & 6 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Loading
Loading