Commit Graph

34 Commits

Author SHA1 Message Date
bvandeusen 87fcaa6a0d fix(chat): gate qwen3 thinking on message content instead of always-on
The frontend hardcoded think=true on every chat send (ChatPanel full +
widget variants, KnowledgeView minichat), which defeated the _should_think
gate on the backend and made qwen3:14b spend 5-20s on chain-of-thought
reasoning for every turn — even "hi". This was the root cause of the
warm-path TTFT variance tracked in followup_ttft_variance.md: the logged
ttft_ms was really prefill + full thinking phase, bouncing with the depth
of the model's reasoning, not with cache or eviction.

All three frontend callers now pass think=false and let _should_think be
authoritative. The classifier is now a real content-based gate: explicit
think_requested=True still forces on as an override (briefing discuss
actions, future UI toggles, MCP callers), otherwise messages <80 chars
without reasoning keywords skip thinking, messages >=400 chars or
containing keywords like why/explain/analyze/debug/review/etc. get it.

Generation timing now separately records think_requested, the final
think decision, first_token_ms (first any chunk), and thinking_ms
(duration of the thinking phase). ttft_ms keeps its existing semantic
(first content token) so existing log analysis still works. The timing
log line surfaces all four fields so the old "just a big ttft number"
ambiguity is gone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 00:53:47 -04:00
bvandeusen 8276e5050a fix(chat): let backend auto-generate conversation titles
KnowledgeView and WorkspaceView were passing explicit titles
("Knowledge chat", "Project — Workspace") to createConversation(),
which prevented the backend's auto-title generation from firing
(condition: `not conv_title`). Pass no title so the background
title generator names conversations after their first message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 11:33:53 -04:00
bvandeusen 436e339c48 fix(knowledge): task cards showed 'LIST' badge instead of 'Task'
The type-badge template had no v-else-if for task, so tasks fell
through to the v-else which renders 'List'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 10:07:23 -04:00
bvandeusen 61f95fb9ed style(knowledge): align task card theming with note/list pattern
Task card was the outlier in the Knowledge view: half-width fade-to-
transparent gradient bar while note/list use full-width two-stop
gradients, and the type badge was purple while the border+bar were gold.

