Skip to content

feat: restructure as a workspace, add Claude Desktop and a Mac app - #37

Open
lauridskern wants to merge 45 commits into
mainfrom
cindara/standalone-mac-app-options
Open

lauridskern wants to merge 45 commits into
mainfrom
cindara/standalone-mac-app-options

Conversation

@lauridskern

@lauridskern lauridskern commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Restructures the repo as an npm workspace so the server and UI can be shared across hosts. It also merges Mike's Claude Desktop support (#34) into that layout and adds a standalone Mac app.

Repository layout

Path Contents
packages/shared Protocol types and pure logic
packages/core MCP server, device runtimes, tool handlers
packages/ui Host-independent React views and panel controllers
packages/mcp-app The MCP Apps panel shell that Codex, Claude Desktop and other MCP Apps hosts load
packages/skills Agent skills for the shared tools
packages/test-support Fixtures shared by tests across packages
packages/native Native helper sources, prebuilt binaries in vendor/
packages/runtimes Pinned agent-device and serve-emu runtimes
apps/codex-plugin Codex manifest, launcher, assets, plus its server (updater) and panel (update banner)
apps/claude-desktop Claude Desktop MCPB manifest
apps/mac SwiftUI shell, its web page and its server
tooling Shared build, native, release and device smoke scripts (was scripts/); each app's tooling/ holds its packaging and smoke tests

The published release/latest layout is unchanged. The packaged plugin has the same files as before; bundles differ only in path comments and Sentry debug IDs.

