Skip to content

feat: Apple TV support#76

Open
douglowder wants to merge 20 commits into
EvanBacon:mainfrom
douglowder:tvos
Open

feat: Apple TV support#76
douglowder wants to merge 20 commits into
EvanBacon:mainfrom
douglowder:tvos

Conversation

@douglowder

@douglowder douglowder commented May 26, 2026

Copy link
Copy Markdown

serve-sim for Apple TV (tvOS)

The npx serve of Apple TV simulators.

Drive a booted tvOS simulator from the browser or the CLI — focus navigation, the Siri remote's discrete buttons, long presses, and text input all over the same MJPEG stream + WebSocket channel that powers the iOS preview.

npx serve-sim --tv
# → Preview at http://localhost:3200

tvOS support is opt-in via a top-level --tv flag. Apple TV simulators don't share much input surface with iOS (no touchscreen, different button transports), so serve-sim's iOS-default behaviors stay iOS-only unless --tv is passed.

Features

  • Full streamed preview of the 1080p Apple TV framebuffer.
  • Siri remote: directional pad, select, menu, the round Apple TV / Home button, play/pause, volume, Siri.
  • Long-press support (--hold [ms]) for the buttons that have a discrete tvOS long-press gesture.
  • Web-preview keyboard: arrows + Enter + Escape + media keys drive the remote; every other key types into the focused tvOS text field.
  • Same serve-sim type "..." CLI as iOS, for scripted text input.
  • 4K-at-native-resolution sims auto-filtered to keep the encoder happy.

Quick start

  1. Boot an Apple TV simulator (Xcode → Open Developer Tool → Simulator, or xcrun simctl boot "Apple TV (3rd generation)" etc.).

  2. Start serve-sim with --tv:

    npx serve-sim --tv                  # foreground preview at http://localhost:3200
    npx serve-sim --tv --detach -q      # background daemon; returns JSON
    npx serve-sim --tv -d "Apple TV"    # pin a specific tvOS device
  3. Open the preview URL in your browser, or drive the sim from the CLI.

When auto-selecting, --tv prefers a 1080p Apple TV sim and skips full-resolution 4K variants — the 3840×2160 framebuffer chokes the H.264 encoder. The "(at 1080p)" 4K sims are kept because they output a 1080p framebuffer. Pass -d <name> to override.

Without --tv, findBootedDevice() still prefers an iOS sim, so existing iOS workflows are unaffected.

Siri-remote buttons

serve-sim button <name> [--hold [ms]] [-d udid]
Name Effect Transport
remote_up / remote_down / remote_left / remote_right Move focus one cell. Keyboard HID (arrows 0x52 / 0x51 / 0x50 / 0x4F)
remote_select Confirm focused item. Equivalent to clicking the Siri-remote touchpad. Keyboard HID (Return, 0x28)
remote_menu Step one screen back. Consumer Page (Menu Escape, 0x46)
remote_tv Round "Apple TV" / Home button — return to home screen. Consumer Page (AC Home, 0x223)
remote_play_pause Toggle video playback. Consumer Page (Play/Pause, 0xCD)
remote_volume_up / remote_volume_down Volume — accepted but the tvOS sim doesn't model speaker volume. Consumer Page (0xE9 / 0xEA)
remote_siri Open the voice-command UI. Consumer Page (Voice Command, 0xCF)

Why two transports

Directional keys + remote_select ride the keyboard HID path because tvOS's focus engine listens to USB-HID keyboard events natively. The discrete remote buttons (menu, tv, play_pause, volume_*, siri) go through IndigoHIDMessageForHIDArbitrary on the Consumer Page with target 0x15 (the tvOS remote routing target).

Long press

--hold [ms] holds the press for ms (default 1500) before releasing. Honored only by the buttons with a discrete tvOS long-press gesture; every other button silently ignores it.

Button Long-press effect
remote_select Focus context menu / wiggle mode
remote_play_pause Older Siri-remote sleep gesture and some app-specific behaviors
remote_menu Jump to the home screen instead of stepping one screen back
serve-sim button remote_select --hold        # 1500ms hold
serve-sim button remote_play_pause --hold 800

Web preview controls

When the preview is showing a tvOS sim, these keys map to Siri-remote events:

Key Sends
Arrow keys remote_up / down / left / right
Enter / Return remote_select
Escape remote_menu
PlayPause (Mac media key) remote_play_pause
AppleTV remote_tv

