Architecture evidence for agent-authored SwiftUI

Give coding agents a map of SwiftUI state before they edit it.

SwiftUI Semantic Audit turns ownership, writes, Bindings, synchronization, effects, and component boundaries into deterministic evidence. A semantic diff records what changed after the edit.

  • Open sourceMIT License
  • Seven CLI commandsScan through doctor
  • Provider independentNo embedded model API

Semantic X-ray

Evidence for the same UI behavior

This editorial view summarizes accepted fixture evidence. It is not a product GUI.

Manual synchronization

BindingMirroredLocally two owners
struct BindingMirrorEditor: View {
    @Binding var profileName: String
    @State private var editableName = ""

    var body: some View {
        TextField("Name", text: $editableName)
            .onAppear { editableName = profileName }
            .onChange(of: profileName) {
                editableName = profileName
            }
            .onChange(of: editableName) {
                profileName = editableName
            }
    }
}

Reading the graph

One value has two mutable representations and reciprocal copy paths.
  1. OwnerprofileName is borrowed from the parent.
  2. OwnereditableName is local State.
  3. CopyonAppear seeds the local value.
  4. BindTextField writes to the local value.
  5. SyncEach representation copies into the other.
Findings mirrored-state manual-two-way-sync

Direct Binding

GoodDirectBinding one owner
struct DirectBindingField: View {
    @Binding var name: String

    var body: some View {
        TextField("Name", text: $name)
    }
}
No mirror finding is expected. The parent remains the owner and the field receives direct write authority.

Fixture-backed patterns

Three patterns worth seeing

The finding identifies topology. The safer shape depends on the value's owner and lifetime. Behavior tests still decide whether the change is safe.

1

Keep commands visible

A custom setter can hide a method call behind value-shaped syntax.

Accepted fixture excerpt command-shaped-binding
@Bindable var model: CommandPagerModel

selection: Binding(
    get: { model.page },
    set: { model.selectPage($0) }
)
When the method only performs an identity write, expose that write directly.
Accepted clean fixture no finding
@Binding var value: Int

selection: Binding(
    get: { value },
    set: { next in value = next }
)

A method with validation or side effects needs an explicit action boundary, not deletion.

2

Narrow the component boundary

Passing one model through extracted views keeps every layer coupled to the owner.

Representative accepted topology depth 2
struct Middle: View {
    @Bindable var model: FeatureModel
    var body: some View {
        Leaf(model: model)
    }
}

struct Leaf: View {
    @Bindable var model: FeatureModel
}
Give a reusable leaf the value and action it actually consumes.
Accepted clean boundary focused input
struct GoodFocusedLeaf: View {
    let title: String
    let reload: () -> Void

    var body: some View {
        Button("Reload", action: reload)
    }
}

A screen or composition root may legitimately receive a broad model. The agent must establish that role.

3

Keep derived state derived

A stored flag creates write paths when its value is already determined by inputs.

Accepted fixture excerpt stored-derived-state
@State private var username = ""
@State private var password = ""
@State private var canSubmit = false

.onChange(of: username) { _, _ in
    canSubmit = !username.isEmpty && !password.isEmpty
}
When the value has no independent lifetime, compute it from its sources.
Safer shape, pending behavior tests computed
@State private var username = ""
@State private var password = ""

private var canSubmit: Bool {
    !username.isEmpty && !password.isEmpty
}

Debouncing, server validation, or a separate lifetime may justify stored state.

Protected case

It knows when not to change code.

A local draft is valid when the UI has real commit and discard behavior. The distinction comes from action and copy topology, not button names.

Representative protected transaction no finding expected
@Binding var name: String
@State private var draft = ""

func applyEdits() { name = draft }
func abandonEdits() { draft = name }

TextField("Name", text: $draft)
Button("Apply") { applyEdits() }
Button("Discard") { abandonEdits() }
.onAppear { draft = name }
  • Separate lifetimeThe draft remains local until commit.
  • Explicit commitApply writes the draft to the external owner.
  • Explicit rollbackDiscard restores the owned value.
  • No mechanical fixThe valid transaction stays intact.

Agent workflow

A workflow built for review

One router selects the smallest specialist workflow. Each phase keeps deterministic facts separate from agent judgment.

  1. 1

    Audit

    Build the exact source, validate a fresh compiler Index Store, and map the target cluster before broad source reading.

    $swiftui-semantic-audit
  2. 2

    Refactor

    Change one established ownership or data-flow cluster without altering its behavior or transaction boundary.

    $swiftui-dataflow-refactor
  3. 3

    Review

    Read the compatible semantic diff before the raw Git diff, then verify the implementation with builds and tests.

    $swiftui-change-review

