Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

## Unreleased

## 2.5.0

**Notes now works like a notepad, sticky-note wall, and vision board instead of a file browser.** Capture first, shape the note in an editor, and organize it without leaving the Notes tab.

### Added

- **Living Notes workspace.** Notes render as responsive sticky cards with dedicated types for notes, tasks, ideas, links, and read-later items. The composer/editor supports title, body, category, tags, color, status, due date, source link, pinning, Archive, and Trash.
- **Smart organization.** Search, type views, Pinned/Archive/Trash views, category facets, and tag facets work over an indexed vault feed.
- **Vision Board.** Editable sections and drag-and-drop card ordering are persisted separately in `.flowkey/board.json`, keyed by stable note IDs. Removing a board placement never mutates or deletes the note.
- **Recoverable removal.** The default delete action moves a note to Trash. Restore is immediate; permanent deletion is available only inside Trash and requires explicit confirmation.
- **Safe schema-v2 migration.** Existing Markdown notes gain a stable `note_id` and richer metadata without losing their body, source path, or unknown frontmatter. A backup is written to `.flowkey/backups/v1/` before the first rewrite.
- **Conflict-aware, atomic writes.** Notes and board state use atomic replacement and revisions, so a stale editor reports a conflict instead of overwriting newer work.

### Changed

- **The Notes tab contains notes only.** Vault, category, extraction, and local-model controls moved to Config → Notes & capture and save with the rest of Config.
- **`Ctrl+Alt+N` opens the Notes composer.** A fresh selection prefills the draft; no selection opens a blank composer. Prior clipboard contents are never used as an implicit fallback, and capture no longer saves before review.
- **Local-model enrichment cannot rewrite authored content.** It may fill only blank or generated metadata, preserving user-written titles and bodies.
- **Legacy note actions remain compatible.** Existing callers can continue reading or moving by relative path while the new workspace uses stable IDs.

### Fixed

- **Date-only due dates keep their calendar day.** A value such as `2026-08-04` displays as August 4 in every timezone instead of shifting to the prior day west of UTC.
- **Note migration no longer races concurrent edits or vanishes notes.** Upgrading a pre-2.5 note to the new schema is now serialized against other note writes, and a note whose file can't be rewritten (e.g. a read-only location) still appears in search and listings instead of silently disappearing.
- **"Move to Trash" respects the same conflict check as every other note action.** If a note changed since you opened it, trashing it now reports the conflict instead of acting on stale data.
- **A Quill hiccup no longer breaks Meetings.** A transient error from Quill during a scheduled digest run, the Overview widget, or a meeting Q&A now degrades gracefully instead of surfacing as an error or silently skipping a whole batch.

## 2.4.3

**`prompt:` handles vague requests properly.** A request too thin to break into requirements now produces a prompt that names what is missing, instead of handing the request back reworded.
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ Flowkey is a Windows desktop assistant that adds local-LLM hotkeys for grammar f