Every other key (letters, digits, Space, Backspace, Tab, …) is forwarded as a USB HID keyboard event, so it lands in whatever text field has focus on the sim — the on-screen keyboard in Settings, account fields, search bars, etc.

Mouse/touch forwarding on the stream surface is turned off automatically for tvOS. Apple TV has no touchscreen, and clicks were being interpreted by the helper's mouse-NSEvent path and bouncing the sim back to the home screen.

Typing text

Both paths reach a focused text field on tvOS:

  • The web preview forwards every non-navigation key as a USB HID keystroke.

  • serve-sim type "..." sends the same keystrokes from the CLI without needing the browser:

    serve-sim type "hello world"
    echo "from stdin" | serve-sim type --stdin
    serve-sim type --file ./snippet.txt

If a text field is not focused, the keystrokes still arrive at the helper but tvOS has nowhere to route them — navigate focus to the field with the remote buttons first.

End-to-end example

# 1. Confirm a tvOS sim is booted
xcrun simctl list devices booted | grep -i "Apple TV"

# 2. Start serve-sim with --tv
URL=$(npx serve-sim --tv --detach -q | jq -r '.url')
echo "preview: $URL"

# 3. Open the app you want to drive
xcrun simctl launch booted com.acme.media

# 4. Navigate with the remote
serve-sim button remote_down
serve-sim button remote_right
serve-sim button remote_select

# 5. Toggle playback or go home
serve-sim button remote_play_pause
serve-sim button remote_tv

# 6. Type into a focused text field
serve-sim type "search query"

# 7. Long-press for context menus / wiggle mode
serve-sim button remote_select --hold

# 8. Read the accessibility tree (works on tvOS)
curl -s http://localhost:3100/ax | jq '.children[0].AXLabel'

# 9. Cleanup
serve-sim --kill

What doesn't work on tvOS

  • serve-sim tap and serve-sim gesture — no touchscreen on Apple TV.
  • iOS hardware buttons (home, swipe_home, app_switcher, lock, siri, side_button) — they target iOS hardware buttons. Use remote_tv for home and remote_siri for Siri.
  • serve-sim rotate — Apple TV is always landscape.
  • serve-sim camera <bundle-id> — camera injection isn't supported on tvOS. tvOS apps rarely use AVFoundation, and the dylib injection path was validated against iOS sims only.
  • serve-sim permissions — the TCC database schema and push-notification state layout on tvOS differ from iOS. The subcommand was tested only against iOS.
  • remote_volume_up / remote_volume_down — the keystroke lands but the tvOS sim doesn't model speaker volume, so the press is a no-op.

How it works

┌──────────────┐   simctl io   ┌─────────────────┐  MJPEG / WS  ┌─────────┐
│ tvOS Simulator│ ──────────►  │ serve-sim-bin   │ ───────────► │ Browser │
│  (1080p)      │   (Swift)    │ (per-device)    │              └─────────┘
└──────────────┘               └─────────────────┘
                                       ▲
                                  state file in
                                $TMPDIR/serve-sim/
                                       ▲
                               ┌──────────────────┐
                               │ serve-sim CLI    │
                               └──────────────────┘

The same Swift helper drives every Apple simulator family. tvOS-specific input is layered on:

  • Button cases for remote_* in HIDInjector.sendButton.
  • A pressConsumer(usage:) helper that calls IndigoHIDMessageForHIDArbitrary(target=0x15, usagePage=0x0C, usage, direction) — the only transport that dispatches per-button Siri-remote events into the sim.
  • --hold support: the press / sleep / release sequence runs on a dedicated dispatch queue so the WebSocket handler thread isn't blocked.

See also

Summary by CodeRabbit

  • New Features

    • Opt-in Apple TV (tvOS) simulator support via a top-level --tv flag across commands.
    • Siri Remote support: navigation buttons, media/menu/actions, and discrete long-presses via --hold [ms].
    • Web preview maps keyboard keys to remote navigation and supports typing into focused text fields.
    • Device selection and display updated for Apple TV form factor.
  • Documentation

    • New tvOS guide, button reference, workflows, and explicit list of unsupported tvOS actions and behaviors.

Review Change Stack

douglowder and others added 15 commits May 24, 2026 17:16
Captures the survey of iOS-specific code in serve-sim, the open design
choices around Siri remote input transport and frame chrome, and a
phased implementation sketch. Posted for discussion before any code
lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Threads a `tv` option through findBootedDevice / pickDefaultDevice and
the follow/detach/serve entry points. When --tv is passed, the auto-
selection prefers booted tvOS sims and filters out the full-resolution
4K models to keep the streaming framebuffer at 1080p — the "(at 1080p)"
4K variants are kept since their framebuffer is 1080p.

