Design: App Foundation
Context
Stet's foundation must deliver a native, calm, reading-first macOS experience and the plumbing that feeds it: forge auth, PR/file sync, and code/Markdown rendering. It realizes ADR-0001 (macOS 26 + Apple silicon, SwiftUI + Liquid Glass), ADR-0002 (viewer-first, inbox-first, document-less), and ADR-0007 (GitHub + Gitea via OAuth, Keychain, polling-first sync, SwiftGit2). Everything else in the product — review markup (SPEC-0002) and agent dispatch (SPEC-0003) — is built on top of this shell.
The guiding constraint is that this layer must never feel like an editor. The home surface is an inbox; the canvas renders; editing is not a mode.
Goals / Non-Goals
Goals
- A three-region window (inbox · canvas · Tour rail) with native Liquid Glass chrome, correct on macOS 26.
- "Sign in with GitHub/Gitea" via OAuth with Keychain-stored tokens, reused across launches.
- A
Forgeprotocol that makes GitHub and Gitea genuinely interchangeable for PR listing, diff/content reads, and (later) PR creation. - An inbox that stays current via polling-first sync (webhook optional).
- A code canvas (CodeEditSourceEditor) and a Markdown renderer (Textual), both read-optimized.
- In-process git (SwiftGit2) for worktree/diff/blame.
Non-Goals
- Review markup, emoji verbs, voice/scribble, brief compilation — SPEC-0002.
- Container runtime, harness adapters, workflow execution, PR creation — SPEC-0003.
- Multiplayer / team review — future.
- Windows/Linux/iOS — out of scope by ADR-0001.
Decisions
Window and design system
SwiftUI WindowGroup with a NavigationSplitView-style three-region layout.
Chrome surfaces (toolbar, sidebars) use adaptive materials (.regularMaterial /
.thinMaterial); content surfaces are opaque for reading comfort. Materials must
adapt to content behind them — no fixed frosted panels (ADR-0001). SF Pro for UI,
SF Mono for code.
Authentication
A ForgeAuth component runs OAuth via ASWebAuthenticationSession (loopback
redirect) as the primary path and the OAuth 2.0 device flow as the headless
fallback. Tokens are persisted with a thin Keychain wrapper (kSecClassGenericPassword,
per-forge account key). The same OAuth2 machinery is parameterized for GitHub and
Gitea (endpoints, scopes) so there is one implementation, two configs.
Forge abstraction and sync
A Forge protocol exposes: listInbox(), pullRequest(id), diff(pr),
fileContents(ref, path), and (used by SPEC-0003) createPullRequest(...).
GitHub and Gitea REST implementations back it. A SyncEngine polls on an adaptive
interval (backing off when idle, tightening on activity, respecting rate limits)
and merges results into the inbox model; if the user supplies a reachable webhook
endpoint, webhook events short-circuit the poll for affected resources.
Canvas
Code: CodeEditSourceEditor (tree-sitter via CodeEditLanguages) wrapped in an
NSViewRepresentable, configured read-optimized (selection + annotation, no
primary text entry), with minimap, gutter, and diff. This AppKit interop is the
sanctioned exception from ADR-0001/0002. Markdown: Textual, rendering GFM with
selectable text and highlighted code blocks. A CanvasDocument model abstracts
"what is being viewed" (file, diff hunk, markdown doc) so the Tour rail (SPEC-0002)
can drive scroll/highlight.
Local git
SwiftGit2 (libgit2) provides worktree creation, diff computation, and blame
in-process. Diffs for PR rendering are computed locally from fetched refs where
possible, falling back to forge-provided diffs.
Risks / Trade-offs
- 1.0/young dependencies. Apple platform newness (ADR-0001), Textual 0.5.x, and SwiftGit2's libgit2 FFI each carry risk. Mitigation: thin wrappers behind our own protocols so any one can be swapped.
- Constraining an edit-capable canvas. CodeEditSourceEditor can edit; we must lock it to read/annotate and resist scope creep.
- Polling latency vs. rate limits. The adaptive interval needs tuning per forge; webhook path is the escape hatch.
Migration / Rollout
Greenfield. Milestone order: (1) window shell + design system; (2) OAuth + Keychain
Forgefor GitHub; (3) inbox + sync; (4) code canvas; (5) Markdown canvas; (6) SwiftGit2 diffs; (7) GiteaForge. Gitea can trail GitHub by one iteration but theForgeprotocol is designed for both from the start.
Open Questions
- Exact OAuth scopes minimally required for inbox + later PR creation per forge.
- Whether to bundle a prebuilt libgit2 or build from source in CI.
- Adaptive polling parameters (floor/ceiling intervals) per forge tier.