- k-card--task::before: full width with gold→amber gradient (#d4a017
  → #fbbf24) matching the two-stop pattern
- badge--task: switched from purple to amber so the badge matches
  the card chrome
2026-04-11 16:30:36 -04:00
bvandeusen f2debd8a5b feat(knowledge): show organization for person cards, category for place cards
Exposes birthday, organization, address (person) and website, category (place)
from entity_meta in the knowledge API response; updates KnowledgeView cards
to display organization and category as visible meta chips/text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:32:15 -04:00
bvandeusen 0ca39a2e34 feat(knowledge): redesign new item button as gradient CTA with icon menu for all 5 types 2026-04-08 12:34:52 -04:00
bvandeusen 16d9af8b96 fix(theme): increase dark mode contrast for borders, glows, and card edges; DRY color values into CSS variables 2026-04-08 12:22:03 -04:00
bvandeusen 2ac894b5d1 refactor(theme): DRY hardcoded violet values into CSS custom properties
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 12:19:38 -04:00
bvandeusen f30e90ef8d feat: narrator empty states, scroll fades, glow buttons, violet color sweep 2026-04-08 11:17:06 -04:00
bvandeusen 00f82f8cba feat(knowledge): card type DNA, violet hover bloom, amber timestamps, narrator empty states
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:14:31 -04:00
bvandeusen 34729cf1cf feat(knowledge): add task cards with status/priority/due-date display 2026-04-08 10:32:51 -04:00
bvandeusen ed715dcc23 feat(knowledge): note types, counts, new-note button, audit fixes
- Add note_type (note/person/place/list) selector + entity metadata fields
  (relationship, email, phone / address, hours) to NoteEditorView
- Pre-select type via ?type= query param from KnowledgeView new-note dropdown
- KnowledgeView: add split "New note / ▾" button with type dropdown
- KnowledgeView: show per-type counts on sidebar filter buttons (when > 1)
- Fix: filter-btn now flex layout so count badge aligns to right edge
- Fix: list_notes count_query was missing parent_id filter (inflated totals)
- Fix: PATCH /api/notes/:id now fires upsert_note_embedding (workspace autosave)
- Fix: get_knowledge_counts endpoint for per-type counts
- Fix: get_knowledge_tags was silently discarding note_type filter (double-stmt bug)
- Fix: NoteEditorView onMounted stray brace from edit session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:53:09 -04:00
bvandeusen 358534efbf fix(events): audit pass — 7 correctness fixes across the events system
Backend:
- tools.py: apply UTC normalization to update_event datetime fields
  (matched create_event which already did this)
- events.py service: allow end_dt/recurrence/project_id to be cleared
  via update_event by permitting None for nullable fields
- events.py service: find_events_by_query now returns upcoming events
  first, falling back to past — prevents AI tools from mutating stale
  past events when a future match exists
- events.py service: list_events now uses overlap logic (start <= to
  AND end >= from) so multi-day events spanning the query boundary
  are included; previously only start_dt was checked

Frontend:
- ToolCallCard: fire fable:calendar-changed on created/updated/deleted
  so CalendarView refetches without requiring a manual page refresh
- KnowledgeView: replace raw apiGet('/api/events') with listEvents()
  client function; also fix today bar which was reading .events off a
  flat array (always empty) — now correctly receives EventEntry[]
- HomeView: use full ISO strings for event date range instead of naive
  UTC-midnight strings; deduplicate inline date math via _dateRange()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:02:10 -04:00
bvandeusen 90afbec4c2 feat(knowledge): two-tier pagination — ID pre-fetch + content batch loading
Backend:
- GET /api/knowledge/ids: returns up to 100 note IDs cheaply (no body
  parsing), supports same filters as /api/knowledge, includes has_more
- GET /api/knowledge/batch?ids=...: fetches full items for given IDs in
  order; used by frontend to load content in controlled batches

Frontend (KnowledgeView):
- Fetch 100 IDs upfront, load first 50 as content on mount
- IntersectionObserver sentinel (root: null) triggers 24-item content
  batches as user scrolls
- Proactive ID refill when queue drops below 48 unloaded IDs
- fetchGen counter invalidates stale in-flight responses on filter reset
- IDs claimed before async fetch to prevent double-loading
- sentinelVisible ref drives post-load re-check when content doesn't
  push sentinel off screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:44:50 -04:00
bvandeusen 5495fd1500 fix(knowledge): debounce scroll handler with rAF to prevent duplicate page loads
Coalesces rapid scroll events into one check per animation frame so
that page++ can only fire once per frame, eliminating the window where
multiple events slip through before loading=true is observed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:25:52 -04:00
bvandeusen 1fc0004e93 fix(knowledge): replace IntersectionObserver with scroll event; increase card height
- Swap IntersectionObserver (race-prone, fired immediately on creation)
  for a passive scroll listener on the grid container — eliminates
  duplicate page loads caused by observer re-creation after DOM updates
- Increase card min-height 100px → 160px so tags + snippet are visible
- Increase snippet line-clamp 3 → 4 for more content preview

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:24:19 -04:00
bvandeusen 3c38c04ad4 fix(knowledge): sentinel DOM ordering + restore tag visibility
Sentinel was first in DOM with order:9999, causing layout recalculation to
trigger IntersectionObserver multiple times (intersecting→not→intersecting)
as items were appended, producing duplicate pages. Move sentinel to AFTER
the v-for items so it's naturally last in both DOM and visual order.

Remove overflow:hidden from .k-card-tags — .k-card overflow:hidden already
clips escaping content; the extra overflow on the tags container was
collapsing its height to zero and hiding all tag pills.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:54:35 -04:00
bvandeusen 4ac26d9326 fix(knowledge): clip card overflow so tags don't escape card boundary
Added overflow:hidden to .k-card so wrapped tags are clipped within the
card border-radius. Added min-width:0 + overflow:hidden on .k-card-tags
so the flex item can shrink properly and doesn't push past the card width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:57:28 -04:00
bvandeusen 00abfcf4db fix(knowledge): remove backup load check causing duplicate items on scroll
The IntersectionObserver fires as soon as it's created (sentinel immediately
intersecting after page 1 renders), while the removed backup check also fires
in the same tick — two concurrent fetchItems(page 2) calls produced duplicate
cards. With sentinel now properly inside the scrolling root, the observer
alone handles progressive loading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:55:11 -04:00
bvandeusen 3887cab66e fix: knowledge infinite scroll + list_events timezone handling
KnowledgeView: sentinel was OUTSIDE the card-grid div, making
IntersectionObserver (root: cardGridEl) never fire since the target must
be a descendant of the root. Moved sentinel inside card-grid with
grid-column:1/-1 + order:9999 so it spans all columns and sits at the
bottom. Fixed backup check to compare against container bounds not viewport.

