Skip to content

Compact UI v2#529

Merged
ikatson merged 79 commits intomainfrom
compact-ui-v2
Jan 17, 2026
Merged

Compact UI v2#529
ikatson merged 79 commits intomainfrom
compact-ui-v2

Conversation

@ikatson
Copy link
Copy Markdown
Owner

@ikatson ikatson commented Jan 12, 2026

Summary

Major WebUI overhaul adding a compact table view, performance optimizations for large torrent lists, and improved UX.

New Features:

  • Compact/table view - Dense list layout with sortable columns, resizable detail pane, keyboard navigation (↑/↓/Enter)
  • Search & filters - Filter torrents by name, status (all/downloading/seeding/paused/error)
  • Virtualized lists - Handles 1000+ torrents smoothly (react-virtuoso)
  • Per-piece visualization - Canvas-based progress display showing individual piece status
  • Mock mode - npm run dev:mock for UI testing without backend (1000 simulated torrents)
  • Test server - make testserver runs a local rqbit instance that simulates torrent traffic for development
  • Bulk API - Fetch all torrent stats in one request instead of N requests

Improvements:

  • Mobile: better card layout, fixed footer visibility, Android nav bar color
  • Dark mode: consistent colors, proper border utilities
  • Performance: memoization, reduced re-renders, lazy-load torrent details
  • Shared config UI between desktop and webui
  • Added prettier for consistent code formatting
localhost_3031_(iPhone 14 Pro Max)

ikatson and others added 25 commits January 11, 2026 14:59
Adds a table-based compact view for torrents with:
- Table layout with columns: Name, Progress, Down, Up, ETA, Peers
- Action bar with bulk Resume/Pause/Delete buttons
- Bottom detail pane with Overview/Files/Peers tabs
- Single and multi-select support via checkboxes
- View mode toggle (persisted to localStorage)
- Defaults to compact mode on large screens (>=1024px)

