Commit Graph

46 Commits

Author SHA1 Message Date
Brian Krabach be79b45787 feat: edge-bar state indicator, stacked sidebar header, × crossfade on hover 2026-04-01 08:33:56 -07:00
Brian Krabach 062a815f83 feat: custom scrollbar styling — thin, dark, brand cyan hover
6px wide, rgba(48,54,61) thumb (--border at 60%), cyan hover/active.
Applied globally via * selector — covers sidebar, grid, settings, and
any future scrollable area. Firefox (scrollbar-width/color) + WebKit
(::-webkit-scrollbar) cross-browser.
2026-04-01 07:20:07 -07:00
Brian Krabach a862327e27 fix: settings item ordering, device name width, activity indicator mode, dot position 2026-04-01 06:52:01 -07:00
Brian Krabach e71ac223a9 feat: reorganize settings tabs, right-align device labels, activity dots, sidebar glow, display config toggles 2026-04-01 06:25:18 -07:00
Brian Krabach 5a67a3eb3e merge: integrate latest upstream (fit view refactor, ttyd kill-by-port, mobile viewport fix) 2026-03-31 09:10:59 -07:00
Brian Krabach b450cf7176 refactor: fit view — pure CSS layout, no DOM measurement in applyFitLayout
Root cause of repeated fit view failures: applyFitLayout() measured
clientHeight/clientWidth/getComputedStyle and set inline tile heights. This
failed in multiple ways:
  - clientHeight = 0 when container is display:none (returning from session)
  - inline tile.style.height destroyed every 2s by innerHTML rebuild in pollSessions
  - getComputedStyle forces style recalc with potentially stale values
  - rAF wrappers couldn't reliably fix timing issues

Fix: pure arithmetic applyFitLayout — no DOM measurement, no inline heights.
The grid already has a definite height from CSS (flex:1 inside height:100dvh).
grid-template-rows: repeat(rows, 1fr) divides that space without JS measurement.

CSS changes:
  - .session-grid--fit { align-content: stretch }
  - .session-grid--fit .session-tile { height: auto } — CSS grid cell controls sizing

JS changes:
  - applyFitLayout() removes all clientHeight/clientWidth/getComputedStyle/style.height
  - applyFitLayout() is now pure arithmetic: count tiles, compute cols×rows, set 1fr templates
  - Removed rAF wrappers from all call sites (renderGrid, closeSession, resize handler,
    applyDisplaySettings) — safe to call synchronously since no layout measurement needed
  - applyDisplaySettings() no longer clears tile heights (never set them anymore)

Tests updated:
  - Added test_fit_view_session_tile_has_height_auto (CSS)
  - Added 'applyFitLayout does NOT measure DOM dimensions' (JS)
  - Updated 'applyFitLayout clears stale...' → 'sets gridTemplateColumns/Rows via arithmetic'
  - Updated rAF-wrapper test to match new direct-call behavior
2026-03-31 07:47:54 -07:00
Brian Krabach 0ed03c4e9d fix: fit view — clear stale layout on recalc, revert to position:absolute bottom:0
Bug 1: applyFitLayout now clears grid-template-rows and tile heights
before recalculating. Prevents stale layout from empty-grid calls on
page reload from interfering with subsequent calculations.

Bug 2: Removed flex justify-content:flex-end approach — didn't work
because pre filled 100% of parent (flex-end had no effect). Reverted
to base CSS position:absolute + bottom:0 which anchors content to the
bottom. With 80 lines in fit mode, content always overflows the tile
and excess is clipped at the top by tile-body overflow:hidden.
2026-03-31 06:55:05 -07:00
Brian Krabach 679416a5bf feat: reorganize settings dialog — add Multi-Device tab, enable/disable toggle, device name as page title
- Add multi_device_enabled: false to DEFAULT_SETTINGS in settings.py
- Add fifth 'Multi-Device' tab (data-tab='devices') to settings dialog sidebar
- Move #setting-device-name, #setting-remote-instances, #add-remote-instance-btn,
  #setting-view-mode, and #setting-view-scope out of Display/Sessions tabs into
  the new dedicated devices panel
- Add #setting-multi-device-enabled checkbox at top of Multi-Device tab;
  when unchecked, all other fields in the tab are greyed out and disabled
