docs(installer): finalize decisions — self-contained, auto-check, full uninstall

- App + Worker now self-contained (zero .NET runtime dep on target)
- Collapse Manage mode into "update check -> Config view" on every
  subsequent launch; Repair + Uninstall become buttons in Config
- Uninstall removes {InstallDir} and ~/.todo-app in full (no prompt
  to keep data) — matches user's stated intent

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-04-15 08:21:12 +02:00
parent 43a10cff95
commit 0498fbae47

View File

@@ -37,8 +37,9 @@ works from inside a source checkout on a machine with the .NET SDK installed:
`CreateShortcutsStep`, `RegisterServiceStep`) are fine to keep.
The installer also contains a partial "Settings" window
(`Views/SettingsWindow.xaml`, `Views/SettingsViewModel.cs`) — that wiring will
be reused for the Manage-mode branch described below.
(`Views/SettingsWindow.xaml`, `Views/SettingsViewModel.cs`) — that wiring is
reused for the Config view shown on subsequent launches (see Mode detection
below).
## High-Level Design
@@ -54,7 +55,8 @@ which is never public. The installer only needs to hit `releases/ClaudeDo`.
**2) An installer rewrite** that replaces the three publish/deploy steps with
a single `DownloadAndExtractStep`, detects existing installs via a marker
file, and branches into Install / Update / Manage modes.
file, and on subsequent launches checks the Gitea API for updates before
deciding whether to show the Update flow or jump straight to the Config view.
## Release Artifacts
@@ -71,9 +73,10 @@ Decisions:
- **One combined app+worker zip** (not two separate). Reasons: one download,
one extract, guaranteed version-locked pair.
- **Self-contained installer exe** — user does not need .NET installed.
- **App + Worker**: framework-dependent (`--self-contained false`), same as
today. This assumes the target machine has the .NET 8 Desktop Runtime.
*Open decision — see "Decisions to revisit".*
- **App + Worker: self-contained** (`--self-contained true`, `-r win-x64`).
Zero runtime dependency on the target machine, at the cost of a larger
download (~100 MB combined zip). Decision: acceptable trade-off — the
installer is one-click, not per-user-problem-to-debug.
- **Checksums file** — plain text, one line per asset (`<sha256> <filename>`),
verified by installer before extract.
@@ -98,8 +101,8 @@ File: `.gitea/workflows/release.yml`
1. Checkout
2. Setup .NET 8 SDK
3. Derive version from tag (`${{ gitea.ref_name }}` without the `v` prefix)
4. `dotnet publish src/ClaudeDo.App -c Release -r win-x64 --self-contained false /p:Version=$VERSION -o out/app`
5. `dotnet publish src/ClaudeDo.Worker -c Release -r win-x64 --self-contained false /p:Version=$VERSION -o out/worker`
4. `dotnet publish src/ClaudeDo.App -c Release -r win-x64 --self-contained true /p:Version=$VERSION -o out/app`
5. `dotnet publish src/ClaudeDo.Worker -c Release -r win-x64 --self-contained true /p:Version=$VERSION -o out/worker`
6. `dotnet publish src/ClaudeDo.Installer -c Release -r win-x64 --self-contained true /p:Version=$VERSION /p:PublishSingleFile=true -o out/installer`
7. Zip `out/app` + `out/worker` as `ClaudeDo-<version>-win-x64.zip` with
`app/` and `worker/` as top-level dirs
@@ -133,17 +136,45 @@ Written at the end of every successful install or update to
The installer reads this on startup (from the default install dir, or a
path supplied via CLI arg) to decide which mode to run in.
### Mode detection (`InstallModeDetector`)
### Launch flow (`InstallModeDetector`)
| `install.json` state | Mode | Wizard flow |
|-------------------------------------------|---------------------|-----------------------------------------------------|
| absent | **Install** | Welcome → Paths → UiSettings → Service → Install |
| present, version < installer's version | **Update** | Welcome (shows old→new) → Install (download + swap) |
| present, version == installer's version | **Manage** | Welcome → choose: Repair · Edit Config · Uninstall |
| present, version > installer's version | **Downgrade warn** | Welcome explains; user can cancel or force Install |
On every launch, the installer checks for `install.json` first:
The installer's own version comes from its assembly (`AssemblyInformational
Version`), set by the workflow's `/p:Version=$VERSION`.
```
install.json absent?
-> Install mode: Welcome -> Paths -> UiSettings -> Service -> Install
(writes install.json at the end)
install.json present?
-> Query https://git.kuns.dev/api/v1/repos/releases/ClaudeDo/releases/latest
(short timeout; if offline, treat as "no update available")
latest.tag_name > installed.version
-> Update mode: Welcome ("Update v0.1.0 -> v0.2.0, Update / Later")
If user accepts -> Install steps (download + swap service)
If user declines -> fall through to Config view
latest.tag_name <= installed.version (or API unreachable)
-> Config view: directly open Paths/UiSettings/Service tabs,
prefilled from existing ~/.todo-app/*.json.
Action buttons: Save · Repair · Uninstall.
```
Key properties:
- **First run = wizard**, as today — no behavior change for new users.
- **Every subsequent run = update check first**, then either offer update or
drop straight into Config. No "Manage page" with a menu of actions — the
Config view *is* the default, and Repair/Uninstall are buttons on it.
- **Offline / API error = not fatal**: if the release endpoint can't be
reached, the installer silently skips the update check and opens Config.
The user is never blocked from managing an existing install by a network
issue.
- **Downgrade** (installed version > latest) is treated the same as "no
update available" — we don't ever offer a downgrade.
The installer's own version (shown for reference in Config) comes from its
assembly (`AssemblyInformationalVersion`), set by the workflow's
`/p:Version=$VERSION`. The *installed* version comes from `install.json`.
### New step: `DownloadAndExtractStep`
@@ -178,15 +209,25 @@ naturally non-destructive.
- **Conditional:** `RegisterServiceStep` only if service is not currently
registered (covers someone who unregistered it manually)
### Manage mode — branches
### Config view — actions
- **Repair:** re-download + extract (same as Update), re-create shortcuts,
re-register service. Leaves config/DB alone.
- **Edit Config:** opens Paths / UiSettings / Service pages prefilled from
existing `~/.todo-app/*.json`. Save writes the JSON files and offers
"Restart service" if worker config changed. No download.
- **Uninstall:** stops + unregisters service, removes shortcuts, deletes
`InstallDir`. Asks separately whether to delete `~/.todo-app` (config + DB).
- **Save** (primary): writes the Paths / UiSettings / Service fields to
`~/.todo-app/*.json`. If worker config changed, prompts "Restart service?"
and calls `sc stop` / `sc start` if accepted. No download.
- **Repair:** re-download + extract (same as Update flow), re-create
shortcuts, re-register service. Leaves config/DB alone. Confirmation
dialog before starting.
- **Uninstall:** confirmation dialog ("This removes ClaudeDo *and* all of
your tasks, config, and database. Type UNINSTALL to confirm."). On
confirm:
1. Stop + unregister service (`sc stop`, `sc delete ClaudeDoWorker`)
2. Remove Start Menu / Desktop shortcuts
3. Delete `{InstallDir}` (including `install.json`)
4. Delete `~/.todo-app` in full (config + DB + logs)
5. Exit
Everything is removed. No "keep my data" option — that was explicitly
declined in the design discussion.
### Files to add
@@ -245,34 +286,31 @@ an install in a half-deleted state.
## Decisions to Revisit
1. **Framework-dependent vs self-contained App/Worker.** Framework-dependent
keeps the zip small (~10 MB) but requires the user to install the .NET 8
Desktop Runtime separately. Self-contained is ~80 MB per process but
zero-dependency. Current plan: framework-dependent, matches today's
behavior. If install friction becomes a problem, flip the flag in the
workflow and installer messaging.
2. **Release notes content.** Auto-generated `git log` summary vs manual
1. **Release notes content.** Auto-generated `git log` summary vs manual
notes in the tag message vs empty. Start empty; revisit when there are
enough releases to care.
3. **Signed installer.** Out of scope for v1. Users will see a SmartScreen
2. **Signed installer.** Out of scope for v1. Users will see a SmartScreen
warning the first time. Note this in the README.
4. **Installer distribution page.** A simple `README.md` badge or a pinned
3. **Installer distribution page.** A simple `README.md` badge or a pinned
"Latest release" link on the Gitea repo home is enough for v1.
## Success Criteria
- On a fresh Windows VM with no source checkout and no .NET SDK:
- On a fresh Windows VM with **no source checkout, no .NET runtime, and no
.NET SDK**:
1. Download `ClaudeDo.Installer-<ver>.exe`.
2. Run it.
3. Complete the wizard.
4. ClaudeDo App launches, Worker service is running, a task can be created
and picked up.
- Running the same installer a second time shows Manage mode.
- Running the same installer a second time, with no new release published,
opens directly in the Config view after a quick update check.
- Publishing a new tag, then running the installer on the existing install,
performs an update without touching `~/.todo-app/todo.db` or the config
JSONs.
offers the update; accepting performs it without touching `~/.todo-app/todo.db`
or the config JSONs.
- Uninstall leaves no trace: `{InstallDir}` gone, `~/.todo-app` gone, service
unregistered, shortcuts removed.
- The entire release pipeline runs on `git.kuns.dev` with no manual steps
beyond `git tag vX.Y.Z && git push --tags`.