Commit Graph

191 Commits

Author SHA1 Message Date
Brian Krabach bee74f5c1b fix: add style property to openSettings test mocks for multi-device field state 2026-03-31 06:28:55 -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 881b6cc9f3 merge: integrate upstream changes with multi-device federation 2026-03-31 05:48:49 -07:00
Brian Krabach c330f340ff fix: renderGrid shows status tiles when no sessions; _previewClickHandler forwards sourceUrl
- renderGrid early-return now builds status tiles for auth_required and
  unreachable sources even when visible.length === 0. Hides empty-state
  when status tiles are present, shows it only when truly nothing to display.

- _previewClickHandler now looks up the session in _currentSessions to
  recover its sourceUrl before calling openSession, ensuring remote
  sessions opened via hover-preview connect to the correct instance
  instead of falling back to the local API path.

Tests added (3 new, 214 → 217 total):
- renderGrid shows auth tile and hides empty-state when no sessions but source is auth_required
- renderGrid shows offline tile and hides empty-state when no sessions but source is unreachable
- _previewClickHandler looks up sourceUrl from _currentSessions before calling openSession

Closes code-review Important issues #1 and #2.

Co-authored-by: Amplifier <amplifier@anthropic.com>
2026-03-31 04:29:49 -07:00
Brian Krabach 9befe9997e test: add formatLastSeen verification tests across all time ranges 2026-03-31 04:09:43 -07:00
Brian Krabach ddc52f2b36 feat: grid click handler routes sourceUrl via data-source-url attribute on tiles
- In buildTileHTML, make data-source-url attribute conditional: only add when
  session.sourceUrl is non-empty (local sessions omit the attribute entirely)
- In renderGrid click/keydown handlers, pass sourceUrl with || '' fallback so
  local sessions (where data-source-url is absent) pass empty string to openSession
- Add 2 tests: one verifying data-source-url present for remote sessions,
  one verifying data-source-url absent for local sessions (empty sourceUrl)

Task: task-9-grid-click-routes-source-url
2026-03-31 04:01:56 -07:00
Brian Krabach 28d32d21be feat: closeSession skips DELETE for remote sessions
- Guard DELETE /api/sessions/current with if (!_viewingSourceUrl) check
- Reset _viewingSourceUrl to '' after the conditional DELETE
- For remote sessions, the remote instance doesn't need to know we
  stopped watching - just disconnect WebSocket and update local UI

Tests added:
- closeSession does NOT fire DELETE for remote session (non-empty _viewingSourceUrl)
- closeSession still fires DELETE /api/sessions/current for local session

Task: task-8-close-session-remote-aware
2026-03-31 03:53:34 -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 ac0b84c1d9 feat: openSession — full remote routing via sourceUrl 2026-03-31 03:40:57 -07:00
Amplifier 3444310bb2 feat: accept sourceUrl parameter in connectWebSocket for remote sessions (task-6)
🤖 Generated with Amplifier
2026-03-31 03:27:30 -07:00
Brian Krabach d955c90c0b feat: add openLoginPopup function and delegated click handler for auth tile login buttons
- Add openLoginPopup(remoteUrl) function after buildOfflineTileHTML that strips
  trailing slashes from remoteUrl before appending /login and opens a popup
  window with width=500,height=600
- Wire up delegated click handler for .source-tile__login-btn buttons inside
  bindStaticEventListeners, right after the existing delegated kill-session handler
- Export openLoginPopup from module.exports

Tests: 4 new tests covering openLoginPopup export, window.open call
with correct URL and dimensions, trailing slash handling (with and without)
2026-03-31 03:20:21 -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 2862f494f5 feat: integrate source status tiles into renderGrid
- Append auth tiles for auth_required sources after session tiles
- Append offline tiles for unreachable sources after session tiles
- _setSources test helper was already defined and exported (verified)
- All 197 tests pass (195 pre-existing + 2 new)
2026-03-31 03:11:47 -07:00
Brian Krabach bf87f05d82 test: add formatLastSeen branch coverage for seconds, hours, and days 2026-03-31 03:06:29 -07:00
Brian Krabach fe5337840d refactor: remove Compact view mode — keep Auto + Fit only 2026-03-31 03:05:18 -07:00
Brian Krabach e07cf196c6 feat: add formatLastSeen and buildOfflineTileHTML functions
- Add formatLastSeen(ms) to format millisecond timestamps into relative
  strings (Xs ago, Xm ago, Xh ago, Xd ago, Never for null)
- Add buildOfflineTileHTML(source) to render unreachable device tiles
  with Offline badge and last-seen timestamp
- Both functions added after buildAuthTileHTML in app.js
- Both exported via module.exports
- 7 new tests covering export, CSS class, device name, badge, last-seen,
  HTML escaping, and null handling
2026-03-31 02:45:36 -07:00
Brian Krabach 83e5318ba1 feat: add buildAuthTileHTML for auth-required federation tiles
- Add buildAuthTileHTML(source) function to frontend/app.js after buildSidebarHTML
- Function renders an <article class='source-tile source-tile--auth'> with device name,
  login button (with data-url), and hint text
- HTML-escapes both name and url to prevent XSS
- Export buildAuthTileHTML under '// Federation tiles' comment in module.exports
- Add 5 tests covering: export, article class, device name, login button data-url, HTML escaping

Task: task-2-build-auth-tile-html
2026-03-31 02:37:05 -07:00
Brian Krabach 55c80b4c9b refactor: move intra-function imports to module level in test_frontend_css 2026-03-31 02:31:54 -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 7100fd881e style: apply ruff formatting to test_frontend_css.py (wrap long assertion) 2026-03-31 02:13:10 -07:00
Brian Krabach 918d92386f test: add Phase 2 integration tests — export verification and e2e pipeline (task-19) 2026-03-31 01:56:05 -07:00
Brian Krabach a9e1206ac6 style: restore ES6 syntax in detectBellTransitions and update JSDoc 2026-03-31 01:49:56 -07:00
Brian Krabach c7c92ee0da feat: use sessionKey for cross-device dedup in detectBellTransitions (task-18)
Replace s.name with s.sessionKey || s.name as the map key in
detectBellTransitions so two sessions with the same name on
different devices are tracked independently.

- prevMap construction uses s.sessionKey || s.name as key
- next filter lookup uses the same s.sessionKey || s.name key
- Falls back to s.name when no sessionKey is present (backward compatible)

Co-authored-by: Amplifier <amplifier@example.com>
2026-03-31 01:37:49 -07:00
Brian Krabach 2b5b6e3003 feat: skip connect and terminal mount for remote sessions in openSession
When opts.sourceUrl is non-empty (remote session), skip the POST
/api/sessions/{name}/connect call and skip window._openTerminal(name).
Remote terminal connections are deferred to Phase 3.
Local sessions (empty sourceUrl) continue to work as before.

Co-authored-by: Amplifier <amplifier@amplified.dev>
2026-03-31 01:31:09 -07:00
Brian Krabach 70b8df1458 feat: sidebar device grouping when multiple sources configured (task-16)
- Replace renderSidebar to group sessions by deviceName when _sources.length > 1
- Render sidebar-device-header h4 elements before each device's sessions
- When single source configured, render flat list with no headers
- Click handlers now pass sourceUrl to openSession
- Add tests: groups with header when multiple sources, flat when single source

🤖 Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-03-31 01:16:21 -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 200137a995 feat: add view mode and scope selectors to Display settings tab
Task: task-14 Settings dialog — view mode selector and scope toggle
2026-03-31 00:40:51 -07:00
Brian Krabach b2ff3613de feat: add loadGridViewMode and saveGridViewMode with local/server scope
- Add loadGridViewMode() that reads grid view mode preference based on
  viewPreferenceScope in display settings: returns gridViewMode from
  localStorage display settings when scope is 'local' (default), or
  grid_view_mode from _serverSettings when scope is 'server'. Returns
  'flat' as default when no preference is set.
- Add saveGridViewMode(mode) that saves to appropriate scope (localStorage
  via saveDisplaySettings when local, patchServerSetting when server)
  and updates _gridViewMode module variable.
- Wire loadGridViewMode() into DOMContentLoaded startup after applyDisplaySettings
  so _gridViewMode is initialized from stored preference on page load.
- Export both functions from module.exports for testing.

Closes task-13
2026-03-31 00:33:34 -07:00
Brian Krabach 393891173f fix: improve test assertion precision and add null guard in renderFilterBar
- Fixed imprecise pill count assertion in test_app.mjs by changing /filter-pill/g regex to /<button/g to accurately count button elements without double-counting when active pill has both classes
- Removed redundant OR fallback in active pill test, now relying only on specific regex for Laptop pill
- Added defensive null guard 'allSessions = allSessions || []' at entry of renderFilterBar in app.js to prevent TypeError

🤖 Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-03-31 00:26:12 -07:00
Brian Krabach 5738c39f7f feat: add filtered view mode with device pill bar (task-12)
- Add <div id="filter-bar" class="filter-bar"> to index.html before session-grid div
- Implement renderFilterBar(container, allSessions) that renders pill buttons
  for each unique device name plus an 'All' button
- Active device pill gets filter-pill--active class
- Add _setActiveFilterDevice(device) test helper
- Bind delegated click handler on filter-bar in bindStaticEventListeners
  that sets _activeFilterDevice and re-renders grid
- Export renderFilterBar and _setActiveFilterDevice

Co-authored-by: Amplifier <amplifier@amplified.dev>
2026-03-31 00:16:15 -07:00
Brian Krabach 2e8cf6ddee feat: add grouped view mode to renderGrid with device-group-header sections
- Add renderGroupedGrid(sessions, mobile) that groups sessions by deviceName
  and creates <h3 class="device-group-header"> headers for each group
- Rewrite renderGrid to: apply device filter in 'filtered' mode, apply sort
  order, call renderGroupedGrid when _gridViewMode === 'grouped', render
  filter bar in 'filtered' mode, bind click handlers with sourceUrl