- Gate buildSources() remote instances behind multi_device_enabled (smart default:
  treated as enabled if remote_instances is non-empty for backward compat)
- Wire device name → document.title: updates title on openSettings() and on
  every keystroke in the device name input field
- Add _updateMultiDeviceFieldsState() helper to enable/disable fields
- Add #multi-device-fields CSS transition for smooth opacity animation
- Add 19 new tests (settings, HTML, JS, CSS) covering all new behavior
2026-03-31 06:16:44 -07:00
Brian Krabach b282e42ae8 fix: mobile viewport cutoff (100dvh) + fit view content anchoring (flex-end)
Mobile: 100vh includes browser chrome on mobile — bottom row clipped.
Fix: 100dvh with 100vh fallback (progressive enhancement) in .view
and .session-tile--expanded.

Fit view: scrollTop hack reset every 2s by poll cycle and had no effect
when content didn't overflow. Fix: replace position:absolute+scrollTop
with flex+justify-content:flex-end on tile-body. Content naturally
anchors to bottom without JS. No scrollbar hiding needed.

Tests: remove test_fit_view_pre_has_top_zero (old behavior), add
test_view_uses_dvh_fallback, test_fit_view_tile_body_uses_flex_end,
test_fit_view_pre_is_static. Update JS test for new flex approach.
2026-03-31 05:54:41 -07:00
Brian Krabach 881b6cc9f3 merge: integrate upstream changes with multi-device federation 2026-03-31 05:48:49 -07:00
Brian Krabach 0bb66b1801 fix: fit view — reapply layout on session close, show more lines, bottom-anchor content
Bug 1: closeSession() didn't call applyFitLayout when returning to
dashboard — tiles kept old heights from before the overview was hidden.
Fix: add rAF call after overview.style.display = '' so the layout is
remeasured once the overview becomes visible again.

Bug 2: Only 20 lines shown in tiles — not enough for tall fit-mode
tiles, and content sat at the top. Fix:
  - buildTileHTML shows 80 lines in fit mode (vs 20 in auto)
  - renderGrid rAF sets pre.scrollTop = pre.scrollHeight after
    applyFitLayout so content anchors at the bottom (like a real
    terminal). Hidden scrollbar via scrollbar-width:none + ::-webkit.

Tests: 3 new source-scan tests added and verified RED before GREEN.
2026-03-31 03:41:28 -07:00
Brian Krabach ebec7c3625 fix: fit view — defer layout measurement to rAF, stretch pre to fill tile
Bug 1: applyFitLayout measured parentElement.clientHeight before the
browser reflowed the DOM — returned 0 on first render. Fix: wrap in
requestAnimationFrame so measurement happens after layout pass.
Applies to all three call sites: renderGrid(), applyDisplaySettings(),
and the window resize handler.

Bug 2: .tile-body pre had position:absolute bottom:0 but no top:0.
In fit mode (taller tiles), content sat at the bottom with a gap above.
Fix: .session-grid--fit .tile-body pre { top: 0; overflow-y: auto }
2026-03-31 03:19:37 -07:00
Brian Krabach fe5337840d refactor: remove Compact view mode — keep Auto + Fit only 2026-03-31 03:05:18 -07:00
Brian Krabach 38a032b2d5 feat: add CSS for source-tile offline and auth-required states
Add Phase 3 source tile component CSS rules to style.css, inserted
before the @media (prefers-reduced-motion) block as specified.

Rules added:
- .source-tile: base flex column layout (centered, 12px gap, 24px padding)
- .source-tile--offline: opacity 0.45, dashed border
- .source-tile--offline .source-tile__name: var(--text-dim) color
- .source-tile--offline .source-tile__badge: var(--err) bg, bold, uppercase
- .source-tile--offline .source-tile__last-seen: 11px font, dim color
- .source-tile--auth: var(--warn) border-color, dashed border
- .source-tile__name: 15px/600 font, var(--text) color
- .source-tile__login-btn: accent bg, 8px 20px padding, opacity transition
- .source-tile__login-btn:hover: opacity 0.85
- .source-tile__login-btn:focus-visible: 2px solid accent outline
- .source-tile__hint: 11px font, var(--text-muted) color

grep -c 'source-tile' style.css returns 11 lines (all required selectors present).

