The unmarked-folder span and size string allocated per row per frame
for nothing, and a filter/map_or_else chain built a string that only
one state ever fills. A matches! fills it directly.
A folder queued from the browse tree is really the queue of every file
under it, so its row reports the loudest status among those files:
working while anything moves, paused, queued, a failure once quiet, and
✓ only when every file is done. A working folder shows the percent the
queued bytes have come in its size cell.
Span::raw takes a &'static str; the to_string allocated for every
unmarked row of every frame. Points the doc comment at the Downloads
pane, which is what actually marks downloads.
Queueing a file or folder from a user's browse tree left no trace in
the window it was queued from. A row whose download is in the transfer
list now carries the same status glyph the Downloads pane uses —
⋯ queued, ↯ active, ⏸ paused, ✓ done — and an active row shows its
percent beside the size. Folders mark through their files: queue a
folder and watch the ✓ spread down it.
Four panes had grown the same dance around ratatui's table: clamp the
selection, take the visible page, hand the table a page-local state,
keep the page start. It is now page_window and render_page in a paging
module beside page_of, and the two popups page by the same count they
draw, tab bar or header included.
Five typed filters had grown the same key handling too. edit_filter now
takes a key for all of them, which also stops a control or alt chord
from typing its letter into a filter, stops Enter, which keeps a filter,
from resetting the view it is over, and lets the results and room-list
filters page like the others. Their titles and the typing shortcuts
come from one place as well.
A browsed share only moved a row at a time, so a folder of a few
hundred files stood between the user and the next folder. J and K now
jump between folder rows, H and L close or open every folder, and /
filters the whole tree by path the way the results and the room list
filter: type to narrow, Enter keeps it, Esc clears it, then hides. A
folder under a filter shows the paths that match and d downloads just
those. The keys popup gains a browse section and the bar lists them.
Moving through a browsed share felt laggy for the same reason the
results did: the popup re-flattened the whole expanded tree on every
frame, cloning every row's name and path, then built a table row for
each, and every keypress flattened it twice more. A heavy sharer's
tree runs to tens of thousands of rows once a few folders are open.
The flattened rows now live on the browse state and are rebuilt only
when a folder opens or closes, and the popup draws the visible page
alone. In the harness a 30,000-row tree went from 11ms to 0.35ms a
frame and a keypress from 2.7ms to under a microsecond.
The downloads pane had the same shape (every transfer became a row
each frame, and a select-all can queue ten thousand), so it takes the
same page window, which now lives beside page_of for every pane to
share.
The cursor row set a foreground, and ratatui only skips a cell's colour
when that field is None — so selecting a row repainted its gold sizes,
blue usernames and status glyphs into one flat cream. Table cursors now
tint the background only, over a warm oxide wash instead of neutral.
Browse files, their sizes and the settings popup rendered in the raw
terminal foreground; they take the palette now, and free slots read
green when a peer has one.
Replace the ANSI Light* palette with the design system's tokens
(tokens/colors.css): oxide accent, phosphor focus, tape/signal/alarm
semantics, paper text on dust chrome. Only foregrounds and the row
highlight are painted so the user's terminal background shows through.
Route every bordered pane through a single pane_block() helper carrying
one column of horizontal padding, and give legends a consistent
[n] Title form with the tag in signal. The command bar now derives its
cursor position from the block's inner rect instead of a hardcoded
border offset.
Also collapses the download-status glyph match that was duplicated
across three render sites into download_status_glyph().
- Retry actually retries: 'r' in the browse popup re-requests a
timed-out user (previously the 'Press b to retry' hint did nothing —
b wasn't handled inside the popup). BrowseTabs::retry_active is
unit-tested.
- Friendlier timeout copy that explains the likely cause: "Couldn't
reach <user>. They may be offline, or their connection can't be
reached (both of you may be behind a router/firewall). Press r to try
again."
- Replace protocol terminology in user-facing labels: 'Peer slots' ->
'Free slots', 'Peer upload speed' -> 'Upload speed'.
Replace the single browse view with BrowseTabs (one tab per user),
mirroring the chat-room tabs. Browsing a new user (from search results
or a chat room's member list) opens or focuses their tab; Tab/Shift-Tab
switch between open users, 'w' closes the active tab, Esc hides the
popup while keeping the tabs. A tab bar shows the open users when more
than one is open, and each tab polls/loads independently. Re-browsing a
timed-out user retries. BrowseTabs is unit-tested.
Wire browsing a user's shared files into the TUI as a modal tree popup:
- press 'b' on a highlighted search result to browse that result's owner, or
'b' elsewhere to type a username (new CommandBarMode::Browse);
- a new models/browse.rs synthesizes the flat SharedDirectory list into a
nested, collapsible tree (intermediate folders reconstructed from the
backslash paths; file download paths kept byte-exact from the original names),
with expand/collapse state and a flatten-to-rows renderer — fully unit-tested;
- the popup polls take_browse_result each tick (with a timeout), navigates with
arrows/hjkl, expands/collapses folders, and queues downloads: Enter on a file,
'd' on a file or an entire folder subtree (recursively) — all funneled into
the existing Downloads pane;
- shortcuts bar advertises browse keys and surfaces our own sharing status.