Commit Graph

27 Commits

Author SHA1 Message Date
kami 7a95097cc7 feat: web UI redesign with PWA support, voice page, and mobile layout
- Redesign dash/history/notifications/trace pages with unified CSS framework
  (ui.css): cards, badges, scrollable tables, responsive grid, PWA shell.
- Add /voice page with chat-bubble transcript, listening indicator, and
  push-to-talk button for voice interaction.
- Add mavweb.js: client-side JS for live transcript polling, auto-scroll,
  push-to-talk, and service-worker registration.
- Update PWA manifest, icon, and service-worker for installable web app.
- Add WebAuthn credential management (register/list/delete) with modal UI.
- Update main.go: /voice route, WebAuthn handlers, new page navigation.
- Update handlers_test.go for new voice and WebAuthn routes.
2026-07-10 15:49:18 +04:00
kami 6bab68e96d reminders: add ListReminders IPC + /reminders web page
Store layer: ListReminders returns the n most recent reminders
(newest first). IPC: new MethodListReminders wired through server,
client, and lockedAPI. Web: /reminders page with table of created
time, fire time, status badge, and payload text; empty state with
prompt to ask maven for a reminder. Sidebar entry under Automation.
2026-07-06 22:12:10 +04:00
kami 15fe7bbc74 mavweb: passkey enrollment wraps encryption key, assertion unlocks daemon
- webauthn.go: keyIPC interface for StoreEncryptionKey/Unlock, wired
  through PasskeyHandle. RegisterFinish calls StoreEncryptionKey with
  the credential's public key after successful enrollment. AssertFinish
  calls Unlock with the stored public key after assertion (alongside
  existing AssertStepUp call).
- server.go: fix data race on s.api by switching from bare CoreAPI field
  to atomic.Value. SetAPI uses Store(), dispatch uses Load(). No more
  race-flagged tests.
- make test green (303+, -race)
2026-07-06 13:29:31 +04:00
kami 4793d77fa9 mavweb: gate /api/revert behind passkey step-up
RevertFact voids the latest fact for a key — a store mutation — but
/api/revert had no step-up gate, while POST /tools required L3. Close the
inconsistency: thread the same *webauthn.PasskeySession into handleRevert
and reject with 403 when a configured session isn't asserted. nil session
(WebAuthn unconfigured) keeps prior behavior — transport-level auth only.

Tests: un-asserted session → 403 and RevertFact not called; asserted → 200.
The RevertFact mock now records its key so the gate assertion is meaningful.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:04:11 +04:00
kami 9ffefd9e8c maven: gofmt handlers_test.go (fix stale alignment)
The Task-7 verification commit (d52f60c) added the CalendarEvents mock
method but left fakeCore's struct block misaligned, so `gofmt -l` still
flagged this file despite the "all gates green" claim. Realign it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:49:03 +04:00
kami d52f60c54e maven: fix test mocks for CalendarEvents interface (verification)
- Add CalendarEvents method to recordingAPI in auth_test.go
- Add CalendarEvents method to fakeCore in handlers_test.go