Tests: 15 new tests added to test_frontend_css.py covering all selectors,
properties, positioning before reduced-motion block, and brace balance.
All 150 CSS tests pass.
2026-03-31 02:23:24 -07:00
Brian Krabach 88102751c8 style: fix code quality issues from review for task-15 remote instances UI
- Deduplicate CSS: merge shared properties of .settings-remote-url and
  .settings-remote-name into a combined selector, keeping only flex
  values in individual rules. Reduces 18 lines to 10.

- Fix remove button hover color: .settings-remote-remove:hover now uses
  #ef4444 (red) and rgba(239, 68, 68, 0.1) background, consistent with
  .tile-delete:hover and .sidebar-delete:hover for destructive actions.

- Add aria-label attributes to urlInput and nameInput in
  _buildRemoteInstanceRow for screen-reader accessibility:
  'Remote instance URL' and 'Remote instance display name'.

- Add 2 tests to verify aria-label attributes are set on both inputs
  (test_build_remote_instance_row_url_input_aria_label,
  test_build_remote_instance_row_name_input_aria_label).

All 615 tests pass.

Co-authored-by: Amplifier <amplifier@sourcegraph.com>
2026-03-31 01:09:27 -07:00
Brian Krabach c8a963f28e feat: add Remote Instances management UI to Settings (task-15) 2026-03-31 00:55:26 -07:00
Brian Krabach d897c05c61 feat: add device-badge, group-header, filter-bar, and sidebar-device-header CSS classes 2026-03-30 23:29:30 -07:00
Brian Krabach 8c0675157a feat: three dashboard view modes — Auto, Fit, Compact
Auto: current behavior (auto-fill grid, fixed tile height, scrollable).
Fit: calculates cols × rows to fill viewport exactly, zero scroll.
Compact: 80px tiles, high density, auto-fill with 200px min-width.

Toggle button (▦) in overview header cycles modes. Fit recalculates
on window resize. View mode persisted in localStorage alongside other
display settings (viewMode: 'auto' default in DISPLAY_DEFAULTS).

- CSS: .session-grid--fit and .session-grid--compact modifiers
- JS: VIEW_MODES, cycleViewMode(), applyFitLayout() functions
- HTML: #view-mode-btn between + and ⚙ in overview header
- Tests: 8 new JS tests, 3 CSS tests, 1 HTML test all passing
2026-03-30 22:22:59 -07:00
Brian Krabach e6add00f0d feat: loading placeholder tile while new session is being created
- Inject .tile--loading skeleton tile into #session-grid immediately after
  POST /api/sessions succeeds, giving instant visual feedback while the
  backend creates the tmux session (which may take several seconds)
- Remove the placeholder when the poll finds the session or on timeout
- Add shimmer animation via @keyframes shimmer to style.css
- Tile placed after original .tile-body pre rule to avoid selector-substring
  match in _extract_rule_block CSS tests
2026-03-30 07:39:18 -07:00
Brian Krabach b38c87185a fix: settings dialog always visible — display:flex overrode native <dialog> hidden state
<dialog> is hidden by default via UA stylesheet (display:none). Our CSS
set display:flex unconditionally, overriding the UA rule and making the
dialog always visible. Fix: only apply display:flex when the dialog has
the [open] attribute (added by showModal()).
2026-03-30 07:14:24 -07:00
Brian Krabach 17616166bc feat: kill session — DELETE endpoint, hover × button with confirmation 2026-03-30 03:35:39 -07:00
Brian Krabach 424a4f5377 fix: settings modal dismiss — add close button, fix backdrop click 2026-03-30 03:27:47 -07:00
Brian Krabach 5b971a0ecc refactor: apply code quality suggestions from task-6-mobile-fab review
Three suggestions from the code review applied:

1. Extract _createSessionInput() factory to eliminate five-line
   duplication between showNewSessionInput and showFabSessionInput.
   Both functions now call the shared factory. Updated 4 existing
   tests to check _createSessionInput's body; added 7 new tests
   covering factory behavior and factory usage in each consumer.

2. Fix .new-session-fab:focus-visible outline color from var(--accent)
   to var(--bg) — the FAB background is already var(--accent), so
   using the same color for the outline created zero contrast.
   Added 1 new test to guard this going forward.

