<!-- Generated by Vibe Doc v0.3.2 -->
<!-- Date: 2026-04-17T00:38:55.155Z -->
<!-- Classification: IntegrationConnector -->
<!-- Source artifacts: 69 files scanned -->
<!-- Confidence: 9 high, 0 medium, 0 low sections -->

---
docType: adr
version: 1.0
templateVersion: 1
---
<!--
Generated: 2026-04-17T00:38:55.153Z
Classification: IntegrationConnector
Source artifacts: 69
High confidence sections: 9
Needs input: 0
-->

# Architecture Decision Records — Sanduhr für Claude

> Windows v2.0 "Diamond" — architectural decisions captured from the approved design spec, implementation plan, changelog, and README.
> Primary source: `docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md`

---

## ADR-001: Rewrite in PySide6, not C#/WPF/WinUI

**Date:** 2026-04-16
**Status:** Accepted

**Context:** The v1 `sanduhr.py` widget (615 LOC, Python/tkinter) lacked Windows-native visual quality — no Mica, no DPI awareness, no real OS integration. A full-quality Windows port was needed to match the already-shipped Mac SwiftUI version. The core question was which technology stack could close the quality gap at acceptable implementation cost.

**Decision:** Rewrite using Python + PySide6 (Qt 6). Qt 6 provides real Win11 Mica via DWM API access, proper DPI scaling, Segoe UI Variable typography, and CSS-driven theming — all while keeping the implementation language consistent with v1. The spec phrases the ROI argument directly: "80% of WPF polish for 20% of the effort."

**Alternatives considered:**
- **Native C#/WPF or WinUI 3** — Evaluated and explicitly rejected. The extra week of work buys a marginal polish delta no end user will notice. A full rewrite in a different language also severs the feature-parity shortcut (porting Swift logic to Python stays cheap; porting Swift logic to C# does not).
- **Tauri (Rust + WebView)** — Rejected. This would require throwing away the Mac SwiftUI work for uniformity, which is not acceptable. Also adds Rust and web-layer complexity for no tangible benefit over Qt.
- **Shared Rust core** — Rejected for the same reason as Tauri; the architecture would require more infrastructure than the problem demands.
- **Upgrade existing tkinter v1** — Tkinter cannot produce Mica, proper DPI handling, or Win11-grade visuals. Not a viable path to the target quality bar.

**Consequences:** Feature ports from the Mac SwiftUI branch remain fast since both are implemented in scripting-level languages. The Python ecosystem (keyring, cloudscraper, PyInstaller) is a natural fit for all integration needs. The tradeoff is a ~95MB installed footprint from bundling the Qt runtime — acceptable for a desktop utility.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Non-goals" and "Goal" sections -->

---

## ADR-002: Twin-codebase architecture (Mac SwiftUI + Windows PySide6)

**Date:** 2026-04-16
**Status:** Accepted

**Context:** Sanduhr targets two desktop platforms with meaningfully different OS idioms. The Mac version was already shipped as a native SwiftUI app with NSVisualEffectView vibrancy and Keychain storage. A cross-platform approach would require either replacing the Mac codebase or finding a shared UI layer.

**Decision:** Maintain two independent, platform-specific codebases: `mac/` (Swift/SwiftUI) and `windows/` (Python/PySide6). Shared elements are limited to concepts that translate across the boundary without code: the API client shape (same endpoints, same cookie header), pacing math (ported verbatim as pure functions), and Keychain/Credential Manager service name (`com.626labs.sanduhr` — identical string on both platforms).

**Alternatives considered:**
- **Shared Rust core with native UI shells** — Would require replacing the existing Swift UI layer and adding substantial cross-language FFI complexity. Rejected as over-engineering for a two-platform side project.
- **Electron or Tauri (single codebase)** — Would produce a web-rendered UI that can't match the OS-native visual quality (Mica, SwiftUI vibrancy) that is the primary differentiator of v2. Rejected.
- **Qt on both platforms** — Would abandon the Mac's mature SwiftUI codebase with no user-visible benefit. Rejected.

