Skip to content

feat(chat): remove legacy chat.html, React 19 architecture, a11y and UX polish#323

Draft
maxnoller wants to merge 50 commits intomainfrom
chat-ux-improvements
Draft

feat(chat): remove legacy chat.html, React 19 architecture, a11y and UX polish#323
maxnoller wants to merge 50 commits intomainfrom
chat-ux-improvements

Conversation

@maxnoller
Copy link
Copy Markdown
Member

@maxnoller maxnoller commented Apr 15, 2026

Summary

Legacy cleanup

  • Delete docs/chat.html (4,066 lines) — the React console at /admin/chat fully replaces it
  • Add 301 redirect from /chat and /chat.html to /admin/chat in the gateway
  • Update gateway, Docker, and npm E2E tests for the removal

React 19 architectural improvements

  • Replace 9 useState calls with single useReducer (typed ChatState/ChatAction with SESSION_SWITCH, SESSION_ID_UPDATE, HISTORY_LOADED, MESSAGES_SET, etc.)
  • Replace useQuery for history with useSuspenseQuery + ChatErrorBoundary wrapped in QueryErrorResetBoundary for proper error retry
  • Add useTransition for session switches (handleOpenSession, handleEditSave) with aria-busy and pulse animation on the active sidebar session
  • Replace raw validateToken useEffect with useQuery(fetchAppStatus) (reuses existing validateToken via re-export)
  • Add prefetchQuery on sidebar session hover for instant switching
  • Derive branchInfoMap with useMemo instead of separate useState
  • Simplify useChatStream interface: setSessionId/setError accept plain (string) => void
  • Invalidate chat-history cache after stream completes so switching away and back refetches correctly
  • Early-exit in buildBranchInfoMap when branchFamilies is empty (avoids O(n) per RAF frame during streaming)

Accessibility

  • Focus-visible outlines on all interactive buttons (matches styles.css color-mix pattern)
  • focus-within on .messageBlock to show message actions on keyboard navigation
  • prefers-reduced-motion media query to disable slideIn, sessionPulse, and thinkingBounce animations
  • aria-label on message action buttons (copy, edit, regenerate) and branch nav buttons (previous/next)
  • aria-label="Edit message" on the edit textarea
  • Unique aria-label="Open chat sidebar" on mobile hamburger to avoid collision with app shell sidebar trigger

Mobile polish

  • 480px small-phone breakpoint with wider bubbles (95%) and tighter padding
  • Slash suggestions capped at max-height: 40vh on mobile
  • Fix sidebar overlay blocking backdrop click: inset: 0 replaced with explicit top/left/bottom

UX improvements

  • Auto-collapse admin sidebar to icon-only mode on the chat page; restore on navigation away
  • Add title tooltips to collapsed sidebar nav icons and brand mark
  • Show sidebar expand trigger button (aria-expanded="false") when collapsed on desktop

Test plan

  • npm --workspace console run test — 165 tests pass
  • npx vitest run tests/gateway-http-server.test.ts — 179 tests pass
  • Visit /chat → verify 301 redirect to /admin/chat
  • On /admin/chat, Tab through all interactive elements → verify visible focus rings
  • Resize to <480px → verify mobile layout (wider bubbles, tighter padding)
  • Enable prefers-reduced-motion in devtools → verify no animations
  • Verify admin sidebar collapses to icons on chat page, expands on other pages
  • Hover collapsed sidebar icons → verify tooltips
  • Click expand trigger (≡) → sidebar expands; navigate away → stays expanded
  • Switch sessions via sidebar → verify pending pulse animation and instant load (prefetched)
  • Send a message, switch to another session, switch back → verify messages persist (history cache invalidated)
  • Edit a message → verify no Suspense flash (startTransition)
  • Trigger a history load error → verify "Try again" button works (QueryErrorResetBoundary)

- Delete docs/chat.html (4066 lines) — the React console at /admin/chat
  fully replaces it; the gateway 301 redirect is already in place
- Update gateway/docker/npm E2E tests for chat.html removal
- Add accessibility: focus-visible outlines on all interactive buttons,
  focus-within to show message actions on keyboard navigation,
  prefers-reduced-motion to disable animations, aria-labels on
  message action buttons, Space key handler on sidebar session items,
  unique aria-label on mobile chat sidebar trigger
- Add mobile polish: 480px small-phone breakpoint with wider bubbles
  and tighter padding, slash suggestions max-height on mobile, fix
  sidebar overlay blocking backdrop click
- Auto-collapse admin sidebar to icons on the chat page; add tooltips
  to collapsed sidebar icons; show expand trigger when collapsed
@maxnoller maxnoller marked this pull request as draft April 15, 2026 13:45
…y, useTransition

- Replace 9 useState calls with a single useReducer (ChatState/ChatAction)
  with typed actions: SESSION_SWITCH, SESSION_ID_UPDATE, HISTORY_LOADED,
  MESSAGES_SET, ERROR_SET/CLEAR, EDIT_START/CANCEL, APPROVAL_BUSY_SET,
  MOBILE_SIDEBAR_TOGGLE, RESET
- Replace useQuery for history with useSuspenseQuery — errors throw to a
  new ChatErrorBoundary in the router, removing manual error-state handling
- Add useTransition for session switches with aria-busy={isPending} and
  a pulse animation on the active sidebar session during load
- Replace raw validateToken useEffect with useQuery(fetchAppStatus)
- Add prefetchQuery on sidebar session hover for instant switching
- Derive branchInfoMap with useMemo instead of separate useState
- Add sessionItemPending animation with prefers-reduced-motion support
- Simplify useChatStream interface: setSessionId and setError accept
  plain (string) => void instead of React.Dispatch<SetStateAction>
… a11y and perf

- Reuse existing validateToken from api/client instead of duplicate fetchAppStatus
- Fix ChatErrorBoundary: wrap in QueryErrorResetBoundary so "Try again"
  retries the failed query instead of immediately re-throwing
- Add early-exit in buildBranchInfoMap when branchFamilies is empty
  (avoids O(n) iteration per RAF frame during streaming)
- Fix stale isMobile closure in sidebar collapse cleanup
- Remove redundant Space key handler on session buttons (native behavior)
- Add aria-labels to branch navigation buttons
…ET action

- handleNewChat: use SESSION_SWITCH instead of RESET + SESSION_ID_UPDATE
  (two dispatches caused an intermediate render with stale sessionId)
- handleEditSave: use SESSION_SWITCH instead of SESSION_ID_UPDATE when
  switching to a branch (old messages stayed visible until history loaded)
- Remove unused RESET action from reducer (SESSION_SWITCH covers all cases)
@maxnoller
Copy link
Copy Markdown
Member Author

Code review

Found 2 issues:

  1. staleTime: Infinity on useSuspenseQuery for chat history means the cache is never invalidated after streaming adds new messages. If a user sends messages in session A (messages accumulate in local state via MESSAGES_SET), then switches to session B and back to session A, useSuspenseQuery returns the stale cached history (pre-streaming), and the HISTORY_LOADED effect overwrites the newer messages. The only invalidateQueries call in the component targets chat-recent, not chat-history. Either invalidate the history cache after streaming completes (refreshRecent is the natural place to also invalidate history), or lower staleTime to a finite value like 45_000 (what the previous useQuery used).

queryKey: ['chat-history', auth.token, sessionId],
queryFn: () => fetchChatHistory(auth.token, sessionId),
staleTime: Infinity,
});

  1. handleEditSave dispatches SESSION_SWITCH without wrapping in startTransition, while handleOpenSession correctly does. Both trigger a session change that causes useSuspenseQuery to suspend. Without startTransition, React immediately shows the Suspense fallback ("Loading chat..."), causing a visual flash that discards the current chat content. handleOpenSession avoids this by deferring the commit. The same pattern should be applied to handleEditSave.

