SwiftUI architecture evidence for coding agents

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

Find the local copy and the writes that keep it synchronized with its parent. SwiftUI Semantic Audit builds a deterministic semantic twin: a source-linked map of ownership, copies, and calls that Codex or Claude can inspect before choosing a change.

  • Open sourceMIT License
  • One skill to invokeCodex or Claude Code
  • Provider independentNo embedded model API

Inside the semantic twin

The agent sees ownership and data flow before it edits.

The editor below keeps two copies of the same name and synchronizes every change. The graph identifies both copy paths; the agent still needs to establish whether edits should be immediate or deferred.

Manual synchronization

BindingMirroredLocally local mirror
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 twin

One value has two mutable representations and reciprocal copy paths.
  1. AccessprofileName is a Binding to external state; its owner is outside this excerpt.
  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
Inspect the JSON evidence
{
  "rule": "mirrored-state",
  "severity": "high",
  "confidence": "strong-inference",
  "evidence": [
    { "file": "Fixture.swift", "kind": "assignment",
      "startLine": 10, "endLine": 10 },
    { "file": "Fixture.swift", "kind": "assignment",
      "startLine": 13, "endLine": 13 },
    { "file": "Fixture.swift", "kind": "assignment",
      "startLine": 16, "endLine": 16 }
  ]
}

Selected fields from slice.finding; IDs and other fields omitted. Locations refer to the full source example.

Direct Binding

BindingMirrorEditor direct input
struct BindingMirrorEditor: View {
    @Binding var profileName: String

    var body: some View {
        TextField("Name", text: $profileName)
    }
}
For immediate editing, the same field writes through profileName. If Save/Cancel is required, keep a local draft and remove the premature write instead.

Use cases in code

Three SwiftUI problems agents can inspect with evidence

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.

Command in the setter 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.
Same model, direct write no command wrapper
@Bindable var model: CommandPagerModel

selection: $model.page

Keep validation and side effects if selectPage performs them. Direct projection is appropriate only when the method does nothing beyond assigning page.

2

Narrow the component boundary

A reusable leaf that needs a title and a reload action also receives the model that owns them.

Model forwarded to a leaf depth 2
struct Middle: View {
    @Bindable var model: FeatureModel
    var body: some View {
        Leaf(model: model)
    }
}

struct Leaf: View {
    @Bindable var model: FeatureModel
    var body: some View {
        Text(model.title)
        Button("Reload") { model.reload() }
    }
}
Keep Middle as the adapter: Leaf(title: model.title, reload: { model.reload() }).
Same leaf, focused inputs focused input
struct Leaf: View {
    let title: String
    let reload: () -> Void

    var body: some View {
        Text(title)
        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.

Flag stored alongside its inputs 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.
Same inputs, computed flag 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

A real local draft is not a fix target.

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.

Edits stay local until Apply 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.

Use cases

Ask for the SwiftUI work you already need.

Invoke $swiftui-semantic in Codex or /swiftui-semantic in Claude Code, then describe the outcome. The skill selects the workflow internally.

  1. 1

    Understand unfamiliar state flow

    Ask who owns a value, why it is synchronized, or where a model crosses component boundaries.

    Explain who owns this state and why it is synchronized.

  2. 2

    Fix a data-flow problem

    Ask to remove duplicated state, callback plumbing, or a suspicious Binding without changing behavior.

    Remove this duplicated state without changing behavior.

  3. 3

    Review an agent-authored change

    Ask whether an existing diff changed ownership, write paths, or dependencies in ways that may affect transaction semantics.

    Review these SwiftUI changes for ownership regressions.

Semantic diff

See what changed in the architecture.

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
Write access Binding to external state Binding to external state Preserved
Local mirror State representation Absent Removed
Synchronization Reciprocal copy path Absent Removed
Field write Through local draft Direct Binding Explicit

Semantic surface

What the twin can inspect

Thirty bounded rules cover ownership, writes, Bindings, component boundaries, interaction, layout, and environment. 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
  • reusable-component-owner-dependency
  • 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 is fact—and what is judgment.

The skill routes the work. The CLI extracts deterministic facts; Codex or Claude makes contextual decisions.

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.

Facts and assessments

Read/write paths are extracted facts. Findings are rule assessments; product intent and behavior still need review.

No embedded model API

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

The analysis does not cover every runtime, control-flow, security, or performance defect. A clean report does not prove behavior, and a JSON slice is not guaranteed to be smaller than source. Builds, source review, and behavior tests remain necessary.

Installation

Install or update with one prompt

Paste the matching request into Codex or Claude Code. The agent follows the current release guide and verifies the result.

Paste into Codex or Claude Code

Full guide

New installation

Install SwiftUI Semantic Audit from this GitHub guide. Install Homebrew first if needed, then the CLI and all four agent skills:
https://github.com/potapenko/swiftui-semantic-audit/blob/0.6.0/docs/getting-started/installation.md

Existing installation

Update SwiftUI Semantic Audit to the latest stable release using the guide linked from https://swiftui-audit.dev/#install. Update the Homebrew CLI and all four agent skills separately, then verify that they use the same release.

FAQ

Using the skill safely

Read the documentation
What is the semantic twin?

It is a deterministic, simplified representation of supported SwiftUI ownership and data-flow facts. The CLI extracts it from source and compiler evidence; the agent uses it for reasoning without rewriting the records.

Which skill should I use?

Use swiftui-semantic. Describe the SwiftUI outcome you need in Codex or Claude Code; the skill selects the internal workflow and carries its evidence forward.

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?

Agent workflows use a fresh compiler Index Store from the current build and the matching analysis configuration. They stop rather than present stale, mismatched, or lower-resolution evidence as a semantic result.

Does the semantic twin update continuously?

Release 0.6.0 adds the project watcher. Agent workflows use its live twin only when swiftui-audit project status --wait indexed --format json returns a fresh indexed status receipt matching the current workspace and analysis configuration. Syntax previews, failed generations, and stale receipts are diagnostics, not current evidence.

Are local drafts reported as duplicate state?

A real Apply/Discard boundary can justify local state. Check every write to the external value: finding a pair of buttons alone does not establish that edits remain private.

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.