| 2026-10-09 |

webclient: a stale session list no longer erases a session just created
...
Starting a session from the welcome screen sets selectedProfileId and
then awaits the create POST. The profile watcher fires on the microtask
that the await yields, so the list refetch is issued while the POST is
still in flight — it returns a page that predates the new session, and
fetchSessions replaced the whole array, so the placeholder createSession
had just inserted was gone. The session only came back on a manual
refresh. Reproduced in a store test.
sessions.js now remembers a session it created along with the id of the
last list request that had already started at that moment, and a page
that started after the creation is still believed. So the rescue is
bounded by one round trip — it cannot turn into a ghost row: a request
that left once the session existed reports it missing and it goes, and
deleteSession clears the mark outright. Search and special-list pages
are never padded this way, since they answer a different question.
Eugene Sukhodolskiy
committed
19 hours ago
|
| 2026-10-06 |
webclient: Synapse reactions settings + service sessions toggle
...
- sidebar: service-sessions toggle (special=true fetch replaces the
regular list, active search drops out), muted style + robot icon on
special items in the list
- settings: Synapse reactions panel — enable switch, completion push
preference (always / only failures / never), notification destination
(app / app+Synapse / Synapse, Synapse options disabled while no
source key), reaction instructions editor with save + edit history
(author and reason per version)
- stores: synapseReactions store; sessions store fetches respect
showSpecial; getSessions passes special param
- vitest for api/session store/panel; dist rebuilt
Eugene Sukhodolskiy
committed
3 days ago
|
| 2026-10-05 |
webclient: session-list polish from review pass
...
- documentTitle: persistent nameById map — names for sessions beyond the
loaded list page (list only holds ~30); map filled by fetches and
loadSession meta, never wiped by later refetches
- createSession: placeholder inserted at the position the server list
would give it (after pinned run, last_active desc) so refetch no
longer visibly moves the new row
- SessionItem: skip empty .session-icons wrapper when no icons
- drawer breakpoint made exclusive (-sidebar-drawer 1280 -> 1279.98):
viewport of exactly 1280px now gets the desktop sidebar
Eugene Sukhodolskiy
committed
4 days ago
|
webclient: fix session-list flicker and double-load on select
...
- fetchSessions merges into existing items (preserve object references)
instead of wholesale array replacement — DynamicScroller reuses rows,
the list no longer repaints/reflows on every refetch (measured: sidebar
repaint spikes 8-17% per switch -> 0)
- AppSidebar.handleSelect no longer refetches the list on a plain select;
refetch only when leaving search (the array still held search results)
- loadSession now lives only in handleSelect — SessionList.onSelect used
to start a first load and handleSelect raced it with a second one
(double fetch + double buildMessageList, incl. search-jump target)
- pinSession mutates in place instead of replacing every item object
- SessionItem.highlightText matches on the raw string so <mark> offsets
stay correct when the text contains &/</>
Eugene Sukhodolskiy
committed
4 days ago
|
| 2026-05-15 |

