feat(chat): per-chat URL routing + browser tab title mirroring#134
Open
constkolesnyak wants to merge 4 commits into
Open
feat(chat): per-chat URL routing + browser tab title mirroring#134constkolesnyak wants to merge 4 commits into
constkolesnyak wants to merge 4 commits into
Conversation
Session rows used to be <div onClick={switchSession}>, so Vimium couldn't
hint them and there was no shareable URL per chat — the address bar always
read /chat.
- SessionSidebar: wrap each session row (conversations + system) in a
react-router <Link to={`/chat/${id}`}>. Menu buttons inside the row now
preventDefault on click so opening the kebab doesn't navigate.
- ChatPage: drop the onSelect prop; add a navigate() effect that mirrors
activeSession into the URL (replace), so createSession / WS-driven
switches / deleteSession auto-pick all update the address bar too.
The previous "mirror activeSession into the URL" effect ran on every click: the <Link> set sessionId=A instantly, but activeSession was still B until switchSession() resolved a tick later. The effect saw the mismatch and yanked the URL back to /chat/B, which then re-triggered switchSession, which re-triggered the mirror — infinite flip. Guard the mirror: only push activeSession into the URL when the URL can't be the source of truth (no sessionId, or sessionId points to a session that's no longer in the list — i.e. just deleted). On a normal click the URL is valid, so the effect stays out of the way and lets switchSession catch up naturally.
A fresh tab opened with /chat/A would briefly bounce to a different chat because two effects raced before loadSessions() resolved: - the URL-mirror useEffect ran with sessions=[], decided "URL points to unknown session", and yanked URL → /chat/<store-default> - the WS session_switched handler also ran on connect with activeSession="" and switched to the server-side default, which then propagated to URL - ChatPage: drop the mirror effect entirely; instead navigate explicitly from handleCreateSession and handleDeleteSession (the two places that change activeSession without a URL change). useCallback for stability. - App.tsx: global new-chat shortcut awaits createSession() and navigates to /chat/<new-id> with replace so the URL reflects the just-made chat. - sessionHandlers: handleSessionSwitched no longer overrides when the URL already names a specific chat — ChatPage's useEffect[sessionId] will switch to it. Prevents the new-tab flash through the server default.
Sets document.title to the cleaned session title (same strip rules as the sidebar: leading '#' and 'Implement:' removed). Falls back to plain "Nerve" when there is no active session or when ChatPage unmounts, so other pages don't inherit a stale chat title.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Each chat now has its own URL (
/chat/<sessionId>), the sidebar renders sessions as<Link>so they're middle-clickable / Vimium-hintable / shareable, and the browser tab mirrors the active session's title. Race conditions around URL ↔ activeSession sync are handled explicitly rather than via mirror effects.Independent of the broader keyboard-shortcut work (those parts of the original commits were lifted out — this PR is the URL routing + tab title only).
Changes
Sidebar sessions as
<Link to="/chat/<id>">—SessionSidebar.tsx. Previously<div onClick={switchSession}>, so Vimium couldn't hint them and there was no shareable URL per chat (address bar always read/chat).URL → activeSession is the only mirror direction —
ChatPage.tsx. Reading the URL intoactiveSessionhappens in theuseEffect[sessionId]block. Going the other way (mirror activeSession into URL via effect) is intentionally NOT done — it races withloadSessions()(which starts withsessions=[], so any "URL is unknown" check is unreliable on a fresh tab) and with the server'ssession_switchedWS message that fires before our store knows the URL's session exists. Instead each call-site that changes the active session navigates explicitly:handleCreateSession— creates session, thennavigate('/chat/<new>')handleDeleteSession— deletes, thennavigateto the new active or to/chatCmd+click opens the correct chat in a new tab — fresh-tab opened with
/chat/Aused to briefly bounce to a different chat because two effects raced beforeloadSessions()resolved. Fixed by collapsing the duplicate URL-mirror effect (see (2)) so the fresh tab just trusts the URL.Browser tab title mirrors active session title —
ChatPage.tsx. Same strip rules as the sidebar (leading#andImplement:removed). Falls back to"Nerve"when there is no active session or whenChatPageunmounts, so other pages don't inherit a stale chat title.Tests
npm run buildclean (✓ built in 8.78s). No new tests — pure web feature. Verified by:/chat/<id>and stays on that chat/chat/<id>loads the right session even beforeloadSessions()resolves/chatresets the title to"Nerve"Files
Note
The original four commits (
dd578d9,d04eac7,bd63a27,f399c5d) bundled this change with chat-scoped keyboard shortcuts (useKeyboardShortcuts(chatShortcuts)). Those parts are tracked separately in the keyboard-shortcuts PR (#133) and were dropped here so this PR stands alone.