Everything runs locally through [FastFlowLM](https://fastflowlm.com) (AMD Ryzen AI NPU) or, on machines without the NPU, through [Ollama](https://ollama.com) (CPU/GPU) as a secondary provider. No cloud service, analytics, or telemetry is used by the app.

Current version: `2.4.3`
Current version: `2.5.0`

## What's new in 2.5

- **Notes is now a real thinking workspace.** Capture plain notes, tasks, ideas, links, and read-later items as readable sticky cards. Search them, filter by type, category, or tag, and organize them with colors, pins, due dates, status, Archive, and a recoverable Trash.
- **The Vision Board keeps important notes alive.** Create and rename board sections, drag cards into the order you want, and keep unplaced notes nearby. Board placement never changes or deletes the underlying note.
- **`Ctrl+Alt+N` opens a quick composer.** Selected text is staged as an editable draft; with no selection, Flowkey opens a blank note. Nothing is saved until you choose to save it, and old clipboard text is never captured by accident.
- **Notes settings moved to Config.** The Notes tab is only for writing and organizing. Vault location, categories, extraction limits, and local-model enrichment now live together under Config → Notes & capture.
- **Existing Markdown notes migrate safely.** Notes receive stable IDs and richer metadata when first read. Flowkey preserves the body and unknown frontmatter, writes a v1 backup under the vault's `.flowkey/backups/v1/` directory before rewriting, and uses atomic saves with edit-conflict detection.

## What's new in 2.4

Expand Down Expand Up @@ -120,7 +128,7 @@ Launch the app with AutoHotkey v2:
| `<custom>:` + `Ctrl+Shift+G` | Any mode you define in Dashboard → Config → Custom modes (e.g. `translate:`) |
| `Ctrl+Alt+C` | Open chat (the Chat tab has a "My notes" toggle that grounds replies in your notes vault) |
| `Ctrl+Shift+A` | Ask in chat with selected text |
| `Ctrl+Alt+N` | Capture a note |
| `Ctrl+Alt+N` | Open Notes quick capture (selected text prefills the composer; no selection opens it blank) |

Prefix tip: put the keyword on the first line of your selection — `prompt: rough idea here` (or `prompt` on its own line, then the text). Without a prefix, `Ctrl+Shift+G` always runs a grammar fix.

Expand All @@ -135,7 +143,8 @@ The dashboard is a web page served by the local daemon — open it from the tray
- **Models:** pull models with live progress — pick a suggestion or type any name (on Ollama, anything from the [library](https://ollama.com/library) works); set active, remove. Suggestions are hardware-aware: detected RAM/VRAM caps the model size (e.g. 32 GB RAM → ~4B on the NPU; 8 GB VRAM → ~9B on the GPU), oversized models are hidden, and free-typing one asks before pulling.
- **Benchmark:** works on both providers — `flm bench` on FastFlowLM (~10–20 min, NPU), timed generations with native metrics on Ollama (~1–3 min, server keeps running).
- **History:** Telemetry view shows what ran and how fast; Exposed view shows stored request/result text only for rows captured while history storage was visible. The History tab includes the same redacted/visible storage toggle as Config.
- **Notes:** browse or search your vault.
- **Notes:** write and organize notes, tasks, ideas, links, and read-later items; use smart views, categories, tags, pins, Archive/Trash, or arrange cards on the Vision Board.
- **Notes settings:** Config → Notes & capture controls the vault, categories, link extraction, and optional local-model enrichment. The Notes tab itself contains no settings.
- **Meetings:** connect the local [Quill](https://quillapp.com) app to search meetings, read AI digests (pre-computed after-hours), review action items (accept / reject), and generate a weekly review. Off by default — enable in Config → Meetings.
- **Notifications:** per-event toggles, dedupe window, Do-Not-Disturb, and quiet hours; every toast (shown or muted) is logged to the Telemetry feed.

Expand Down
47 changes: 44 additions & 3 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Caveman-encoded (compression, not amputation). Paths / ids / action names / numb
- LLM: FastFlowLM NPU @ `:52625` | Ollama @ `:11434`, OpenAI-compat `POST /v1/chat/completions`
- dashboard: daemon-served `scripts/ui/web/{index.html,app.js,styles.css}`, CSP `default-src 'self'`
- paths: `scripts/paths.py` → USER_ROOT/{config,data,logs}; `_version.py` = version src of truth
- version: `2.3.0` (prompt-v2 speed+quality release); `2.2.0` released (`v2.2.0` on `25b8794`); repo `agr77one/Fastflow`
- version: `2.5.0` = living Notes workspace + vision board; repo `agr77one/Fastflow`
- run tree = `flowkey-pub2` (worktree, branch `live`=origin/main). old `FastFlowPrompt_Local_Setup`=1.5.0 stale.

## §I interfaces
Expand All @@ -26,6 +26,15 @@ Caveman-encoded (compression, not amputation). Paths / ids / action names / numb
- api: `POST /action/<name>` ! header `X-FFP-API: 1` → 200 `{ok,result,error,elapsed_ms}`
- api: `GET /` → dashboard; `GET /healthz` → `{ok,version,api,actions}`
- action: `config_snapshot` → full cfg; `apply_config_patch {patch}` → merge (whitelist `filter_config_patch`)
- action: `notes_query {query?,kind?,status?,category?,tag?,sort?,limit?,offset?}` → `{results,count,facets}`
- action: `note_create {title?,body?,kind?,category?,tags?,color?,due?,source?}` → note
- action: `note_update {note_id,revision,patch}` → note | conflict
- action: `note_organize {note_id,revision?}` → note; local LLM may update category/suggested metadata only
- action: `note_trash {note_id}` / `note_restore {note_id}` → note; `note_delete {note_id,permanent:true}` → deleted
- action: `notes_board_get` → `{board,placements}`; `notes_board_save {revision,board}` → board | conflict
- action: `note_stage_capture {text?,source_app?}` / `note_take_staged` → quick-capture payload
- data: note Markdown frontmatter schema v2 → stable `note_id`, `kind`, `status`, `tags`, `color`, `pinned`, `due`, `created`, `updated`, `revision`
- data: `<vault>/.flowkey/board.json` → board sections + placements keyed by `note_id`
- action: `recent_history {limit?}` → newest history rows; `input_text`/`output_text` iff stored @ write-time
- action: `prompt_builder_preview {settings?,sample?}` → deterministic local preview (`⊥` LLM call)
- config: `prompt_builder.prompt_version` ∈ {`v1`,`v2`}; default `v2`; v1 = instant rollback
Expand All @@ -43,12 +52,12 @@ Caveman-encoded (compression, not amputation). Paths / ids / action names / numb
- action: `meeting_actions_list {range:week|month}` → `{range,items:[{id,text,owner,status,...}],counts}`
- action: `meeting_action_set_status {id,status:pending|accepted|rejected}` → `{ok}`
- action: `meeting_week_summary {week_offset}` → `{ok,week_label,meeting_count,summary}`
- mcp: Quill @ `http://127.0.0.1:19532/mcp` — Streamable-HTTP, SSE `data:`, `Mcp-Session-Id` header; init→notifications/initialized→tools/call
- mcp: Quill @ `http://127.0.0.1:19532/mcp` — Streamable-HTTP, SSE `data:`, `Mcp-Session-Id` header; init→notifications/initialized→tools/call; `get_transcript {meeting_id,include_private_notes:true}`; tool `isError` → typed failure
- cmd: `flm serve <model> --pmode turbo --host 127.0.0.1 --port 52625`
- data: `data/{meeting_digests,meeting_action_status,meeting_skips,notifications,chat_threads}.jsonl`
- autostart: HKCU Run `FastFlowPrompt` → bundled `AutoHotkey64.exe` + `grammarFix.ahk`; `FlowkeyGitSync` → `sync.ps1`
- sched: Windows task `FlowkeyGitSync` daily 12:00 → `sync.ps1` (ff-only pull, guarded)
- ACTIONS count = 75
- ACTIONS count = 86

## §V invariants

Expand Down Expand Up @@ -98,6 +107,22 @@ Caveman-encoded (compression, not amputation). Paths / ids / action names / numb
- V45: surfaced v2 text → article agreement + fixed typo map normalized (∵ render copies user wording verbatim); ⊥ meaning change
- V46: A/B rubric = 8 items; R8 = ⊥ section restates `<task>` (coverage-of-task ≥ 0.8), boilerplate-excluded set-wise on constraints; R8 false ⇒ disqualifying ∀ other scores; gate pass ≥ 7/8 ∧ R8 ∧ ⊥ invented
- V40: model picker + installed list = app-styled elements (⊥ native `<datalist>`/`<select size>`, ∵ browser chrome ignores page CSS); theme-aware + keyboard-navigable (↑↓/Enter/Esc)
- V47: Notes tab = notes + organization only; note config controls ∈ Config tab
- V48: ∀ note API/UI identity = stable `note_id`; file move/rename ⊥ identity change
- V49: AI enrichment ? fill blank/generated metadata; user-authored body/title ⊥ overwrite
- V50: default remove → Trash, recoverable; permanent delete ! explicit Trash action + confirm
- V51: v1 Markdown → schema v2 migration preserves body + unknown frontmatter + source path; migration backup ∃ before rewrite
- V52: board placement references `note_id`; remove placement ⊥ delete/mutate note
- V53: capture hotkey w/ selection → staged prefill; ⊥ selection → blank composer; stale clipboard ⊥ silent capture
- V54: note writes + board writes atomic; stale `revision` → conflict, ⊥ overwrite
- V55: note card/editor controls keyboard reachable; desktop split workspace + ≤720px stacked layout
- V56: note `due` date-only value renders same calendar day ∀ timezone; ⊥ UTC date shift
- V57: Quill transcript call matches discovered schema (`meeting_id` + `include_private_notes`); MCP JSON-RPC/tool `isError` ∨ validation payload → typed failure, ⊥ LLM input/cache; poisoned legacy digest → ⊥ idempotency hit, eligible reprocess
- V58: model-created note category accepted ⟺ config opt-in ∧ `is_new` ∧ high confidence ∧ safe normalized ≤2-segment slug; accepted category atomically deduped+sorted into cfg; otherwise Inbox + suggestion; V49 holds
- V59: Config → keyboard-accessible section nav + collapsible groups + one selected section visible + sticky Save/Revert; view state local-only persisted; ≤720px responsive
- V60: future Activity workspace may join Telemetry+History + explicit `Save as note`; Notes remains authored knowledge; V6,V25,V26 hold
- V61: note schema-migration write ∈ `_NOTES_LOCK` (double-checked, serialized vs concurrent note writes); migration-write failure → index/lookup fall back to unmigrated read, ⊥ silently drop note
- V62: `ffp_quill` public read fns (`get_minutes`,`get_transcript`,`search_meetings`,`list_recent_meetings`) catch `QuillToolError` → soft-degrade (empty text/list); V57's typed-failure raise stays internal to `call_tool`, ⊥ leaks past the public API

## §T tasks

Expand Down Expand Up @@ -133,6 +158,16 @@ T27|x|streaming Chat tab (SSE): `ffp_chat.stream_send` + daemon `_STREAM_ACTIONS
T30|x|clarify shape for underspecified requests + typo/article normalization + R8 echo gate + reported input in fixed set (2.4.3)|V44,V45,V46
T29|x|bench memory guard + keep-warm/bench mutual exclusion + provider-error surfacing (2.4.2)|V41,V42,V43
T28|x|model picker 2.4.1: footprint/MoE sizing + never-hide + active-model health + merged single Models card + app-styled combobox|V38,V39,V40
T31|x|Notes schema v2 repository: stable ids, zero-loss migration, CRUD, Trash, indexed query, atomic board store|V7,V48,V49,V50,V51,V52,V54
T32|x|daemon Notes v2 actions + backward-compatible read/move/delete + staged capture|V1,V2,V3,V7,V48,V50,V53,V54
T33|x|move vault/categories/extraction/LLM Notes settings → Config single-save; Notes tab config-free|V3,V47
T34|x|Notes-only card workspace: composer, editor, smart views, filters, tags, archive/Trash, vision board drag/order|V5,V47,V48,V50,V52,V54,V55,V56
T35|x|capture hotkey → Notes quick composer w/ staged selection or blank body|V53
T36|x|2.5.0 docs/version/migration + full release gates|V18,V20,V47,V48,V49,V50,V51,V52,V53,V54,V55
T37|x|repair Quill transcript schema/error handling + poison-cache retry + re-digest latest|V12,V14,V20,V23,V57
T38|x|guarded local-model category creation + sorted category manager + note organize action|V3,V7,V20,V49,V58
T39|x|compact Config section navigation + collapsible cards + sticky save|V5,V20,V55,V59
T40|.|Activity workspace: merge Telemetry+History, card/detail UI, explicit Save as note|V5,V6,V25,V26,V60
```

## §B bugs
Expand Down Expand Up @@ -179,4 +214,10 @@ B35|2026-07-27|`flm bench qwen3.6-moe:35b-a3b` died 4s in w/ driver `0xc01e0200`
B36|2026-07-27|keep-warm thread ⊥ aware of benchmarks: warms/reloads active model on 15min tick during a 10-20min bench ∴ NPU+mem contention mid-run|V42; `ffp_benchmark.is_running()` gate in `_warm_model_once`
B37|2026-07-27|FLM returns HTTP **200** + `{"error":"Failed to load <model> model!"}`; `_call_openai_compatible`/`ffp_chat` read only `choices` ∴ real cause discarded → "Local LLM returned no usable text"|V43; surface the error body
B34|2026-07-27|self-caught: `_active_model_health` tested membership vs `_provider_list("all")`; ollama "all" = installed+suggested (`ffp_provider_runtime:80`) ∴ never-pulled model → false `installed=True` (⊥ warn)|V39; trust `details` (unfiltered, authoritative) else re-list w/ `installed` filter
B41|2026-07-29|Notes due `2026-08-04` parsed as UTC midnight → EDT displayed Aug 3|V56; parse date-only @ local noon
B42|2026-07-29|Quill `get_transcript` sent `id`; live schema requires `meeting_id`+`include_private_notes`; `call_tool` ignored `isError` ∴ 133-char validation error fed to LLM + cached as digest|V57
B43|2026-07-29|`fresh_modules` teardown popped `notes` after test collection; later daemon-action tests patched a stale module while action-local `import notes` resolved a new module ∴ tests touched the real vault + became order-dependent|rebind `sys.modules["notes"]` to the isolated test module; V20 full-suite gate
B44|2026-08-13|pre-release review: V57's `call_tool` raise (the B42 fix) went ⊥ caught by `get_minutes`/`get_transcript`/`search_meetings`/`list_recent_meetings`; `run_batch`'s pagination loop, `meeting_overview`, and `process_meeting`'s `NoContentError` skip-path all broke on any Quill tool error: scheduled batch could silently no-op (status never updated), Overview widget 500'd instead of `reachable=False`, dead meetings retried forever instead of perma-skip|V62; catch `QuillToolError` in the 4 public read fns, restore module's documented fail-soft contract
B45|2026-08-13|pre-release review: `_ensure_note_schema`'s migration write ran outside `_NOTES_LOCK` ∴ could race a locked `note_update`/`note_organize` on the same file (dueling `uuid4()` note_id, last-writer-wins corruption); `_load_note_index`/`_find_note_path` silently dropped a note from every listing/lookup on migration-write failure (e.g. read-only file)|V61; lock + double-check inside `_ensure_note_schema`; index/lookup fall back to unmigrated read on failure instead of dropping the note
B46|2026-08-13|pre-release review: `trash_note` was the only note-mutating fn ⊥ taking a `revision` param ∴ "Move to Trash" could silently act on a note that changed since the editor loaded it, unlike update/organize/archive; also `_act_note_archive`/`_act_notes_board_save` used a bare `int()` revision parse (raw 500 on bad input) unlike `_act_note_update`/`_act_note_organize`'s guarded parse|V54; add revision param + conflict check to `trash_note` + daemon action + app.js call site; guard the two bare `int()` parses to match the others
```
1 change: 1 addition & 0 deletions config/grammar_hotkey.config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"fetch_timeout_seconds": 8,
"max_extracted_chars": 2000,
"low_confidence_to_inbox": true,
"allow_new_categories": true,
"generate_title": true,
"generate_summary": true
},
Expand Down
2 changes: 1 addition & 1 deletion installer/installer.iss
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
#define AppURL "https://github.com/agr77one/Fastflow"
#define AppExeName "Flowkey.exe" ; symbolic — actual launchers below
; Keep in lockstep with scripts\_version.py.
#define AppVersion "2.4.3"
#define AppVersion "2.5.0"

[Setup]
AppId={{8A4F1E6C-9B3D-4E62-9F7A-FASTFLOW140}}
Expand Down
2 changes: 1 addition & 1 deletion installer/sign.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
.EXAMPLE
# Sign the installer
$env:FFP_SIGN_PFX_PASSWORD = "ChangeMe!"
.\sign.ps1 -FilePath ..\out\Flowkey-Setup-2.4.3.exe
.\sign.ps1 -FilePath ..\out\Flowkey-Setup-2.5.0.exe
#>

[CmdletBinding(DefaultParameterSetName = "Sign")]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fastflowprompt"
version = "2.4.3"
version = "2.5.0"
description = "Local-LLM-powered grammar fix, prompt rewrite, chat, and dashboard for Windows."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion scripts/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Single source of truth for the app version. Read by grammar_fix.py."""

__version__ = "2.4.3"
__version__ = "2.5.0"
Loading