diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
new file mode 100644
index 0000000..c7894f9
--- /dev/null
+++ b/apps/web/src/app/page.tsx
@@ -0,0 +1,5 @@
+import { AppShell } from "../components/app-shell.js";
+
+export default function Page(): string {
+ return AppShell();
+}
diff --git a/apps/web/src/components/app-shell-model.ts b/apps/web/src/components/app-shell-model.ts
new file mode 100644
index 0000000..fd44e5b
--- /dev/null
+++ b/apps/web/src/components/app-shell-model.ts
@@ -0,0 +1,36 @@
+import { APP_NAV_ITEMS, renderSidebarHtml } from "./sidebar.js";
+import { renderInspectorHtml } from "./inspector.js";
+
+export { APP_NAV_ITEMS } from "./sidebar.js";
+
+export const APP_SHELL_PANEL_WIDTHS = {
+ sidebar: "236px",
+ inspector: "320px",
+} as const;
+
+export function renderAppShellHtml(): string {
+ return `
+
+ AgentDeck
+ Local workspace
+
+
+ ${renderSidebarHtml("chat")}
+
+
+
+ ${APP_NAV_ITEMS.slice(0, 5)
+ .map((item) => `${item.label}
${item.id}
`)
+ .join("")}
+
+
+ ${renderInspectorHtml()}
+
+
`;
+}
diff --git a/apps/web/src/components/app-shell.test.ts b/apps/web/src/components/app-shell.test.ts
new file mode 100644
index 0000000..a2d8c13
--- /dev/null
+++ b/apps/web/src/components/app-shell.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from "vitest";
+
+import { APP_NAV_ITEMS, APP_SHELL_PANEL_WIDTHS, renderAppShellHtml } from "./app-shell-model.js";
+
+describe("app shell", () => {
+ it("renders top bar, sidebar, main work area, and contextual inspector", () => {
+ const html = renderAppShellHtml();
+
+ expect(html).toContain("AgentDeck");
+ expect(html).toContain("agentdeck-sidebar");
+ expect(html).toContain("agentdeck-work-area");
+ expect(html).toContain("agentdeck-inspector");
+ });
+
+ it("includes dense workspace navigation entries", () => {
+ expect(APP_NAV_ITEMS.map((item) => item.label)).toEqual([
+ "Chat",
+ "Agents",
+ "Workflows",
+ "Tasks",
+ "Runtimes",
+ "Audit",
+ "Settings",
+ ]);
+ });
+
+ it("uses stable panel widths for desktop layout", () => {
+ expect(APP_SHELL_PANEL_WIDTHS).toEqual({
+ sidebar: "236px",
+ inspector: "320px",
+ });
+ expect(renderAppShellHtml()).toContain("grid-template-columns:236px minmax(0,1fr) 320px");
+ });
+});
diff --git a/apps/web/src/components/app-shell.tsx b/apps/web/src/components/app-shell.tsx
new file mode 100644
index 0000000..0780214
--- /dev/null
+++ b/apps/web/src/components/app-shell.tsx
@@ -0,0 +1,5 @@
+import { renderAppShellHtml } from "./app-shell-model.js";
+
+export function AppShell(): string {
+ return renderAppShellHtml();
+}
diff --git a/apps/web/src/components/inspector.tsx b/apps/web/src/components/inspector.tsx
new file mode 100644
index 0000000..784bb5f
--- /dev/null
+++ b/apps/web/src/components/inspector.tsx
@@ -0,0 +1,10 @@
+export function renderInspectorHtml(): string {
+ return ``;
+}
diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx
new file mode 100644
index 0000000..27b8d8d
--- /dev/null
+++ b/apps/web/src/components/sidebar.tsx
@@ -0,0 +1,21 @@
+export interface NavItem {
+ readonly id: string;
+ readonly label: string;
+}
+
+export const APP_NAV_ITEMS: readonly NavItem[] = [
+ { id: "chat", label: "Chat" },
+ { id: "agents", label: "Agents" },
+ { id: "workflows", label: "Workflows" },
+ { id: "tasks", label: "Tasks" },
+ { id: "runtimes", label: "Runtimes" },
+ { id: "audit", label: "Audit" },
+ { id: "settings", label: "Settings" },
+];
+
+export function renderSidebarHtml(activeItemId = "chat"): string {
+ return ``;
+}
diff --git a/apps/web/src/index.ts b/apps/web/src/index.ts
index 81e1b23..8af70a2 100644
--- a/apps/web/src/index.ts
+++ b/apps/web/src/index.ts
@@ -1 +1,11 @@
export const webAppName = "@agentdeck/web";
+
+export { AppShell } from "./components/app-shell.js";
+export {
+ APP_NAV_ITEMS,
+ APP_SHELL_PANEL_WIDTHS,
+ renderAppShellHtml,
+} from "./components/app-shell-model.js";
+export { renderInspectorHtml } from "./components/inspector.js";
+export { renderSidebarHtml } from "./components/sidebar.js";
+export type { NavItem } from "./components/sidebar.js";
diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css
new file mode 100644
index 0000000..e20e91d
--- /dev/null
+++ b/apps/web/src/styles/globals.css
@@ -0,0 +1,173 @@
+:root {
+ color: #172026;
+ background: #f4f6f7;
+ font-family:
+ Inter,
+ ui-sans-serif,
+ system-ui,
+ -apple-system,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+}
+
+.agentdeck-shell {
+ min-height: 100vh;
+ background: #f4f6f7;
+}
+
+.agentdeck-topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 48px;
+ padding: 0 16px;
+ border-bottom: 1px solid #d9e0e4;
+ background: #ffffff;
+}
+
+.agentdeck-brand {
+ font-size: 15px;
+ font-weight: 700;
+}
+
+.agentdeck-topbar-meta {
+ color: #66747d;
+ font-size: 12px;
+}
+
+.agentdeck-layout {
+ display: grid;
+ min-height: calc(100vh - 48px);
+}
+
+.agentdeck-sidebar,
+.agentdeck-inspector {
+ background: #ffffff;
+}
+
+.agentdeck-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding: 10px;
+ border-right: 1px solid #d9e0e4;
+}
+
+.agentdeck-nav-item {
+ display: block;
+ padding: 8px 10px;
+ border-radius: 6px;
+ color: #28343b;
+ font-size: 13px;
+ text-decoration: none;
+}
+
+.agentdeck-nav-item.is-active {
+ background: #e6f0ed;
+ color: #0e4f43;
+ font-weight: 700;
+}
+
+.agentdeck-work-area {
+ min-width: 0;
+ padding: 16px;
+}
+
+.agentdeck-work-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ padding-bottom: 14px;
+ border-bottom: 1px solid #d9e0e4;
+}
+
+.agentdeck-work-header h1 {
+ margin: 0 0 4px;
+ font-size: 20px;
+}
+
+.agentdeck-work-header p {
+ max-width: 720px;
+ margin: 0;
+ color: #5c6870;
+ font-size: 13px;
+}
+
+.agentdeck-work-header button {
+ min-width: 88px;
+ height: 32px;
+ border: 1px solid #0e4f43;
+ border-radius: 6px;
+ background: #0e4f43;
+ color: #ffffff;
+ font-weight: 700;
+}
+
+.agentdeck-work-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(120px, 1fr));
+ gap: 10px;
+ margin-top: 14px;
+}
+
+.agentdeck-work-grid article {
+ min-height: 84px;
+ padding: 10px;
+ border: 1px solid #d9e0e4;
+ border-radius: 8px;
+ background: #ffffff;
+}
+
+.agentdeck-work-grid h2 {
+ margin: 0;
+ font-size: 13px;
+}
+
+.agentdeck-work-grid p {
+ margin: 8px 0 0;
+ color: #66747d;
+ font-size: 12px;
+}
+
+.agentdeck-inspector {
+ padding: 12px;
+ border-left: 1px solid #d9e0e4;
+}
+
+.agentdeck-panel-title {
+ margin-bottom: 12px;
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.agentdeck-inspector-list {
+ display: grid;
+ gap: 10px;
+ margin: 0;
+}
+
+.agentdeck-inspector-list div {
+ padding-bottom: 10px;
+ border-bottom: 1px solid #edf1f3;
+}
+
+.agentdeck-inspector-list dt {
+ color: #66747d;
+ font-size: 11px;
+}
+
+.agentdeck-inspector-list dd {
+ margin: 3px 0 0;
+ font-size: 13px;
+}
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 1155e6e..75cd1aa 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -2,8 +2,9 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
- "outDir": "dist"
+ "outDir": "dist",
+ "jsx": "preserve"
},
- "include": ["src/**/*.ts"],
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["dist", "node_modules"]
}
diff --git a/docs/development/task-backlog.md b/docs/development/task-backlog.md
index a46e4ff..e879e56 100644
--- a/docs/development/task-backlog.md
+++ b/docs/development/task-backlog.md
@@ -424,10 +424,10 @@ Status markers:
**Tasks:**
-- [ ] Implement top bar, left sidebar, main work area, and right inspector.
-- [ ] Add navigation entries for Chat, Agents, Workflows, Tasks, Runtimes, Audit, and Settings.
-- [ ] Use dense tool-style layout with stable panel widths.
-- [ ] Commit with message `feat: add web app shell`.
+- [x] Implement top bar, left sidebar, main work area, and right inspector.
+- [x] Add navigation entries for Chat, Agents, Workflows, Tasks, Runtimes, Audit, and Settings.
+- [x] Use dense tool-style layout with stable panel widths.
+- [x] Commit with message `feat: add web app shell`.
**Acceptance Criteria:**