tools.py list_events: apply same UTC normalization as create_event (treat
naive datetimes as UTC, handle Z suffix). Update tool description to
explicitly request full-day UTC ranges so the LLM doesn't send local time
without offsets, which caused the recall query to miss UTC-stored events.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:10:43 -04:00
bvandeusen eda9c5ce43 feat: migrate KnowledgeView mini-chat to ChatPanel + enable note picker everywhere
- KnowledgeView: replace custom mini-chat (voice, PTT, manual scroll, message
  rendering) with ChatPanel variant="full"; gains RAG scope chip, TTS listen
  mode, volume control, and note picker automatically (-218 lines)
- ChatInputBar: remove briefingMode guard on note picker so attach/search works
  in briefing, workspace, widget, and knowledge chat surfaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:16:36 -04:00
bvandeusen d624d38412 fix: KnowledgeView infinite scroll root + calendar event refresh
- KnowledgeView: IntersectionObserver was watching the viewport instead
  of the card-grid scroll container, causing infinite scroll to stop
  loading after only ~29 items. Pass card-grid element as `root`.
- CalendarView: listen for 'fable:calendar-changed' custom event and
  call refetchEvents() so tool-created events appear without navigation.
- ChatPanel: dispatch 'fable:calendar-changed' when create_event,
  update_event, or delete_event tool calls succeed.
- tools.py: normalize naive datetimes to UTC before storing events so
  timezone comparisons in list_events queries are always consistent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:28:33 -04:00
bvandeusen 8024706870 fix(knowledge): fill viewport on load when sentinel stays visible 2026-04-03 14:07:52 -04:00
bvandeusen 750a91898a perf(llm): route background tasks to dedicated model to preserve KV cache
Background tasks (title generation, tag suggestions, project summaries,
RSS classification) were using qwen3:8b and wiping its KV cache after
every response, preventing prefix cache hits on subsequent user messages.

Adds OLLAMA_BACKGROUND_MODEL (default: qwen2.5:0.5b) config var and
routes all background LLM calls to it, keeping qwen3:8b's KV cache
warm between user messages for consistent sub-second TTFT.

Also adds infinite scroll to KnowledgeView (replaces load-more button)
and bakes spaCy en_core_web_sm into the Docker image to eliminate the
pip install on every startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 01:33:54 -04:00
bvandeusen a473f6e039 fix: minichat markdown rendering and weather temperature unit preference
- KnowledgeView minichat: render assistant messages through renderMarkdown
  so headers, bold, lists etc. display correctly instead of raw markdown
- get_weather tool: read user's temp_unit from briefing_config and convert
  temperatures to °F when preferred; also include temp_unit in the
  returned payload so the model can label values correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 00:52:54 -04:00
bvandeusen 07f4956550 fix: center minichat widget and fix weather tool success status
- KnowledgeView minichat: add margin-inline: auto so the widget centers
  within the content area when max-width is reached on wide screens
- weather get_weather tool: return success: true on both the arbitrary
  location path and the cached locations path so ToolCallCard shows
  the correct success state instead of always flagging as error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 00:50:42 -04:00
bvandeusen 9549eb85bc feat: live card refresh, list checkboxes, minichat width cap
KnowledgeView:
- Watch streamingToolCalls; call fetchItems+fetchTags on create/update
  note or task so the card grid reflects changes made via the minichat
- Cap minichat to max-width: var(--page-max-width) so it matches chat column width

WorkspaceView + WorkspaceNoteEditor:
- Expose reload() from WorkspaceNoteEditor via defineExpose
- Call noteEditorRef.reload() alongside taskPanelRef.reload() when
  create_note/update_note tools succeed in the SSE watcher

KnowledgeView list cards:
- Backend: parse markdown task list into list_items [{text, checked}] + body
- Card renders up to 6 items with real checkboxes; toggleListItem()
  does an optimistic update then PATCHes /api/notes/:id
- Progress bar kept below items; "+N more" shown when list is long

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 21:30:28 -04:00
bvandeusen 23a7ed7822 refactor: DRY layout bounds via --page-max-width and --sidebar-width CSS vars
Add --page-max-width (1200px), --page-padding-x (1rem), and --sidebar-width (260px)
to theme.css so all views share a single source of truth.

- HomeView: 1100px → var(--page-max-width) (aligns with all other views)
- NotesListView, TasksListView, ProjectListView, ProjectView, CalendarView: var(--page-max-width)
- ChatView: sidebar + context sidebar → var(--sidebar-width); inner message/input
  column max-widths → var(--page-max-width)
