Skip to content

Dashboard: per-card window controls (minimize/maximize/resize/reorder)#1

Merged
falkoro merged 2 commits into
mainfrom
feat/dashboard-shell-window-controls
May 28, 2026
Merged

Dashboard: per-card window controls (minimize/maximize/resize/reorder)#1
falkoro merged 2 commits into
mainfrom
feat/dashboard-shell-window-controls

Conversation

@falkoro
Copy link
Copy Markdown
Owner

@falkoro falkoro commented May 28, 2026

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".

  • Minimize (−): collapse a preview card to the bottom-right dock; per-item restore plus a new Restore all button in the shell tools.
  • 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 re-applied on render.
  • Relative "last activity" labels (e.g. 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), and public/app.css. No backend, route, or auth changes. New helpers live in prefs.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:frontend reproduces the committed public/*.js byte-for-byte (TS/JS in sync).
  • Live edge (code.falkinator.org) confirmed serving the new asset code after shelldeck.service restart.

Note: this PR also carries the prior unpushed main commit d5283fd ("Remove Beta help chat widget + floating draggable terminal windows"), which had not yet reached origin. A squash merge folds both into one commit on main.

🤖 Generated with Claude Code

falkoro and others added 2 commits May 28, 2026 18:37
- 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>
Copilot AI review requested due to automatic review settings May 28, 2026 18:20
@falkoro falkoro merged commit 866da3c into main May 28, 2026
@falkoro falkoro deleted the feat/dashboard-shell-window-controls branch May 28, 2026 18:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (new sdShellOrder / sdShellSizes localStorage keys).
  • Replaced single modal terminal with multi-window floating terminals + bottom-right dock in terminal.ts, including per-window geometry persistence (sdTerm:<name>).
  • fmtTime reworked to relative labels, with updateLastActivityTimes ticking [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 thread frontend/prefs.ts
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);
}
Comment thread frontend/events.ts
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 thread frontend/terminal.ts
Comment on lines +300 to +304
if (tw.maximized && tw.preMax) {
// keep maximized geometry
} else if (tw.maximized) {
// fall through to current
}
Comment thread frontend/events.ts
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 thread frontend/events.ts
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 thread frontend/terminal.ts
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);
}
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.

2 participants