iOS-default behavior is unchanged; --tv is opt-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the runtime allowlist regex in listAllSimulators() to include
tvOS, and add Apple TV / tvOS entries to deviceKind() and
runtimeOrder() so the picker groups and sorts them sensibly. Apple TV
falls after Vision Pro in the ordering — matches "less commonly used"
positioning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apple TV renders to a television, so the device frame has zero bezels
and no inner radius — the stream fills the panel. SIMULATOR_SCREENS
gains 1080p entries for plain "Apple TV" and the "(at 1080p)" 4K
variants so aspect ratios resolve correctly without the iPhone fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tvOS Siri-remote button cases to sendButton:

  - remote_up / down / left / right — USB HID Keyboard arrow usages
    (tvOS's focus engine listens to keyboard events directly)
  - remote_select                   — USB HID Keyboard Return (0x28)
  - remote_menu                     — Consumer Page Menu Escape (0x46)
  - remote_tv                       — Consumer Page AC Home (0x223)
  - remote_play_pause               — Consumer Page Play/Pause (0xCD)
  - remote_volume_up / volume_down  — Consumer Page Volume Inc/Dec
                                       (0xE9 / 0xEA)
  - remote_siri                     — Consumer Page Voice Command (0xCF)

The Consumer-page buttons go through IndigoHIDMessageForHIDArbitrary
on target 0x15 (the tvOS remote routing target) — the path
Simulator.app uses to translate Mac media keys into simulator events.
This is the only transport that actually dispatches discrete Siri-
remote button events; the IndigoHIDMessageForButton source codes
extracted from Simulator.app's `appleRemote*ButtonAction:` selectors
all funnel through a generic back/home path and do not address the
individual buttons.

Verified live on a booted "Apple TV 4K (3rd generation) (at 1080p)"
sim: select opens apps, menu steps back, tv goes home, play_pause
toggles video playback, siri opens the voice command UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SimulatorView gains a deviceType prop. When deviceType === "tv", a
window keydown listener maps ArrowKeys / Enter / Escape / PlayPause to
sendButton("remote_*"). client.tsx passes its existing deviceType
through so tvOS mode activates automatically when the picker lands on
an Apple TV.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends ButtonEventPayload with an optional holdMs field that plumbs
from the `serve-sim button --hold [ms]` CLI flag through the
WebSocket to the helper. The HIDInjector branches on holdMs in
pressKey / pressConsumer — when set, the press is dispatched on
buttonQueue so the press-sleep-release sequence doesn't block the
input handler. The CLI also defers closing the WebSocket until
hold-time-plus-50ms so the up event survives.

Long-press is only meaningful for tvOS gestures, so the flag is
honored by:
  - remote_select     (focus context menu / wiggle mode)
  - remote_play_pause (Siri remote info / sleep on older remotes)
  - remote_menu       (jump to home instead of stepping back)

Other buttons silently ignore holdMs. Default duration when --hold is
passed without an argument is 1500ms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the consolidated button mappings and --hold long-press
plumbing in HIDInjector.swift, ClientManager.swift, main.swift, and
Protocol.swift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues, one commit:

1. Clicking the streamed image was firing touch events into the sim,
   which on tvOS the helper still forwards via the mouse-NSEvent path.
   That input was being interpreted as something the focus engine
   couldn't handle, blanking the screen and bouncing back to home.
   Fix: set pointerEvents="none" on the input overlay when
   deviceType === "tv", since Apple TV has no touchscreen anyway.

2. Pressing Enter fired no remote_select. The root cause was a
   duplicate-keystroke race: client.tsx's generic keyboard pass-through
   (which sends every key as a USB HID keystroke for typing into iOS
   apps) was firing in parallel with SimulatorView's tvOS remote-button
   handler. Both routed Enter to keyboard usage 0x28 — two press
   events for the same key, which the focus engine dropped as a
   duplicate. Arrow keys tolerated the dup; Enter didn't.
   Fix: short-circuit the client.tsx pass-through when deviceType ===
   "tv". SimulatorView's remote handler is the single source of truth
   for tvOS navigation keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Apple TV / tvOS to the skill description and intro, extends the
common-operations table to cover --tv startup and the remote_*
button names + --hold flag, and adds a new "Apple TV (tvOS) support"
section that documents:

- Device selection (--tv flag, 1080p auto-pick)
- The Siri-remote button set and its two transport routes (keyboard
  HID for directional/select, Consumer Page via HIDArbitrary for the
  discrete remote buttons)
- Long-press behavior (--hold, default 1500ms)
- Web-preview keyboard map
- What's intentionally gated off on tvOS (mouse forwarding, keyboard
  pass-through) and why
- What doesn't work / wasn't tested (tap, gesture, iOS buttons,
  camera, permissions, rotation, audible volume)

Also updates the "do not invent button names" anti-pattern with the
tvOS button set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Threads tvOS coverage through the reference files that the SKILL.md
overview points at:

- buttons-rotation.md: full Siri-remote button table with effects and
  transports (keyboard HID vs Consumer-Page), a long-press section,
  and a tvOS-doesn't-rotate note in the rotation gotchas.
- gestures.md: tvOS note at the top — tap and gesture don't reach an
  Apple TV; redirect to the remote_* buttons.
- camera.md: tvOS callout — injection not supported / not validated.
- permissions.md: tvOS warning — TCC schema differs, subcommand
  tested only against iOS.
- endpoints.md: button event now documents the holdMs field, and the
  touch / multi-touch / orientation / keyboard rows are tagged with
  their tvOS applicability.
- workflows.md: new Workflow 8 — driving a tvOS sim end to end,
  including what NOT to do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier "gate the iOS keystroke pass-through off entirely for
tvOS" fix solved the duplicate-press race on Enter (3b2866c), but it
also blocked letter / digit / Space / Backspace keys from reaching
tvOS text-input modals — so the on-screen keyboard could only be
typed by selecting each character.

Narrow the gate: only the codes that SimulatorView's remote handler
owns (ArrowUp/Down/Left/Right, Enter/NumpadEnter, Escape) are
suppressed on tvOS. Every other key still passes through as a USB
HID keyboard event, which tvOS routes into the focused text field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier docs said the keyboard pass-through was fully gated off
for tvOS and that `type` was rarely useful — both no longer accurate
after the previous commit. Update:

- SKILL.md: the "Web preview keyboard map" section now notes that any
  non-navigation key is forwarded as a USB HID keystroke, and a new
  "Typing text" subsection covers both the web preview and `serve-sim
  type` as paths into a focused tvOS text field. The "doesn't work"
  list loses the `type` bullet.
- endpoints.md: the `0x06` (JSON keyboard event) bullet now describes
  the selective filter — arrows / Enter / Escape suppressed on tvOS,
  everything else forwarded.
- workflows.md: Workflow 8 gets a step demonstrating `serve-sim type`
  into a focused text field on tvOS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-level TV.md mirrors README.md's structure but focuses on the
tvOS surface: --tv startup, Siri-remote button vocabulary with
transports, --hold long-press, web-preview keyboard map, text input,
end-to-end recipe, what doesn't work, and a transport-internals
"How it works" section. Cross-links back to the agent skill docs.

README.md's features bullet now mentions Apple TV with a TV.md
pointer (using the same root-relative path style as the existing
`skills/serve-sim` link, which only works because the README is
symlinked at the repo root).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ab9f9772-e6de-4b3a-9779-aed02f742cd8

📥 Commits

Reviewing files that changed from the base of the PR and between df31eb9 and 45e6b87.

📒 Files selected for processing (1)
  • packages/serve-sim-client/src/simulator/SimulatorView.tsx

📝 Walkthrough

Walkthrough

This PR adds comprehensive Apple TV (tvOS) simulator support to serve-sim, including device type definitions, tvOS runtime enumeration and device selection, Siri-remote button protocol implementation with configurable hold-press behavior, client-side input routing, CLI --tv and --hold option threading, and extensive user documentation covering tvOS-specific workflows and constraints.

Changes

Apple TV (tvOS) Support