**Consequences:** Each platform can evolve independently at native speed. A new feature requires two implementations, but the spec explicitly calls this out as acceptable: "feature ports from Swift remain fast and cheap" in Python. The pacing engine (`pacing.py`) is ported verbatim from v1 and stays pure Python, so the math doesn't diverge between ports.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Non-goals" and "Architecture" sections; README.md Platforms table -->

---

## ADR-003: Non-disruptive branch strategy (`windows-native` branch, root untouched)

**Date:** 2026-04-16
**Status:** Accepted

**Context:** A v1 `sanduhr.py` on `main` was already in use by people who run the widget directly from source. The v2 Windows rewrite is a substantial structural change (new `/windows/` directory, modular package layout). Merging that work prematurely to `main` would break or confuse users on the existing v1 path.

**Decision:** Cut a `windows-native` branch from `main`. All v2 Windows work lives there. The root `sanduhr.py` on `main` is left completely untouched. Branch `main` does not get reorganized until both the Windows and Mac platforms are stable and ready to unify under a single repo structure.

**Alternatives considered:**
- **Merge to `main` immediately** — Rejected. Anyone cloning `main` today gets a working widget; disrupting that for in-progress v2 work is unnecessary risk.
- **Fork to a separate repo** — Would fragment releases, issues, and the shared `docs/screenshots/` reference artifacts. Rejected.

**Consequences:** Users on `main` are unaffected during development. Each platform is tagged separately (`v2.0.0-windows`, `v1.0.0-mac`) rather than a single unified version. The `docs/specs/` directory is added on the branch for cross-platform feature specs going forward — one spec, two ports per feature.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Repo & branch strategy" section; CHANGELOG.md v2.0.0-windows entry -->

---

## ADR-004: Mica via direct ctypes DWM API, not Qt stylesheet

**Date:** 2026-04-16
**Status:** Accepted

**Context:** Win11 Mica is the primary visual differentiator of v2 — translucent blurred system backdrop applied to the app window. Qt 6 does not expose Mica through its stylesheet or platform API surface; it must be applied through direct Windows API calls.

**Decision:** Call `DwmSetWindowAttribute` directly via Python `ctypes` with `DWMWA_SYSTEMBACKDROP_TYPE = DWMSBT_MAINWINDOW` to apply Mica. Rounded corners are similarly set via `DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_ROUND`. A runtime build-number check (Windows build 22000+ required for Mica) gates the call. Win10 fallback renders a solid theme background color with a custom `paintEvent` rounded rectangle using `WA_TranslucentBackground`. All of this is encapsulated in a single helper module (`mica.py` / `apply_mica()` in `themes.py`).

**Alternatives considered:**
- **Qt stylesheet transparency hacks** — Qt's `setAttribute(WA_TranslucentBackground)` can produce transparency but cannot produce the blurred-desktop Mica material. Would result in a fully transparent or acrylic-style window, not the frosted glass Mica look. Rejected.
- **Skip Mica, use solid bg on all Windows** — Technically simpler, but Mica is a primary feature of the v2 spec and the stated quality bar for matching the Mac vibrancy experience. Rejected.

**Consequences:** Direct DWM calls are undocumented in the Qt layer, making this a Windows-only code path tightly coupled to OS version detection. The three-branch fallback (Mica / Win11 no-Mica / Win10) adds complexity but is isolated to `mica.py`. The Matrix theme deliberately opts out of Mica entirely (forces `DWMSBT_NONE`) — the branching already exists, so this is a zero-cost addition.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Window-level material" section under Visual Design -->

---

## ADR-005: Windows Credential Manager via `keyring`, matching Mac Keychain service name

**Date:** 2026-04-16
**Status:** Accepted

**Context:** v1 stored the session key as plaintext in `~/.claude-usage-widget/config.json`. A live Claude.ai session token stored in plaintext is a credential hygiene risk. The Mac v2 version already uses Keychain-backed storage. The Windows version needed a comparable approach.

**Decision:** Use the `keyring` library to store credentials in Windows Credential Manager. Service name is `com.626labs.sanduhr`, account names are `sessionKey` and `cf_clearance` — identical to the Mac Keychain entry names. This cross-platform name consistency means the same credential lookup logic can be described in shared specs without platform branching.