Semantic diff

Record the architecture change.

A compatible snapshot diff records changes to representations, relationships, metrics, and findings. This ledger summarizes the fixture refactor shown above.

Architecture fact Baseline Current Review evidence
Canonical owner Parent Binding Parent Binding Preserved
Local mirror State representation Absent Removed
Synchronization Reciprocal copy path Absent Removed
Field write Through local draft Direct Binding Explicit

Semantic surface

29 bounded rules

Each rule evaluates supported ownership and data-flow topology. A finding is evidence for review, not a command to edit.

Read the rule reference

Ownership

  • mirrored-state
  • observable-state-mirror
  • stored-derived-state
  • model-aware-descendant
  • multi-owner-component
  • cross-feature-owner-dependency

Writes and effects

  • value-setter-pair
  • command-shaped-binding
  • manual-owner-synchronization
  • hidden-command-in-lifecycle
  • view-owned-external-effect

Bindings and sync

  • manual-two-way-sync
  • callback-binding-tunnel
  • binding-factory
  • multi-source-binding

Components

  • observable-model-tunnel
  • broad-observable-input
  • service-or-repository-in-view
  • preview-requires-app-composition

Interaction and layout

  • imperative-focus-lifecycle
  • selection-corrective-loop
  • geometry-driven-product-layout
  • geometry-escapes-layout-boundary
  • geometry-triggered-model-effect
  • manual-positioning-as-layout
  • gesture-button-emulation

Platform and environment

  • environment-command-router
  • imperative-platform-view-update
  • direct-global-platform-command

Trust and limits

Know what the tool owns

The boundary is deliberate: deterministic extraction in the CLI, contextual decisions in the surrounding agent workflow.

Released and inspectable

Source, immutable tag, archive, and MIT license are public.

No automatic source rewriting

The CLI reports evidence. It never edits project source.

Deterministic, not prescriptive

Facts are canonical. Architectural judgment remains separate.

No embedded model API

The CLI stays provider independent; the agent host controls its own data handling.

The bounded analysis is not a full type checker, control-flow engine, security audit, or performance profiler. A clean report or semantic diff does not prove runtime behavior.

Installation

Install in two separate steps

Homebrew owns the executable. The agent host owns the optional router and specialist skills.

1

Install the CLI

macOS 13 or later, Homebrew, and Xcode 26.6.

brew install potapenko/tap/swiftui-semantic-audit
swiftui-audit --version

The formula builds the tagged package from source and installs only swiftui-audit.

2

Add the agent skills

Paste this bounded setup request into Codex or Claude Code.

Install exactly the four SwiftUI Semantic Audit agent skills for release 0.4.0.
The CLI is already managed by Homebrew; verify `swiftui-audit --version` first.
Read and follow the tagged installation guide:
https://github.com/potapenko/swiftui-semantic-audit/blob/0.4.0/docs/getting-started/installation.md
Clone only tag 0.4.0 from
https://github.com/potapenko/swiftui-semantic-audit.git into a stable user-owned path.
Before linking anything, verify the origin, tag, and exact commit
189dc44c928f7f61b393f6e4ca7d8f6f5d183a48.
Detect this agent host and link all four sibling skill directories into its
documented personal skill directory. Do not overwrite, delete, move, or repoint
an existing path. Finish with the tag, commit, CLI version, installed paths,
host, and verification receipt.

The workflow stops on version, source, or destination conflicts.

Then ask normally. Use $swiftui-semantic in Codex or /swiftui-semantic in Claude Code.

Run the first audit

FAQ

Product boundaries

Read the documentation
Is this a linter?

No. The CLI compiles supported source facts into a semantic graph and evidence-backed findings for agent reasoning. It does not score style or prescribe a wrapper.

Does it change project source?

No. The CLI is non-mutating. A coding agent may propose a focused refactor after establishing ownership and behavior, but the tool itself never rewrites code.

Why does the agent workflow require a fresh Index Store?

Compiler-backed symbol identity and cross-file relations provide the evidence level required by the bundled workflows. They stop rather than present weaker evidence as a semantic result.

Are local drafts reported as duplicate state?

Not when the graph proves a real transaction with commit and discard behavior. Missing or fake discard topology does not receive that protection.

Does a clean semantic diff prove the refactor is correct?

No. The diff verifies supported architecture facts. Relevant builds, behavior tests, product invariants, and source review remain required.

Does the CLI send source to a model provider?

No. The CLI has no embedded model API. A surrounding agent host may read source under its own product and data policies.