diff --git a/src/installer-dashboard/web/src/App.tsx b/src/installer-dashboard/web/src/App.tsx index 4c79caf..6448c13 100644 --- a/src/installer-dashboard/web/src/App.tsx +++ b/src/installer-dashboard/web/src/App.tsx @@ -3,11 +3,11 @@ import { InstallerDashboard } from "./InstallerDashboard"; export function App(): JSX.Element { return ( -
+
Skip to Main Content -
+
diff --git a/src/installer-dashboard/web/src/InstallerDashboard.tsx b/src/installer-dashboard/web/src/InstallerDashboard.tsx index ea883da..d25d5d4 100644 --- a/src/installer-dashboard/web/src/InstallerDashboard.tsx +++ b/src/installer-dashboard/web/src/InstallerDashboard.tsx @@ -1634,22 +1634,26 @@ export function InstallerDashboard(): JSX.Element { }, [appUpdate, appUpdateBusy]); return ( -
-
-
-

ICA COMMAND CENTER

-

Multi-source

-
-

Skills & Hooks Dashboard

-

Manage repositories once, then install source-pinned skills and hooks across targets.

-
- {sources.length} sources - {installedSkillCount} skills installed - {installedHookCount} hooks installed +
+
+
+
+

ICA DESKTOP WORKSPACE

+

Installer Workspace

+

+ Keep source and installation operations in a persistent desktop shell instead of a dashboard landing page. +

+
+
+ {sources.length} sources + {installedSkillCount} skills installed + {installedHookCount} hooks installed +
-
+
+
@@ -1805,28 +1809,28 @@ export function InstallerDashboard(): JSX.Element { )}
-
- - {error && ( -
- Action needed: {error} -
- )} - {catalogLoading && ( -
-
- Loading skills catalog - {Math.round(catalogLoadingProgress)}% -
-
{catalogLoadingMessage || "Working…"}
-
- )} -
-
+
- {activeTab === "skills" && ( + {activeTab === "skills" && (
- )} + )} - {activeTab === "hooks" && ( + {activeTab === "hooks" && (
- )} + )} - {activeTab === "settings" && ( + {activeTab === "settings" && (

Repository Management

@@ -2571,9 +2575,9 @@ export function InstallerDashboard(): JSX.Element {
- )} + )} - {activeTab === "state" && ( + {activeTab === "state" && (

States & Reports

@@ -2826,6 +2830,8 @@ export function InstallerDashboard(): JSX.Element {
)} +
+ {skillPickerOpen && (
setSkillPickerOpen(false)}>
- setSkillPickerQuery(event.target.value)} + setSkillPickerQuery(event.target.value)} aria-label="Search local skill bundles" />
diff --git a/src/installer-dashboard/web/src/styles.css b/src/installer-dashboard/web/src/styles.css index 1a34f2d..a0699b0 100644 --- a/src/installer-dashboard/web/src/styles.css +++ b/src/installer-dashboard/web/src/styles.css @@ -12,7 +12,6 @@ --text: #121826; --text-soft: #4b5b72; --text-muted: #65758a; - --hero-meta-bg: color-mix(in srgb, var(--accent) 10%, #f2f5fa); --status-error-border: color-mix(in srgb, var(--accent) 22%, #d5dbe5); --status-error-bg: color-mix(in srgb, var(--accent) 12%, #f6f8fb); --status-error-text: #4b5b72; @@ -75,7 +74,6 @@ body[data-mode="dark"] { --text: #e9eef4; --text-soft: #c4d0dc; --text-muted: #9eacbc; - --hero-meta-bg: color-mix(in srgb, var(--accent) 30%, #1e2329); --status-error-border: color-mix(in srgb, var(--accent) 42%, #3d4652); --status-error-bg: color-mix(in srgb, var(--accent) 24%, #1d2228); --status-error-text: #cfdbe7; @@ -374,71 +372,85 @@ input[type="radio"]:focus-visible { padding: var(--space-8) var(--space-6) var(--space-9); } -.hero { - background: var(--surface); - border: 1px solid var(--line); - border-radius: 12px; +.desktop-shell-app { + min-height: 100vh; +} + +.desktop-shell-app-main { + display: block; +} + +.desktop-shell-frame { + display: grid; + gap: var(--space-5); +} + +.desktop-shell-header { + background: + linear-gradient(135deg, color-mix(in srgb, var(--accent) 7%, var(--surface)) 0%, var(--surface) 62%), + var(--surface); + border: 1px solid color-mix(in srgb, var(--line) 80%, transparent); + border-radius: 14px; padding: var(--space-5) var(--space-6); - box-shadow: none; - margin-bottom: var(--space-5); + box-shadow: inset 0 1px 0 color-mix(in srgb, #ffffff 36%, transparent); } -.hero-topline { +.desktop-shell-header-bar { display: flex; - align-items: center; + align-items: flex-start; justify-content: space-between; - gap: var(--space-3); + gap: var(--space-4); } -.eyebrow { - margin: 0; - font-size: 0.72rem; - text-transform: uppercase; - letter-spacing: 0.12em; - color: var(--text-muted); - font-weight: 400; +.desktop-shell-header-copyblock { + display: grid; + gap: var(--space-3); + max-width: 46rem; } -.stamp { +.desktop-shell-header h1 { margin: 0; - border-radius: 8px; - border: 1px solid color-mix(in srgb, var(--line) 76%, transparent); - color: var(--text-soft); - background: transparent; - padding: 0.14rem 0.46rem; - font-size: 0.7rem; -} - -.hero h1 { - margin: var(--space-3) 0 0; - font-size: clamp(1.38rem, 2.5vw, 1.78rem); - line-height: 1.2; + font-size: clamp(1.4rem, 2.5vw, 1.82rem); + line-height: 1.12; font-weight: 300; - letter-spacing: -0.01em; - text-wrap: balance; + letter-spacing: -0.02em; } -.hero > p { - margin: var(--space-3) 0 0; +.desktop-shell-header-copy { + margin: 0; color: var(--text-soft); - max-width: 68ch; - font-size: 0.94rem; + font-size: 0.95rem; + line-height: 1.58; } -.hero-meta { - margin-top: var(--space-4); +.desktop-shell-header-meta { display: flex; flex-wrap: wrap; + justify-content: flex-end; gap: var(--space-2); } -.hero-meta span { - border-radius: 8px; - background: transparent; - border: 1px solid color-mix(in srgb, var(--line) 75%, transparent); +.desktop-shell-header-meta span { + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--line) 76%, transparent); + background: color-mix(in srgb, var(--surface) 72%, transparent); color: var(--text-soft); - padding: 0.15rem 0.44rem; - font-size: 0.7rem; + padding: 0.2rem 0.55rem; + font-size: 0.72rem; +} + +.desktop-shell-workspace { + display: grid; + gap: var(--space-5); +} + +.eyebrow { + margin: 0; + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--text-muted); + font-weight: 400; } .desktop-shell-grid { @@ -1839,10 +1851,6 @@ pre { line-height: 1.55; } -.dashboard-main { - display: block; -} - .skip-link { position: absolute; left: 0.75rem; @@ -1873,10 +1881,19 @@ pre { padding: var(--space-7) var(--space-5) var(--space-8); } - .hero { + .desktop-shell-header { padding: var(--space-5); } + .desktop-shell-header-bar { + flex-direction: column; + align-items: stretch; + } + + .desktop-shell-header-meta { + justify-content: flex-start; + } + .panel-spacious { padding: var(--space-6); } @@ -1971,14 +1988,10 @@ pre { grid-column: span 1; } - .hero { + .desktop-shell-header { padding: var(--space-5); } - .hero-meta { - gap: var(--space-1); - } - .tab-nav { width: 100%; gap: var(--space-2); diff --git a/tests/installer/desktop-shell-frame-red-phase.test.ts b/tests/installer/desktop-shell-frame-red-phase.test.ts new file mode 100644 index 0000000..db4e079 --- /dev/null +++ b/tests/installer/desktop-shell-frame-red-phase.test.ts @@ -0,0 +1,53 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +function readWorkspaceFile(relativePath: string): string { + return fs.readFileSync(path.resolve(process.cwd(), relativePath), "utf8"); +} + +test("RED: app shell stops using dashboard-main as the primary desktop wrapper", () => { + const app = readWorkspaceFile("src/installer-dashboard/web/src/App.tsx"); + + assert.doesNotMatch( + app, + /className="dashboard-main"/, + "Desktop shell work should remove the dashboard-main wrapper from the main window entry composition.", + ); + assert.match( + app, + /className="desktop-shell-app"/, + "Desktop shell work should introduce a desktop-shell-app wrapper for the main window.", + ); +}); + +test("RED: installer dashboard removes hero landing markup and exposes desktop shell frame regions", () => { + const ui = readWorkspaceFile("src/installer-dashboard/web/src/InstallerDashboard.tsx"); + + assert.doesNotMatch( + ui, + /className="hero"/, + "Desktop shell work should remove the hero landing section from the main window.", + ); + assert.doesNotMatch( + ui, + />\s*Skills & Hooks Dashboard\s*