Layer / File(s) Summary
Device type definitions and client rendering
packages/serve-sim-client/src/simulator/deviceFrames.tsx, packages/serve-sim-client/src/simulator/SimulatorView.tsx
DeviceType union includes "tv". DEVICE_FRAMES gains borderless 960×540 TV frame. SIMULATOR_SCREENS maps Apple TV 1080p variants to 1920×1080. simulatorMaxWidth returns fixed 960 for TV. DeviceFrameChrome returns null for TV. SimulatorViewProps adds optional deviceType prop and adjusts overlay/pointer behavior when isTv.
Runtime enumeration and device sorting
packages/serve-sim/src/middleware.ts, packages/serve-sim/src/client/utils/devices.ts
listAllSimulators() now includes tvOS runtimes. deviceKind() recognizes "apple tv" names. runtimeOrder() recognizes "tvos" prefix so tvOS devices are surfaced and sorted.
Device selection and booted device lookup
packages/serve-sim/src/device.ts, packages/serve-sim/src/index.ts
Adds FindBootedOptions and isTvLowResolution(). findBootedDevice()/pickDefaultDevice() accept {tv?: boolean} and prefer 1080p tvOS devices when tv mode is enabled. CLI threads tv through serve/follow/detach flows and updates error messaging.
Button event protocol and message wiring
packages/serve-sim/Sources/SimStreamHelper/Protocol.swift, packages/serve-sim/Sources/SimStreamHelper/ClientManager.swift, packages/serve-sim/Sources/SimStreamHelper/main.swift
ButtonEventPayload adds optional holdMs. ClientManager.onButton updated to (String, UInt32?). WebSocket handler and main.swift forward holdMs to HIDInjector.sendButton().
HID remote button injection with hold support
packages/serve-sim/Sources/SimStreamHelper/HIDInjector.swift
Resolves IndigoHIDMessageForHIDArbitrary for arbitrary consumer HID messages. sendButton accepts optional holdMs. Directional remote keys are sent via keyboard HID (pressKey). Consumer/media/menu/play_pause buttons use arbitrary consumer-page HID messages with optional hold sequencing on a dedicated queue for discrete long-press gestures.
Client-side input routing and simulator view integration
packages/serve-sim-client/src/simulator/SimulatorView.tsx, packages/serve-sim/src/client/client.tsx
Client defines tvReservedCodes for navigation keys and early-returns when deviceType is tv to avoid forwarding them as HID keystrokes. SimulatorView registers global keydown mappings for tv remote keys to sendButton and disables pointer/touch forwarding when isTv. deviceType is passed into SimulatorView.
CLI --tv flag and --hold option threading
packages/serve-sim/src/index.ts
Adds top-level --tv option. follow(), detach(), and serve() thread tv into device selection. button() accepts optional holdMs, includes it in the WebSocket payload, and delays socket close by (50 + holdMs) to avoid dropped release events. button subcommand adds --hold [ms] parsing and defaults.
User documentation for tvOS
TV.md, packages/serve-sim/README.md, skills/serve-sim/SKILL.md, skills/serve-sim/references/*
TV.md introduces tvOS opt-in, Siri-remote button vocabulary and transports, long-press semantics, web preview mappings, typing behavior, an end-to-end workflow, limitations, and implementation notes. README and skill/reference docs updated to document tvOS-specific constraints and workflows.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • EvanBacon/serve-sim#57: Introduces Agent Skill documentation structure that this PR extends with tvOS/Apple TV coverage (SKILL.md, reference docs).

Poem

🐇📺 I hopped on the stream to press and to hold,
Remote keys now speak, consumer and bold.
TV frames and docs, a workflow to try,
Long-press queues hum while the MJPEGs fly.
🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Apple TV support' directly and clearly summarizes the primary change—adding Apple TV/tvOS support to serve-sim—which is the main objective across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 81.48% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
packages/serve-sim-client/src/simulator/SimulatorView.tsx (1)

1-1: 💤 Low value

Filename violates kebab-case guideline.

As per coding guidelines, TypeScript/JavaScript files should use kebab-case. This file should be named simulator-view.tsx instead of SimulatorView.tsx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serve-sim-client/src/simulator/SimulatorView.tsx` at line 1, Rename
the file from SimulatorView.tsx to simulator-view.tsx and update all import
paths referencing "SimulatorView" to the new kebab-case filename (e.g., imports
of SimulatorView or from '.../SimulatorView' should point to
'.../simulator-view'); ensure the exported component name (SimulatorView) and
any default export remain unchanged so internal symbols (component
SimulatorView) still match existing references.
packages/serve-sim-client/src/simulator/deviceFrames.tsx (2)

250-261: ⚡ Quick win

DeviceFrameChrome lacks explicit tv case.

The switch statement doesn't handle type === "tv" and will fall through to the default PhoneFrameChrome, which renders iPhone-specific chrome including a Dynamic Island. While the TV frame defines all bezels as 0 (making chrome invisible), explicitly returning null or a dedicated borderless component for TV would be clearer and more maintainable.

📐 Proposed fix to add explicit tv case
 export function DeviceFrameChrome({ type = "iphone", streaming = false }: { type?: DeviceType; streaming?: boolean }) {
   switch (type) {
     case "ipad":
       return <IPadFrameChrome streaming={streaming} />;
     case "watch":
       return <WatchFrameChrome streaming={streaming} />;
     case "vision":
       return <VisionProFrameChrome streaming={streaming} />;
+    case "tv":
+      // Apple TV renders borderless; no chrome overlay needed.
+      return null;
     default:
       return <PhoneFrameChrome streaming={streaming} />;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serve-sim-client/src/simulator/deviceFrames.tsx` around lines 250 -
261, DeviceFrameChrome currently falls through to PhoneFrameChrome for unknown
types, causing TV sims to render iPhone chrome; update the switch in
DeviceFrameChrome to include an explicit "tv" case and return either null or a
dedicated borderless TV component (e.g., a new TvFrameChrome or simply null)
instead of PhoneFrameChrome so TV devices render without Dynamic Island/phone
bezels; modify the switch that references DeviceFrameChrome and DeviceType to
add the "tv" branch and ensure streaming prop is passed or ignored
appropriately.

1-1: 💤 Low value

Filename violates kebab-case guideline.

As per coding guidelines, TypeScript/JavaScript files should use kebab-case. This file should be named device-frames.tsx instead of deviceFrames.tsx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serve-sim-client/src/simulator/deviceFrames.tsx` at line 1, The
filename deviceFrames.tsx violates the kebab-case rule; rename the file to
device-frames.tsx and update all imports/exports that reference deviceFrames.tsx
(search for "deviceFrames" in import paths) so they point to device-frames.tsx,
and ensure any tooling/config (exports index files or barrel files) that lists
the old name is updated accordingly.
TVOS-CONSIDERATIONS.md (1)

1-98: ⚡ Quick win

Consider updating or archiving this planning document.

This document uses future tense and describes implementation phases, but based on the PR summary and other documentation, the tvOS support appears to be fully implemented. Consider either:

  • Updating it to reflect the completed state (past tense, "what was done")
  • Moving it to a historical/archive location
  • Removing it if no longer needed
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TVOS-CONSIDERATIONS.md` around lines 1 - 98, The planning doc
"TVOS-CONSIDERATIONS.md" is out of date; either convert it to a historical
post-mortem or archive/remove it: update the prose from future to past tense and
add a short "Status" section listing completed Phases (reference the Phase
1..Phase 5 headings and key changes like enumeration unlock and Siri-remote HID
work), or move the file to an archive folder (or delete) and update any
README/CLAUDE.md references to point to the new location or the completed
implementation notes; ensure the file header clearly indicates "Archived" or
"Post-mortem" and include links to the implemented artifacts (e.g., package
names/sections mentioned in the document) so readers can find the actual code.
TV.md (1)

152-164: ⚡ Quick win

Add language specifier to fenced code block.

The ASCII diagram should specify a language (or use text) to comply with Markdown linting standards.

📝 Proposed fix
-```
+```text
 ┌──────────────┐   simctl io   ┌─────────────────┐  MJPEG / WS  ┌─────────┐

As per coding guidelines, the static analysis tool markdownlint-cli2 flagged this: "Fenced code blocks should have a language specified (MD040)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TV.md` around lines 152 - 164, The fenced ASCII diagram block lacks a
language specifier and triggers MD040; update the opening fence for the diagram
(the triple backticks above the ASCII art in TV.md) to include a language label
such as "text" (e.g., change ``` to ```text) so the code block is explicitly
marked and satisfies markdownlint; no other changes to the diagram content are
needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/serve-sim-client/src/simulator/SimulatorView.tsx`:
- Around line 63-68: The JSDoc for deviceType mentions that "Space" is forwarded
to Siri-remote but the keyboard handler in SimulatorView that forwards
Enter/Escape/arrow keys does not include Space; either remove "Space" from the
deviceType JSDoc or update the key mapping in the SimulatorView keyboard handler
(the key-to-remote mapping that currently handles Enter, Escape and arrow keys)
to also recognize the Space key (e.g., handle ' ' and 'Space' key values and
forward them as the appropriate Siri-remote action).

In `@skills/serve-sim/references/workflows.md`:
- Line 261: Replace the hardcoded URL in the curl command "curl -s
http://localhost:3100/ax | jq '.children[0].AXLabel'" with the same dynamic port
discovery pattern used earlier in this document/workflows (i.e., resolve the
service port at runtime and interpolate it into the curl URL), so that the
command queries http://localhost:<discovered_port>/ax rather than always using
3100; update the line containing that curl invocation accordingly to use the
discovered port variable or lookup expression consistent with workflows 1–4.
- Line 272: The guidance in workflows.md conflicts with permissions.md: change
the sentence about "Treat `serve-sim permissions` results as authoritative on
tvOS" to state the opposite — that `serve-sim permissions` is unvalidated on
tvOS and its results should be treated as untested/unreliable because the TCC
layout differs; explicitly reference the `serve-sim permissions` subcommand and
align wording with the existing warning in permissions.md so readers know not to
treat the output as authoritative on tvOS.
- Line 260: Replace the invalid URL concatenation in the curl call: change the
string literal using the `%` character ("$URL%/.sim/api") to a proper shell-safe
form such as "${URL}/.sim/api" (or "$URL/.sim/api") so the curl command that
extracts '.streamUrl' with jq uses a valid URL; update the curl invocation in
the line with the comment "# discover the helper port" accordingly.

---

Nitpick comments:
In `@packages/serve-sim-client/src/simulator/deviceFrames.tsx`:
- Around line 250-261: DeviceFrameChrome currently falls through to
PhoneFrameChrome for unknown types, causing TV sims to render iPhone chrome;
update the switch in DeviceFrameChrome to include an explicit "tv" case and
return either null or a dedicated borderless TV component (e.g., a new
TvFrameChrome or simply null) instead of PhoneFrameChrome so TV devices render
without Dynamic Island/phone bezels; modify the switch that references
DeviceFrameChrome and DeviceType to add the "tv" branch and ensure streaming
prop is passed or ignored appropriately.
- Line 1: The filename deviceFrames.tsx violates the kebab-case rule; rename the
file to device-frames.tsx and update all imports/exports that reference
deviceFrames.tsx (search for "deviceFrames" in import paths) so they point to
device-frames.tsx, and ensure any tooling/config (exports index files or barrel
files) that lists the old name is updated accordingly.

In `@packages/serve-sim-client/src/simulator/SimulatorView.tsx`:
- Line 1: Rename the file from SimulatorView.tsx to simulator-view.tsx and
update all import paths referencing "SimulatorView" to the new kebab-case
filename (e.g., imports of SimulatorView or from '.../SimulatorView' should
point to '.../simulator-view'); ensure the exported component name
(SimulatorView) and any default export remain unchanged so internal symbols
(component SimulatorView) still match existing references.

In `@TV.md`:
- Around line 152-164: The fenced ASCII diagram block lacks a language specifier
and triggers MD040; update the opening fence for the diagram (the triple
backticks above the ASCII art in TV.md) to include a language label such as
"text" (e.g., change ``` to ```text) so the code block is explicitly marked and
satisfies markdownlint; no other changes to the diagram content are needed.

In `@TVOS-CONSIDERATIONS.md`:
- Around line 1-98: The planning doc "TVOS-CONSIDERATIONS.md" is out of date;
either convert it to a historical post-mortem or archive/remove it: update the
prose from future to past tense and add a short "Status" section listing
completed Phases (reference the Phase 1..Phase 5 headings and key changes like
enumeration unlock and Siri-remote HID work), or move the file to an archive
folder (or delete) and update any README/CLAUDE.md references to point to the
new location or the completed implementation notes; ensure the file header
clearly indicates "Archived" or "Post-mortem" and include links to the
implemented artifacts (e.g., package names/sections mentioned in the document)
so readers can find the actual code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b92f915-512d-4b7a-a4b3-e828ffef98f1

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3f718 and 78c1246.

📒 Files selected for processing (22)
  • TV.md
  • TVOS-CONSIDERATIONS.md
  • packages/serve-sim-client/src/simulator/SimulatorView.tsx
  • packages/serve-sim-client/src/simulator/deviceFrames.tsx
  • packages/serve-sim/README.md
  • packages/serve-sim/Sources/SimStreamHelper/ClientManager.swift
  • packages/serve-sim/Sources/SimStreamHelper/HIDInjector.swift
  • packages/serve-sim/Sources/SimStreamHelper/Protocol.swift
  • packages/serve-sim/Sources/SimStreamHelper/main.swift
  • packages/serve-sim/bin/serve-sim-bin
  • packages/serve-sim/src/client/client.tsx
  • packages/serve-sim/src/client/utils/devices.ts
  • packages/serve-sim/src/device.ts
  • packages/serve-sim/src/index.ts
  • packages/serve-sim/src/middleware.ts
  • skills/serve-sim/SKILL.md
  • skills/serve-sim/references/buttons-rotation.md
  • skills/serve-sim/references/camera.md
  • skills/serve-sim/references/endpoints.md
  • skills/serve-sim/references/gestures.md
  • skills/serve-sim/references/permissions.md
  • skills/serve-sim/references/workflows.md

Comment thread packages/serve-sim-client/src/simulator/SimulatorView.tsx
Comment thread skills/serve-sim/references/workflows.md Outdated
Comment thread skills/serve-sim/references/workflows.md Outdated
Comment thread skills/serve-sim/references/workflows.md Outdated
douglowder and others added 4 commits May 26, 2026 12:29
CodeRabbit's pre-merge "Docstring Coverage" check flagged the branch
at 45.83% (threshold 80%). Document the functions added/modified for
tvOS support:

- HIDInjector.swift: sendButton (now covers the holdMs param and the
  remote_* button vocabulary); split the consumerTarget constant out
  with its own doc so the pressConsumer docstring attaches correctly.
- client/utils/devices.ts: parseSimctlList, deviceKind, runtimeOrder.
- deviceFrames.tsx: getDeviceType, simulatorAspectRatio,
  fallbackScreenSize, simulatorMaxWidth.
- index.ts: button (holdMs behavior) and serve (--tv threading).

Comment-only change; the helper binary is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeviceFrameChrome fell through to PhoneFrameChrome for the "tv" type,
overlaying iPhone bezels and a Dynamic Island on tvOS sims. Add an
explicit "tv" case returning null — Apple TV outputs to a television,
so the borderless DEVICE_FRAMES.tv geometry should render with no
chrome.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SimulatorView deviceType JSDoc: Space is not mapped to a Siri-remote
  button (it passes through as text input). Correct the doc to list
  only arrow keys / Enter / Escape as forwarded, and note other keys
  type normally.
- workflows.md Workflow 8: fix the broken `"$URL%/.sim/api"` curl
  (stray %), and replace the hardcoded http://localhost:3100/ax with
  the runtime port-discovery pattern used in workflows 1-4.
- workflows.md: reword the permissions bullet so it aligns with
  permissions.md — serve-sim permissions is unvalidated on tvOS and
  its results shouldn't be treated as authoritative.
- TV.md: add a `text` language label to the "How it works" ASCII
  diagram fence to satisfy markdownlint MD040.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/serve-sim-client/src/simulator/SimulatorView.tsx (1)

64-68: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align tv keyboard docs with the actual key map.

This updated JSDoc now says Space is text input, but the inline comment at Line 550 still says Space is forwarded as a Siri-remote button. Please update that comment to match the real mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serve-sim-client/src/simulator/SimulatorView.tsx` around lines 64 -
68, The JSDoc in SimulatorView now states that Space is treated as text input
for device kind "tv", but the inline comment near the key handling logic still
claims Space is forwarded as a Siri-remote button; update that inline comment in
the key-handling block (look for the handler function like onKeyDown /
handleKeyDown or the switch that maps keys for device kind "tv") to reflect the
actual mapping: Space should be documented as passing through as text input (not
forwarded), while arrow keys, Enter and Escape remain Siri-remote button
presses; keep the rest of the comment consistent with the JSDoc wording.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/serve-sim-client/src/simulator/SimulatorView.tsx`:
- Around line 64-68: The JSDoc in SimulatorView now states that Space is treated
as text input for device kind "tv", but the inline comment near the key handling
logic still claims Space is forwarded as a Siri-remote button; update that
inline comment in the key-handling block (look for the handler function like
onKeyDown / handleKeyDown or the switch that maps keys for device kind "tv") to
reflect the actual mapping: Space should be documented as passing through as
text input (not forwarded), while arrow keys, Enter and Escape remain
Siri-remote button presses; keep the rest of the comment consistent with the
JSDoc wording.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f6c89d9-5192-4a07-9c9f-86b4e0a3a150

📥 Commits

Reviewing files that changed from the base of the PR and between e5e68b6 and df31eb9.

📒 Files selected for processing (4)
  • TV.md
  • packages/serve-sim-client/src/simulator/SimulatorView.tsx
  • packages/serve-sim-client/src/simulator/deviceFrames.tsx
  • skills/serve-sim/references/workflows.md
✅ Files skipped from review due to trivial changes (2)
  • skills/serve-sim/references/workflows.md
  • TV.md

The comment above the tvOS keydown effect still claimed Space was
forwarded as a Siri-remote button. Align it with the actual map and
the deviceType JSDoc: arrows / Enter / Escape (plus PlayPause and
AppleTV media keys) are forwarded; Space and other keys pass through
as text input.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant