Commit Graph

17 Commits

Author SHA1 Message Date
2668401104 feat(ux): B3.1 + B3.4 — fresh-start re-entry + MicroStep where-was-I
B3.1: app shell tracks lastLaunchAt; >7-day gap opens a 24h
fresh-start window. While the window is active: a "Welcome back. This
week starts fresh." banner offers Archive old Inbox (Inbox-only via
archive_old_inbox_cmd, system-attributable copy on success), the
morning-triage modal is suppressed, the momentum sparkline is hidden,
and the nudge bus stays quiet. Banner is per-session dismissable.

B3.4: MicroSteps shows a "Last completed: X. Next: Y." re-orientation
banner when re-opening a parent task whose decomposition has been idle
>=30 min OR sat across a session boundary. Speaker button reads the
banner aloud via existing TTS. Per-parent last-activity timestamps are
persisted to a single localStorage key. Banner self-dismisses on first
interaction.

Shared inFreshStartWindow gate extracted to src/lib/utils/freshStart.ts
so ShutdownRitualPage (B3.14), TasksPage, MorningTriageModal, and
nudgeBus all read the same source of truth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:38:31 +01:00
fc3a56c0dc feat(ux): B3.6 — editable energy labels (global + per-profile) + first-tap prompt
Settings → Energy: edit global energy labels (label + description per
level) with reset-to-defaults. Per-profile override toggle in the
Profiles section ("Customise for this profile" / "Use global").
EnergyChip and Tasks-page energy controls now resolve labels via
resolveEnergyLabels(profile, settings) — global by default, profile
override when present.

First-time prompt: on first energy-chip tap of the day (per profile)
or first-ever tap to Zero, an inline non-modal prompt asks "What does
Zero mean for you today?" with example chips. Per-day gate via
energyPromptLastShownDate; one-shot first-Zero gate via
energyPromptZeroSeen; Settings escape resets both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:30:06 +01:00
0111fc9e08 feat(tasks): B2a part 2 — task_lists CRUD + archive cmds + frontend wire
Storage: 5 task_lists CRUD functions (list/create/update/delete/
import) and 4 archive functions (list_archived_tasks/archive_task/
unarchive_task/archive_inbox_older_than). 8 new tests cover CRUD,
import idempotency, atomic-failure rollback, dependent-task null-out,
archive scope, archive_inbox_older_than Inbox-only invariant, and
unarchive round-trip.

Tauri: 9 new commands (5 task_lists + 4 archive) registered in the
invoke_handler. New file commands/task_lists.rs mirroring tasks.rs
structure. TaskDto extended with archived/archivedAt.

Frontend: loadTaskLists rewired to read from SQLite via
list_task_lists_cmd on Tauri runtime, with a one-time migration
that imports kon_task_lists localStorage into SQLite via
import_task_lists_cmd and clears localStorage only on success.
mapTaskRow handles the new archived fields. TasksPage gets an
Archived filter pill (hidden when empty) and a per-row archive
icon. moveTaskList/sortTaskLists kept local pending a future
order-column migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:04:17 +01:00
6389fc2ce7 feat(tasks): PR 1.2 — default to Today + WIP Now lane reads filtered set
Flips the Tasks landing bucket from All to Today so the cold-open answer to
"what should I do now?" is scoped to today's commitments rather than the
full backlog. Refactors WipTaskList to accept a tasks prop instead of
reading the global store, and wires it into TasksPage so the Now lane and
the bucket list below share one filtered set as their source of truth —
the lane consumes the first three top-level rows and the bucket list
excludes those IDs, so no task is ever rendered in both panels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:25:55 +01:00
0a8cb55447 feat(copy): PR 1.1 — research-grounded copy + prompt corrections
Add cue-anchored "When [cue], [action]" framing to the task-decomposition
prompt where natural cues are present (Gollwitzer-style implementation
intentions, d=0.65 effect size). Soften Bionic Reading and accessibility-
font copy to honest preference framing per the v3 audit (Strukelj 2024;
Doyon n=2,074). Update timer nudge from "Still on that timer?" (which
read as judgmental) to "Timer's still running." Replace stale Tasks
page header copy promising automatic extraction.

Audio envelopes (focusTimer 20ms ramp, sounds.ts 10ms attack) verified
correct per memo §B; no code change needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:12:58 +01:00
a15167c44e feat(ux): dogfood pass — onboarding, tasks, LLM chip, float popout
Bundles a session of dogfood UX feedback plus the two Cursor Bugbot
findings on the auto-titles branch.

Onboarding (FirstRunPage):
- Welcome leads with "Set up automatically"; system breakdown and the
  full model list move behind a "Choose manually" disclosure
- Morning / evening / autostart modal copy trimmed to one short
  sentence each; CTAs shortened
- "Corbie" autostart string reverted to "Kon"
- Already-downloaded models are clickable in the picker so the
  Settings → About → Replay onboarding flow doesn't re-download
- Autostart "No thanks" now does isEnabled() → disable() to actually
  remove the OS login item when replaying after a previous "Yes"

Tasks page:
- Bucket nav (All / Inbox / Today / Soon / Later) now a horizontal
  pill row; was stacking because nav was block-level
- List sidebar sized to content via self-start max-h-full instead of
  stretching to viewport when sparse
- Energy chip surfaces at opacity-60 when unset (was opacity-0,
  hidden until hover) so the affordance is discoverable
- "Brain-Dead" energy label → "Zero" everywhere user-facing; enum
  stays brain_dead to avoid a destructive DB migration

LLM status chip (llmStatus.svelte.ts + Dictation/Settings):
- Chip no longer auto-warms when the engine isn't loaded; it's hidden
  unless ready / generating / loading / error
- refreshLlmStatus takes { force: true } so post-load reconcile clears
  stale "warming"; ambient refreshes still preserve in-flight state
- markError exported; failed loads surface "AI error" with detail
  rather than silently going to off
- check_llm_model is the source of truth (replaces the bool-only
  get_llm_status path in the store)

Float popout window:
- Native decorations off — was stacking two titlebars + two close X's
  on KWin, one of which silently failed
- ResizeHandles mounted outside the animate-float-enter wrapper so
  fixed-position handles anchor to the viewport, not the transformed
  root; secondary-windows capability gains
  core:window:allow-start-resize-dragging for tasks-float
- GTK Utility WindowTypeHint applied pre-map (mirroring the preview
  window) so KWin Wayland honours always-on-top reliably
- visible_on_all_workspaces(true) so the pinned tasks list follows
  workspace switches
- togglePin does hide()+show()+focus() on re-pin to nudge the
  compositor into re-evaluating window state
- Pop-out / Edit / Open viewer buttons hidden on Android via
  isAndroid() — the multi-window Tauri commands stub out there

Build / Bugbot:
- src-tauri Cargo.toml: whisper feature now chains whisper-vulkan, so
  the dev runner's --no-default-features --features whisper
  invocation actually pulls Vulkan acceleration instead of silently
  falling back to CPU-only
- jsconfig.json's inherited "types": ["node"] fixed by adding
  @types/node; corresponding @ts-expect-error in vite.config.js
  removed now that process is a known global

