Skip to content

web-ui: browse + new-chat palettes and sidebar chrome polish - #558

Open
evebouf wants to merge 6 commits into
mainfrom
evebouf/electron-native-app-wrapper
Open

web-ui: browse + new-chat palettes and sidebar chrome polish#558
evebouf wants to merge 6 commits into
mainfrom
evebouf/electron-native-app-wrapper

Conversation

@evebouf

@evebouf evebouf commented Aug 17, 2026

Copy link
Copy Markdown

Adds the Browse and New chat command palettes with shared grid keyboard navigation, and cleans up the sidebar chrome around them.

Palettes

browse.ts, new-chat.ts, grid-nav.ts — the two palettes plus the arrow-key grid navigation they share.

Their titles now use the same type as the tile labels beneath them (14px, normal weight, --foreground) instead of an 11px semibold muted eyebrow, so the header reads as part of the same system.

Sidebar

  • Selected chats use the same tint as hover (foreground 4%). The old 9% mix read as too heavy against the light sidebar. Trade-off worth a look: a selected row is now visually identical to a hovered one, so selection only reads when the pointer is elsewhere.
  • Chevron and glyph share one slot. The folder/channel/group glyph shows at rest; the chevron cross-fades in on hover or keyboard focus; a collapsed group shows the chevron persistently so its state is never ambiguous. The whole header row is still the toggle, so the chevron was only ever an indicator. With the indent rail tightened alongside it, chat titles start 10px further left (29px -> 19px) and project rows with a glyph gain 23px (48px -> 25px).
  • One tooltip, one hover style, everywhere. Project options, new chat, archive, conversation options, and the sidebar collapse toggle all use the app's floating tooltip rather than the browser's native title bubble, and share a single hover treatment (--secondary fill, --foreground icon) via the base .session-menu-btn rule.
  • Accessibility fallout, fixed: the conversation kebab had only a title, so removing it would have left it unlabelled — it now has aria-label="Options for <chat>". The sidebar toggle's title was hardcoded to "Hide sidebar" in markup and only corrected later by an imperative updater; label derivation now lives in one sidebarToggleLabel() helper, so the tooltip says "Show sidebar" when collapsed.
  • The new-chat tooltip drops the project name ("Start a new chat" rather than "Start a new chat in Personal"). The aria-label keeps it — a screen reader user has no visual row context to tell those buttons apart.

Verification

npm run typecheck and the full web-ui suite (556 tests) pass; eslint plugins/web-ui is clean.

Not yet done, and worth knowing before this lands:

  • No visual demo. The repo asks for one on front-end changes and I haven't rendered these against a live instance — every claim above is read off the CSS, not off a screenshot. Happy to boot /dev-instance and attach before/afters.
  • No fresh-context review pass yet. Requested separately.

One specific thing to eyeball: --secondary is oklch(0.97 0 0), lighter than a hovered chat row (~0.945). In the project header that fill sits on an untinted background and reads as a subtle darker chip, but on a chat row — itself tinted while hovered — the icon button may read as a lighter patch instead of a darker one. If it looks inverted, the fix is a row-scoped color-mix(in srgb, var(--foreground) 9%, transparent), same size and shape, preserving the direction of contrast.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

test added 6 commits August 16, 2026 23:50
Adds the Browse and New chat command palettes with shared grid keyboard
navigation, and cleans up the sidebar chrome around them.

Palette titles ("Browse", "New chat") now use the same type as the tile
labels they sit above — 14px, normal weight, foreground colour — instead
of the 11px semibold muted treatment, so the header reads as part of the
same system rather than an eyebrow label.

Sidebar:

- Selected chats use the same tint as hover (foreground 4%). The 9% mix
  read as too heavy against the light sidebar.
- The project chevron and the folder/channel/group glyph share one 14px
  slot: the glyph shows at rest, the chevron cross-fades in on hover or
  focus, and a collapsed group shows the chevron persistently. With the
  indent rail tightened, chat titles start 10px further left and project
  rows with a glyph gain 23px.
- Every sidebar icon button — project options, new chat, archive,
  conversation options, sidebar collapse — now uses the app's floating
  tooltip instead of the browser's native title bubble, and shares one
  hover treatment (--secondary fill, foreground icon). The conversation
  kebab and the sidebar toggle gained accurate aria-labels in the
  process; the toggle's was previously hardcoded to "Hide sidebar" even
  while collapsed.
- The new-chat tooltip drops the project name ("Start a new chat"); the
  aria-label keeps it, since a screen reader has no row context.
Hovering anything with a `title` produced the OS bubble — a second,
slower, unstyled tooltip sitting next to our own. Every one of the 88
sites across the web UI now either drops the attribute or routes through
the floating tooltip.

The plumbing is a `tip()` lit element directive in tooltip.ts, so a
call site is one binding rather than four event handlers. It wraps
attachTooltip/detachTooltip, which split.ts uses directly for the
dockview tab element it does not render through lit. The four
hand-written handler sets added earlier collapse into it.

Three kinds of site, treated differently:

- A title that merely repeated visible text is gone, not converted. The
  nav rows are the exception: their labels are display:none in the
  collapsed rail, so they keep a tooltip while collapsed and gain an
  aria-label, and toggling the sidebar now re-renders the nav so the
  tooltip tracks the state.
- Icon-only controls get tip() plus an aria-label where the title had
  been carrying the accessible name on its own — attach files, send,
  view pasted text, remove attachment, refresh conversations.
- Truncated or abbreviated text (queued message, row context, commit
  SHA, approval command, skill description) keeps its full text on
  hover through tip().
Brand mark is now an image: the ship painting fills the 22px tile via a
sized background-image. The earlier `content: url()` approach rendered the
JPEG at its natural size and let overflow clip it, so only the top-left
corner (sky) showed. Orgs that set `branding.mark` still get their letter —
`--brand-mark` defaults to `none`, and the pseudo-element covers the image
with the accent when a mark is injected. Wordmark moves to an old-style
serif so it reads as a brand rather than UI chrome.

Aligned the brand row to the nav: mark centre and wordmark left edge now
match the nav icons and labels exactly, and the gap under the header equals
the row-to-row gap.

Chrome: "Sessions" and "Browse" drop their uppercase transform, the Home row
never renders as selected, a divider closes the quick-nav block, the search
icon leaves the Sessions header, and the personal group drops its redundant
person glyph. The project + gets the app's own tooltip instead of the
browser's title bubble. Archived's count adopts the project count's size
through one shared rule so the two can't drift.

Selection is background-only: sidebar chat rows no longer bold their title
and browse tiles no longer draw a border. Palettes get roomier padding.

The top bar drops the "New chat" placeholder and the tooltip that echoed it;
a real title still gets one, since it truncates.
The composer's Effort/Model/Harness popovers shouted their headers in 11px
uppercase with letter-spacing while the Browse modal titles its palette in
plain 14px foreground text. Two treatments for the same thing; keep the
calmer one and drop .menu-title's weight, tracking, caps, and muted colour.
The Skills and context-filter popovers share the class and follow suit.

The sidebar group labels carried the same emphasis for no reason — Personal
and Archived are destinations, not headings — so they lose their 600 weight
and Archived its tracking.

The row actions were three glyph sizes across two rows that sit under the
same hover: the project + at 14, both option menus at 17, archive at 15.
Settle on 15 everywhere; the 26px buttons and hit targets are untouched.
The pulsing blue dot that marked a working chat grew a box-shadow ring and
flashed its opacity on a 1.6s loop — two animated properties fighting for
attention in a 30px row. It becomes a scrolling swell wave: one path, one
transform, same blue. `syncWorkingPulse` had to change with it. It bailed on
`instanceof HTMLElement`, and an SVG is not one; the animation also lives on
an inner <g> rather than the element the ref lands on, so every wave would
have started at its own phase. It now takes any Element and pins
`getAnimations({ subtree: true })`, so the marks stay in step the way the
dots did.

The same wave replaces the chat-loading spinner. Its <style> block moves out
of the SVG into shell.css — inline styles would be re-parsed on every lit
render and duplicated per instance — and the viewBox is cropped to the band
the stroke actually occupies so a row-sized mark isn't mostly empty space.
Narrowing crops the viewBox rather than squeezing the width, which would
shorten the wavelength and read as a different wave.

Chats-page rows were double-padded: `.chat-row { padding: 0 }` is meant to
hand padding to the inner anchor, but `.list-row`'s 12px is declared 650
lines later at equal specificity and won, stacking 44px of vertical padding.
Scoping it to `.list-row.chat-row` lets the intended zero land — 66px rows
become 42px. Hover and selected painted solid `--secondary`, a light slab
that sits wrong in dark mode; both now use the sidebar's selected token, a
4% mix of --foreground that inverts per theme.

Sidebar: the footer avatar is an image rather than initials, the day
separators above the session list are hidden, the indent rail beside a
project's children is gone, and the right-edge inset drops from 29px to a
uniform 22px across the collapse toggle, project +, and Web-only switch.
knip has been failing the Lint job: the tooltip and settings refactors left
behind exports with no consumers. Three are still used inside their own
module and only lose the keyword — showTooltip and detachTooltip back the
tip() directive and attachTooltip, placeMenuPopover backs toggleFormMenu.
SEARCH_HOTKEY_LABEL has no caller at all now that the sidebar's search row
carries no hotkey hint, so it goes; isMac stays for the ⌘K binding and the
palette's footer hints.
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.

1 participant