Skip to content

feat: typed command results, batch 3 (clipboard, appstate) — Phase 2#937

Merged
thymikee merged 1 commit into
mainfrom
refactor/phase2-typed-results-batch3
Jun 29, 2026
Merged

feat: typed command results, batch 3 (clipboard, appstate) — Phase 2#937
thymikee merged 1 commit into
mainfrom
refactor/phase2-typed-results-batch3

Conversation

@thymikee

Copy link
Copy Markdown
Member

What

Phase 2 (typed results) batch 3: wire clipboard and appstate into the CommandResultMap spine as closed discriminated unions, grounded in the handlers' literal returns. Continues #913/#934.

command source result
clipboard src/core/dispatch.ts handleClipboardCommand union on action: read{ text }, write{ textLength, message }
appstate src/daemon/handlers/session-state.ts handleAppStateCommand union on platform: Apple (ios/macos) session state, or Android { package, activity }

Notable: handler-grounding caught a gap

The previous hand-written AppStateCommandResult mirror omitted the iOS-only device_udid / ios_simulator_device_set locators that the handler actually returns. The new closed contract includes them — a strictly more accurate public type.

Mechanics (same template as batches 1-2)

  • New src/contracts/clipboard.ts and src/contracts/app-state.ts (moved out of the open client-types.ts mirror, dropping the DaemonResponseData index signature).
  • CommandResultMap gains clipboard / appstate; client methods return CommandResult<'name'>.
  • Public export names preserved (ClipboardCommandResult, AppStateCommandResult re-exported via client-types.tsindex.ts) — no API break.
  • The parity test now pins all 12 migrated commands; the public-root export test gains clipboard/appstate samples.

Tighter type surfaced a latent test bug

Closing clipboard to a discriminated union exposed an unguarded .text/.textLength access in an Android integration test (previously masked by the Record index signature). Fixed with discriminant guards — exactly the kind of unsafe access the migration is meant to eliminate.

Verification

  • tsc --noEmit exit 0
  • oxfmt + oxlint --deny-warnings clean
  • fallow audit --base origin/main clean
  • vitest core/contracts/client/commands → 104 files / 791 tests pass
  • Layering Guard grep empty

Remaining for Phase 2

keyboard (platform×action union — needs the Android keyboard-state types) is the next typed-results batch; wait/alert are genuinely open (wait also has an internal name-collision; alert's iOS path returns a generic Record) and will become CommandRequestResult in the mirror-cleanup PR. Then (b) the TypedError graft and (c) deleting the remaining client-types.ts result mirror.

Wire two more commands into the CommandResultMap spine as closed shapes,
grounded in the handlers' literal returns:

- clipboard (src/core/dispatch.ts handleClipboardCommand) -> a discriminated
  union on `action`: read => { text }, write => { textLength, message }.
- appstate (src/daemon/handlers/session-state.ts handleAppStateCommand) -> a
  discriminated union on `platform`: Apple (ios/macos) session state — now
  including the iOS-only device_udid / ios_simulator_device_set locators the
  previous hand-written mirror OMITTED — or Android package/activity.

Both result types move from the open client-types.ts mirror (DaemonResponseData
& {…}) into new src/contracts/{clipboard,app-state}.ts and are wired through
CommandResult<'clipboard'> / CommandResult<'appstate'>. Public export names are
preserved (re-exported via client-types.ts -> index.ts), so no API break.

Tightening clipboard to a closed union surfaced an unguarded .text/.textLength
access in an Android integration test (previously masked by the Record index
signature); fixed with discriminant guards. The parity test now pins all 12
migrated commands; the public-root export test gains clipboard/appstate samples.

Verified: tsc --noEmit, oxfmt + oxlint --deny-warnings, fallow audit clean,
Layering Guard empty, 791 tests across core/contracts/client/commands pass.
@github-actions

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.4 MB 1.4 MB 0 B
JS gzip 450.7 kB 450.7 kB 0 B
npm tarball 553.6 kB 553.9 kB +281 B
npm unpacked 2.0 MB 2.0 MB +1.3 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.9 ms 27.4 ms +0.5 ms
CLI --help 47.3 ms 48.8 ms +1.5 ms

Top changed chunks: no changes in the largest emitted chunks.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed current head 71a2f3c. I checked the #913/#934 typed-results context, the diff, the clipboard and appstate handler return literals, and the command routing traits. No actionable blockers found: public export names are preserved, clipboard is correctly modeled as a read/write discriminated union, appstate matches the Apple-session vs Android-state handler branches including iOS locators, and all 21 checks are green. Marking ready-for-human.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Jun 29, 2026
@thymikee thymikee merged commit 41358fe into main Jun 29, 2026
21 checks passed
@thymikee thymikee deleted the refactor/phase2-typed-results-batch3 branch June 29, 2026 17:08
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-29 17:08 UTC

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant