refactor(gateway): promote /chat to a top-level console SPA route#394
Merged
refactor(gateway): promote /chat to a top-level console SPA route#394
Conversation
Move the /chat surface from the static chat.html page to the console SPA. Extract isConsoleSpaPath so /chat and /admin share one auth gate and one mount point, split serveConsole into serveConsoleAsset and serveConsoleIndex, and add a top-level /assets/* route so the console can load its bundled assets from the root. console/vite.config.ts flips base from /admin/ to / because assets are now served at /assets rather than /admin/assets. The legacy docs/chat.html file is left on disk and remains reachable at /chat.html; a follow-up PR removes it once nothing references it.
Promoting /chat to a top-level SPA route means ChatPage is no longer wrapped in AppShell, which previously supplied the flex-column height chain that .chatPage's height: 100% resolved against. Use 100dvh so the page sizes to the viewport on its own.
Along with the /chat routing promotion, restore the chat UI pieces users expect at /chat: - Sidebar brand header with the HybridClaw logo + name and a sidebar collapse trigger. - Full-width "+ New Conversation" button and an always-visible "Recent Chats" label that falls back to a "No recent chats yet." placeholder. - Footer shows HybridClaw version. - Message column left-aligned (max 1100px), tighter left padding, so messages sit near the sidebar instead of centered in a narrow column. - .chatPage sized to 100dvh / 100% width so the standalone route fills the viewport without relying on an AppShell ancestor. - use-chat-stream reuses getErrorMessage() instead of inlining the Error-or-String dance in two places.
- Drop the hover-only opacity on message action buttons so copy / edit / reload icons sit beneath every message the way the legacy chat.html UI presented them. - Split the composer into a textarea row on top with the attach / send buttons on a row below, matching the target layout.
The Button ghost variant was painting a panel background and border around the copy / edit / reload icons below each message. Raise the .actionButton override specificity and strip the background and border so the icons match the legacy minimal look.
…ckground Mirror the reference chat page by tinting the selected session's title and snippet with the primary colour rather than giving it a raised background.
Drop the 1100px cap so the composer's right edge lines up with the top-right nav capsule and fills the main column instead of stopping short of it.
… restore theme toggle - Drop the 1px outline on the sidebar "+ New Conversation" button so it reads as a plain labelled action, matching the reference. - Drop the 1100px cap on the message column so user bubbles right-align to the main-column edge along with the composer and nav capsule. - Put the ThemeToggle back next to the version string in the sidebar footer.
Match the reference chat page where the composer's send button is an outlined primary-colour circle rather than a filled one.
Give the standalone chat sidebar its own brand row (46px logo, 18px/700 brand name) instead of reusing admin's smaller brand styles, widen the "+ New Conversation" padding to match the reference, and add a gap before the search input so the header breathes the same way.
- History loader now carries the preceding user turn onto each assistant message so the regenerate button renders after a reload instead of only appearing on messages received during the current session. - Drop the blue focus-within border on the composer and the blue focus-visible outline on the textarea; the surrounding border is enough visual feedback. - Recolor the send button to match the reference's neutral gray outline instead of the primary accent.
Reorder so the retry icon sits left of the copy icon, matching the reference chat page.
Bump the shared sidebar brand logo to 44px so the admin and chat sidebars look balanced next to each other, and flip the admin sidebar from collapsible="none" to "icon" with a SidebarTrigger in the header so desktop users can collapse it to an icon rail.
…ltips The collapsed state set only width: var(--sidebar-width-icon), but the expanded flex: 0 0 var(--sidebar-width) on the base selector still reserved the full width. Override the flex basis so the rail actually collapses to 4rem. Also stamp title attributes on admin nav links so users can read the label on hover when the sidebar is collapsed.
Bump the brand title to 18px/700 and center it against the 44px logo so the admin brand no longer reads tiny next to the bigger mark.
The standalone chat sidebar was duplicating the admin brand layout with its own chatBrandRow / chatBrand / chatBrandLogo / chatBrandName classes. Swap them for the shared sidebarStyles so /chat and /admin share one brand component, use the default sidebar width so the two routes line up, and bump the chat topbar horizontal padding to 24px to match the admin .main-panel so the ViewSwitchNav capsule stays put when switching between /chat and /admin. Update the sidebar test to expect the new desktop collapse trigger, and mock ThemeToggle in the chat-page test so it doesn't reach for window.matchMedia at module load.
…ndlers - Export SidebarBrand (with optional subtitle) and SidebarMeta from the shared admin sidebar and reuse both in ChatSidebarPanel, deleting the chat-local brand + footer duplicates (and their now-unused CSS). - Drop the ChatSidebarProvider wrapper (just re-export SidebarProvider) and fold ChatSessionList + renderSessionListBody into a single function - one less indirection layer. - Collapse ERROR_CLEAR into ERROR_SET with an empty string. - Change MessageBlock's onBranchNav to receive the message so the parent can pass a stable handleBranchNav and drop the per-row inline closure that was defeating MessageBlock's memo; likewise stabilize onEdit through handleEditOpen. Drop a dead role === 'thinking' check - that role isn't in the ChatMessage union. - Inline the replayRequest ternary via a local replayContent variable for readability.
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
isConsoleSpaPath()so/chatand/adminshare one auth gate and one SPA mount point.serveConsole/resolveConsoleFileintoserveConsoleFile+serveConsoleAsset+serveConsoleIndex./assets/*route so the console bundle can load its assets from the root instead of/admin/assets.console/vite.config.tsbase from/admin/to/to match.After this change,
/chatserves the console SPA instead of the legacydocs/chat.html. The legacy file is left on disk for now — a follow-up PR deletes it once nothing references it.Split out of #323 to keep the routing change reviewable on its own.
Test plan
/chatreturns 200 with<title>HybridClaw Admin</title>(console SPA)/admincontinues to serve the console SPA/chat/*sub-paths fall through to the SPA for client-side routing/assets/<hashed-name>.jsserves the console's JS bundle/chatand/adminas beforepnpm test tests/gateway-http-server.test.tsandpnpm test tests/npm-install.e2e.test.tspass