Claude Desktop (#34)

Mike's branch is merged with his history kept. It adds a Claude manifest, .mcpb packaging, capability-driven host behavior (fullscreen, inline sizing, theme tokens and fonts, context attachments, clipboard), annotation drafts and log session handles. Fitting it to the workspace layout took three changes:

  • Panel shell: the MCP Apps shell moved from apps/codex-plugin/src to packages/mcp-app, because both Codex and Claude Desktop load it. Mike's display-mode and context-attachment controls moved there with it.
  • Claude files: the manifest is in apps/claude-desktop, and claude:config, package:claude and test:claude run from tooling/.
  • Shared code: Mike's shared changes (chat-message, standard MCP context attachments) use HostBridge, and PanelContext still accepts no OpenAI extensions for the Mac app.

The Cursor (#35) and VS Code (#36) PRs are not included. Mike closed both to restart them after #34.

Shared UI seam

  • Shared views depend on HostBridge (packages/ui/src/host-bridge.ts), the seven App operations they use. Codex passes its ext-apps App.
  • DeviceWorkspace takes over the iOS/Android panel orchestration from the Codex shell, so both hosts share it.
  • The server exports configureUI. It serves ui://mobile-dev/mac.html only when a runtime ships mac.html, which the Codex package doesn't.
  • Page bundling moves to tooling/ui-page.mjs.
  • Telemetry: UI and server carry a host tag: codex, claude, mac or other.
    • MCP Apps panels label themselves once connected: codex when the host advertises OpenAI extensions, otherwise the mapped host name.
    • The server labels itself from the MCP client name. The Mac app also sets MOBILE_DEV_HOST=mac.
    • The allowlist accepts only those values, so raw host and client names are never sent.
    • Only telemetry uses the label; behavior still follows capabilities. Tests cover both sides and docs/telemetry.md documents it.

Host separation

Adding a host now means adding an app; the shared packages know no host by name.

  • Server: runStdioServer takes a host's ServerFeatures. Codex's self-updater moved to apps/codex-plugin/server and the Mac page resource to apps/mac/server, so the shared server and the Claude bundle no longer carry them.
  • Panel: startMcpApp takes a host's workspace chrome. The update banner moved to apps/codex-plugin/ui; Claude loads the shared entry.
  • Views: packages/ui names context attachments (HostAttachments) instead of depending on @openai/mcp-extensions.
  • Build and version: dist/ holds the host-neutral runtime and panel, and host pieces build into dist/hosts/<host>. Each app packages from the runtime only; Claude no longer packages through Codex. The version comes from the root package.json, and each package step writes it into its host's manifest.
  • Skills: the product skills are in packages/skills, and Codex packages the ones it supports. build-codex-native-plugins is a repo-maintenance skill and moved to .agents/skills.
  • Shared: packages/shared no longer imports vendored runtimes; a test keeps its copied constants equal to the originals.
  • Tests: each package and app has a test/ folder, and shared fixtures live in packages/test-support.
  • Sentry: the projects are renamed to mobile-dev-ui, mobile-dev-server and mobile-dev-native (done in Sentry already). IDs and DSNs are unchanged, so history and events stay put. The slugs live in tooling/sentry-projects.mjs.

Review cleanup

A strict code-quality pass before merge removed duplication the restructure had left:

  • WorkspaceTools (packages/ui) owns the four tool panels, their show/close policy, availability, host changes and disposal. The Codex workspace and the Mac page only pick the tool and draw chrome. On the Mac, switching away from Performance now keeps capture running, like Codex; closing the tools stops it.
  • startCardApp runs the recording, comparison and TraceSift cards, and applyHostStyles replaces four copies of host theming. Three entry files are gone, and a new test covers every card's connect, theme, render and teardown.
  • createPlugin takes named options instead of seven positional ones.
  • Codex's server and panel build in the Codex package step, like the Mac app's. The shared build no longer imports an app, and dist/hosts is gone.

Mac app

  • Window: SwiftUI with a native sidebar (tools, iOS/Android toggles, server status). The content area is a web view with the shared views, without the Codex toolbar or title bar. It reaches the window's top edge and stays clear of the traffic lights when the sidebar is collapsed. Minimum size is 1280×720.
  • Server: runs over stdio exactly as in Codex and relays JSON-RPC to the page. Before the page connects, the app fetches the page through MCP, so it gets the server-owned telemetry environment, opt-out and anonymous identity. The page has no chat capabilities, so chat-only actions stay hidden.
  • Build: npm run build:mac writes release/mac/Mobile Dev.app, signed ad hoc and linked against the current macOS SDK. npm run test:mac launches it, waits for the page to connect and draw, and checks that quitting stops the server.

Before merging / after pulling

  • Native hashes: native sources are pure renames (R100), but their recorded source hashes include repo paths and build-script text, so they were re-recorded without rebuilding binaries.
    • After pulling, run npm run rebuild:baguette once. Its hash lives in the untracked vendor/baguette/release.json.
    • CI will rebuild the native helpers on the next release because their artifact keys changed.
  • Repo name: the repository is now callstackincubator/mobile-dev (renamed on GitHub). GitHub redirects the old URL for git, the API and raw files, so released versions keep updating. The updater accepts both the old and the new marketplace URL, and a test covers installs made before the rename. Links and the README install command use the new name.

Not included

  • Mac distribution: Developer ID signing, notarization, and source-map upload for the Mac page.
  • Native Sentry for the Swift shell.
  • Agent connections to the Mac app.

Verification

  • npm test: 822 pass. packages/core/test/tracesift-client.test.ts fails intermittently: about 5 in 24 runs under parallel load, with an undici ReadableStream is already closed error. It does the same on main. CI on PRs doesn't run tests, but the release job does. tests/tracesift-client.test.ts is intermittently flaky (undici stream state) both on this branch and before the restructure.
  • npm run typecheck:telemetry is clean. A full tsc run reports 23 errors, fewer than the 25 main had.
  • The Codex package smoke test now checks that the updater and banner are present; the Claude smoke test checks that both are absent.
  • npm run test:package, npm run test:codex-plugin (real Codex install) and npm run check:mcp-budget pass.
  • npm run package:claude and npm run test:claude pass (relocated bundle, development and release, with and without form elicitation).
  • npm run build:mac and npm run test:mac pass, with live iOS simulator and Android emulator streams in the app.

Merging

Use Create a merge commit. #34's head commit is in this branch, so GitHub marks #34 as merged only with a merge commit; squash or rebase leaves it open. #35 and #36 were already closed by their author.

grabbou and others added 30 commits October 23, 2026 14:48
Move host-independent code into packages/ (shared, core, ui, native,
runtimes) and the Codex plugin's manifest, launcher, skills, assets and
panel shell into apps/codex-plugin. Build scripts move to tooling/.

The published release/latest layout is unchanged: the packaged plugin
matches the previous layout file-for-file, and bundles differ only in
module path comments and Sentry debug IDs. Native sources are pure
renames; their recorded source hashes are refreshed because the hashes
include repo-relative paths and build-script contents.
Map the shared theme tokens to standard MCP host variables with the
existing light/dark fallbacks, replacing the Claude Desktop-only
overrides, and apply host-provided fonts in each app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop MOBILE_DEV_HOST and every claudeDesktop branch so behavior follows
what the client and install advertise, including in Claude Code:

- Use one host-neutral set of server instructions and tool descriptions.
- List mobile_choose_devices only for clients advertising openai/elicitation forms.
- Enable the updater only in release builds running from a Codex plugin package.
- Show the display mode control whenever the host offers the other mode.
- Size the inline workspace from MCP Apps containerDimensions.

Remove docs/claude-desktop.md; the MCPB bundle ships the repo README, which
now introduces Codex and Claude support and links each feature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared views now depend on HostBridge, the subset of the MCP Apps App they
use, so another host can provide it. The iOS/Android panel orchestration
moves from the Codex shell into DeviceWorkspace, the server exposes
configureUI and serves a mac.html page when a runtime ships one, and the
page bundling moves to tooling/ui-page.mjs.

UI and server telemetry gain a low-cardinality host value (codex or mac);
the server's metadata allowlist accepts only those two.
A SwiftUI window with a native sidebar (tools and visible device
platforms) hosts the shared UI in a web view. The app starts the bundled
server over stdio as Codex does, reads its page through MCP so it gets
the server's telemetry metadata, and relays JSON-RPC between server and
page. The page implements HostBridge with an MCP client and offers no
chat capabilities.

npm run build:mac assembles release/mac/Mobile Dev.app (signed ad hoc);
npm run test:mac launches it, waits for the page to connect and draw,
and checks quitting stops the server.
SwiftPM recorded the deployment target as the SDK version, so AppKit gave
the window the macOS 15 appearance. The build now records the SDK it
compiles against and checks the binary.
While the sidebar is collapsed, the leading panel's top row is padded by
the width of the traffic lights and sidebar button; views without a
toolbar row move below them. Shared top rows are marked panel-toolbar.
The web view ignored every safe area, so it ran under the floating sidebar.
It now ignores only the top edge. The smoke test also clears a snapshot
left by an earlier run.
Main's native app storage changes land in packages/core and packages/ui;
their imports use the workspace paths and the storage panel takes a
HostBridge.
Brings in #34 and fits it to the workspace:
- The MCP Apps panel shell moves from apps/codex-plugin/src to
  packages/mcp-app, since Codex and Claude Desktop load the same panel;
  Mike's display-mode and context-attachment controls join it.
- The MCPB manifest lives in apps/claude-desktop; its config, packaging
  and smoke scripts move to tooling/.
- Shared UI keeps HostBridge, so chat-message and the standard MCP context
  attachments work in every host, and PanelContext still accepts no
  OpenAI extensions for the Mac app.
- Host telemetry no longer assumes Codex: panels label themselves codex
  (OpenAI extensions), claude or other once connected, the server labels
  itself from the MCP client name, and the allowlist accepts only those
  values and mac.

Co-authored-by: Mike Grabowski <grabbou@gmail.com>
@lauridskern lauridskern changed the title feat: restructure as a workspace and add a standalone Mac app feat: restructure as a workspace, add Claude Desktop and a Mac app Oct 23, 2026
- Server: createPlugin takes ServerFeatures and runStdioServer runs the
  shared server with a host's features. Codex's self-updater moves to
  apps/codex-plugin/server and the Mac page resource to apps/mac/server,
  so the shared server and Claude Desktop's bundle no longer carry them.
- Panel: startMcpApp takes a host's workspace chrome. The update banner
  moves to apps/codex-plugin/ui; Claude Desktop loads the shared entry.
- Build: dist/ holds the host-neutral runtime and panel; host pieces build
  into dist/hosts/<host>. Each app's tooling/ packages from the runtime
  only, so Claude Desktop no longer packages through Codex.
- Version: the root package.json is the source; host manifests carry no
  version and each package step writes it.

Smoke tests now check the Codex package has the updater and banner and
the Claude bundle has neither.
PanelContext and RecordingController take a HostAttachmentsSource, a
vendor-neutral interface for hosts that keep the panel's context as
composer attachments. The MCP Apps shell supplies it from OpenAI's
extension, so packages/ui no longer depends on @openai/mcp-extensions.
The tool skills move to packages/skills, and each host's package step
includes the ones its host supports; the Codex plugin still ships
mobile-dev and mobile-dev-setup. The build-codex-native-plugins skill
helps agents working on this repository, so it moves to .agents/skills.
Shared code copied serve-emu's startup stage and outcome names and the
Baguette pin from vendored files; a test keeps the copies equal to their
sources, so packages/shared imports nothing outside itself.
Each package and app has a test/ folder, tooling/test covers the build
and release scripts, and fixtures shared across packages move to
packages/test-support. npm test runs all of them from the repository
root.
Uploads, releases and the server bundler read the ui, server and native
slugs from tooling/sentry-projects.mjs, so renaming a project changes one
line.
The ui, server and native projects are renamed in Sentry to host-neutral
slugs; their IDs and DSNs are unchanged, so history and incoming events
stay in the same projects.
The repository moves to callstackincubator/mobile-dev. Links, the release
notes URL and the updater's endpoints use the new name, and the updater
still accepts the old marketplace URL that existing installs keep; GitHub
redirects it.
- WorkspaceTools owns the four tool panels, their selection policy,
  availability, host changes and disposal; the Codex workspace and the
  Mac page only choose the tool and draw chrome.
- startCardApp runs the recording, comparison and TraceSift cards, and
  applyHostStyles replaces four copies of host theming.
- createPlugin takes named options instead of seven positional ones.
- Codex builds its own server and panel in its package step, so the
  shared build no longer imports an app and dist/hosts is gone.

A card test covers the shared bootstrap in every card view.
The feature list adds app control, device settings and network
conditions, and TraceSift profile analysis. Install covers Codex, Claude
Desktop and the Mac app, and a table maps the packages and apps.
Codex's lg radius is 12.5px with its superellipse scale, a size larger
than the 10px scale the panel's radii are built on. Its md radius is
10px, so the panel uses that and keeps 10px when a host sends none.
The shared panel shows Expand / Back to chat whenever a host advertises
both display modes. Claude needs it to leave the expanded view, but Codex
has its own fullscreen and sidebar controls, so the duplicate button
misbehaved there. Codex's entry now turns it off; Claude keeps it.
The MCP SDK labels every converted tool schema draft-07. Claude Desktop
validates 2020-12 only and refuses mobile_open_simulator for its draft-07
output schema. The schemas use no keyword whose meaning differs between
the dialects, which a test checks, so the server declares 2020-12 when it
lists its tools.
@grabbou

grabbou commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Shall we first squash merge #34 into main, and then, rebase/merge this one? that way we can ship Claude a bit sooner

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.

2 participants