- Add _setGridViewMode(mode) test-only helper
- Export renderGroupedGrid and _setGridViewMode from module.exports
2026-03-31 00:00:34 -07:00
Brian Krabach 300919c7d7 feat: device badge on sidebar items in buildSidebarHTML (task-10) 2026-03-30 23:49:31 -07:00
Brian Krabach 4084544530 refactor(task-9): extract badgeHtml const and tighten device badge tests
- Extract inline ternary for device badge into `const badgeHtml` variable
  above the return block, consistent with how `bellHtml` is handled
- Tighten data-session-key/data-source-url test assertions to check exact
  attribute values rather than just attribute presence
- Add XSS test: verifies deviceName containing HTML is escaped in the badge

All 159 tests pass.
2026-03-30 23:41:50 -07:00
Brian Krabach 5980a6c080 feat: add device badge and data attributes to buildTileHTML (task-9)
- Show <span class="device-badge"> after tile name when _sources.length > 1
  and session.deviceName is present (multi-source federation context)
- Add data-session-key and data-source-url attributes to tile article
  element for unique session identification across sources
- Tests: 3 new tests covering badge visibility, badge omission for single
  source, and presence of new data attributes
2026-03-30 23:34:22 -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 4ed8303b6e feat: update getVisibleSessions to only hide local sessions by name
- Replace getVisibleSessions filter logic: only hide sessions where
  sourceUrl is empty/absent AND name matches hidden_sessions list
- Remote sessions with the same name as a hidden local session now
  remain visible (task-7 spec requirement)
- Export getVisibleSessions in module.exports so tests can access it

Tests added:
- 'getVisibleSessions exported and filters hidden sessions': verifies
  export and that local hidden sessions are filtered
- 'getVisibleSessions hides local sessions by name but not remote
  sessions with same name': verifies remote sessions survive

All 155 tests pass.
2026-03-30 23:22:55 -07:00
Brian Krabach 48e87af3f8 feat: wire buildSources into loadServerSettings startup
After fetching /api/settings and caching in _serverSettings,
call _sources = buildSources(_serverSettings) so the polling
layer immediately knows which sources to query.

Implements task-6 of multi-device federation phase 1.
2026-03-30 23:14:48 -07:00
Brian Krabach e95a8d4282 refactor: improve pollSessions error handling and test coverage 2026-03-30 23:09:08 -07:00
Brian Krabach e7ed300b37 feat: rewrite pollSessions for multi-source parallel polling (task-5) 2026-03-30 22:59:08 -07:00
Brian Krabach 593f1639db style: align tagSessions and mergeSources with ES6 file conventions 2026-03-30 22:52:36 -07:00
Brian Krabach 55f9597e1a feat: add tagSessions and mergeSources for multi-source parallel polling
These two functions enable multi-source session federation:
- tagSessions(sessions, deviceName, sourceUrl): tags each session with
  deviceName, sourceUrl, and sessionKey (format: sourceUrl::name),
  returns new objects without mutating originals
- mergeSources(results): takes [{source, sessions}] objects, tags each
  source's sessions and concatenates into a single flat array

Both functions are exported in module.exports.
6 new tests added covering all specified behaviors.
2026-03-30 22:42:55 -07:00
Brian Krabach 07c61a0baf style: expand test-only helper bodies to match section formatting 2026-03-30 22:37:08 -07:00
Brian Krabach 844311b475 feat: add federation state globals and test-only helpers to app.js
Add new module-level globals:
- let _gridViewMode = 'flat'
- let _activeFilterDevice = 'all'

Add test-only helper functions:
- _setSources(sources): sets internal _sources array
- _setServerSettings(settings): sets internal _serverSettings
- _getGridViewMode(): returns current _gridViewMode value
- _getSources(): returns current _sources array

All 4 helpers exported in module.exports.

Tests added (all passing, 143/143):
- '_setSources sets internal _sources array'
- '_setServerSettings sets internal _serverSettings'
- '_getGridViewMode returns current _gridViewMode value'
- '_getSources returns current _sources array'

Task: task-3 (federation state helpers)
2026-03-30 22: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 ecf57441cf feat: add buildSources to construct session source list from settings
🤖 Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-03-30 22:21:45 -07:00
Brian Krabach 93c5170581 feat: refactor api() to accept optional baseUrl for cross-origin requests
Add 4th parameter `baseUrl` to `api()`. When provided, strips trailing
slash and prepends to path, and sets `credentials: 'include'` for
cross-origin cookie sending. Behavior is unchanged when no baseUrl is
given (backward compatible). Export `api` in module.exports.

All 135 tests pass.

🤖 Generated with Amplifier <https://github.com/microsoft/amplifier>

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
2026-03-30 22:12:57 -07:00
Brian Krabach 3a102ba9d8 fix: add 18px font option, click-outside-dialog to dismiss settings
Added 18px to font size select. Dialog click handler checks
e.target === settingsDialog (click landed on ::backdrop area,
not dialog content) and closes settings.
2026-03-30 22:07:38 -07:00
Brian Krabach d0eb91dca7 refactor: replace fixed sleeps with deterministic polling in ws proxy tests; document CORS permissiveness 2026-03-30 21:56:05 -07:00
Brian Krabach b1cbeeb02c test: expand WebSocket proxy test suite 2026-03-30 21:47:17 -07:00