Manage repositories once, then install source-pinned skills and hooks across targets.
-
-
{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*,
+ "Desktop shell work should remove the dashboard landing heading from the main window.",
+ );
+ assert.match(
+ ui,
+ /className="[^"]*desktop-shell-frame[^"]*"/,
+ "Desktop shell work should introduce a desktop-shell-frame root container.",
+ );
+ assert.match(
+ ui,
+ /className="[^"]*desktop-shell-header[^"]*"/,
+ "Desktop shell work should introduce a desktop-shell-header region.",
+ );
+ assert.match(
+ ui,
+ /className="[^"]*desktop-shell-workspace[^"]*"/,
+ "Desktop shell work should introduce a desktop-shell-workspace region.",
+ );
+});