3. Add early-return guard in showFabSessionInput against duplicate
   overlays: if (document.querySelector('.fab-input-overlay')) return;
   Prevents a second overlay being created if called programmatically
   while one is already open. Added 1 new test.

532 tests pass (9 new tests added, 523 unchanged).
2026-03-30 02:00:22 -07:00
Brian Krabach 9978544dc1 fix: use fixed-position overlay for FAB session input (mobile visibility) 2026-03-30 01:45:51 -07:00
Brian Krabach dbb8b2bd73 feat: add mobile FAB for new session creation 2026-03-30 01:34:31 -07:00
Brian Krabach d76042560a feat: add sidebar + New sticky footer with inline input
- Add div.sidebar-footer with button#sidebar-new-session-btn.sidebar-new-btn
  ('+ New') to #session-sidebar in index.html, positioned after #sidebar-list
- Add CSS for .sidebar-footer (padding: 8px, border-top, flex-shrink: 0)
- Add CSS for .sidebar-new-btn (width: 100%, dashed border, dim text, hover
  effects with muted text and border color)
- Bind #sidebar-new-session-btn click to showNewSessionInput() in
  bindStaticEventListeners()
- Add 15 tests covering HTML structure, CSS rules, and JS binding

🤖 Co-authored-by: Amplifier <amplifier@example.com>
2026-03-30 01:23:37 -07:00
Brian Krabach 84fb7826d7 feat: add header + button with inline name input for session creation
- Add showNewSessionInput(btn): creates inline text input with class
  'new-session-input', placeholder 'Session name…', autocomplete off,
  spellcheck false; hides button, inserts input before it, focuses it.
  Handles Enter (create), Escape (cancel), and blur (150ms delayed cleanup).
- Add createNewSession(name): POSTs to /api/sessions, shows toast with
  created session name, calls pollSessions(), and if ss.auto_open_created
  !== false calls openSession(data.name) after 500ms delay.
- Bind #new-session-btn click to showNewSessionInput in bindStaticEventListeners.
- Export showNewSessionInput and createNewSession from module.exports.
- Add .new-session-input CSS (bg, border 1px solid accent, border-radius 4px,
  font-size 13px, font-ui, padding 4px 10px, width 180px, outline none) and
  ::placeholder { color: var(--text-dim) } in style.css.
