Skip to content

refactor(gateway): promote /chat to a top-level console SPA route#394

Merged
furukama merged 21 commits intomainfrom
gateway-chat-spa-mount
Apr 22, 2026
Merged

refactor(gateway): promote /chat to a top-level console SPA route#394
furukama merged 21 commits intomainfrom
gateway-chat-spa-mount

Conversation

@maxnoller
Copy link
Copy Markdown
Member

Summary

  • Extract isConsoleSpaPath() so /chat and /admin share one auth gate and one SPA mount point.
  • Split the old serveConsole / resolveConsoleFile into serveConsoleFile + serveConsoleAsset + serveConsoleIndex.
  • Add a top-level /assets/* route so the console bundle can load its assets from the root instead of /admin/assets.
  • Flip console/vite.config.ts base from /admin/ to / to match.

After this change, /chat serves the console SPA instead of the legacy docs/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

  • /chat returns 200 with <title>HybridClaw Admin</title> (console SPA)
  • /admin continues to serve the console SPA
  • /chat/* sub-paths fall through to the SPA for client-side routing
  • /assets/<hashed-name>.js serves the console's JS bundle
  • Docker-mode session auth still gates /chat and /admin as before
  • pnpm test tests/gateway-http-server.test.ts and pnpm test tests/npm-install.e2e.test.ts pass

maxnoller and others added 21 commits April 22, 2026 13:13
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.
@furukama furukama merged commit fd99fa6 into main Apr 22, 2026
5 checks passed
@furukama furukama deleted the gateway-chat-spa-mount branch April 22, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants