App nav dock and tool sidebar
The macOS-style icon dock that carries the app's top-level destinations, and the sidebar column that hosts it on every tool route.
- Component:
apps/debate-ai.com/components/layout/CategoryDock.tsx - Sidebar shell:
packages/debate-webview/src/components/layout/AppSidebarShell.tsx - The one sidebar column:
packages/debate-videos/src/ui/layout/ResizableSidebarLayout.tsx(hide/show state insidebar-collapse.tsbeside it), filled bypackages/debate-webview/src/components/layout/app-sidebar/off the video pages - The tree in it:
VideoSidebarTree, mounted off the video pages throughLibrarySidebarTree(packages/debate-videos/src/components/category-gallery/) - Route predicates:
packages/debate-videos/src/components/category-gallery/sidebar-routes.ts(re-exported for app code asapps/debate-ai.com/lib/sidebar-routes.ts) - Dock primitive:
apps/debate-ai.com/lib/ui/layout/dock.tsx
What it shows
Five destinations — Videos, Shared, Debate, Practice vs AI, Research — plus a
gear-icon Settings menu, with Alt+1…Alt+5 shortcuts in dock order. The
icons carry no hover labels — in the sidebar-hosted dock the tooltips crowded
together over the nav tree — so each item's name lives in its aria-label
(read by screen readers) and in the icon art itself.
Practice vs AI opens a menu instead of navigating. A plain click on that
icon does not go to /practice/versus-ai — it opens the site-nav menu described below
(NavMenu, in CategoryDock.tsx). The page itself is still one tap away, as
the first tool listed under that menu's Practice section, and the Alt+4
shortcut still jumps straight there.
Tools are not dock icons. The tools catalog is reached from the sidebar nav tree's Research / Practice / Coaching / Insights sections, and the whole feature catalog from the Features row in the Settings menu's Site Links submenu — next to a Docs row for the help site. Holding the dock to five destinations is what lets it fit inside the sidebar column it is hosted in — see below.
The sidebar button (mobile)
The sidebar is md+ only, so the mobile bottom dock starts with a Sidebar
button. It opens a left drawer (MobileSidebarDrawer) whose content follows
the view in the centre (mobileSidebarKind, lib/mobile-sidebar.ts):
| View | Drawer |
|---|---|
Video library (/videos, /lectures, glossary, rankings, statistics) | The sidebar tree (Videos, tool sections) and footer |
/research/cards, /reason-editor | Document panels above the sidebar tree |
/debate | Its own flows/rounds sidebar — the button dispatches debate:open-sidebar and the workspace opens its own sheet |
| Everything else | The sidebar tree and footer |
The drawer closes on navigation.
The desktop sidebar's footer
On md+ the column has no brand row at the top — it opens straight onto the
dock. Its bottom row, pinned under the scrolling tree, is the account menu
(on every page, the video library's included) with the hide button beside
it — see Hiding the sidebar. The row shows your avatar
and name only, never your email address, with a badge on the avatar counting
unread notifications. The account menu holds Settings, a Notifications
submenu (every unread notification: pick one to open its link and clear it, X
to clear it in place, Clear all, and a link to /notifications; clearing
is the page's mark-as-read), an Organizations submenu (switch between Personal
and your organizations, or create one — see
Contacts), a Theme submenu
(Light / Dark / System and every colour theme, with swatches — the same picker
as the dock's Settings menu, and synced to the account the same way), a
Site links submenu (every FOOTER_LINKS entry: Docs, GitHub, Support,
Privacy, Reddit…) and Sign out. Signed out, the row is a Sign in button with a
globe button beside it that lists the same site links.
Docs and Settings are always on screen just above the account row, on every page, signed in or out and whatever is selected, so you never have to open a menu to reach them. The phone drawer ends with the same two links.
The other site links used to be a row of links above the account row; they
live in the account menu now. The phone layouts below md, which have no
account row, keep their footer.
Two menus, not one
The dock carries two independent dropdowns, each with its own DropdownMenu
root — opening one does not affect the other, and either can be open at once:
-
The Practice vs AI icon opens the site-nav menu (
NavMenu). Command Palette at the top, then a submenu per sidebar section — Videos, Research, Practice, Coaching, Insights — derived from the data the sidebar itself renders rather than restated:Menu submenu Comes from Videos SIDEBAR_VIDEO_LINKS(components/category-gallery/sidebar-video-links.ts), minus the glossary and topic & video statistics, which are listed under Insights insteadResearch / Practice / Coaching / Insights SIDEBAR_TOOL_SECTIONSOn a phone, where the sidebar column below is
md+only, this menu is the only way to reach those links at all. -
The gear icon opens
SettingsMenu— Settings, the light/dark toggle and Theme submenu, Hide Dock (see Hiding the dock), one Site Links submenu listing every footer link (site and debate-community links together, fromFOOTER_LINKS), and the account block.
There is no "Apps" submenu. The site-nav menu used to restate the dock's
own five icons as text and then hang the whole feature catalog underneath,
one nested submenu per category — a second, deeper copy of /practice/features
hanging off the dock that was already showing those five icons. One
Features row reaches the catalog instead, and a Docs row the help
site; both are footer links, so the sidebar's link rows carry them too.
apps/debate-ai.com/lib/nav/dock-menu-sections.ts does the site-nav menu's
composition, and lib/nav/__tests__/dock-menu-sections.test.ts fails if a
sidebar destination is missing from it. That coupling is the point: the
Videos links and the glossary/rankings pair below the tree were in the
sidebar and in no mobile menu at all, because the menu was a hand-kept list.
Both menus scroll (max-h-[min(560px,80vh)]) — SettingsMenu's theme/links
block above the account rows can still run past a phone viewport.
Wiring the Practice vs AI icon to a second DropdownMenu root, rather than
sharing the gear's, means it sits inside Dock's children instead of
wrapping the whole dock — so Dock's magnification props land on the
wrapper component instead of the DockItem two levels down.
PracticeVsAiTrigger forwards mousex/magnification/distance/iconSize
by hand so that icon still resizes and tracks the cursor like its siblings.
/videos additionally prints the tree inline in its own md:hidden block,
under the quick-link tiles: the tiles are the Videos section only, so
ToolNavTree follows them with defaultExpanded={false} (its sections start
collapsed there — expanded, forty rows would push the video grid off the
screen).
Hiding the dock
Choose Hide Dock in the gear menu and the row of app icons folds away to a
small arrow tab where it sat: hanging down from the top-left corner or the top
of the sidebar, or rising from the bottom of the screen on a phone. Hover the
tab (or Tab to it from the keyboard, or tap it on a touch screen) and the full
dock slides back over it; move away and it folds up again. It stays out while
one of its menus is open, so the same menu's Show Dock row puts it back
for good. The choice holds on every page and is remembered in this browser
(app-dock-hidden in localStorage, broadcast by
packages/debate-webview/src/lib/nav/dock-hidden.ts so every dock instance
agrees).
CollapsibleDock in CategoryDock.tsx does the folding. Shown, it renders
the dock untouched. Hidden, it keeps the dock mounted inside an absolutely
positioned, transparent panel over the tab, and reveals it with CSS alone:
group-hover, keyboard focus inside (:focus-visible, so a mouse click that
leaves focus on an icon does not pin it open), a tap on the tab, or a dock
menu trigger with data-state=open.
Where it renders
The dock has two forms, and which one a route gets is decided by
hasEmbeddedDock:
Sidebar-hosted (<CategoryDock embedded />). On /videos (whose own
<aside> takes it through LecturesVideoGridView's dockSlot) and on every
route the tool-nav tree links to (where AppSidebarShell wraps the page in
the generic sidebar), the dock sits at the top of that column (300px wide until you
drag it — see Resizing the sidebar).
/doc is the exception: the Docs button opens the research agent, which draws
its own full-height workspace. Its sidebar — the files tree and the "Open Tabs"
list — is the research agent's own, not the generic tool sidebar, and the dock
sits at the top of that column (OWN_SIDEBAR_DOCK_HREFS, in
packages/debate-videos/src/components/category-gallery/sidebar-routes.ts).
The research agent's own app dock is never mounted here (it is the host's to
draw), and on a phone the dock's Sidebar button opens the agent's sidebar
(the OPEN_OWN_SIDEBAR_EVENT window event, the same one /debate listens for).
Choosing Settings in the dock's menu from /doc opens /settings on the
research agent's first tab (?category=research-account); from anywhere else it
opens /settings on its first tab. Either way the agent's sections and the app's
own share one sidebar.
The Research button was called Docs and lived at /doc until October
2026. Old /doc and /doc/<document> links answer with a permanent (308)
redirect to /research/docs and /research/docs/<document>, from the same
table as the other category moves
(packages/debate-data-sync/src/routes/category-paths.ts). It sits under
/research/docs rather than /research because /research is the Research
Workspace hub.
That now includes /practice/rankings and /practice/glossary. Both are rows
in the sidebar's own tree, and both used to answer with a bare page — no
sidebar, and no fixed dock either, since every /videos path counts as
already carrying a sidebar-hosted one. LecturesSidebarShell wraps them in
the same column (dock, nav tree, footer), so clicking either link leaves the
nav exactly where it was.
The sidebar is md+ only, though, and the same rule that hides the fixed dock
hides its mobile bottom bar. So on a phone those two pages still answered with
nothing to navigate by. LecturesSidebarShell now also renders the md:hidden
block the grid renders — the quick-link tiles, the tool tree (collapsed) and
the footer — so the pair has a way out of it at every width.
Fixed fallback. Everywhere else — the home page, /login, /legal/* —
it renders fixed in the top-left corner on desktop and as a bottom bar on
mobile. The two never show at once.
Route matching is by path prefix, so a nested route under a tree entry
(/research/cards/awards under /research/cards, a document route under /research/docs) keeps the
sidebar rather than dropping to the fixed dock. The trailing / in the
comparison keeps /cardsy from matching /cards.
Search floats over the results
The search box, the season and style filters, the sort and the grid/rows toggle used to sit in the sidebar on a desktop and in a bar above the results on a phone — two copies, which is why a half-typed query seemed to disappear when a window was resized across the breakpoint.
There is one now, and it is not in the sidebar: a small magnifier in the top-right corner of the results, which follows them as you scroll. Hover it on a desktop, tap it on a phone, or Tab to it, and the whole control opens over the results. It stays open while you are typing in it or while a search is active, and closes on Escape or a click outside. Whatever you typed is still there the next time you open it.
That is also what lets the sidebar stay the same on /practice/glossary and
/practice/rankings, which have nothing to search: the column is now the same
column everywhere in the library.
Results open as rows
/videos opens as the dense row list rather than the thumbnail grid — about
four times as many results per screen. The grid is one toggle away inside the
search panel. The choice is not remembered between page loads.
One sidebar everywhere
Every view draws the same sidebar: the one the video pages always had — the
dock, then Videos and the Research / Practice /
Coaching / Insights sections, then the footer links. The tool and
research pages, /admin, the legal pages and the help docs used to get a
different column (shadcn's sidebar-07, tool sections only), and /docs
showed Fumadocs' own sidebar beside that one. Now:
- Off the video pages
AppSidebarShellwraps the page in the sameResizableSidebarLayoutthe video pages use, andLibrarySidebarTreemounts the sameVideoSidebarTree, reading the library's counts itself (useVideoMeta). - Page-specific panels are sections of it. The REASON document panels
sit above the tree on
/reason-editorand are the whole column on/research/cards, as before. - On
/docsthe docs' page tree is a section of it.debate-help-docs' layouts portal the page tree (app-sidebar-docs-tree.tsx) into a slot the column leaves for it (DOCS_SIDEBAR_SLOT_ID), at the bottom of the column below the Insights section, andstyles/docs.csshides Fumadocs' desktop sidebar and closes its grid column. BelowmdFumadocs' drawer is still the docs' navigation. Only the folders on the path to the current page start expanded, and the current page is scrolled into view when the docs open and after each navigation. - Off
/docsthe tree is not drawn; the Docs and Settings links pinned above the account row (SidebarPinnedLinks) reach it from every page. - The mobile drawer shows the same tree (
MobileSidebarDrawer).
/debate and /research/docs keep their own workspace sidebars
(ownsItsLayout) — see Known gaps.
Resizing the sidebar
On every page with the sidebar, drag the thin line on the sidebar's right edge to make the column wider or narrower (between 220px and 640px). The width you choose is remembered in this browser and every page opens the sidebar at that same width, so the column keeps its shape as you move between them. Double-click the line to put it back to its default 300px. The line also takes keyboard focus, and the arrow keys resize it.
Hiding the sidebar
Hide the sidebar with the panel button at its bottom-right, by pressing
Ctrl+B (⌘B on a Mac), or by dragging its edge past half its minimum
width. It collapses to nothing and the page takes the full width. A small tab
on the left edge of the window brings it back, as do the same shortcut and
dragging the edge back out. The choice holds on every page and is remembered
in this browser (app-sidebar-collapsed in localStorage; sidebar-collapse.ts
broadcasts it, so the column and the dock always agree).
Peeking. While the sidebar is hidden, move the pointer to the very left
edge of the window (within 8px) and it slides out over the page — slightly
translucent, at your usual width — without pushing the page aside. Move away
from it and it slides back after a moment (or press Esc); it stays hidden
until you pin it with the panel button at its foot, which reads Keep
sidebar open while peeking. A menu opened from it (the account menu) keeps
it out until the menu closes. Each time it comes or goes it plays one of ten
animations at random (slide, elastic, wiggle, zoom, flip, bounce, swing, drop,
blur or jelly), and none if your system asks for reduced motion.
(ResizableSidebarLayout: a pointermove
listener while collapsed restyles the same <aside> as a fixed overlay, so
the content panel never resizes.)
Pages with their own sidebar. The flow workspace (/debate) and the
research docs (/research/docs) draw a sidebar of their own, so there the app
sidebar starts hidden and theirs is the only one on screen. It still peeks
from the left edge, and pinning it keeps it open until you leave the page; it
doesn't change whether the sidebar is hidden anywhere else.
Icons that move. This is on by default; turn off Animate sidebar icons in Settings → Preferences to stop it. Hovering a row in the sidebar, or tabbing to it (the icon or its label), makes its icon wiggle, bounce, pulse, spin, shake, flip, jelly or tada, a different one from the last time, for either half a second or a full second, and tints it a random color while it plays. Each icon plays at most once every three seconds. The research docs' own sidebar does the same. If your system asks for reduced motion, the icon only changes color.
While the sidebar is hidden the dock it carried floats at the top-left
instead (CategoryDock lifts its suppression while useSidebarCollapsed()
is true), so the five destinations stay one click away.
While a text editor has the keyboard (CardMirror, the REASON editor, any text field), Ctrl/⌘+B stays bold and does not toggle the sidebar.
Each tree section (Videos, Research, Practice, Coaching, Insights) opens and closes on its own; see The nav tree's headings. The Docs, Github, Support, Privacy and Reddit links sit on one compact line after the last section and scroll with the list rather than staying pinned. Docs and Settings are pinned at the bottom above the account menu, which links to Settings, Notifications and Contacts and signs you out. Signed out, that row is a Sign in button that opens the sign-in dialog without leaving the page.
On a phone nothing changes: the sidebar is not drawn below the md
breakpoint, and the dock's Sidebar button still opens the drawer described
above.
Staying inside the sidebar
A dock sized to its own contents (w-max, the default fluid: false form)
is wider than the sidebar at its default 300px. Hosted there it
either forced the sidebar to scroll sideways or reached over its border onto
the page beside it — which on /reason-editor and /research/docs is a CardMirror
editor.
The sidebar-hosted form passes fluid to Dock, which:
- takes its width from the column (
w-full max-w-full min-w-0) instead of from the item count, and - wraps an overflowing row onto a second line (
flex-wrap) instead of growing past the column, so a future sixth or seventh destination still cannot escape it.
CategoryDock also shrinks the icons for that form: EMBEDDED_ICON_SIZE
(34px) at rest and EMBEDDED_MAGNIFICATION (46px) hovered. Six items at 34px
with gap-1.5 and p-2 come to 250px against 276px of usable column width,
and a magnified icon adds 12px more — so the row fits without wrapping at
every breakpoint, and the wrapping above is the backstop rather than the
normal case.
The dock stays overflow-visible so a magnified icon isn't clipped at the
dock's edge; the sidebar column is the clipping boundary.
The nav tree's headings
Below the dock, the same column carries the nav tree
(VideoSidebarTree on /videos, its ToolNavTree tail everywhere else).
It is three levels deep:
| Level | Element | What it is |
|---|---|---|
| 1 | h1 | A top-level section: Videos, Coaching, Research, Practice |
| 2 | h2 | A subgroup inside one — College Policy, Favorites, Lectures |
| 3 | span | A leaf link: a tool, a debate format |
Two rules hold for every h1:
-
It is not a link. The row renders without an
href, so clicking it does nothing but toggle the section. Each section's flagship tool is the first link inside it (/coachingunder Coaching), so nothing became unreachable — a click on the heading no longer navigates away from the page you are on just because you wanted to see what else is in the group. -
One section is open at a time, and it follows the route. The tree is an accordion: opening a section closes the one that was open, and a closed section renders none of its links. Which one is open comes from the current path (
sidebar-active-section.ts), so clicking an app dock button loads that destination's section into the sidebar and nothing else. The state is per-mount, and opening another section by hand still works.It used to render every section's links up front. That put around fifty
next/links in the sidebar on/videos, and the router prefetched an RSC payload for each one the moment the page mounted — a burst of requests racing the video feed and its thumbnails on the page that already felt slowest. The tree links now also passprefetch={false}, so the ones that are rendered are fetched on click rather than on sight.Resolution is longest-match over the links the tree actually renders:
/research/cards/libraryopens Research, which lists it, and/practice/versus-aiopens Practice ("Debate Versus AI"). A dock destination in no tool section —/research/cards,/debate,/reason-editor— matches nothing and opens nothing. The dock's Research button (/research/docs) opens Research, by prefix on the Research Workspace link./research/docsand/reason-editorused to sit in Research as "Debate Docs" and "Reason Editor"; they are the dock's own Research button and a top-level editor, so the rows only restated what the icons above them already said. Both routes keep their sidebars —/research/docsthrough the dock link list,/reason-editorthroughEXTRA_SIDEBAR_HREFS, which is what gives the editor its file-navigation panels.
SidebarToolSection.href outlives the heading link: sidebar-routes.ts
still folds it into the set of paths that get the tool sidebar.
Indentation follows nesting, not level — each expanded node pads its own
child list — so a leaf under College Policy sits a step deeper than one
hanging straight off Coaching, wherever in the tree it is.
Every tool has an icon
Each tool in Coaching, Research and Practice shows a small icon of its own next to its name — a magnifying glass on Research Workspace, a gavel on Judge Profiles, swords on Debate Versus AI — so a long section can be scanned by shape rather than read line by line.
They are all drawn in the same muted grey as the section headings above them, at the same 16px, on purpose: the icons are there to tell the rows apart, not to compete with them or with the page you are reading beside the column. The Glossary and Rankings links at the end of Practice used to be the exception — full-color artwork sitting among the grey glyphs — and now match the rest.
Tests
packages/debate-videos/test/video-sidebar-render.test.tsx— the heading structure above: College Policy under a Videosh1, noh1inside an anchor, every section a toggle button, and only the section holding the current route open (with the closed ones rendering none of their links).packages/debate-videos/test/sidebar-active-section.test.ts— the route-to-section mapping itself, including the longest-match and dock-wins-a-tie rules above.apps/debate-ai.com/lib/ui/__tests__/dock.test.tsx— thefluidvariant's classes, host-set icon sizing, and that magnification never shrinks an icon below its resting size.packages/debate-videos/test/sidebar-routes.test.ts— that/toolsis no longer a dock destination but is still a sidebar one, and the prefix matching (including/cardsynot matching/cards).packages/debate-videos/test/sidebar-video-links.test.tsx— that the tree renders everySIDEBAR_VIDEO_LINKSentry and the mobile quick-link tiles render all but the glossary/statistics pair, which has no tile and is reached from the Practice section.packages/debate-videos/test/lectures-sidebar-docs-slot.test.tsx— that/videos'md:hiddenblock carries the tool sections, collapsed.packages/debate-videos/test/lectures-sidebar-shell.test.tsx— that the rankings and glossary branches render the dock, the nav tree and the footer rather than a bare page, on a phone as well as a desktop.apps/debate-ai.com/lib/nav/__tests__/dock-menu-sections.test.ts— that every sidebar destination (videos, tool sections, footer links) is reachable from the Settings menu, which is the whole nav belowmd, that there is no Apps section, and that/practice/featuresand/docsare reachable.packages/debate-videos/test/unified-sidebar.test.tsx— that the tree mounted off the video pages carries the video sections and every tool section, that the column has a resize handle, a hide button and the footer it is handed, renders shown on the server, and that the hide/show choice is stored and broadcast.packages/debate-webview/test/lib/layout/frame-navigation.test.ts— which clicks are left to the browser, and which links out of/docsbecome a full page load.packages/debate-webview/test/lib/nav/dock-hidden.test.ts— that the dock starts shown, the Hide Dock choice is stored, broadcast and cleared, and still applies to the page when storage is blocked.packages/debate-webview/test/lib/nav/dock-idle-prefetch.test.ts— which destinations are warmed in idle time, and that data saver turns it off.
All Videos is the home page
All Videos (/videos) is the first row under Videos and lists every
round and lecture in the library. It is where / lands. The lecture library
on its own is Lectures (/lectures), a single row at the end of the same
section. The lecture categories are not in the sidebar: the lectures page
shows them as a bar of cards at its top (see
Lecture categories).
One collection highlighted at a time
The sidebar's collections (All Videos, Lectures, College Policy, HS Policy, HS Policy 1v1 (LD),
Public Forum, Greatest of All-Time, My Favorites, Watch History) are one list, and
only the open one is highlighted. Lectures lights up on every lecture page,
category pages included (browsingLectures), and never alongside a round
collection. packages/debate-videos/test/sidebar-single-highlight.test.tsx
pins it.
Team Rankings stays highlighted on a team or school page. /teams/<team>
and /schools/<school> are opened from the Team Rankings row (/coaching/rankings)
rather than nested under it, so a plain path match never lit that row while
reading a profile — ToolNavTree's isToolActive treats both prefixes as
still-on-Team-Rankings, the same relationship EXTRA_SIDEBAR_HREFS already
uses to keep the sidebar itself mounted on those pages (see
Where it renders and sidebar-routes.ts).
The highlighted row scrolls into view
TreeItem scrolls its own row into view (scrollIntoView({ block: "nearest" })) whenever it becomes the highlighted one — on mount, and on navigating to
a route that lights up a different row. "nearest" moves the sidebar's own
scroll only as far as needed to bring the row on screen, and not at all if it
is already visible, so following a link inside the currently-visible part of
a long section (Practice runs to fifteen-plus rows) never jumps the column.
The feature catalog opens inside the app
/practice/features — the catalog of every surface in the app — is reached from the
footer under the sidebar and from the dock's Site Links menu. Following
either used to reload the whole tab into a bare page: the sidebar and the dock
were gone, the persistent player stopped mid-lecture, and the page's only way
back was a "Back" pill of its own.
It is now an ordinary page of the app. It renders in the same sidebar as every surface it catalogues, the footer and the menu reach it through the router rather than by loading a new document, and the "Back" pill is gone — the sidebar is how you leave it.
One link is deliberately still a full page load: /docs is a separately built
help site served as static files, not a route this app knows, so it is
followed the old way. Outside sites still open in a new tab.
Switching pages is a React render, not a frame load
Every dock destination is an ordinary route rendered as React in the one app
document. Nothing loads in an <iframe>: the dock, the sidebar and the
player stay mounted, and a sidebar link on /videos routes the same document
like any other link.
To make a switch a render from cache, the dock fetches each destination's
code ahead of the click. Hovering or focusing an icon prefetches that route,
and once the current page has loaded the dock prefetches the others one at a
time in idle time (lib/nav/dock-idle-prefetch.ts), so touch screens and
quick clicks get the same head start. The idle pass skips the two heavy
routes, /research/docs and /practice/versus-ai (the whole practice stack:
the AI opponent, the round and matchup simulators, coach mode, the drill
generator and the judge); hovering their icons still warms them. Each route is
warmed at most once per page load, even when the sidebar's embedded dock
remounts and restarts the pass. It also does nothing on a data-saver or 2G
connection.
Only the app shell's dock runs the app-wide notifications and contacts polls; the sidebar's embedded copy doesn't, and every other mounted copy of those hooks (the account menu's badge, the contacts page) reuses the request one copy already made in the same 30-second tick.
A bar across the top shows a page switch in progress
Every page transition, from a dock icon, any in-app link, or back and forward, shows a slim bar across the top of the window in the theme's primary colour, like YouTube's. It creeps forward while the next page loads, fills when the new page has painted, and fades out. Switches that land within 150 ms never show it, and with reduced motion turned on it moves without animating.
- Bar:
packages/debate-webview/src/components/ui/RouteProgressBar.tsx - Pacing and state:
packages/debate-webview/src/lib/ui/route-progress.ts - Start and finish signals:
packages/debate-webview/src/lib/ui/use-route-loading.ts
The full-screen loading orb no longer shows for page transitions; it is left
for code that asks for it with beginLoading.
Known gaps
/debate(the flow workspace) and/research/docs(the research agent) still draw their own full-height workspace sidebars instead of the shared one, so they have no resize handle or hide button of their own design and their contents are not yet sections of the shared column.- A hidden dock's tab does not reveal it on a pen or a touch screen that reports hover; it opens on a tap there too, and a tap elsewhere does not fold it again until the next navigation or a second tap on the tab.
- On
/docsthe docs' search box sits in Fumadocs' header, not in the shared column, and with the sidebar hidden the docs have no dock (the docs don't mount the floating one).