Co-Authored-By: opencode <opencode@anthropic.com>
2026-07-06 04:20:16 +04:00
kami 311c5cb1cd mavweb: ui design system refresh — shared nav, cards, component classes
- Rewrote ui.css with design tokens, card/btn/badge/dot components
- All pages wrapped in <main class=page> with max-width container
- Replaced inline <style> blocks with ui.css classes
- passkey page now uses shared nav.site template
- PWA voice page unified under shared nav.site (no more separate tab nav)
- Inline lang toggle moved from nav to voice page body
2026-07-06 00:41:16 +04:00
kami 2506327b62 progress: document voice-assistant gaps; theme the destructive checkbox
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 18:47:46 +04:00
kami d7c0cf89d3 mavweb: unify UI — shared ui.css + nav partial across all pages
One theme (the PWA's dark palette) for dash/history/trace/notifications/
tools/passkey via static/ui.css; shared nav template with active-page
highlight; tables wrapped in .scroll so they pan on phones; PWA nav no
longer clips the RU/EN toggle; dash 'updated' timestamp fixed (selector
matched nothing). AGENTS.md documents the local preview/screenshot recipe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 18:36:31 +04:00
kami 8d823000d1 fix: code-review findings on overnight-jul5
- digest queue no longer dropped on failed dispatch (retry next tick)
- collapsed reminders marked fired/rescheduled only after digest delivers
- /tools step-up gate skipped when WebAuthn is not configured (was 403 forever)
- passkey credential store rolls back memory on persist failure
- auth_test fake updated for TickTrace (branch build break)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
2026-07-05 17:50:21 +04:00
kami 5c34fb14f9 mavweb: add notifications history page at /notifications
RecentNudges IPC method, store adapter, dispatch, and client proxy.
Web UI at /notifications showing recent nudge history with color-coded
outcomes, nav links from /dash and /history.
2026-07-05 13:20:24 +04:00
kami 85013f7b8b mavweb: add rule trace page at /trace
New template showing the most recent tick's rule evaluation results
with color-coded table, expandable gate detail panels, nav links
to dash and history. Uses the TickTrace IPC method from #15.
2026-07-05 13:17:47 +04:00
kami 5afff001c3 mavweb: add in-process auth gate for POST /tools
Add a local PasskeySession that handleTools checks before processing
any POST action (enable/disable). If the session hasn't been asserted
within the 5-minute TTL, return 403 Forbidden.

Changes:
- webauthn/session.go: add IsStepUp() convenience method (nil-safe)
- webauthn.go: PasskeyHandle holds a *PasskeySession; AssertFinish
  calls session.Assert() after IPC step-up
- main.go: create stepUpSession, pass to handleTools and
  newPasskeyHandle; handleTools returns 403 if !session.IsStepUp()
- handlers_test.go: update TestEnableTool_NoInProcessAuthGate to
  expect 403; add TestEnableTool_WithAuthGate_RequiresStepUp for
  the happy path with asserted session; update all 10 call sites
2026-07-05 11:55:44 +04:00
kami c225ba37b2 pwa: add bilingual lang toggle to cheatsheet
Split cheatsheet into separate RU/EN <dd> elements with CSS class
toggle. Toggle buttons in nav bar (RU/EN). Language also controlled
by ?lang=ru|en query parameter (default: ru).

The rest of the PWA stays English; voice responses remain Russian.
2026-07-05 11:50:57 +04:00
kami 00a3bba9cb pwa: add app icon SVG and update manifest
Create a minimal SVG icon (512x512, dark rounded square with blue 'M')
and update manifest.json to reference it with purpose 'any'.

This fixes the blank tile on mobile add-to-home-screen and PWA install
prompts.
2026-07-05 11:49:44 +04:00
kami 6b80fd0c0f tools: add scope column for capability model
Add a 'scope' TEXT column (default 'homelab') to the tools table so tools
can be namespaced by scope (e.g. "homelab:restart", "datacenter:reboot").
Backward-compat: bare name defaults to "homelab" scope.

Changes:
- Migration #1: ALTER TABLE tools ADD COLUMN scope
- store.Tool: add Scope field, update all SQL and scanTool()
- ipc.Tool DTO and request types: add Scope field
- CoreAPI interface: pass scope in ProposeTool/EnableTool
- storeAPI adapters: forward scope
- cmd/mavend/voice: pass scope (empty → homelab)
- cmd/mavweb/tools: show scope column in UI tables, hidden fields
- All tests updated for scope field
- Migration test made dynamic (startVer = len(migrations))
2026-07-05 11:40:15 +04:00
kami 185f4f578e mavweb: extend test coverage to credentials, signal, dash, history, revert handlers
- New credentials_test.go: 4 test functions covering credentialStore
  (new, save, lookup, update, persistence across restarts).
- Extended handlers_test.go: fakeCore now supports WriteFact, Presence,
  RecentFacts/Nudges/Notes, RevertFact. Added 7 new test functions:
  TestNoCache, TestHandleSignal (5 subtestcases covering method guard,
  nil core, unknown key, known key, write error), TestHandleDash (3),
  TestHandleHistory (3), TestHandleRevert (6), and ListToolsError 502.
- Fixed history.html: Go html/template requires conditional class
  rendered as separate <tr> branches, not inline attribute.
2026-07-05 02:31:05 +04:00
kami a02e10fd11 ipc+mavweb: add revert/undo endpoint to void latest fact for a key
- New store.VoidLatestFact() method finds latest non-voided fact for
  a key and writes a void-marker row pointing at it (transactional).
- New IPC method MethodRevertFact with CoreAPI.RevertFact interface,
  storeAPI adapter, server dispatch, and client proxy.
- New HTTP endpoint POST /api/revert?key=<key> in mavweb.
- History page adds a 'revert' button per non-voided fact row with
  JS confirmation and optimistic UI (marks row voided on success).
- All existing store, IPC, and mavweb tests pass.
2026-07-05 02:18:39 +04:00
kami f8ba396fec mavweb: add /history page for command history
- New /history route displays recent facts in a dedicated page with
  voided-row styling (line-through + opacity + badge).
- Navigation link added to /dash page header.
- Handler calls core.RecentFacts(ctx, 200) and renders historyTmpl.
2026-07-05 02:13:13 +04:00
kami 44807b612c webauthn: persist credentials to JSON file instead of in-memory map
- New credentialStore type in credentials.go loads/saves
  map[id]localCred to a JSON file. Thread-safe with sync.RWMutex,
  writes to disk on every mutation.
- PasskeyHandle replaces sync.RWMutex+map with *credentialStore.
  Inline save/lookip/update closures delegate to store methods.
- newPasskeyHandle now takes a storePath parameter and returns an
  error; callers updated.
- New -passkey-file flag (default ./passkeys.json) configures the
  credential store path in main.go.
- Tests use os.CreateTemp in t.TempDir() so each test gets an
  isolated, auto-cleaned store file.
2026-07-05 02:09:56 +04:00
kami 7683a9b32c ipc: promote startup socket-wait to a shared DialWait; use in all modules
The cold-start crash-loop wasn't mavweb-specific — mavpoll and mavcaldav also
ipc.Dial + exit on failure, so they crash-looped until core booted too. Moved
the retry into ipc.DialWait (capped backoff, bounded) and switched mavweb,
mavpoll, mavcaldav to it. mavweb's local dialCoreWithRetry is gone.

Test: server appears after DialWait starts → it waits and connects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:17:10 +04:00
kami a38e733514 fix: local timezone for replies + quiet-hours; guard presence "ago" overflow
Timezone: the container ran in UTC, so mavend answered clock/date queries
(voice.go replySystem) and evaluated quiet-hours (gather.go) in UTC. Fixed at
the root — process TZ — rather than per-call: TZ=Europe/Samara in compose +
tzdata in the image (debian-slim strips it, without which Go ignores TZ and
stays UTC). One knob fixes replies and quiet-hours for every daemon; change the
zone in compose.

Overflow: the dash "ago" helper ran time.Since on a zero timestamp (no presence
yet / fresh db), saturating to ~292y and rendering "2562047h47m…". Guard zero →
"never".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:13:13 +04:00
kami 1a3ef572d1 mavweb: wait for core socket at startup instead of crash-looping
mavweb log.Fatal'd if mavend's socket wasn't up yet, so under compose it
crash-looped (relying on restart:unless-stopped) until core finished booting
its models. depends_on only orders container start, not socket readiness.
dialCoreWithRetry polls with capped backoff up to 60s; still fatal past the
deadline. Mid-life core restarts remain covered by ipc.Client's redial-on-drop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:08:56 +04:00
kami 88fb4912c4 mavweb: add a command cheatsheet to the voice page
Temporary reference while testing utterances: a collapsible <details> panel
(native, no JS) listing the six router intents (act/reminder/fact/note/query/
system) with real example phrasings pulled from models/seeds, RU-first since
the voice lang is ru.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:05:16 +04:00
kami de2058c851 mavweb: handler tests + gate DisableTool at step-up
- handlers_test.go: first tests for cmd/mavweb (feature-ranking #2). Covers
  the /tools enable/disable surface (arg parsing, error mapping, html escaping)
  and the webauthn handler contracts (method guards, malformed input). 14 cases.
  Verified the enable path is genuinely gated: an un-asserted call fails at the
  mavend IPC boundary (Requirement(EnableTool)=AuthStepUp), so mavweb stays a
  trust-nothing pass-through and core mediates.

- policy.go: DisableTool now also requires AuthStepUp. It mutates the same tool
  allowlist as EnableTool and is a lever to silence a security-relevant tool;
  gating allowlist mutation uniformly beats a split rule. ProposeTool stays
  maven-callable (no passkey). Corrects the stale api.go comment that claimed
  all three gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:22:10 +04:00
kami 6239eca243 items 5-7: passkey step-up, tools enable/disable, note RAG — end to end
Completes the three in-flight open items and fixes the away-fallthrough bug.

Item 7 — passkey step-up (WebAuthn):
- internal/webauthn: ES256/P-256 register + assert with real ecdsa signature
  verification, minimal CBOR/COSE decode, PasskeySession (L2→L3 on assert,
  decays after TTL). Drop the RS256 offer we can't verify (register-ok/
  assert-fail trap). Verify rpIdHash + UP/UV flags in FinishAssertion — UV is
  the step-up gesture. Round-trip test with negative cases (tampered sig,
  missing UV, wrong origin).
- cmd/mavweb: /auth/passkey enroll+assert page (the only surface that can do
  a WebAuthn gesture) + the four begin/finish endpoints. Without this the
  daemon's PasskeySession swap leaves /tools enable permanently blocked.
- daemon wires PasskeySession as the auth Session + srv.StepUp; policy gates
  MethodAssertStepUp at AuthRead.

Item 5 — tools page: DisableTool through store/ipc/client/wire; /tools grows a
disable action and a link to the passkey page. Lifecycle test.

Item 6 — note RAG: PhraseQuery on the phraser (LLM-composed answer over top-k
notes, raw-notes fallback); IntentQuery routes through it. Stub returns a
deterministic summary.

Item 2 — away-fallthrough: on ErrVoiceNoSession the dispatcher now reroutes
through the AWAY table (sev3→ntfy, sev4→telegram-repeat-til-ack, sev≤2→drop)
instead of silently dropping / mis-routing to the present-list remainder.
Covers DispatchNudge + DispatchReminder. 4 tests.

Also: re-add ProposeTool to CoreAPI (dropped in a comment rewrite), fix
missing imports + a duplicate block left mid-edit, drop dead AssertStepUpFunc,
gitignore /mavcaldav.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:41:13 +04:00
kami e00cb07658 fix zombie leak, add quiet-hours toggle, improve query reply, configurable router threshold, JS dashboard 2026-07-03 00:42:35 +02:00