feat: add URL-based session linking (#180)#225
Conversation
roborev: Combined Review (
|
- feat: add URL sync for session deep linking and msg param (wesm#180) - feat: migrate router from hash-based to History API (wesm#180) - docs: add implementation plan for URL-based session linking (wesm#180) - feat: migrate all callers to path-based routing (wesm#180) - refactor: remove pendingNavTarget, replaced by URL sync (wesm#180) - fix: prevent filter reset when URL sync deselects a session (wesm#180) - fix: address code review findings for URL routing (wesm#180)
- Remove duplicate session navigation in PinnedPage and SubagentInline; let the App.svelte URL sync effect be the single source of session selection to prevent racing fetches and duplicate list entries. - Preserve non-routing query params (e.g. desktop) across navigations by capturing sticky bootstrap params at init and merging them in #buildUrl. - Remove ineffective urlSyncSuppressed flag — Svelte 5 effects run asynchronously so the synchronous set/unset was a no-op; the activeId === currentUrlSessionId guard already prevents loops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sm#180) - Add #refreshSticky to update sticky params whenever params change: after every navigation method and on popstate. Prevents stale snapshot from silently reverting a changed desktop value. - Move buildSessionHref from standalone function to RouterStore method so it includes sticky params. Middle-click / copy-link on subagent "Open session" links now preserves desktop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split #refreshSticky into #updateSticky (programmatic navigations) and #replaceSticky (popstate). Programmatic navigations only update sticky keys that are explicitly provided, preserving omitted ones. Popstate does full replacement since it reflects complete URL state. Fixes bug where two consecutive navigate() calls without desktop would drop it after the first hop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a chain-link icon button next to the find and actions buttons in the session breadcrumb bar. Clicking it copies the full session URL (including sticky params like desktop) to the clipboard, with a brief checkmark confirmation matching existing copy feedback patterns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set activeSessionId synchronously in navigateToSession before the async fetch. Prevents the URL-sync effect from seeing null and bouncing back to /sessions while the session metadata loads. - Wrap decodeURIComponent in parsePath with try/catch to handle malformed percent escapes (e.g. /sessions/foo%) without crashing. - Merge sticky params into this.params in all navigation methods so router.params matches the actual URL query string. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track the copied state by session ID instead of a boolean, matching the existing copiedSessionId pattern. Prevents the checkmark from persisting if the user navigates to a different session before the timeout expires. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change Vite base from "./" to "/" so asset paths are absolute. With
relative paths, navigating to /sessions/{id} caused the browser to
resolve ./assets/main.js as /sessions/assets/main.js, breaking direct
URL navigation and deep links. The Go server's serveIndexWithBase
already rewrites absolute paths for reverse-proxy base path deployments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wesm#180) - Add store.params assertions to sticky param router tests so the params-sync fix has direct coverage beyond just window.location. - Add navigateToSession regression test verifying activeSessionId is set synchronously before the async fetch resolves, preventing the deep-link bounce. - Clear/restart the copy-link timer on re-copy so the earlier timer cannot prematurely clear the confirmation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The favicon was still using a relative path (./favicon.svg) which resolves to /sessions/favicon.svg on deep-link routes. The Go server's serveIndexWithBase already rewrites href="/ for base-path deployments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
24998c0 to
b284546
Compare
Mounts the component, clicks the copy-link button twice with a 1s gap, and verifies the confirmation stays visible for 1.5s after the second click (not cleared early by the first timer). Uses vi.useFakeTimers to control time advancement. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
…#180) - Remove sessions.selectSession() call in TopSessions.handleSessionClick so the App.svelte URL sync effect handles session selection and metadata fetching. Same single-source pattern as PinnedPage and SubagentInline. - Guard the msg=last resolution effect against resolving on a different session's messages by checking messages.sessionId matches the pending scroll target. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
…#180) The original handleSessionClick passed include_one_shot into the router params when analytics had it enabled. The migration to URL-driven navigation dropped this, so one-shot sessions clicked from TopSessions could be filtered out of the list when navigating back. Now sets the sessions filter directly and invalidates caches before navigating. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wesm#180) Tests the guard logic from TopSessions.handleSessionClick: verifies the sessions filter is set and caches invalidated when analytics has includeOneShot enabled, and that redundant invalidation is skipped when the filter is already set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sm#180) Remove the inline logic re-implementation from sessions.test.ts. Add a proper component test that mounts TopSessions, clicks a session row, and asserts the real handler sets sessions.filters.includeOneShot, calls invalidateFilterCaches, and navigates via router.navigateToSession. Also covers the skip-invalidation and disabled-analytics paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hs (wesm#180) Stub invalidateFilterCaches and navigateToSession with mockImplementation to prevent real API calls and history mutations. Reset all mutated singleton state (analytics.topSessions, filters, URL) in afterEach. Assert router.navigateToSession("sess-1") in all three test branches. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sm#180) Snapshot loading/errors state in beforeEach and restore in afterEach so mountWithData mutations to topSessions loading and error state don't leak into other test files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
roborev: Combined Review (
|
|
Invalid findings. Will merge once CI finishes |
Summary
Migrate the frontend router from hash-based to HTML5 History API path-based routing, enabling shareable deep links to sessions and messages.
Router overhaul
/sessions,/sessions/{id},/insights,/pinned,/trash,/settings?msg=Nand?msg=lastquery params for deep-linking to specific messages within a sessionApp.svelteURL sync effect is the single source of truth for session activation, eliminating duplicate navigation calls that caused race conditionspopstatehandlerSticky query params
desktopare preserved across all navigations without callers needing to pass them explicitly#updateSticky(programmatic navigations, partial update) and#replaceSticky(popstate, full replacement) to handle the distinct semantics correctlyrouter.paramsreflects the merged URL query string including sticky paramsCopy session link
Deep-link reliability
activeSessionIdsynchronously innavigateToSessionbefore the async metadata fetch, preventing the URL sync effect from bouncing back to/sessionsdecodeURIComponentinparsePathagainst malformed percent escapesbuildSessionHrefmoved to aRouterStoremethod so it includes sticky params for middle-click / copy-link on subagent session linksBuild and asset fixes
basefrom"./"to"/"so assets use absolute paths — relative paths broke on any route deeper than//favicon.svg) for the same reasonhandleSPAalready servesindex.htmlfor unknown paths;serveIndexWithBaserewrites absolute paths for reverse-proxy base path deployments