Architecture
Decision
Version 0: hybrid distribution.
consumer app
├── depends on SweetUIFoundations
└── owns copied component and block source- Foundations: shallow, environment contract, 1 surface modifier,
registryItem(_:)hook (environment value, anchor preference),registryScreen(_:), reporter, knobs; inert without surface. Components/blocks not package products. SweetUIDesignSurface: 2nd, optional debug-build product (tuning panel, preset codec, token engine, per-item knobs), moved from Showcase 2026-09-08, behinddesignSurface(). Foundations independent; MUST NOT import registry items; item-only consumers SHOULD NOT add it; from seeFood's Design mode.
Why this split
Copying both duplicates the foundation contract; packaging both breaks framework compatibility.
Hypothesis: finance/nutrition test, not universal-reuse. Foundation-free item SHOULD be allowed later.
Source tree
Sources/SweetUIFoundations/: stable package APIRegistry/items/: machine-readable item declarationsRegistry/sources/components/: copied styles, focused modifiers, compositionsRegistry/sources/blocks/: copied block sourceSources/SweetUIKit/,Sources/SweetUICLI/:sweetui, SwiftUI-free: validation, resolution, receipts, install, conflict-aware updates, search, preset codes, MCP server, generatorsExamples/Showcase/: iOS consumer, catalog, demo per item, tuning panel, item-screenshot routeExamples/TodoCounter/: 2nd consumer (Composable Architecture), URL package at published tag, Homebrew install, customized preset themesweetui generate catalog | showcase-manifest | site-data | item-tokens | usage-checks: derived from metadataWebsite/: Next.js static export, shadcn/ui, reads onlycontent/registry.json;npm run deploy→ Cloudflare Workers (Website/wrangler.jsonc);.github/workflows/pages.yml→ alt GitHub Pages deployScripts/capture_previews.py: per-item light/dark captures, pinned simulatorTests/SweetUIKitTests/: command, installer, validator, preset, MCP, generator contracts; fixtures, website codec check underFixtures/
Running the tool
3 ways, same engine: swift run sweetui <command> (clone); .build/release/sweetui (release build); Homebrew tap sweetui <command> (any directory).
Registry lookup order: --registry <path> override; nearest ancestor clone (Registry/registry.json); cached pinned-release snapshot, published tag, first use (docs/registry-spec.md, Agent usage).
SweetUIKit: SwiftUI-free, no SweetUIFoundations import. Deps: swift-argument-parser (parser), swift-dependencies (replaceable effects), swift-snapshot-testing (command/generator contracts). Executable owns parsing/output; foundations stays tokens only.
View boundaries
- Prepared display values, bindings, action closures.
Textpreserves caller format/localization. IDs/actions select without store, observable model, router, persistence type. - Transient
@Stateonly if self-contained; else external, coordinated by a view, block, restoration, persistence, or product logic. - Content via
@ViewBuilder. Interactive appearance: matching style protocol. Optional behavior: focusedViewModifier, not initializer growth. - Composed-view rule: initializer = content, bindings, actions, required accessibility. Owned choice (variant, tone, tint, future size/emphasis):
registry-prefixed copy-return, before generic modifiers:InlineAlert("...") { }.registryVariant(.positive),TransactionRow(...).registryTone(.negative),MacroProgress(...).registryTint(.orange). ReturnsSelf(mutated copy); default: prior. Controls:registrystyle (.buttonStyle(.registryOutline)). Text:registrymodifier, variant (registryBadge(.positive)). Theme:registryTheme(_:); size:controlSize. - Composed block MUST NOT own
ScrollView, navigation container, max width. Showcase applies iPad width at call site.
Foundations
RegistryTheme:accent(optional),onAccent,surface,border,positive,negative,disabledOpacity.RegistryMetrics: 3 spacings, control padding, 2 border widths, compact/control/card radii.registryTheme(_:):EnvironmentValues/@Entry, accent→subtree tint (1 scene-root call);nil→app tint (default MUST NOT override).tint(nil)resets not inherits: SwiftUICore'senvironment(\.tintColor, tint)(package-private). Toggle at runtime resets subtree; apply pre-scene, useColor.accentColor.- Presets:
system,graphite,indigo,rose,emerald,amber, starting points not a theme engine.graphite: primary accent, background-colored label.amber: light accent, dark label, provesonAccent. - Semantic tokens replace repeated colors/metrics; token enters foundations once 2 items need it (
everyFoundationTokenHasTwoSemanticConsumers,Tests/SweetUIKitTests/RegistryContractTests.swift). Style stays source-owned until 2 items match. - Tuning panel: theme creator. Since Stage 9, Tune button (
tabViewBottomAccessory) above tab bar opens a floating resizable card. Tool's own window, not a sheet; app stays live. Live tokens, one-tap presets, Copy Swift (RegistryThemeinit), Copy Code, Import. - Custom accent: separate dark value, dynamic
UIColor. Panel/model/tokens live inSweetUIDesignSurface; swift-sharing persistsregistry-tokens.json. - Theme preview:
previewblock'sPreviewWall(ItemDemostheme-preview), live in panel.capture_previews.py --themes/--preset: full screen/preset (not 1 strip); Create/Themes: first screen/preset, iPhone 17; else CSS board. - Preset code (
docs/registry-spec.md, "Preset codes"): theme as 1 string. Read/written by Create page, Showcase,sweetui preset, MCP server.preset apply→RegistryTheme+App.swift;capture_previews.py --preset: any code, pinned simulator. Create page: CSS board, real capture (6 presets), no SwiftUI render.
Compatibility policy
Pre-1.0: patch source-compatible, minor MAY change contract. Items declare upToNextMinor from known-good floor, currently 0.5.0, beta contract (docs/registry-spec.md). Source verified: platform floor, foundation range; receipt records requirement at install.
Installation behavior
Installer reads Registry/registry.json, resolves depth first, validates safe relative paths, preflights collisions, copies exact source. Records versions, dependency declarations, target paths, source/installed digests, non-Swift base snapshots (.sweetui/). Prints package instruction (URL, requirement, product) for closure.
Repeated install skips exact receipt-backed source only; untracked/modified target fails unless --force. MUST NOT edit .xcodeproj, infer target membership, add package dependencies. Xcode buildable folders ease Showcase; not assumed elsewhere.
--plan prints only: ordered closure (versions, kinds), target status (new, up-to-date, modified-would-require-force, would-merge), package requirements, collisions, manual steps. Recipe: native guidance only. --diff: difflib unified diff per receipt-backed owned file vs canonical, exit 0 parity, 1 diff, fails loudly if receipt missing. Neither mode mutates.
Update policy
Copied source stays consumer-owned; --update distinguishes upstream/local/concurrent edits via receipt base. Concurrent: git merge-file behind installer. Clean merge: owned source, base advances. Conflict: files unchanged, reviewable .merge. Preflights full closure first; 1 conflict can't partial-update; not registry-authoritative.
Presentation policy
3 derived surfaces, same metadata, none hand-edited. Markdown catalog (docs/catalog/); Website/content/registry.json (preview paths, install order, usage, source, requirements, accessibility, presets per item); Showcase manifest. Each: byte-exact freshness test.
Website (sidebar, tabs, toggle group, command search, cards, tables) renders JSON, 1 page/item. Contents: preview, install command, usage, source, details. No HTML committed. Item images: Showcase's -item launch, cropped to demo frame; same code/simulator as visual contract.
Discovery policy
sweetui search: deterministic adapter over existing JSON. Filters kind/platform, requires every query term match indexed metadata, emits stable pre-install JSON.
Search stays local. sweetui mcp: stdio adapter, same engine; agent in a consumer searches/plans/installs without leaving editor. Hosted adapter matters if distribution, auth, or catalog scale varies independently of local metadata.
Platform decision
V0 targeted iOS 18/iPadOS (iOS SDK); 2026-08-31 reversed to iOS 26 floor. Items inherit Liquid Glass natively, no pre-26 styling/27-only APIs. Evidence: iOS 27 simulator only, no iOS 26 runtime here; compile-time + 27-runtime verify floor-26. No macOS, watchOS, tvOS, visionOS declared.
Dependency direction
Foundations <- Components <- Blocks <- FlowsDependencies point down only. Registry source MUST NOT import application architecture. Foundations MUST NOT import registry items.
Rejected alternatives
- 1 monolithic UI package: undermines source ownership, progressive adoption
- Copying foundation files per item: duplicates theme contracts
- Production CLI before product slices: validates packaging over product UI. Stage 6 owner authorized the rewrite, now shipped as
sweetui - Generic Button, Toggle, Slider, List, navigation wrapper views: hide Apple primitives instead of native style protocols/modifiers
- Mandatory TCA, MVVM, Observation model, persistence type: leaks application architecture into presentation