Skip to content

finding(mobile): useSpecGesture's own @example passes a scalar swipe.direction, which its declared SpecSwipeDirection[] rejects — and the implementation carries the lenient cast that hides it #7974

Description

@baozhoutao

Found while repairing packages/mobile/README.md for the objectui#5174 UNGATED_DOCS burn-down (batch 14). Out of that PR's scope — its file surface is the README plus the ledger removal — so it is filed here instead of ridden along.

The two halves

1. The shipped @example does not compile. packages/mobile/src/useSpecGesture.ts (around line 70) documents:

config: { type: 'swipe', enabled: true, swipe: { direction: 'left', threshold: 80 } },

SwipeGestureConfig.direction is declared in @object-ui/types as SpecSwipeDirection[] — an ARRAY:

export interface SwipeGestureConfig {
    direction: SpecSwipeDirection[];
    threshold?: number;
    velocity?: number;
}

A reader who copies that example gets TS2322 ('left' is not assignable to SpecSwipeDirection[]). Nothing catches it today: check-doc-snippet-types.mjs reads fenced blocks in Markdown documents, not JSDoc @example blocks in .ts sources, so this example has never been compiled by anything.

2. The implementation reads BOTH shapes, which is why nobody noticed. Same file, the 'swipe' case:

const dir = Array.isArray(config.swipe?.direction)
  ? config.swipe?.direction[0]
  : (config.swipe?.direction as string | undefined);

That is a consumer-side tolerant fallback of exactly the class AGENTS.md commandment #0.1 bans: the declared contract says array, the runtime silently accepts a scalar through an as cast, and the wrong example therefore "works" when hand-tested. Declared-and-not-enforced in one direction, enforced-and-not-declared in the other.

Why the two halves are one card

Repairing only the example leaves the lenient cast, which will let the next author write the scalar again. Removing only the cast turns the shipped example from "will not compile" into "will not compile AND would have thrown"; the example has to move in the same change. Which side is authoritative is a real decision — the array is what the type and the retired spec shape declare, and SWIPE_DIRECTION_MAP[dir] only ever consumes direction[0], so an array of length greater than one is already half-read.

Repro

# the declared type
sed -n '/export interface SwipeGestureConfig/,/^}/p' packages/types/dist/mobile.d.ts
# the example and the cast
sed -n '62,80p;100,112p' packages/mobile/src/useSpecGesture.ts

Not asserted here

  • Whether any caller in this repo or in examples/ passes a scalar today (not measured).
  • Whether @example blocks in .ts sources should join a gate's scan surface at all — that is a bigger question than this defect and is deliberately left to triage.

Unassigned and unlabelled on purpose: triage owns the priority. Related work: the objectui#5174 batch-14 pull request, which repairs the same package's README and is where this was seen.

Filed by an agent seat while working the batch-14 dispatch; see that pull request for the measurement context.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanedomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingneeds-user-decisionpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions