Dashboard: per-card window controls (minimize/maximize/resize/reorder)#1
Merged
Merged
Conversation
- Complete removal of the public "Beta help" / support-chat feature
(widget, /api/support-chat route+handler+LLM proxy+Discord mirror,
all config keys, HTML script tags in login+dashboard, CSS, docs,
source files). Chat for Spot/Cloudevolvers etc. now belongs on
the private falkinator dashboard.
- "Shell in" terminals are now proper floating windows:
* Drag titlebar to move anywhere on screen
* Drag SE corner resize handle (live PTY resize pushed to tmux)
* − Minimize → compact pill dock at bottom-right
* □ Maximize / restore (large viewport size)
* ↺ Reset button (default size+pos, clears saved layout)
* Multiple concurrent terminals supported; re-clicking "Shell in"
focuses/restores the existing window for that session
* Size/position persisted per shell name in localStorage
- Updated README, .env.example, and all call sites.
Builds and checks clean. Deploy: rebuild release + restart shelldeck user service.
Adds desktop-window-style controls to the shell preview cards so they can be managed from the dashboard itself, not only after "Shell in": - Minimize (−): collapse a preview card to the bottom-right dock, with a "Restore all" button in the shell tools and per-item restore in the dock. - Maximize (□): focus one card by minimizing the others; temporary height boost. - Float & resize (⤢): toggle an enlarged state for a single card. - Drag-to-reorder: drag a card header to reorder; order persists in localStorage (sdShellOrder) and survives stream refreshes. - Drag-to-resize: drag the bottom-right handle; size persists per card (sdShellSizes) and is reapplied on render. - Relative "last activity" labels (e.g. "3m ago") on sessions, live-ticked every 30s instead of absolute timestamps. All changes are frontend-only (TS + compiled JS + CSS); no backend or route changes. Helpers live in prefs.ts (persistence), terminal.ts (dock/min/max), events.ts (delegation + drag handlers), render.ts (card controls markup). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds desktop-window-style controls to dashboard shell preview cards (minimize/maximize/float-resize, drag-to-reorder, drag-to-resize with persistence) and converts the live in-browser terminal from a single modal into multiple floating/dockable windows. Also switches the "last activity" display from absolute timestamps to live-ticking relative labels. The bulk of remaining diffs are mechanical rustfmt reflows in the Rust files with no semantic change.
Changes:
- New per-card window controls + drag-to-reorder/resize wired through
events.ts,render.ts,prefs.ts(newsdShellOrder/sdShellSizeslocalStorage keys). - Replaced single modal terminal with multi-window floating terminals + bottom-right dock in
terminal.ts, including per-window geometry persistence (sdTerm:<name>). fmtTimereworked to relative labels, withupdateLastActivityTimesticking[data-act-epoch]nodes every 30s.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/terminal.ts / public/terminal.js | Multi-window terminal model, dock, min/max/reset, preview minimize/maximize/float helpers exposed on window. |
| frontend/events.ts / public/events.js | Click delegation for new card controls, drag-to-reorder + drag-to-resize, Restore-all button, 30s activity tick. |
| frontend/render.ts / public/render.js | Card markup gains window-control buttons and resize handle; uses orderedShellList, applies saved sizes, respects minimized previews; session list emits data-act-epoch. |
| frontend/prefs.ts / public/prefs.js | Adds shellOrder/orderedShellList/saveShellOrder and card-size persistence helpers; DRAG_REORDER_THRESHOLD. |
| frontend/core.ts / public/core.js | fmtTime switched to relative ("just now"/"Nm ago"); new updateLastActivityTimes. |
| public/app.css | Styles for floating term windows, dock, card window controls, reorder/resize states, preview-enlarged. |
| src/routes.rs, src/pages.rs, src/main.rs, src/config.rs | rustfmt reflows only; main.rs startup log changed to "ShellDeck listening on …". |
| README.md | Documents relative last-activity display. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+113
to
+127
| function orderedShellList(shells: ShellPreview[]): ShellPreview[] { | ||
| const saved = shellOrder(); | ||
| const nameSet = new Set(shells.map((s) => s.name)); | ||
| // Remove names from saved list that are no longer in the shell list | ||
| const validSaved = saved.filter((n) => nameSet.has(n)); | ||
| // Add shells that are in the current list but not yet in saved order | ||
| const newNames = shells.map((s) => s.name).filter((n) => !validSaved.includes(n)); | ||
| const merged = [...validSaved, ...newNames]; | ||
| // Update the saved order | ||
| saveShellOrder(merged); | ||
| // Reorder shells array to match | ||
| const byName: Record<string, ShellPreview> = {}; | ||
| shells.forEach((s) => { byName[s.name] = s; }); | ||
| return merged.map((n) => byName[n]).filter(Boolean); | ||
| } |
| const header = target?.closest<HTMLElement>('.terminal-card > header'); | ||
| if (!header) return; | ||
| // Don't initiate drag when clicking buttons, inputs, or the card window controls | ||
| if ((event.target as HTMLElement)?.closest('button,input,textarea,select,[data-minimize-shell],[data-resize-preview],[data-maximize-shell]')) return; |
Comment on lines
+300
to
+304
| if (tw.maximized && tw.preMax) { | ||
| // keep maximized geometry | ||
| } else if (tw.maximized) { | ||
| // fall through to current | ||
| } |
Comment on lines
+319
to
+336
| const onMove = (e: MouseEvent) => { | ||
| const newH = Math.max(280, Math.min(1200, origH + (e.clientY - startY))); | ||
| const newW = Math.max(340, Math.min(window.innerWidth - 40, origW + (e.clientX - startX))); | ||
| card.style.minHeight = `${newH}px`; | ||
| if (newW > 340) card.style.maxWidth = `${newW}px`; | ||
| // Update the pre element's max-height to keep it proportional | ||
| const pre = card.querySelector<HTMLElement>('[data-role="output"]'); | ||
| if (pre) pre.style.maxHeight = `${Math.max(180, newH - 220)}px`; | ||
| // Track that this card has been resized | ||
| card.dataset.sized = '1'; | ||
| }; | ||
|
|
||
| const onUp = () => { | ||
| document.removeEventListener('mousemove', onMove); | ||
| document.removeEventListener('mouseup', onUp); | ||
| card.classList.remove('resizing'); | ||
| if (name) { | ||
| saveShellCardSize(name, { w: card.offsetWidth, h: card.offsetHeight }); |
Comment on lines
+237
to
+248
| dragEl = card.cloneNode(true) as HTMLElement; | ||
| dragEl.classList.add('reorder-clone'); | ||
| dragEl.style.position = 'fixed'; | ||
| dragEl.style.pointerEvents = 'none'; | ||
| dragEl.style.zIndex = '999'; | ||
| dragEl.style.width = `${card.offsetWidth}px`; | ||
| dragEl.style.left = '0'; | ||
| dragEl.style.top = '0'; | ||
| dragEl.style.opacity = '0.85'; | ||
| dragEl.style.transform = 'scale(0.97)'; | ||
| document.body.appendChild(dragEl); | ||
| card.style.opacity = '0.35'; |
Comment on lines
+381
to
+421
| function minimizeShellPreview(name: string): void { | ||
| if (!name) return; | ||
| minimizedPreviews.add(name); | ||
| // Hide the card in the grid if it's currently rendered there | ||
| const card = document.querySelector<HTMLElement>(`[data-shell-card="${name.replace(/"/g, '\\"')}"]`); | ||
| if (card) card.style.display = 'none'; | ||
| renderDock(); | ||
| } | ||
|
|
||
| function restoreShellPreview(name: string): void { | ||
| minimizedPreviews.delete(name); | ||
| const card = document.querySelector<HTMLElement>(`[data-shell-card="${name.replace(/"/g, '\\"')}"]`); | ||
| if (card) { | ||
| card.style.display = ''; | ||
| card.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); | ||
| } else { | ||
| // Card not in DOM — ask for a refresh so renderShells recreates it | ||
| (window as any).loadShells?.().catch(() => {}); | ||
| } | ||
| renderDock(); | ||
| } | ||
|
|
||
| function maximizeShellPreview(name: string): void { | ||
| if (!name) return; | ||
| // Minimize all other previews | ||
| const cards = document.querySelectorAll<HTMLElement>('[data-shell-card]'); | ||
| cards.forEach((card) => { | ||
| const n = card.dataset.shellCard || ''; | ||
| if (n && n !== name && !minimizedPreviews.has(n)) { | ||
| minimizeShellPreview(n); | ||
| } | ||
| }); | ||
| // Make sure this one is visible and prominent | ||
| restoreShellPreview(name); | ||
| const thisCard = document.querySelector<HTMLElement>(`[data-shell-card="${name.replace(/"/g, '\\"')}"]`); | ||
| if (thisCard) { | ||
| thisCard.scrollIntoView({ block: 'start', behavior: 'smooth' }); | ||
| // Optional: give it a temporary boost | ||
| thisCard.style.minHeight = '520px'; | ||
| setTimeout(() => { if (thisCard) thisCard.style.minHeight = ''; }, 8000); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds desktop-window-style controls to the shell preview cards so they can be managed from the dashboard itself, not only after clicking "Shell in".
localStorage(sdShellOrder) and survives stream refreshes.sdShellSizes) and is re-applied on render.3m ago) on the session list, live-ticked every 30s instead of absolute timestamps.Scope
Frontend-only: TypeScript (
frontend/*.ts), the compiled output (public/*.js), andpublic/app.css. No backend, route, or auth changes. New helpers live inprefs.ts(persistence),terminal.ts(dock/min/max),events.ts(event delegation + drag handlers),render.ts(card-control markup).Verification
bun run check(tsc--noEmit) passes.bun run build:frontendreproduces the committedpublic/*.jsbyte-for-byte (TS/JS in sync).code.falkinator.org) confirmed serving the new asset code aftershelldeck.servicerestart.🤖 Generated with Claude Code