- Add 28 new tests covering all spec requirements (19 JS structural, 9 CSS).
2026-03-30 01:08:19 -07:00
Brian Krabach 5f81b87348 feat: add New Session settings tab with template textarea 2026-03-30 01:03:54 -07:00
Brian Krabach 2f909eae89 feat: add Notifications settings tab 2026-03-30 00:18:05 -07:00
Brian Krabach c955aadad1 feat: add Sessions settings tab with server-side persistence 2026-03-29 23:57:22 -07:00
Brian Krabach b20759c6cb feat: add settings dialog HTML structure and CSS 2026-03-29 23:04:11 -07:00
Brian Krabach 80dc082df0 refactor: remove command palette HTML and CSS 2026-03-29 22:12:07 -07:00
Brian Krabach e4efadddf4 fix: remove gradient fades, fix sidebar active indicator full-height
Remove tile-body::before and sidebar-item-body::before gradient overlays
that obscured ANSI colors at the bottom of previews. Replace box-shadow
inset with border-left on sidebar-item--active so the cyan accent stripe
spans the full item height (was only showing on the header).
2026-03-29 19:41:49 -07:00
Brian Krabach 583de6ec2d feat: match preview styling to xterm.js — palette, background, font, line-height
ANSI_COLORS: replaced with xterm.js default GTK/Tango palette.
Background: #000000 on tile-body, sidebar-item-body, preview-popover.
Font: 'SF Mono', 'Fira Code', Consolas, monospace (same as terminal).
Line-height: 1.0 across all preview surfaces (xterm.js default).
Gradient overlays: fade to #000 instead of theme bg.
Result: previews are visually indistinguishable from the terminal.
2026-03-29 19:19:05 -07:00
Brian Krabach 85298abdcc fix: match tile + sidebar preview text color to xterm.js foreground (#c9d1d9) 2026-03-29 18:59:44 -07:00
Brian Krabach 71fdd544b2 fix: match preview text color to xterm.js foreground (#c9d1d9) 2026-03-29 18:48:51 -07:00
Brian Krabach bf76affef2 refactor: remove dimmer overlay, add cyan border to preview popover
ANSI colors make the overlay content readable without dimming. Removed
preview-dimmer, liftHoveredTile(), tile--previewing/item--previewing —
all dead code now. Added var(--accent) 2px border to .preview-popover
for visual separation.
2026-03-29 18:44:55 -07:00
Brian Krabach dbade5a3d9 fix: preview overlay text color — use primary text, not disabled dim 2026-03-29 18:09:09 -07:00
Brian Krabach af95b90b02 feat: hover preview as full-window overlay with text wrap + click-to-navigate
Replace side-positioned popover with a viewport-covering overlay (5vh/5vw
margins). pre-wrap + word-break wraps terminal text to fit any viewport.
pointer-events:none on overlay lets mouseleave fire on the original tile.
Click anywhere while preview is active navigates to that session.
Removes repositionPreview() — no JS positioning needed.
2026-03-29 18:00:36 -07:00
Brian Krabach 905cbf35e5 feat: hover preview dim effect — overlay dims page, hovered tile + popover stand out
1.5s hover delay (up from 350ms) for deliberate preview. When popover
appears, a semi-transparent dimmer covers the page (z-index 499). The
hovered tile/sidebar-item gets z-index 500 (same as popover) so it
appears above the dimmer alongside the preview. Both cleaned up on
mouseleave via hidePreview().
2026-03-29 16:56:09 -07:00
Brian Krabach 40ec20f4ba fix: hover preview — bottom-anchor, flexible width, sidebar support, longer delay
- Auto-scroll to bottom (prompt area is the valuable part)
- Dynamic width based on available space (fixes 3-column layout)
- 350ms delay (prevents brush-over flicker)
- Extend hover preview to sidebar items (popover to the right)
- Remove fixed min/max-width from CSS, calculate in JS
2026-03-29 16:48:52 -07:00
Brian Krabach 6144f39955 feat: hover preview popover on dashboard tiles
Desktop only (guarded by ontouchstart check). On mouseenter of a
session tile, shows a floating popover with the full tmux snapshot
(all lines, not the cropped 20-line preview). Positioned right of
tile if space, else left. 100ms delay prevents flicker on brush-over.
Data from _currentSessions (already in memory, zero API calls).

Also calls hidePreview() in openSession() so the popover is cleaned
up when switching to fullscreen view.
2026-03-29 14:42:30 -07:00
Brian Krabach c1701bf26b fix: sidebar-item flex-shrink:0 — cards were compressing to fit, eliminating scroll
Without flex-shrink:0, flex compresses all sidebar cards proportionally
when the container is shorter than their total height. Cards visually
fit the screen with no overflow — scrollTop stays 0, touch handler
has nothing to scroll. One property restores the intended overflow.
2026-03-28 16:04:02 -07:00
Brian Krabach 3d91bd5c68 feat: touch scroll for terminal and sidebar on mobile
Terminal: add touchstart/touchmove/touchend IIFE on #terminal-container.
Vertical swipe delta translated to _term.scrollLines(n). touchmove is
non-passive (required for e.preventDefault to block page scroll while
swiping inside terminal). mouse wheel scroll unaffected (separate wheel
event path in xterm.js).

Sidebar: add touch-action:pan-y + overscroll-behavior:contain to
.sidebar-list. Tells browser explicitly that vertical panning is allowed
and prevents scroll chaining when reaching list ends.
2026-03-28 14:37:57 -07:00
Brian Krabach 4ffdc1bd99 fix: three mobile/WSL bugs reported by tester
- Mobile idle tiles: display:none → height:36px so session content is
  visible (all sessions are 'idle' with zero bells — was blank on iPhone)
- .tile-pre dead CSS: merge font/color rules into .tile-body pre (the
  actual HTML selector) so snapshot text renders with monospace + muted
  color; update mobile active-tier selector to match
- systemd PATH: strip /mnt/ entries from PATH before writing service
  unit file — WSL paths with spaces caused systemd to reject the line
2026-03-28 09:03:33 -07:00
Brian Krabach 74b63033d7 refactor: rename coordinator → muxplex package, move frontend inside as package data 2026-03-28 02:02:50 -07:00