media: msg.media ?? [],
};
dispatch({ type: 'SESSION_SWITCH', sessionId: branch.sessionId });
} catch (err) {
dispatch({

Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

…h in startTransition

- Invalidate chat-history query cache in refreshRecent so switching away
  and back refetches with streamed messages instead of returning stale data
- Wrap handleEditSave's SESSION_SWITCH in startTransition to avoid Suspense
  fallback flash (consistent with handleOpenSession)
@maxnoller maxnoller changed the title chore: remove legacy chat.html, add chat a11y and UX polish feat(chat): remove legacy chat.html, React 19 architecture, a11y and UX polish Apr 15, 2026
furukama and others added 14 commits April 15, 2026 16:26
- Replace broken <img src="/static/..."> with HybridClaw SVG icon component
  (matches app shell sidebar pattern, works in Vite dev without gateway proxy)
- Fix infinite Suspense remount loop: persist generated sessionId to
  localStorage inside the useReducer initializer so remounts find the stored
  value instead of generating a new random ID each time
- Make "New Conversation" a solid primary button instead of ghost button
- Add composer focus-within border tint
- Add PanelLeft icon component (lucide-react panel-left pattern)
- Replace hidden topbar trigger with a visible PanelLeft button in a
  thin chat header bar — always visible on desktop, toggles admin
  sidebar between collapsed icons and full labels
- On mobile, show hamburger ☰ for chat sidebar instead (PanelLeft
  hidden via .desktopOnly / .mobileOnly responsive classes)
- Remove the aria-expanded CSS hack from styles.css — the chat page
  now owns its own toggle button
…e persistence, always-visible trigger

Sidebar component changes:
- Replace Menu (hamburger) icon with PanelLeft in SidebarTrigger
- Add localStorage persistence for sidebar open/collapsed state
- Add defaultOpen prop to SidebarProvider
- setOpen now accepts functional updater ((prev) => next) for toggle
- Make topbar trigger always visible (remove mobile-only CSS guard)

Chat page simplification:
- Remove auto-collapse useEffect and custom PanelLeft toggle — the
  topbar trigger now handles sidebar toggle on all pages consistently
- Remove unused useSidebar/PanelLeft imports from chat-page

Test fixes:
- Clear localStorage in sidebar test beforeEach to prevent state leaking
  between tests from the new persistence feature
Move SidebarTrigger from the end of the topbar (far right) to the
start (left, adjacent to the sidebar edge). This matches the shadcn/ui
pattern where the trigger sits next to the content it controls.
… title

- Move SidebarTrigger from topbar into the sidebar header row (next to
  the brand logo) — matches shadcn/ui pattern where the toggle lives
  inside the sidebar itself, not in a remote topbar
- When collapsed, the trigger centers in the icon-only sidebar; when
  expanded, it sits at the right end of the header row
- Hide the page title ("Chat") on the chat route — the chat page has
  its own session sidebar with "HybridClaw" branding, so the topbar
  title is redundant. Other pages keep their titles.
- Add topbar-compact class for routes that hide the title
- Clean up: remove topbar-sidebar-trigger CSS, update sidebar test
…age title

- Convert chat session history panel to use the Sidebar component system
  with its own SidebarProvider — matches shadcn/ui pattern with PanelLeft
  toggle in the sidebar header for collapsing the sessions panel
- Remove the "Chat" page title and topbar entirely on the chat route —
  the chat page fills the full content area
- Remove custom mobile sidebar overlay (sidebarOpen, sidebarBackdrop,
  mobileHeader) — the Sidebar component's built-in Sheet handles mobile
- Remove mobileSidebarOpen state and MOBILE_SIDEBAR_TOGGLE action from
  the chat reducer (the Sidebar component manages its own state)
- Simplify chatPage layout from CSS grid to flexbox
- Set chat sidebar --sidebar-width-icon to 0px so it fully hides when
  collapsed (sessions have no icon-only state, unlike the admin nav)
- Wrap chat page in ChatSidebarProvider so the main area can access
  the sidebar context for the trigger button
- Add ChatMainHeader component that shows a PanelLeft trigger when the
  session sidebar is collapsed or on mobile — tapping it opens the
  session panel (as Sheet overlay on mobile, inline on desktop)
…etween providers

- SidebarProvider now accepts a storageKey prop (default: 'hybridclaw_sidebar_state')
  to control which localStorage key is used for persistence
- Pass storageKey={false} to disable persistence entirely (for transient sidebars)
- Chat session sidebar uses storageKey={false} so it doesn't stomp on the
  admin sidebar's persisted state
Add a collapsible prop to the Sidebar component matching the shadcn/ui
API:

- "icon" (default): collapses to --sidebar-width-icon showing only icons
- "offcanvas": collapses to width 0 with hidden content — the sidebar
  slides fully off-screen
- "none": sidebar is not collapsible, always expanded

The collapsed mode is exposed via data-collapsible attribute on the
<aside> element (only set when collapsed), matching shadcn's pattern.

The chat session sidebar now uses collapsible="offcanvas" instead of
the --sidebar-width-icon: 0px CSS variable hack.
…ead CSS

- Escape the 24px main-panel padding on the chat route so both sidebars
  extend full viewport height (negative margin on page-content-full)
- Override nested SidebarProvider layout div to respect the chat page's
  flex container instead of forcing min-height: 100vh
- Add background: var(--page-bg) to chatMain for clear visual contrast
  against the sidebar's --sidebar-bg
- Remove dead CSS: mobileHeader, sidebarOpen from prefers-reduced-motion
Replace the HybridClaw logo + brand text in the session sidebar header
with a simple "Sessions" label. The admin sidebar already shows the
brand — duplicating it in the session panel was redundant.

Remove unused sidebarLogo, sidebarBrand, chatSidebarBrand CSS classes.
… composer

1. Restore the view-switch capsule nav (Chat/Agents/Admin/GitHub/Docs)
   on the chat page — was completely hidden, now shows in compact topbar
2. Add Chat entry to admin sidebar navigation under Overview
3. Make session sidebar non-collapsible (collapsible="none") — remove
   PanelLeft toggle and ChatSidebarProvider/ChatMainHeader
4. Fix session content overflow (add overflow: hidden)
5. Fix composer placeholder vertical centering — use line-height: 36px
   matching send button height when placeholder shown, switch to
   line-height: 1.5 with padding when text is entered
6. Remove dead CSS (chatMainHeader, headerButton, sidebarLogo, etc.)
7. Fix JSX nesting error from ChatSidebarProvider removal
8. Update sidebar test for new Chat nav item
- Extract ViewSwitchNav into its own module (view-switch.tsx) to avoid
  circular import issues with app-shell
- Remove the topbar entirely on the chat route in app-shell — the chat
  page renders the view-switch capsule in its own chatTopbar div
- This prevents the capsule from overlapping the message area (caused by
  page-content-full escaping the main-panel padding while the topbar
  stayed inside it)
- Add SquarePen icon component (lucide square-pen pattern)
- Replace the full-width primary "New Conversation" button with a
  compact 30px icon button in the session sidebar header
- Button sits next to "SESSIONS" label with subtle border, hovers
  to accent color
- Remove ChatSidebarHeader function (inlined into SidebarHeader)
- Remove unused ChatSidebarTrigger export
New component with CSS modules, matching our existing design system:

Variants:
- default — primary background (maps to .primary-button)
- ghost — panel background with border (maps to .ghost-button)
- outline — transparent with border
- danger — danger-soft background (maps to .danger-button)

Sizes:
- default — 36px min-height, standard padding
- sm — 30px min-height, compact padding
- icon — 36x36 square, no padding

Features:
- forwardRef for DOM access
- Accepts all standard button HTML attributes
- Focus-visible ring matching existing color-mix pattern
- SVG icons auto-sized via nested selector
- Disabled state with opacity and pointer-events: none
- type="button" default (prevents accidental form submission)
Replace raw <button> elements with the new Button component:

- EditInline: Save (default) + Cancel (ghost)
- Approval actions: Allow buttons (outline, sm) + Deny (danger, sm)
- Message actions: Copy, Edit, Regenerate (ghost, icon)
- Branch navigation: Previous/Next (ghost, icon)
- New conversation: SquarePen icon (outline, icon)

The Button component provides consistent focus rings, disabled states,
hover effects, and sizing across all chat buttons. Custom CSS overrides
(actionButton, branchButton, newChatButton) are kept for size/position
adjustments only — the visual variant comes from Button.
Remove ~60 lines of hand-rolled button styles (hover, focus-visible,
disabled, background, border) from chat-page.module.css that are now
provided by the Button component. Keep only size overrides (actionButton
28px, branchButton 22px) and the success-colored approvalAllow variant.
Restore focus-visible on composer attach/send buttons (not yet migrated
to Button).
Max Noller and others added 3 commits April 18, 2026 14:22
Dashboard's active-match was gated by 'to === /', which never held after
the /admin restructure. Prefix matching then flagged Dashboard active on
every /admin/* page. Exact matching applies cleanly since all nav items
are top-level admin routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vite base was '/admin/' from when the console lived exclusively under
/admin. Now that /chat is a sibling SPA entry point, tying asset URLs
to the /admin namespace is incidental. Set base to '/' and split the
gateway's console serving into asset-only (/assets/*) vs index-only
(/admin, /chat SPA entries).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…itch dynamic

Two bugs found via browser testing after the standalone-/chat move:

- Chat page rendered blank (error boundary fallback) because Sidebar
  components lost their provider when AppShell was removed from the
  chat route. Wrap ChatPage output in ChatSidebarProvider.
- View-switch hardcoded /admin as the active view; on /chat it still
  highlighted Admin. Derive active state from the current pathname.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maxnoller pushed a commit that referenced this pull request Apr 18, 2026
Extracted from #323 as a foundation PR. Adds a reusable <Button>
primitive with variant/size props and two new lucide-style icons
exported from the icons barrel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- View-switch Docs link now points at /docs instead of relying on the
  legacy /development redirect.
- Pick up the formatter's indentation fix for chat-page.tsx after the
  ChatSidebarProvider wrap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maxnoller pushed a commit that referenced this pull request Apr 18, 2026
Extracted a11y/mobile polish from #323, independent of its larger
React 19 refactor:

- aria-label on copy/edit/regenerate/branch-nav message actions and the
  edit textarea, so screen readers announce purpose instead of glyphs
- .messageBlock:focus-within reveals the action row during keyboard nav
- focus-visible outlines on composer input, attach, and send buttons
- .composer:focus-within border highlight
- @media (max-width: 480px) small-phone breakpoint: wider bubbles,
  tighter padding, smaller empty-state heading
- @media (prefers-reduced-motion: reduce): disable thinking-dot and
  sidebar slide-in animations
- Cap slash suggestions at 40vh below 900px so they don't cover the
  whole screen
- Fix sidebar overlay blocking backdrop clicks: replace inset: 0 on
  .sidebarOpen with explicit top/left/bottom so the 280px panel no
  longer stretches across the viewport

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maxnoller pushed a commit that referenced this pull request Apr 18, 2026
Extracted from #323. Depends on #358 for the shared Button and the
PanelLeft/SquarePen icons consumed by the sidebar header and new
view-switch control.

Behavior:
- Sidebar collapses to an icon-only rail on the chat page and restores
  to the full layout on navigation away
- Collapsed nav items show title tooltips; brand mark gets a tooltip too
- When collapsed on desktop, an expand trigger (aria-expanded="false")
  appears so keyboard users can restore the sidebar
- New view-switch component provides a dedicated toggle for chat vs
  admin surfaces, wired into the router

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Max Noller and others added 2 commits April 19, 2026 12:13
# Conflicts:
#	console/src/routes/chat/chat-page.module.css
#	console/src/routes/chat/chat-page.tsx
#	docs/chat.html
Main added console/src/components/button/ with a superset Button
(loading, render prop, data-disabled/data-loading hooks). The PR's
older button.tsx/button.module.css at the same path shadowed it after
the merge. Delete them so imports resolve to the enhanced component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@maxnoller
Copy link
Copy Markdown
Member Author

Code review

Found 1 issue:

  1. historyQuery was switched to useSuspenseQuery and the previous useEffect that forwarded historyQuery.error to an inline error banner was removed. Because refreshRecent now also invalidates the chat-history cache after every stream finalize, any failed background refetch will throw past the local state and tear down the entire ChatPage into ChatErrorBoundary — losing composer text, active session, loaded messages, and sidebar state. The prior useQuery path surfaced this inline without unmounting. Consider handling historyQuery.error inline (e.g., keep useQuery or catch with a narrower boundary around just the history load).

const recentSessions = recentQuery.data?.sessions ?? [];
const historyQuery = useSuspenseQuery({
queryKey: ['chat-history', auth.token, sessionId],
queryFn: () => fetchChatHistory(auth.token, sessionId),
staleTime: Infinity,
});
useEffect(() => {
const data = historyQuery.data;

Invalidation that can trigger the throw:

const refreshRecent = useCallback(() => {
void queryClient.invalidateQueries({
queryKey: ['chat-recent', auth.token, userId],
});
// Also invalidate the current session's history cache so that
// switching away and back refetches with the streamed messages.
void queryClient.invalidateQueries({
queryKey: ['chat-history', auth.token, sessionIdRef.current],
});
}, [queryClient, auth.token, userId]);

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

Max Noller and others added 3 commits April 19, 2026 12:41
Reverts historyQuery to useQuery and restores the error-forwarding
effect. With useSuspenseQuery, a failed background refetch — triggered
by refreshRecent's invalidateQueries after each stream finalize — would
throw to ChatErrorBoundary and tear down the whole page, losing
composer text, active session, and loaded messages. Errors now show in
the inline banner instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Admin sidebar is now always expanded on desktop; mobile still uses the
sheet. Drops the desktop toggle button and collapsed-state tooltips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maxnoller pushed a commit that referenced this pull request Apr 19, 2026
Revert the logo/wordmark/eyebrow changes in sidebar to avoid conflict with
PR #323, which rewrites the same files. The desktop chrome only depends on
the data-hc-* attributes in index.tsx, not on the branding changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@maxnoller maxnoller marked this pull request as draft April 20, 2026 10:56
Resolve conflicts in chat-page.tsx, chat-sidebar.tsx, chat-page.test.tsx,
chat-page.module.css and accept the PR-side deletion of docs/chat.html.

Integrate main's session-search feature (query, snippet, debounce,
CHAT_UI_CONFIG limits) into the PR's shadcn Sidebar architecture.
Stack chat UX improvements on top of the view-switch nav + non-collapsible
admin sidebar work. Adopt PR 360's Link/useRouterState-based ViewSwitchNav
(replacing the window.location version from this branch) and pick up the
SidebarProvider setOpen bail-out and Trigger/useSidebar exports. Keep the
chat ErrorBoundary + offcanvas collapsible mode added here.

Mock ViewSwitchNav in chat-page.test.tsx since it now requires router
context from the PR 360 Link component.
@maxnoller maxnoller changed the base branch from main to extract-admin-sidebar-collapse April 21, 2026 10:40
Drop the ChatErrorBoundary + QueryErrorResetBoundary wrapper, the sidebar
'offcanvas' collapsible mode, and the .page-content-full class that this
branch had been carrying. PR 360 is the base — use its versions directly
instead of layering this branch's variants on top.
HISTORY_LOADED was clearing state.error unconditionally, which wiped out
errors coming from other sources (e.g. the app-status query) as soon as
the first history payload resolved. Drop the blanket clear; SESSION_SWITCH
already resets error when the user navigates, and historyQuery.error has
its own effect that replaces stale history messages.
- Restore .sidebarSearchWrap, .sidebarSearch, .sessionSnippet styles that
  were dropped during the earlier merge; the search input and snippet
  render unstyled without them.
- Replace inline error coercion in handleEditSave/handleUploadFiles with
  getErrorMessage (already imported at the top of the file).
- Hoist the shared chatSidebarContent wrapper out of the three-way branch
  in ChatSessionList so the branches return just the inner body.
Base automatically changed from extract-admin-sidebar-collapse to main April 21, 2026 17:44
# Conflicts:
#	src/gateway/gateway-http-server.ts
…fetches

Use shared getErrorMessage() in use-chat-stream, guard hover-prefetch
against the active session, and mark the active chat-history query as
stale without an immediate refetch on stream completion. Drop decorative
banner comments and narrative WHAT comments in chat-page.
SidebarCollapsible is 'icon' | 'none' — nothing in the repo sets
data-collapsible="offcanvas", so this rule was dead.
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