New files:
- stores/uiStore.ts - view mode and selection state
- hooks/useIsLargeScreen.ts - responsive breakpoint hook
- components/compact/* - table, action bar, detail pane, tabs
- components/ViewModeToggle.tsx - mode switch button

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Documents the webui codebase structure, patterns, and common tasks
to help future Claude sessions work more efficiently with less
context-gathering.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add peer stats API types (PeerStatsSnapshot, PeerStats, PeerCounters)
- Add getPeerStats() API method for /torrents/{id}/peer_stats endpoint
- Replace large stat cards with compact inline badges
- Implement sortable table with IP, connection type, speeds, and totals
- Compute download/upload speeds in JS from byte deltas between polls

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add clickable column headers (ID, Name, Progress, Down, Up, ETA, Peers)
- Default sort by ID descending (newest first)
- Persist sort preference to localStorage
- Extract reusable SortIcon component shared with PeersTab
- Add ID column visible to the left of Name

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Backend: Add total_pieces field to TorrentDetailsResponse
- Backend: Modify /torrents/{id}/haves to return binary bitmap when
  Accept: application/octet-stream header is present
- Frontend: Add PiecesCanvas component that renders piece bitmap as
  a compact horizontal bar with green (have) and gray (missing) colors
- Frontend: Integrate into OverviewTab in CompactUI

The visualization adapts to piece count: few pieces get rectangles,
many pieces (thousands) aggregate into per-pixel columns with partial
completion shown in light green.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add keyboard navigation: up/down arrows move selection, shift-click
  selects a range of torrents
- Make details pane divider draggable with height persisted to localStorage
- Improve pause/resume: refresh after each operation for immediate feedback,
  skip already paused/live torrents
- Average peer speeds over 5 seconds instead of just last poll delta

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Use /torrents?with_stats=true to fetch all torrent data in a single call
instead of polling individual details/stats endpoints for each torrent.
This reduces HTTP calls from 2N to 1 per polling interval.

- Add TorrentListItem type for bulk response with embedded stats
- Update listTorrents API to support withStats parameter
- Refactor components to use store data instead of individual fetching
- Implement adaptive polling (1s for active torrents, 5s otherwise)
- Fetch file details lazily only when extended view is opened
- Remove unused torrentDataCache from uiStore
- Fix React warning about setState during render

Co-Authored-By: Claude Opus 4.5 <[email protected]>
ikatson and others added 3 commits January 12, 2026 15:00
- Add GET /torrents/limits endpoint to read current rate limits
- Add getLimits/setLimits API methods to webui http-api
- Create TabbedConfigModal component for reusable tabbed config dialogs
- Create RateLimitsTab component shared between desktop and webui
- Add configure button to webui with Rate Limits and Other tabs
- Refactor desktop configure.tsx to use shared TabbedConfigModal

Desktop shows full 6-tab config (requires server restart), while webui
shows rate limits (live API) plus an "Other" tab explaining CLI config.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
ikatson and others added 28 commits January 15, 2026 20:36
- OverviewTab now takes TorrentListItem directly from the store (no API call needed)
- DetailPane caches TorrentDetails per torrent ID
- Details are only fetched when Files tab is clicked (not on torrent selection)
- This avoids unnecessary API calls for the Overview and Peers tabs

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Added detailsCache, getDetails, setDetails to torrentStore (pure cache)
- Both CardLayout and CompactLayout now share the same cache
- Components fetch with loopUntilSuccess and store results via setDetails
- TorrentCardContent now takes TorrentListItem directly

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Simplify CSS class naming: text-text is now default (via body style),
  text-text-secondary → text-secondary, text-text-tertiary → text-tertiary,
  border-border → border-divider
- Add explicit --color-divider CSS vars for proper dark mode support
- Fix PiecesCanvas missing pieces color (use surface-sunken instead of border)
- Deduplicate TorrentTable sorting logic with getSortValue() helper
- Remove duplicate column width classes from TorrentTableRow (th controls width)
- Fix typo: ext-text-secondary → text-secondary

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Fixes desktop styling after CSS variable renames by importing the shared
globals.css from webui, which includes all color definitions and base styles.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Read CSS custom properties from document.body instead of
document.documentElement since the .dark class is applied to body.
Also use --color-divider instead of --color-surface-sunken for missing
pieces as it provides better visibility in dark mode.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add mock API with 1000 generated torrents (npm run dev:mock on port 3032)
- ~30 active torrents, rest paused, with simulated progress
- Stable peer IPs with incrementing counters for speed calculations
- Fix table columns wrapping by adding whitespace-nowrap

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Compact layout: search in ActionBar (always visible)
- Card layout: search shown when > 10 torrents
- Uses CSS visibility instead of filtering for performance
- Debounced input (150ms) with clear button
- Keyboard nav and select-all respect search filter

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add status filter to both views (All/Downloading/Seeding/Paused/Error)
- Add sort dropdown to card view (column + direction)
- Extract shared filter/sort utilities to helper/torrentFilters.ts
- Add performance guidelines to CLAUDE.md
- Uses visibility-based filtering for performance

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ON-COMPLEX]

Performance improvement for 1000+ torrents:
- LCP: 1,175ms -> 141ms (8x faster)
- DOM elements: 44,228 -> ~500 (99% reduction)

Both card and table views now use react-window virtualization.
See architecture/virtualization.md for implementation details and hacks.

Key changes:
- CardLayout: FixedSizeList with AutoSizer, hidden scrollbar
- TorrentTable: itemData pattern to prevent Row flickering
- TorrentTableRow: Nested table structure for column alignment
- Added hide-scrollbar CSS utility

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Replace inline extended view with a modal that uses existing DetailPane
and ActionBar components. This fixes two issues with virtualization:
- Dynamic height problem (modal is overlay, card stays fixed height)
- State reset on refresh (modal state stored in uiStore)

The modal is rendered once in CardLayout instead of per-card to avoid
remounting on list refresh.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Define --border-color-* CSS variables directly in :root and .dark
with actual color values, rather than using var(--color-*) in @theme
which gets resolved at compile time. This ensures border-primary,
border-divider etc. utilities work correctly in both light and dark modes.

Also removes the global border-color rule that was causing CSS cascade
layer conflicts with Tailwind utilities.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Unify tab styling between DetailPane and TabbedConfigModal by creating
a reusable Tabs.tsx component with TabButton and TabList exports.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Remove the SEARCH_THRESHOLD requirement that only showed the search
input, status filter, and sort dropdown when there were >10 torrents.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…tems

react-virtuoso measures item heights dynamically, which fixes the poor
mobile experience where card heights vary significantly. No more fixed
CARD_HEIGHT or ROW_HEIGHT constants needed.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Move padding from the scroll container to individual card items so the
scrollbar appears at the right edge of the screen rather than next to
the torrent cards. Also removes the hide-scrollbar utility class.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…to mock

- Make modals responsive: full width with margins on mobile, max-width on larger screens
- Truncate long titles in modal header so close button is always accessible
- Increase close button tap target size for mobile usability
- Add long torrent names to mock API for testing UI with realistic scene-style names

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Settings like view mode, sort order, and panel height are no longer
persisted across sessions. They reset to sensible defaults on reload.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Set up npm scripts to format TypeScript/TSX files:
- npm run format: format all files
- npm run format:check: check without writing

Updated CLAUDE.md files with instructions to always run prettier
after modifying webui/desktop code.
Capture selected torrents when opening the modal instead of deriving
from live selectedTorrentIds state. This prevents the modal from
receiving an empty torrents array when clearSelection() is called
during deletion, which would cause it to return null before close()
could be called.
- Remove flex-wrap to prevent wrapping to 2 lines on narrow screens
- Change search input min-width to 0 so it can shrink
- Reduce gaps and padding on mobile with responsive sm: prefixes
- Shorten search placeholder to "Search..." for mobile
Add background-color to body element so Android Chrome picks up the
correct surface color for the system navigation bar.
Use dvh (dynamic viewport height) instead of vh to account for mobile
browser chrome. On Android Chrome, 100vh includes the address bar space,
pushing the footer off-screen until the user scrolls.
Reduce padding, gaps, text sizes, and icon sizes on small screens
to fit more cards in the viewport. Changes include:
- Smaller card padding and gaps on mobile
- Smaller status icon and torrent name text
- Line clamping for long names (max 2 lines on mobile)
- Stats row uses flex-wrap instead of grid for better space efficiency
- Smaller stats text and icons on mobile
- Tighter spacing between action buttons
Use h-full instead of hardcoded h-[calc(100dvh-95px)] to properly fill
the parent container via flexbox constraints.
@ikatson ikatson merged commit 1396368 into main Jan 17, 2026
4 checks passed
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