Skip to content

Add configure command for live trait changes#103

Merged
obj-p merged 2 commits intocli-mcp-parityfrom
configure-command
Apr 15, 2026
Merged

Add configure command for live trait changes#103
obj-p merged 2 commits intocli-mcp-parityfrom
configure-command

Conversation

@obj-p
Copy link
Copy Markdown
Owner

@obj-p obj-p commented Apr 15, 2026

Summary

Fourth PR in the CLI/MCP parity stack. Adds `previewsmcp configure` — changes rendering traits on a running preview session.

  • Forwards to the daemon's existing `preview_configure` MCP tool (triggers recompile on the affected session; `@State` is reset).
  • Session resolution mirrors `snapshot` (`--session` / `--file` / sole-running).
  • Unlike `snapshot`, no ephemeral fallback — configuring a missing session is an error.
  • Pass an empty string to clear a trait.
  • Client-side validation via `PreviewTraits.validated` so bad values fail before the RPC.

Supported traits

`--color-scheme`, `--dynamic-type-size`, `--locale`, `--layout-direction`, `--legibility-weight`.

Test plan

5 new tests in `ConfigureCommandTests`:

  • No-traits argument rejects with a clear message
  • Invalid trait value rejected locally
  • No session → descriptive error
  • Dark-mode round-trip — snapshot before + snapshot after verifies the change actually took effect
  • `--session ` explicit targeting

Regression coverage:

  • 30/30 daemon-touching tests pass (DaemonLifecycle + Run + Snapshot + Configure)

Stack

🤖 Generated with Claude Code

obj-p and others added 2 commits April 15, 2026 09:54
Fourth PR in the CLI/MCP parity stack. \`previewsmcp configure\`
forwards trait changes to the daemon's \`preview_configure\` MCP tool.

Session targeting mirrors \`snapshot\`:
- \`--session <uuid>\` — explicit
- \`--file <path>\` — resolve by source file
- no flag — use the sole running session

Unlike \`snapshot\`, there's no ephemeral fallback: configuring a
session that doesn't exist is an error (suggesting \`run --detach\`
as the remedy).

Supported traits:
- \`--color-scheme\` (light / dark)
- \`--dynamic-type-size\` (xSmall..accessibility5)
- \`--locale\` (BCP 47)
- \`--layout-direction\` (leftToRight / rightToLeft)
- \`--legibility-weight\` (regular / bold)

Pass an empty string to clear a trait (matches the daemon's signal
for reverting an earlier override).

Validation happens client-side via \`PreviewTraits.validated\` so bad
values fail before an RPC round-trip. Empty strings are allowed
through as the clear-signal.

Tests (5): validation rejects missing traits, validation rejects
invalid values, no-session error path, dark-mode round-trip
(snapshot before + after verifies the change actually took effect),
and --session explicit targeting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The review of PR #103 caught a real bug: every layer advertised that
passing an empty string to preview_configure would clear a trait
override, but the implementation silently preserved the existing value.

Root cause: parseTraits normalized empty strings to nil, then
PreviewSession.reconfigure used PreviewTraits.merged which does
`other.field ?? self.field` — nil in `other` keeps self's value.
There was no way to signal "clear this field" through the pipeline.

Fixes:

- Add PreviewTraits.Field enum + PreviewTraits.clearing(_:) that
  nulls out the listed fields. clearing is the only way to revert a
  previously-set trait; merged alone can't do it.

- PreviewSession.reconfigure / IOSPreviewSession.reconfigure now
  take an optional clearing: Set<PreviewTraits.Field> and apply
  both merge and clear: `self.traits = self.traits.merged(with: traits).clearing(clearing)`.

- parseTraits now returns a three-tuple (traits, clearedFields,
  error?). clearedFields is populated by inspecting the raw MCP
  params for explicit empty-string values, since
  PreviewTraits.validated normalizes them away before the handler
  can see them.

- handlePreviewConfigure passes the clearedFields set through to
  reconfigure, and considers a call "no-op" only when both traits
  and clearedFields are empty.

- handlePreviewStart explicitly ignores clearedFields — starting a
  session has no existing traits to clear.

New test: configureClearsTrait does a set-then-clear round trip and
asserts the daemon's response summary lists colorScheme=dark after
setting, and has no colorScheme= entry after clearing. Uses the
summary rather than pixel diff because SwiftUI without an explicit
colorScheme falls back to the OS setting, which is dark on a
dark-mode Mac — so a pixel diff would spuriously pass even without
the fix.

All 6 ConfigureCommandTests pass; 235 total non-iOS tests still
green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@obj-p obj-p merged commit 1bd0a43 into cli-mcp-parity Apr 15, 2026
@obj-p obj-p deleted the configure-command branch April 15, 2026 16:10
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