feat(dashboard,cli): update-check frequency, GitHub star button, more resizable modals

Update-check frequency (end-to-end)
- Add `updateCheckFrequency: "manual" | "on-startup" | "daily" | "weekly"`
  to GlobalSettings (default: "daily").
- Backend `performUpdateCheck` honors the frequency: TTL = day/week,
  `manual` returns cache or empty without hitting npm, `on-startup`
  refreshes once per process lifetime then serves cache. `/refresh`
  route forces network regardless.
- Settings → Updates surfaces a working `<select>` for the cadence,
  disabled when auto-checks are off entirely.
- Tests: 4 new cases covering ttlForFrequency mapping, weekly window,
  manual semantics, on-startup once-per-process behavior.

TUI update notice
- Read cached update result synchronously on TUI startup. When an
  update is available, render a yellow notice line on the splash and
  a colored ● next to the version in the status bar.

Settings modal header polish
- Add "Star on GitHub" pill (icon + Star + cached star count from the
  GitHub API, 1h localStorage TTL) and "Help" button (opens project
  Discussions). Both link to the Runfusion/Fusion repo.
- Star button auto-hides after the user clicks it (intent = star),
  tracked in localStorage `fusion:github-star-clicked`.
- Settings → General gets a "Show Star on GitHub button" checkbox so
  users can hide it preemptively. New global setting
  `showGitHubStarButton: boolean` (default true) gates rendering.

More resizable modals
- Task Detail modal: 85vh default, resize: both, persisted via
  useModalResizePersist (key `fusion:task-detail-modal-size`).
- Quick Chat FAB: full 8-direction resize (4 corners + 4 edges) via
  pointer-event handlers; persisted to
  `fusion:quick-chat-size-<projectId>`. Each handle has the right
  cursor + role="separator" for accessibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-27 17:55:15 -07:00
parent c1e4c8df85
commit 61ea16e31f
16 changed files with 752 additions and 98 deletions

View File

@@ -2,6 +2,7 @@ import os from "node:os";
import v8 from "node:v8";
import { execSync } from "node:child_process";
import { appendFileSync } from "node:fs";
import { getCachedUpdateStatus } from "../../update-cache.js";
const TUI_DEBUG_LOG = process.env.FUSION_TUI_DEBUG_LOG;
function tuiDebug(tag: string, data: Record<string, unknown>): void {
@@ -25,6 +26,7 @@ import type {
DashboardState,
InteractiveData,
InteractiveView,
UpdateStatus,
} from "./state.js";
import { SECTION_ORDER } from "./state.js";
@@ -74,6 +76,7 @@ export class DashboardTUI {
interactiveData: InteractiveData | null = null;
interactiveView: InteractiveView = "board";
interactiveInputLocked = false;
updateStatus: UpdateStatus | null = null;
// Subscribers registered by the Ink App component.
private subscribers: Set<() => void> = new Set();
@@ -112,6 +115,17 @@ export class DashboardTUI {
constructor() {
this.logBuffer = new LogRingBuffer();
// Read the update-check cache synchronously at construction time so the
// splash screen and status bar can render the notice immediately, without
// any network access.
const cached = getCachedUpdateStatus();
if (cached) {
this.updateStatus = {
updateAvailable: cached.updateAvailable,
currentVersion: cached.currentVersion,
latestVersion: cached.latestVersion,
};
}
}
// ── Subscription API (for Ink App) ────────────────────────────────────────
@@ -144,6 +158,7 @@ export class DashboardTUI {
interactiveInputLocked: this.interactiveInputLocked,
autoKillVitestOnPressure: this.autoKillVitestOnPressure,
vitestKillThreshold: this.vitestKillThreshold,
updateStatus: this.updateStatus,
};
return this.cachedSnapshot;
}
@@ -353,6 +368,11 @@ export class DashboardTUI {
this.notify();
}
setUpdateStatus(status: UpdateStatus | null): void {
this.updateStatus = status;
this.notify();
}
addLog(entry: Omit<LogEntry, "timestamp">): void {
// If the cursor was sitting on the most recent entry (or there were no
// entries yet), keep it pinned to the new tail so live logs follow the