Add extended traits, project config, and setup plugin#79
Merged
Conversation
4 tasks
dc95d12 to
cec52c0
Compare
…roject config Phase 1: Extended built-in traits - Add locale, layoutDirection, legibilityWeight to PreviewTraits with validation - New variant presets: "rtl", "ltr", "boldText" - Empty string clears a trait (resolves to nil) - BridgeGenerator emits .environment() modifiers for new traits - MCP tool schemas updated for preview_start, preview_configure, preview_variants - CLI flags added to run, snapshot commands - 18 new unit tests (60 total across traits + config) Phase 2: Project config file (.previewsmcp.json) - ProjectConfig type with platform, device, traits, quality, setup fields - ProjectConfigLoader walks up directories to auto-discover config - MCP server loads and caches config per source file directory - CLI commands accept --config flag (explicit or auto-discover) - Precedence: explicit param > config > built-in default - 10 new unit tests for config decoding and directory walking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 3: Setup plugin — replaces micro apps / dev apps
- New `PreviewsSetupKit` SPM library with `PreviewSetup` protocol
- `setUp()` (async throws): runs once per session for SDK init, auth, fonts
- `wrap(_:)`: wraps every preview for theme providers, environment values
- BridgeGenerator generates `@_cdecl("previewSetUp")` entry point with
Task+semaphore bridge for async setUp
- Trait modifiers applied OUTSIDE wrap so explicit overrides take precedence
- Both host apps (macOS + iOS) gain `hasCalledSetUp` flag — setUp called
on first dylib load only, completely outside the hot-reload path
- Setup params threaded through BuildContext, PreviewSession,
IOSPreviewSession, BuildHelpers, MCPServer, and RunCommand
- Config `setup.moduleName`/`setup.typeName` wired from .previewsmcp.json
- Standalone mode warns when setup is configured but no build system found
- 5 new unit tests for setup code generation (65 total)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CLAUDE.md: update Trait Injection with all 5 traits, add Project Config and Setup Plugin sections, update Architecture for PreviewsSetupKit - README.md: add locale/layoutDirection/legibilityWeight examples, rtl/ltr/boldText presets, project config section, setup plugin section with adoption guide - Add example .previewsmcp.json to SPM example project Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security fixes: - Validate locale values reject quotes, backslashes, and newlines to prevent code injection via string interpolation in generated Swift source - Validate setupModule/setupType are valid Swift identifiers before interpolation into generated import statements and function calls Feature completeness: - Thread setupModule/setupType from config to SnapshotCommand and VariantsCommand (both macOS and iOS paths were missing setup params) - Apply config quality to MCP snapshot and variants handlers (was hardcoded to 0.85, now respects .previewsmcp.json quality field) - Remove enum constraints from preview_configure schema for layoutDirection and legibilityWeight so empty-string clearing works Tests: - Add locale injection rejection test - Add setup identifier validation test (67 total) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a ToDoPreviewSetup target demonstrating the PreviewSetup protocol: - AppTheme with custom brand colors and fonts via EnvironmentKey - setUp() with commented examples of Firebase, auth, and DI setup - wrap() injects theme and tint into every preview - .previewsmcp.json updated to reference the setup target Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move .previewsmcp.json to examples/ so all example variants (spm, xcodeproj, xcworkspace, bazel) inherit it via directory walking - Move setup target to examples/PreviewSetup/ as a standalone package so it's independent of any specific example project - Restore spm/Package.swift to its original dependencies (no PreviewsMCP dep) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…/spm/ The shared .previewsmcp.json at examples/ should not include setup config since only the SPM example has the ToDoPreviewSetup module. The xcodeproj, xcworkspace, and bazel examples would fail trying to import it. Fix: shared config has platform/traits/quality only. SPM example gets its own config with the setup section. Config discovery walks up directories, so the SPM config (closer) is found first and the shared one is used by the other examples. Tested all four examples: spm, xcodeproj, xcworkspace, bazel — all pass with snapshot, traits (dark + RTL), and variants (light/dark/rtl presets). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The setup plugin was incorrectly requiring the user's app target to declare a dependency on PreviewsSetupKit. The correct architecture: PreviewsMCP discovers and builds the setup package itself. New SetupBuilder: - Reads setup.packagePath from .previewsmcp.json (relative to config dir) - Runs `swift build` on the setup package independently - Extracts .swiftmodule and library paths from the build output - Passes compiler flags (-I, -L, -l) to the bridge compilation The user's app target has zero knowledge of PreviewsMCP or PreviewsSetupKit. Only the setup package (a separate SPM package) depends on PreviewsSetupKit. Changes: - New SetupBuilder in PreviewsCore — builds setup package, returns flags - ProjectConfig.SetupConfig gains packagePath field - PreviewSession gains setupCompilerFlags for bridge compilation - All CLI commands and MCP handlers use SetupBuilder instead of passing raw module/type names - Tested end-to-end: SPM example with setup, xcodeproj/bazel without Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SPM doesn't create static archives for library targets — it leaves loose .o files. The bridge linker needs .a archives to resolve symbols. SetupBuilder now archives all targets' .o files (including transitive dependencies) into lib<Target>.a before passing -l flags, matching the same pattern used by SPMBuildSystem.archiveDependencyTargets(). This also means setup packages with their own SPM dependencies will link correctly — all dependency targets get archived and linked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the print-statement example with a compelling demonstration: - PreviewEnvironment: @observable singleton configured in setUp() with mock user state (name, subscription tier, feature flags). Persists across hot-reload because setUp() runs once per session. - PreviewBanner: overlay at bottom of every preview showing the mock user ("dev@example.com PRO") — visible proof that the setup plugin is active and what state it configured. - Brand theme: purple accent tint applied via wrap(). Demonstrates the three key value props: 1. setUp() does real work (configures observable state) 2. wrap() makes it visible (banner + tint on every preview) 3. Composes with traits (dark mode, RTL, dynamic type all work) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All four examples (spm, xcodeproj, xcworkspace, bazel) now share the same setup plugin config. PreviewsMCP builds the setup package independently via SetupBuilder — the example's build system (SPM, Xcode, Bazel) doesn't need to know about it. Tested: all four examples render with the PreviewBanner and brand tint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- swift-format --in-place across Sources/, Tests/, examples/PreviewSetup/ - Document previewSetUp lifecycle in docs/communication-protocol.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Important fixes: - Config quality now resolves for both macOS and iOS sessions in MCP snapshot/variants handlers via new configQualityForSession() helper - CLI SnapshotCommand applies config quality instead of hardcoding 0.85 - CLI VariantsCommand --quality flag falls back to config then 0.85 Suggestions fixed: - isValidSwiftIdentifier regex rejects trailing/double dots: ^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$ - SetupBuilder caches iOS SDK path (single xcrun call instead of two) 67 tests pass, all examples verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config-sourced traits (from .previewsmcp.json) previously bypassed validation via TraitsConfig.toPreviewTraits(), which constructed PreviewTraits directly. A malicious locale in the config file could have injected code through the string literal interpolation in BridgeGenerator.traitModifiers(). Fix: toPreviewTraits() now calls PreviewTraits.validated() which rejects locale values containing quotes, backslashes, and newlines. Invalid config traits fall back to empty PreviewTraits(). Also fix standalone mode leaking setupCompilerFlags — set extraFlags to [] since setup is explicitly not supported without a build system. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Other examples don't commit their lock files. Removes noise from diffs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The spm and PreviewSetup examples are SPM packages that generate Package.resolved during builds. Other examples already have .gitignore files for their generated artifacts (xcodeproj, bazel). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ge.swift The README incorrectly showed adding the setup target to the user's app Package.swift. The correct architecture: the setup package is a separate standalone package that PreviewsMCP builds independently via SetupBuilder. The user's app target has zero dependency on PreviewsMCP. Updated README with: - Standalone package directory structure - Separate Package.swift for the setup package - packagePath in config example - Explanation that PreviewsMCP builds it independently Updated CLAUDE.md to mention packagePath and SetupBuilder. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use raw string literals instead of multiline strings inside Data() initializers to avoid line-break disagreements between local and CI swift-format versions (510 vs 602). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SetupBuilder builds the setup package for iOS simulator when the preview target is iOS. The setup package depends on PreviewsMCP to get PreviewsSetupKit. Without an iOS platform declaration, SPM falls back to the default iOS deployment target, which is lower than what swift-syntax and MCP SDK require — causing build failures in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cec52c0 to
98319bb
Compare
…wsMCP SetupBuilder was building the example setup package for iOS, which depended on the full PreviewsMCP package. SPM resolved the entire dependency tree (swift-syntax, MCP SDK, swift-nio, etc.) adding 10+ minutes to the iOS integration test, causing a 600-second timeout. Fix: bundle a local copy of PreviewsSetupKit (37 lines, SwiftUI only) inside the example setup package. Build time drops from 10+ minutes to 2 seconds — zero transitive dependencies to resolve. This mirrors what real-world users should do: depend on PreviewsSetupKit as a lightweight package, not the full PreviewsMCP. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6a55383 to
7ede7a1
Compare
…ory walk 1. Remove dead setupModuleName/setupCompilerFlags from BuildContext — setup data flows through PreviewSession from SetupBuilder.Result, these fields were never populated by any build system 2. Log warning to stderr when config traits fail validation instead of silently returning empty traits (e.g., "colorScheme": "Blue" typo) 3. Eliminate double directory walk — ProjectConfigLoader.find() now returns Result(config, directory) so the config directory is available without a second walk for SetupBuilder 4. Add provenance comment to LocalPreviewsSetupKit copy noting it should be kept in sync with Sources/PreviewsSetupKit 5. Tests updated to verify config directory is returned correctly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three complementary features that give developers the fidelity of running their app with the nimbleness of SwiftUI previews — eliminating the need for micro apps / dev apps.
locale,layoutDirection,legibilityWeightadded topreview_configureandpreview_start, with new variant presets (rtl,ltr,boldText). Empty string clears a trait. Locale injection validated against code injection. Layout direction and legibility weight validated against enum sets..previewsmcp.json): auto-discovered config file for platform, device, trait defaults, and quality. Precedence: explicit param > config > default. Gives AI agents project intent without inference.PreviewsSetupKit): protocol-based system replacing micro apps.setUp()runs once per session (outside hot-reload path) for SDK init, auth, fonts, DI container setup.wrap()runs every render for theme providers and environment values. PreviewsMCP builds the setup package independently viaSetupBuilder— the user's app target has zero dependency on PreviewsMCP. Trait modifiers applied outside wrap so explicit overrides always win.Architecture
The user's app
Package.swiftis completely untouched. Setup works across SPM, Xcode, and Bazel.Changes
.environment()modifiers, MCP schemas, CLI flagsProjectConfigtype +ProjectConfigLoader, MCP/CLI integration with--configflag, quality field used in snapshot/variantsPreviewsSetupKitlibrary,SetupBuilder(builds setup package, archives .o → .a, extracts compiler flags),@_cdecl("previewSetUp")with async bridge,hasCalledSetUpin both host apps, full pipeline wiring.previewsmcp.jsonatexamples/, standalonePreviewSetup/package with observable state + preview banner, tested across all 4 build systemsTest coverage
Test plan
swift test --filter BridgeGeneratorTraitsTests— 57 tests for traits + setup code gen + securityswift test --filter ProjectConfigTests— 10 tests for config decoding + directory walking🤖 Generated with Claude Code