On first v2 launch, a silent migration reads the v1 `config.json`, writes the session key to Credential Manager, moves theme preference to `%APPDATA%\Sanduhr\settings.json`, and deletes the old plaintext file. The user sees nothing; the widget launches connected.

**Alternatives considered:**
- **DPAPI directly (without keyring)** — Would produce equivalent security but requires more Windows-specific boilerplate and gives up the cross-platform API surface `keyring` provides (the same `keyring.set_password` call works on Mac Keychain).
- **Keep plaintext config** — Rejected on credential hygiene grounds. A session token with API access should never live in a plaintext dot-file.

**Consequences:** The migration is one-way and silent — v1 plaintext config is deleted after migration. A v2 user who downgrades to v1 will need to re-enter their session key (v1 has no Credential Manager reader). This is documented as acceptable. On the rare case where `keyring` itself fails (KeyringError), the app falls back to prompting the user on each session rather than crashing.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "credentials.py" module description and "Migration from v1" section -->

---

## ADR-006: No code signing for v2.0

**Date:** 2026-04-16
**Status:** Accepted

**Context:** Unsigned Windows executables trigger a SmartScreen "Windows protected your PC" warning on first run. An OV (Organization Validation) code signing certificate costs approximately $80–200/year. The project is a personal side project distributed via GitHub Releases.

**Decision:** Skip code signing for v2.0. The SmartScreen warning is documented explicitly in `RELEASE_NOTES` and `TEST_PLAN.md` with instructions: "Click More info → Run anyway. This is expected for unsigned apps." This mirrors the Mac v2 behavior (ad-hoc signed, not notarized).

When signing is added in a future release, it requires a two-line addition to `build.ps1` — a `signtool.exe` invocation between the PyInstaller and Inno Setup steps. The path is clear and low-cost to execute.

**Alternatives considered:**
- **OV cert from a CA** — $80–200/year ongoing cost. Not justified for a side project with a small, technically-sophisticated user base who can follow "click More info → Run anyway" instructions. Rejected for v2.0 with explicit intention to revisit if the project goes public.
- **Self-signed cert** — Would suppress the SmartScreen warning on machines that import the cert, but creates friction for end users who must import a cert they don't recognize. Rejected as worse UX than the SmartScreen warning itself.
- **MSIX packaging (Microsoft Store)** — MSIX can bypass SmartScreen via Store distribution, but MSIX requires signing infrastructure and Store submission overhead that is explicitly out of scope (see ADR-007).

**Consequences:** First-run users encounter a SmartScreen warning. The user base is expected to be technically comfortable enough to proceed. The consequence is documented rather than hidden.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Code signing — deliberately skipped for v2.0" section -->

---

## ADR-007: PyInstaller one-folder + Inno Setup installer (not MSIX, not portable zip)

**Date:** 2026-04-16
**Status:** Accepted

**Context:** v2 must be installable by non-technical users without requiring Python to be present. Three viable distribution models exist for a Python Qt app on Windows: MSIX (Microsoft Store path), a portable one-file `.exe`, a one-folder bundle wrapped in a traditional installer, or an unsigned portable zip.

**Decision:** Use PyInstaller in one-folder mode (not one-file) to produce a `dist/Sanduhr/` bundle, then wrap it with Inno Setup to produce `Sanduhr-Setup-v{version}.exe`. One-folder mode gives faster startup and easier crash diagnosis compared to one-file (which extracts to a temp directory on every launch). Inno Setup is the standard tool for Windows freeware installers: it handles `Program Files` install paths, Start Menu shortcuts, optional Desktop shortcut, optional launch-at-login registry entry, and a clean uninstaller — all in a well-tested, widely-trusted package.

**Alternatives considered:**
- **MSIX / Microsoft Store** — Requires code signing infrastructure (see ADR-006) and Store submission overhead. Explicitly listed as a non-goal for v2.0. Can be revisited if the project goes commercial or public.
- **One-file PyInstaller** — Extracts to `%TEMP%` on every run, causing a startup delay and making crash dump directories harder to locate. Offers no user-visible benefit over one-folder when an installer unpacks to `Program Files` anyway. Rejected.
- **Portable zip (no installer)** — Eliminates the Start Menu shortcut, `%APPDATA%` path bootstrapping, and uninstaller. Increases user friction for a tool meant to "just work." Rejected.

