Skip to content

Add preview_configure tool for trait injection#54

Merged
obj-p merged 12 commits intomainfrom
trait-injection
Mar 22, 2026
Merged

Add preview_configure tool for trait injection#54
obj-p merged 12 commits intomainfrom
trait-injection

Conversation

@obj-p
Copy link
Copy Markdown
Owner

@obj-p obj-p commented Mar 22, 2026

Summary

  • New preview_configure MCP tool to change rendering traits (color scheme, dynamic type) on running preview sessions without editing source
  • colorScheme and dynamicTypeSize params added to preview_start and preview_playground for initial configuration
  • --color-scheme and --dynamic-type CLI flags on run and snapshot commands
  • Traits persist across hot-reload cycles and use merge semantics (only provided params change)
  • 15 new tests covering trait generation, merge logic, and full compile pipeline

How it works

Traits are injected as .preferredColorScheme() and .dynamicTypeSize() SwiftUI modifiers in the generated bridge code by BridgeGenerator. Changing traits triggers a full recompile (@State is reset). The PreviewTraits struct flows through PreviewSessionBridgeGenerator and is preserved across file-watcher reloads.

Test plan

  • swift build passes
  • swift test — all 97 tests pass (15 new trait tests)
  • swift-format lint --strict — clean
  • Manual: preview_configure with colorScheme: "dark" → snapshot shows dark background
  • Manual: preview_configure with invalid value → clear error message
  • Manual: --color-scheme dark on CLI snapshot command

Closes #10

🤖 Generated with Claude Code

obj-p and others added 12 commits March 22, 2026 18:44
)

Enable AI agents and CLI users to preview SwiftUI views under different
rendering traits (dark mode, dynamic type sizes) without editing source.
Traits are injected as .preferredColorScheme() and .dynamicTypeSize()
modifiers in the generated bridge code and persist across hot-reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes code injection vulnerability: preview_start and preview_playground
were passing unvalidated trait strings into generated Swift source.
Now parseTraits() validates against allowed values on all paths.
Also adds dynamicTypeSize enum constraints to all MCP tool schemas
and propagates loadPreview errors in preview_configure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After reconfiguring, read currentTraits from the session (post-merge)
so the response shows all active traits, not just the ones passed in
this call. Also adds currentTraits getter to IOSPreviewSession.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename for consistency with the MCP parameter name dynamicTypeSize.
Both --color-scheme and --dynamic-type-size now validate against
PreviewTraits.validColorSchemes/validDynamicTypeSizes at parse time,
producing clear error messages instead of cryptic compilation failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When preview_start or handleIOSPreviewStart is called with traits,
the response now mentions them (e.g., "Traits: colorScheme=dark.")
so the user gets confirmation that traits were applied.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tests: generateOverlaySource with traits, validColorSchemes
contains exactly light/dark, validDynamicTypeSizes has all 12 cases,
and PreviewTraits Equatable conformance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comment explaining the narrow race window where a concurrent
structural file change and preview_configure call could cause the
file watcher to snapshot stale traits when creating a new session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of creating a new PreviewSession in the file watcher slow path
(which snapshots traits and creates a new session, racing with
preview_configure), reuse the existing session and call compile()
on it. Since compile() re-reads the source file and uses the session's
stored traits (which live inside the actor), traits set via
preview_configure are always preserved without a race.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The headless parameter was only wired up for iOS simulator sessions.
macOS windows were always positioned off-screen (-10000, -10000) because
HostApp.headless was derived from mode (.serve), ignoring the tool param.

Thread the headless flag from the MCP tool call through startMacOSPreview
into loadPreview, matching how iOS already handles it. When headless is
false, switch activation policy to .regular so the window can come to
front with a Dock icon.

Also update integration test skill to test the opposite color scheme from
system default so trait changes are always visually obvious.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The success path always returns a valid PreviewTraits, so the optional
wrapper was unnecessary. Remove ?? PreviewTraits() fallbacks at all
three call sites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
macOS does not scale fonts in response to .dynamicTypeSize() — this is
a platform limitation, not a bug. Document in the preview_configure
tool description and CLAUDE.md so users know to test dynamic type on
iOS simulator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@obj-p obj-p enabled auto-merge (squash) March 22, 2026 23:54
@obj-p obj-p merged commit 3445f5f into main Mar 22, 2026
2 checks passed
@obj-p obj-p deleted the trait-injection branch March 22, 2026 23:56
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.

Trait injection: dark mode, dynamic type, device sizes

1 participant