Verification: svelte-check + cargo check pass clean. Manual
device-side validation still pending for float resize and replay
autostart "No thanks" — those are the only remaining confidence items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:43:00 +01:00
dd45f10cd4 polish(phase9): sparkline + badge motion and a11y
Sparkline: friendlier aria-label ("3 completed today. 14 total over
the last 7 days." rather than a numeric list), per-bar <title>
tooltips with absolute date + count, 30 ms stagger entrance via
scaleY animation. Badge: 180 ms opacity + translateY entrance on
mount; conditional render means each new badge re-fires the
animation. Both animations respect prefers-reduced-motion.

The earlier draft tabindex=0 on the SVG was correctly flagged by
svelte-check as noninteractive_tabindex; SVG role="img" + aria-label
is sufficient for SR navigation without putting it in the keyboard
tab order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:17:53 +01:00
3cadbb0f82 feat(gamification): today count + sparkline on Tasks header
Badge renders when today's count > 0. Sparkline renders when the
setting is enabled and any of the last 7 days has a completion.
Wrapped in a narrow aria-live region so increments announce without
re-reading the rest of the header.

Fix: converted todayCount from $derived module export to a getter
function (Svelte 5 derived_invalid_export constraint).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:45:47 +01:00
b344e8a580 fix(a11y): Phase 3 follow-up — implement ARIA radio-group keyboard pattern for energy selector
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
Codex post-implementation review flagged one MAJOR: the energy segmented
control declared `role="radiogroup"` / `role="radio"` but only wired
`onclick`. No arrow-key navigation, no Home/End, no roving tabindex.
Keyboard users got four independent tab stops while assistive tech was
told it was a single radio group — a broken ARIA contract.

Fix (W3C APG Radio Group pattern):
- Extract the options list as `energyOptions` so the render loop and
  the keyboard handler share one source of truth.
- `energyRadioKeydown` handles ArrowLeft/Right/Up/Down (cycle wraps),
  Home (first), End (last).
- Roving tabindex: the currently-checked button gets `tabindex=0`,
  the rest get `tabindex=-1`, matching the APG recipe. Focus moves
  with selection.
- The radiogroup container gets `tabindex="-1"` to satisfy the
  svelte-check a11y rule without creating its own tab stop.

All green: 251 tests, clippy -D warnings, fmt, svelte-check 0/0, build.
2026-04-24 14:58:50 +01:00
1d4f1070a2 feat(energy): Phase 3 — match-my-energy task sort + tri-state tag column
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
Closes Phase 3 of the 2026-04-23 feature-complete roadmap. Incorporates
the Codex plan-review fixes from this session: profile-free index, tri-
state update command, and de-prioritise-not-hide semantics.

Storage (kon-storage):
- Migration v11 adds `energy TEXT` to `tasks` with a CHECK constraint on
  `high | medium | brain_dead | NULL`. Index `(energy, created_at DESC)`
  — deliberately not per-profile because the tasks table carries no
  profile_id column yet (tracked as a separate gap in HANDOVER).
- `TaskRow.energy: Option<String>` plus `task_row_from` read.
- `insert_task` signature grows by one optional arg (`energy`). Allowed
  `too_many_arguments` with a rationale comment — the positional shape
  matches the column order and flipping to a params struct would have
  rippled through every caller for cosmetic benefit only.
- New `set_task_energy(pool, id, Option<&str>) -> TaskRow`. Lives as its
  own function because `update_task` uses COALESCE to let `None` mean
  "preserve" — which would make clearing the tag impossible.
- Two new tests: round-trip including explicit NULL clear, and CHECK
  constraint rejection of unknown values.
- Tests updated for the v10 → v11 version bump.

Tauri (src-tauri):
- `TaskDto.energy`. `CreateTaskRequest.energy` (optional). Inline
  validation against the allowed set before hitting the DB, so frontend
  bugs surface as friendly errors instead of CHECK-constraint failures.
- New `set_task_energy_cmd` command mirroring the storage tri-state API.

Frontend (svelte):
- `EnergyLevel` type added to `types/app.ts`. `TaskDto`, `TaskEntry`, and
  `TaskDraft` grow an `energy` field.
- `SettingsState.currentEnergy` (persisted) + `matchMyEnergy` (persisted
  toggle). Defaults: null + false — no surface change until user opts in.
- `setTaskEnergy(id, EnergyLevel | null)` action on the task store.
  Calls the dedicated Tauri command, updates local state, broadcasts to
  sibling windows.
- `EnergyChip.svelte` — new component. Cycles unset → High → Medium →
  Brain-Dead → unset on click. Colour tokens: accent / warning /
  text-tertiary (deliberately not danger-red for Brain-Dead — the brief
  is explicit that this state must not feel pathologised).
- Chip rendered on every task row in TasksPage and every row in
  WipTaskList. Hidden-until-hover when energy is unset so untagged rows
  stay calm; always visible once tagged because the colour is the signal.
- Tasks page header gains a "I feel" segmented control and a
  "Match my energy" toggle. When both are active, matching tasks sort
  to the top — unset tasks are treated as Medium-equivalent. Nothing is
  ever hidden; this is a de-prioritisation, not a filter.

Deferred / out of scope:
- LLM-driven surfacing (brief says "The AI surfaces...") — deterministic
  client-side sort is v1; LLM layer is a later phase.
- tasks.profile_id + per-profile energy sort — separate migration.

All green: cargo build + 251 tests + clippy -D warnings (0 warnings)
+ fmt + svelte-check (0/0) + npm run build.
2026-04-24 14:53:19 +01:00
d6bf9ed245 refactor(frontend): migrate JS modules to TypeScript
Wholesale JS -> TS migration of the frontend — stores, utils, actions,
and all Svelte component scripts adopt type annotations. Compile-time
surfaces (app.d.ts, lib/types/) added for shared DTO types.

Build plumbing:
  - package.json: dev:frontend script that runs svelte-kit sync first
  - tauri.conf.json: beforeDevCommand points at dev:frontend
  - run.sh: dropped the sed-hack that temporarily blanked beforeDevCommand;
    now relies on npm run dev:frontend to avoid double-Vite
  - jsconfig.json: allowImportingTsExtensions

Preserves all Group 1 behaviour:
  - page.svelte.ts keeps loadHistory / loadTasks Tauri-first, no
    localStorage; saveTranscriptMeta + mapTranscriptRow + mapTaskRow
    intact; update_task_cmd and update_transcript_meta_cmd invocations
    carry the correct payload shape.
  - Toasts, preferences stores typed without behaviour change.
  - Viewer still routes segment edits through saveTranscriptMeta; the
    Task 1.5 TODO markers are gone.

taskExtractor.ts is functionally improved during the migration:
  - multi-task matches in the same sentence
  - list-style shopping-verb expansion (get bread, milk, and cheese)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:05:54 +01:00
jake
3f69543f73 agent: tasks — restyle with WIP limits, manual input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:04:06 +00:00
jake
4c0fd0aeda agent: foundation — sync incremental changes from legacy codebase
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:50:32 +00:00
jake
4bfc4c1374 refactor(kon): standardise page padding — pt-6 px-7 pb-5, tighter task cards
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 02:22:18 +00:00
jake
3eb14d004d feat(kon): add semantic HTML and ARIA labels — main wrapper, live regions, button labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 02:20:21 +00:00
jake
8b34fed606 refactor(kon): add design tokens — radius scale, title size, button component classes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 02:17:25 +00:00
jake
9926a42b7a feat(kon): scaffold hybrid modular workspace
- Cargo workspace with 6 domain crates: core, audio, transcription, ai-formatting, storage, cloud-providers
- Minimal Tauri shell (lib.rs + main.rs) with plugin registration
- Svelte 5 frontend copied from Ramble v0.2
- All crates compile as empty stubs
- App identifier: uk.co.corbel.kon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 20:21:38 +00:00