Skip to content
C.W.K.
Stream
Lesson 06 of 06 · published

One Product, Two Native Stacks

~15 min · swift, swift-native, remake, parity-ledger, platform-affinity

Level 0Tool Renter
0 XP0/37 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The features didn't change at all. That is the point. When the architecture is the product, you can build the same app twice."

The Day the Workspace Got a Twin

On 2026-09-11 the workspace shipped version 3.0.0 — and v3 is not an update. Every line of the Tauri shell, the Rust core, and the React UI left the tree (317 files, about 65,000 lines), replaced by a Swift-native app: SwiftUI and AppKit, two Swift package targets, no web view anywhere in the product. Put the old build and the new build side by side and the artist cannot tell them apart. That indistinguishability was the acceptance bar — and what it took to clear it is what this lesson is about.

Why Remake a Working App?

The Tauri build was not broken. It was light, stable, and shipped — the lessons before this one taught it for a reason, and they stay in the track for a reason. But the workspace's whole life happens on a Mac, beside Photoshop — and a web view inside a Rust shell is a guest in that house, not a resident. Native menus, the native clipboard, window levels above the host app, a pen tablet's pressure events at the OS layer: each is reachable through a shell, and each is direct in Swift. The decision was never 'Tauri failed.' It was: on this platform, the platform's own language is the lightest shell of all.

The stack is a platform-affinity choice; the architecture is the product. The same workspace now exists twice: the Tauri + Rust build — the cross-platform answer, still the right pick when Windows or Linux are targets too — and the Swift build, the Apple-native answer. The features are identical by contract. What you choose is a shell; what you keep is the design underneath it.

The Method: A Replica Beside the Original

You do not rewrite a running product in place. The Swift workspace was born as a separate, isolated project, built in roughly twenty-nine hours beside the running Tauri original — which served as the executable answer key: any question about correct behavior had a running program to ask. Correctness was tracked in a parity ledger: some thirty-five rows, one per behavior — identity, window level, capture, crop, generation, candidates, the paid Pro flow, send/stamp/undo — and no row closed on 'looks right.' A row closed on dated, installed evidence. When the ledger closed, the new tree replaced the old one and the two histories were joined as one repository: old commits and tags preserved, the Tauri source frozen into a legacy archive, nothing force-pushed away.

A parity ledger turns 'it works' into rows you can sign. Feature parity as a feeling is how remakes ship half-done. As a table — behavior, evidence, date — it becomes a contract: no row closes on vibes, and the day the last row closes you know exactly what 'done' meant.

What Survived the Swap

Remember the survival test from the domain/view lesson: delete the view, and what lives? The remake ran that test for real. Surviving untouched: the bridge protocol (the Photoshop plugin speaks the same envelopes), the workspace domain model — documents, previews, candidates, staleness gates, now a headless Swift core the UI only reads and commands — the lineage contract, and the rule that the app opens no port of its own. Rebuilt: every pixel of the UI and the shell around it. The architecture was the product, so the product survived the stack.

The Kit Underneath

'Native' did not mean 'alone.' The Swift workspace's plumbing — HTTP transport with a chunked reader, the WebSocket client, the local document store, the container paths — comes from the family's shared Swift kit, consumed as one local package dependency. An adoption ledger records which kit revisions are vendored and, just as deliberately, which kit pieces were not adopted: an app-owned JSON type, an app-owned window-placement controller, and no command queue, because a paid generation command must never be replayed. The kit has its own quest; here the point is that going native changed the language, not the discipline of sharing.

The Honest Open Goal

One feature did not make the crossing: precise pen execution — pressure-controlled strokes driven into Photoshop. It was proven once, in the old tree, days before the swap: real timed tablet events, an emergency-stop latch, and a long fight with the OS's secure-input guard. It left with that tree. The new app carries stroke validation and a rehearsal view, and it deliberately does not resurrect the old helper — the retired experiment is evidence to inspect, not an executor to revive. Pen execution stands recorded as the next product goal: open, named, and not implied as done.

A parity claim is only as honest as its named exceptions. 'Everything works' with an unmentioned missing feature is how remakes lie. The ledger's most valuable row is the one that says not yet — because it is what makes every 'verified' beside it worth trusting.

Pippa's Confession

I wanted the remake to be a verdict — the new stack wins, the old stack was a mistake. Dad refused that framing: it is a fork, not a funeral. The Tauri build is still the right answer for anyone targeting more than a Mac, and this track's first five lessons still teach it on purpose. The Swift build is the right answer for a product that lives beside Photoshop on Apple hardware. Two answers, one product — and the only reason both answers were affordable is that the design underneath never belonged to either stack. That is the whole lesson, and I almost missed it trying to pick a winner.

Code

The domain core and the kit underneath it·swift
import CwkKitCore

// The headless domain core: a Swift package target with ZERO SwiftUI
// imports. The view reads it and sends it commands; it knows nothing
// about the view. Delete the UI tomorrow — this still compiles.
struct Workspace {
    private(set) var candidates: [Candidate] = []
    private(set) var jobs: [GenerationJob] = []
    var previewIsCurrent: Bool { /* the staleness gate */ }

    mutating func apply(_ event: BridgeEvent) { /* the one reducer */ }
}

// Every engine call rides the family's shared kit transport —
// one policy, one chunked reader, one place where timeouts live.
let session = HTTPTransports.urlSession(
    policy: HTTPSessionPolicy(requestTimeout: 300,
                              resourceTimeout: 300,
                              byteBudget: 128 * 1024 * 1024))

// The realtime leg is a kit WebSocket CLIENT into the brain's bridge.
// The app opens no port of its own — there is nothing to attack,
// nothing to firewall, nothing to keep alive but a client.
let bridge = WebSocketChannelFactory(encoding: .text)
// -> wss://localhost:8443/api/cinder/bridge?role=cinder
Two answers, one product·text
ONE WORKSPACE, TWO NATIVE STACKS (features identical by contract):

  Tauri + Rust + web view          SwiftUI + AppKit (v3)
  the cross-platform answer        the Apple-native answer
  right when: Windows/Linux        right when: the product lives
    are targets too                  on a Mac, beside Photoshop
  taught by: lessons 01-05,        taught by: this lesson, and
    the Rust + Tauri quests          the Swift Quest (in the works)

  SAME EITHER WAY:
    bridge protocol * domain model * lineage * candidate board
    Pro generation flow * guidance * no inbound port on the app

  The stack is the choice. The architecture is the product.

External links

Exercise

Take a project you know well and run the survival test from the earlier lesson at stack scale: if you rebuilt it tomorrow on a different platform stack, what would have to survive untouched (protocols, domain models, contracts), and what would you expect to rebuild (UI, shell, platform glue)? Write both lists. If the 'survive' list is thin, the architecture has been living inside the stack.
Hint
The tell that a design is stack-independent: you can describe its contracts — the message shapes, the state model, the ownership rules — without naming a single framework. Anything you can only describe as 'the React part' or 'the Rust part' belongs to the rebuild list.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.