**Consequences:** The installed footprint is ~95MB in `Program Files` — larger than a native C# app but comparable to other Qt/Electron desktop tools users already have installed. The build pipeline is fully scripted (`build.ps1`) and mirrors the Mac `build.sh` / `make-dmg.sh` in structure. Unused Qt modules (`QtWebEngine`, `QtMultimedia`, `QtQuick3D`) are explicitly excluded in `sanduhr.spec`, trimming ~120MB from the bundle.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Packaging & distribution" section -->

---

## ADR-008: User-droppable theme JSONs in `%APPDATA%\Sanduhr\themes\`

**Date:** 2026-04-16
**Status:** Accepted

**Context:** v1 had five hardcoded themes (Obsidian, Aurora, Ember, Mint, Matrix) with no way for users to add custom color schemes without modifying source code. The v2 rewrite is an opportunity to open theming to users without adding a GUI theme editor.

**Decision:** Themes at runtime are loaded from two sources: built-in themes compiled into `themes.py`, and user-created JSON files placed in `%APPDATA%\Sanduhr\themes\*.json`. Both sources are merged on startup; filename (minus extension) becomes the theme key. An `AGENT_PROMPT.md` file in the themes directory documents the JSON schema and includes a prompt users can hand to an LLM: "describe a vibe, get a theme JSON back." Theme creation requires no code changes and no GUI.

**Alternatives considered:**
- **GUI theme editor** — More discoverable, but adds significant implementation complexity (color pickers, live preview) for a power-user feature. Rejected for v2.0.
- **Themes in source only** — Would require technical users to fork the repo to add a theme. The `AGENT_PROMPT.md` approach lets non-technical users create themes via an LLM conversation. Rejected.

**Consequences:** Any syntactically invalid theme JSON fails gracefully (the file is skipped and a warning is logged — startup is not blocked). The built-in themes are always available as fallback. The LLM-prompt pattern is a novel UX approach that makes the feature accessible without a traditional UI.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "themes.py" module description and per-theme glass tuning section -->

---

## ADR-009: All glass tuning exposed as per-theme dials, not hardcoded constants

**Date:** 2026-04-16
**Status:** Accepted

**Context:** The five themes have meaningfully different visual characters — Mint is the airiest (most translucency), Obsidian is the densest, Matrix opts out of glass entirely. Hardcoding glass parameters (card alpha, border glow, accent bloom) in the rendering code would require branching on theme name throughout `tiers.py` and `widget.py`, coupling visual styling to rendering logic.

**Decision:** Each theme dict in `themes.py` carries four glass tuning dials: `glass_alpha` (card translucency), `border_alpha` (1px border intensity), `accent_bloom` (drop shadow blur and alpha for accent elements), and `inner_highlight` (optional 1px light-catch line at card top). `TierCard` reads whatever values the active theme specifies — no per-theme branching in card rendering code. Matrix sets `glass_alpha = 1.0` (opaque) and opts out of Mica via a flag in the same dict; the apply function checks the flag, not the theme name.

**Alternatives considered:**
- **Hardcoded per-theme branches in rendering** — `if theme_key == "mint": alpha = 0.50 elif theme_key == "obsidian": alpha = 0.68` — this pattern couples theme names to rendering logic and makes user-added themes (from ADR-008) unable to control glass behavior. Rejected.
- **Single global glass setting** — Would eliminate the distinctive character differences between themes (Mint's airy feel vs. Obsidian's density). Rejected.

**Consequences:** Adding a new theme (built-in or user-dropped JSON) automatically gets full glass tuning support without any changes to `TierCard` or `widget.py`. The visual character of each theme is fully self-contained in `themes.py`. Drop shadows apply only to static elements (card frame, percentage text); the progress bar fill — which updates on every refresh — stays flat to avoid repaint cost. Win10 fallback can dial back effects further without touching rendering logic.

<!-- Source: docs/superpowers/specs/2026-04-16-windows-v2-diamond-design.md — "Per-theme glass tuning" section and "Implementation pattern" note -->