- KnowledgeView: filter panel 180px → var(--sidebar-width); minichat left offset updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:56:33 -04:00
bvandeusen d5771a3d5c fix: remove view max-width constraints; widen graph panel
- Drop max-width from .knowledge-root so the graph panel can use the full
  viewport width without hitting a cap
- Drop max-width from .chat-page (message bubbles are already self-constraining)
- Increase normal graph panel width 420px → 500px
- Increase expanded width to min(960px, 60vw) so it scales with the viewport
  and updates minichat right-offset to match

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:39:21 -04:00
bvandeusen 0d12d01115 feat: constrain Chat/Knowledge width + persistent expandable graph panel
Layout:
- Chat and Knowledge views now max out at 1600px and center on wide
  screens, consistent with the rest of the app; app-content overflow
  is set to hidden for both so they manage their own scroll

Graph panel (Knowledge):
- Open/closed state persisted to localStorage (fa_knowledge_graph_open)
  — stays open across navigation and page refreshes
- Expanded state persisted (fa_knowledge_graph_expanded): chevron button
  in the panel header toggles between 420px (normal) and 700px (expanded)
- Minichat right offset follows the panel width with a matching transition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:01:18 -04:00
bvandeusen 145c18d8a3 refactor: DRY calendar additions + define --color-surface in theme
- Define --color-surface in theme.css (light: #f0f0f8, dark: #1a1b22)
  — was used across 10+ components with no-op fallbacks; now properly
  defined alongside --color-bg and --color-bg-card
- Extract shared date formatters into utils/dateFormat.ts:
  fmtTime, fmtDateTime, fmtRelativeDateTime, fmtDayLabel, fmtCompact
- Replace duplicate inline formatters in CalendarView, HomeView
  (formatUpcomingTime), and KnowledgeView (formatEventDate)
- CalendarView: replace hardcoded rgba(99,102,241,0.4) hover colour
  with color-mix(in srgb, var(--color-primary) 40%, transparent);
  fix --color-input-bg fallback to use var(--color-bg); remove
  hardcoded hex fallbacks now that --color-surface is defined

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:27:23 -04:00
bvandeusen 5924e565b1 fix: floating mini-chat overlay style + weather precip fallback
- KnowledgeView mini-chat: replace harsh border-top with upward box-shadow
  and rounded top corners (16px); remove padding-bottom from content area
  so widget truly overlays cards without pushing layout; add collapse
  toggle (chevron) that hides messages without closing the conversation
- WeatherCard: show precip_mm as fallback when precipitation_probability_max
  is null but actual rainfall is expected (Open-Meteo omits probability for
  some forecast days even when rain is shown)
- Pass precip_mm through weather service → frontend type definitions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 09:04:47 -04:00
bvandeusen a0620c4949 fix: knowledge view — chat input styling, autofocus, graph component (no iframe)
- Mini-chat input bar now uses shared --color-input-bar-* CSS variables and
  the same chat-input-bar pill pattern as all other chat interfaces
- chatInputEl focused on mount (autofocus on page load)
- Graph panel: replaced iframe (blocked by X-Frame-Options: DENY) with
  inline GraphView component; CSS :deep override constrains its height
  to fill the panel instead of 100vh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:49:02 -04:00
bvandeusen 80f30b705d feat: Knowledge view + entity types (People, Places, Lists)
Data model:
- Migration 0036: adds note_type TEXT (default 'note') and metadata JSONB
  to the notes table; index on note_type
- Note model: entity_type property, note_type/metadata in to_dict()
- create_note() accepts note_type and metadata params

Backend:
- /api/knowledge — unified paginated endpoint: type/tag/sort/q filters,
  semantic search via embeddings, excludes tasks
- /api/knowledge/tags — distinct tags across knowledge objects
- New LLM tools: create_person, create_place, create_list, add_to_list,
  clear_checked_items — all wired into execute_tool()
- People and places auto-injected as compact summary into LLM system prompt

Frontend:
- KnowledgeView replaces HomeView at /; left filter panel (type+tag),
  toolbar (search, sort, graph toggle), card grid with type-aware cards
  (indigo=note, emerald=person, amber=place, sky=list), load-more pagination
- Today bar: upcoming events, overdue task count, Briefing/Chat links
- Floating mini-chat sticky to bottom: creates/continues a conversation
  inline, message history expands upward, close button ends session
- Graph panel: toggles as a 420px right panel at full viewport width
- AppHeader: Knowledge, Chat, Briefing, Calendar, Tasks, Projects
- Router: / → KnowledgeView; /knowledge redirect; HomeView import removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:01:03 -04:00