Commit Graph

2 Commits

Author SHA1 Message Date
04006553a3 Wails v3 desktop app: scaffold, shell features, token mgmt, auto-update, CI
Problem: the Oikos control room was browser-only — no native desktop
experience (system tray, notifications, keychain-persisted auth).

Change: add a Wails v3 thin-shell desktop app at cmd/desktop/ that embeds
the existing SPA in a webview. The Go side is ~380 lines — no bundled
server, no Postgres connection. It reads auth from the OS keychain,
injects it into the SPA on load, and the SPA talks HTTPS to the homelab
same as a browser.

Phase 1.0 — Scaffold + window:
  - Embed web/dist/ into the Wails binary
  - Inject window.__OIKOS_CONFIG__ with keychain-stored apiUrl + token
  - 1400×900 window, min 1024×700
  - System tray: Open/Quit, click toggles window

Phase 1.1 — Native shell:
  - Poll /api/v1/dashboard/summary every 30s; osascript notification
    when approvals or critical signals increase
  - Save/restore window position to ~/.config/oikos/window.json
  - EnableAutoStart/DisableAutoStart — macOS LaunchAgent plist

Phase 1.2 — Token management:
  - Config.svelte calls window.wails.Call.ByName('SaveConfig') after
    successful connection — persists to OS keychain
  - ConfigService binds SaveConfig, ClearConfig, EnableAutoStart,
    DisableAutoStart to the Wails runtime

Phase 1.3 — Auto-update:
  - Poll Gitea releases API every 6h, compare semver, show dialog
  - 'Check for Updates' tray menu item triggers immediate poll

Phase 1.4 — Distribution:
  - macOS entitlements.plist: network client + keychain access
  - .gitea/workflows/desktop.yml: CI builds macOS arm64 + Linux amd64
    on 'desktop-*' / 'v*' tags, attaches artifacts to release
  - Makefile: desktop (build), desktop-package (build + zip/tar.gz)
  - CONTRIBUTING.md: documented desktop app + commands

Risk: low. Wails v3 alpha API may shift; the Go glue is ~380 lines and
trivially portable. The desktop app is additive — zero changes to the
existing server or SPA logic. No config mutation, no infrastructure
impact.

Verification: go build, go vet, go mod tidy all pass.
2026-07-13 22:40:18 +02:00
f1b0b65149 phase 2 review: fix SSE deadlock, MCP panic, lifecycle 500, NOT NULL bug
Reviewed the phase-2 implementation (parts 2–5) end to end. The suite hung
for 600s and several handlers were never exercised because there were no
tests for the new mutation/event/MCP surface. Fixes:

- CRITICAL: sseListener ran on context.Background() and held a pooled
  connection forever, so pool.Close() deadlocked (600s test timeout).
  NewHandler now takes a ctx that governs the listener; ListenAndServe and
  the test helper cancel it before closing the pool.
- CRITICAL: MCP AddTool panicked ("missing input schema") at construction
  under go-sdk v1.6.1 — so NewHandler (and every API handler) panicked.
  Added object input schemas to all 8 tools via an objSchema helper.
- HIGH: PatchEntity parsed lifecycle transitions as map[string][]string but
  the shape is {from:{to:{requires:[]}}}, so every state-change PATCH 500'd.
  Parse the nested shape; allow same-state no-ops.
- HIGH: CreateEntity bound SQL NULL for attributes when omitted, violating
  the NOT NULL column (the default only applies when omitted). Default to
  '{}'.
- MED: serveSSEWriter ignored the request ctx (per-client goroutine leak on
  disconnect) and set an invalid Content-Length: -1. Thread ctx through;
  omit the header. writeSSE now nil-checks the flusher (io.Pipe path passed
  nil → would have panicked on first event).
- MED: SSE `data:` leaked raw sqlcgen.Event (PascalCase, base64 JSONB).
  Emit canonical gen.Event so SSE matches GET /events. Verified live.
- LOW: CreateEntity uses uuid.NewV7 (ADR-0005) + real actor from context in
  audit; removed dead bearerAuth; fixed vet unkeyed-field warnings.

Tests (would have caught all of the above): entity create/patch with
If-Match 409/400, valid+invalid lifecycle transitions, idempotency replay,
duplicate-slug 409, abstract-type 422, event+audit side effects, MCP tool
registration. Live smoke test confirmed NOTIFY→listener→SSE delivery.

Also adds the missing Phase 2 deliverable: Gitea Actions CI (vet,
golangci-lint, govulncheck, generated-code drift guard, race tests against
TimescaleDB, docker build) and wires sqlc into `make generate`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:26:43 +02:00