fix(recall): stabilize scheduled callback system and improve UX
...
Backend fixes:
- stop_session now stops headless recall runs via _busy_sessions dict
- _fire_recall sets user ContextVars so tools work correctly
- MaxIterationsReached treated as success, not failure
- skip_next_recall uses GREATEST(trigger_at, now) for overdue recalls
- schedule_recall rejects past trigger times
- timezone offset double-adjustment fixed for aware datetimes
- _fire_recall registers _AgentRun for reconnect/replay support
- session_sync race with stream_start fixed
Frontend improvements:
- Recall banner moved to ChatHeader with live Cancel/Skip buttons
- Recall messages styled with is_recall flag and badge
- Real-time recall updates via WebSocket (recall_update events)
- Recall filter moved to sessions-header as toggle button
- Session list shows clock icon for sessions with pending recall
- Empty state messages for empty/filtered session lists
- Fixed missing api import in ChatHeader.vue
Tests:
- Updated scheduler_loop tests for _busy_sessions dict change
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
Add self-recall (scheduled callback) system
...
Core features:
- schedule_recall tool: once/recurring/immediate callbacks
- manage_recall tool: cancel/skip/list scheduled recalls
- Natural-language time parser (ISO, relative, "tomorrow at 09:00")
- PostgreSQL-backed RecallScheduler with lazy pool init
- Background recall_scheduler_loop with asyncio.Semaphore(3)
- _busy_sessions guard prevents user messages during headless runs
- Agent.run() preserves thinking field for session history visibility
- API endpoints: GET/DELETE/POST for session recall, admin list
- Frontend: recall badge, filter, cancel/skip in sidebar and chat header
- Tests: parser, scheduler CRUD, tools, API, scheduler loop (53 tests)
- Manuals: schedule_recall.md and manage_recall.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
| 2026-05-14 |
Final search UX polish: focus, cache, spinner opacity, scroller key
...
- AppSidebar: auto-focus input when search opens via querySelector.
Spinner icon now hides via opacity:0 instead of visibility:hidden.
- SessionsStore: add searchCache ref + restoreSearch(). Cache stores last
search results (items, hasMore, nextOffset) so reopening search avoids
re-fetching when query hasn't changed.
- SessionList: add :key on DynamicScroller to force recreate when switching
between search/normal mode, fixes pinned item spacing bug.
- Tests pass (51/51)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 May
|
Fix search spinner placement, msg-flash disappearance, results cleanup
...
- AppSidebar: spinner moved inside GnSearchField input-group-addon via CSS
(:deep .input-group-addon overlay). Removed spinner from sessions-label.
- AppSidebar: closeSearch and handleSelect now call sessionsStore.exitSearch()
and reload normal session list, so search results disappear after selection.
toggleSearch restores previous query and re-activates search on reopen.
- CSS msg-flash: removed animation-fill-mode:both and background-color:transparent
from keyframes, so message bubble keeps its original background after flash.
- SessionsStore: added exitSearch() method (preserves query, disables active flag).
- Tests pass (51/51)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 May
|
Add session search across messages with backend + frontend
...
Backend:
- GET /sessions now accepts `search` query param
- _session_summary computes match_indices and match_preview from messages
- pg_session_store: messages ILIKE added to search_list and count_all
- In-memory store: search_list also filters by message content
Frontend:
- AppSidebar: search toggle icon + debounced input, Ctrl+K shortcut
- SessionItem: highlight matching text, show match_preview from search
- SessionList: pass searchQuery to SessionItem
- SessionsStore: searchQuery/searchActive state, setSearch/clearSearch
- API layer: getSessions accepts search param
- MessageList: scroll to target message + brightness flash animation
- ChatStore: loadSession accepts targetMessageIndex, scrollToMessageIndex ref
- CSS: msg-flash keyframe animation in app.scss
- Tests updated for new getSessions signature
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 May
|
| 2026-05-02 |
Paginate session list loading
Eugene Sukhodolskiy
committed
on 2 May
|
| 2026-04-16 |

Add session name generation via LLM
...
Backend:
- Session model gets name: str | None field
- SQLite migration: ADD COLUMN name TEXT
- PostgreSQL: ADD COLUMN IF NOT EXISTS name TEXT (applied on pool init)
- SessionStore: add set_name() abstract method, implemented in all stores
- navi/core/name_generator.py: LLM worker that reads user messages and
returns a 3–6 word title or None if content isn't substantial yet
- POST /sessions/{id}/generate-name endpoint: fires LLM, saves and
returns name; skips if session already named or has no user messages
- GET /sessions and GET /sessions/{id} now include name field
Client:
- api.generateSessionName(id) — calls the new endpoint
- sessions store: updateName(id, name) mutation
- chat store: after stream_end, _tryGenerateName() runs fire-and-forget;
skips silently if session already has a name or if request fails
- SessionItem already displays session.name (falls back to id prefix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Migrate to Vue webclient; rename old client to old_webclient
...
- client/ → old_webclient/ (vanilla JS client preserved as reference)
- webclient/ — new Vue 3 + Pinia webclient (source + dist build)
- vite.config.js: outDir changed to webclient/dist/
- main.py: serve /assets and /images from webclient/dist/,
index.html from webclient/dist/index.html
- .gitignore: exclude webclient/node_modules/, include webclient/dist/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|