From 9dd6c6f1858cd0fed3a4b34051f3d16829f04906 Mon Sep 17 00:00:00 2001 From: jaylfc Date: Tue, 16 Jun 2026 19:27:59 +0100 Subject: [PATCH 1/7] feat(store): add Studios section to Store app - Add "studios" to NavId union and NAV array (after "apps", Sparkles icon) - Add studioState field to CatalogApp type for installed/available/soon lifecycle - Add STUDIOS_APPS catalog entries (type "studio") with cover gradients - Add NAV_TYPE_MAP entry for "studios" -> ["studio"] - Wire StudiosView into the content switcher (alongside discover/community) - New StudiosView component: hero card (Coding Studio), 4x2 taOS Studios grid with Soon badges, community studios horizontal scroll, layout chips row - StudiosView.test.tsx: 6 tests covering headings, cards, Soon badges, hero --- .../src/apps/StoreApp/StudiosView.test.tsx | 61 +++ desktop/src/apps/StoreApp/StudiosView.tsx | 486 ++++++++++++++++++ desktop/src/apps/StoreApp/index.tsx | 12 +- desktop/src/apps/StoreApp/types.ts | 2 + 4 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 desktop/src/apps/StoreApp/StudiosView.test.tsx create mode 100644 desktop/src/apps/StoreApp/StudiosView.tsx diff --git a/desktop/src/apps/StoreApp/StudiosView.test.tsx b/desktop/src/apps/StoreApp/StudiosView.test.tsx new file mode 100644 index 00000000..024b5548 --- /dev/null +++ b/desktop/src/apps/StoreApp/StudiosView.test.tsx @@ -0,0 +1,61 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { StudiosView } from "./StudiosView"; + +afterEach(() => { cleanup(); }); + +describe("StudiosView", () => { + it("renders the three section headings", () => { + render(); + expect(screen.getByText("taOS Studios")).toBeInTheDocument(); + expect(screen.getByText("Community Studios")).toBeInTheDocument(); + expect(screen.getByText("Studio layouts")).toBeInTheDocument(); + }); + + it("renders all taOS studio card names", () => { + render(); + const expected = [ + "Images Studio", + "Game Studio", + "Coding Studio", + "Design Studio", + "Music Studio", + "App Studio", + "Office Suite", + "Web Studio", + ]; + for (const name of expected) { + expect(screen.getAllByText(name).length).toBeGreaterThan(0); + } + }); + + it("shows Soon badges on the unreleased studios", () => { + render(); + // 5 studios are "soon": Design, Music, App, Office, Web + const soonBadges = screen.getAllByText("Soon"); + expect(soonBadges.length).toBe(5); + }); + + it("shows Coding Studio in the hero section with the featured eyebrow", () => { + render(); + // The eyebrow text calls it out as featured + expect(screen.getByText(/Featured/i)).toBeInTheDocument(); + // Coding Studio name appears at least twice (hero heading + grid card) + expect(screen.getAllByText("Coding Studio").length).toBeGreaterThanOrEqual(2); + }); + + it("renders community studio names", () => { + render(); + expect(screen.getByText("Pixel Art Studio")).toBeInTheDocument(); + expect(screen.getByText("Lo-fi Beats Kit")).toBeInTheDocument(); + expect(screen.getByText("API Forge")).toBeInTheDocument(); + expect(screen.getByText("Retro FPS Kit")).toBeInTheDocument(); + }); + + it("renders layout chip names", () => { + render(); + expect(screen.getByText("Photo Retoucher")).toBeInTheDocument(); + expect(screen.getByText("Chiptune")).toBeInTheDocument(); + expect(screen.getByText("Static Site Kit")).toBeInTheDocument(); + }); +}); diff --git a/desktop/src/apps/StoreApp/StudiosView.tsx b/desktop/src/apps/StoreApp/StudiosView.tsx new file mode 100644 index 00000000..40767669 --- /dev/null +++ b/desktop/src/apps/StoreApp/StudiosView.tsx @@ -0,0 +1,486 @@ +import { Check, Star } from "lucide-react"; +import type { CatalogApp } from "./types"; + +/* ------------------------------------------------------------------ + Studio catalog entries (first-party, type "studio") + ------------------------------------------------------------------ */ + +const STUDIOS: CatalogApp[] = [ + { + id: "images-studio", + name: "Images Studio", + type: "studio", + category: "Creative", + version: "1.0.0", + description: "Make and edit images on your own GPU, NPU, or CPU.", + tagline: "Generate, edit, upscale", + installed: true, + compat: "green", + studioState: "installed", + cover: "radial-gradient(120% 120% at 30% 20%,#3a3357,transparent 60%),linear-gradient(140deg,#211d30,#14121b)", + }, + { + id: "game-studio", + name: "Game Studio", + type: "studio", + category: "Creative", + version: "1.0.0", + description: "Describe a 3D game and play it in the browser. Runs entirely offline.", + tagline: "Offline AI game maker", + installed: true, + compat: "green", + studioState: "installed", + cover: "radial-gradient(120% 120% at 70% 25%,#1f4a4f,transparent 60%),linear-gradient(140deg,#10242a,#0c181c)", + }, + { + id: "coding-studio", + name: "Coding Studio", + type: "studio", + category: "Dev", + version: "1.0.0", + description: "An agent writes, runs, and previews your app on the cluster.", + tagline: "Describe, build, preview", + installed: false, + compat: "green", + studioState: "available", + cover: "radial-gradient(120% 120% at 35% 25%,#34384a,transparent 60%),linear-gradient(140deg,#1b1d27,#121319)", + }, + { + id: "design-studio", + name: "Design Studio", + type: "studio", + category: "Creative", + version: "0.0.0", + description: "Canva-style design with AI, on a shared canvas engine.", + tagline: "Graphics and layouts", + installed: false, + compat: "green", + studioState: "soon", + cover: "radial-gradient(120% 120% at 65% 20%,#4a3a4f,transparent 60%),linear-gradient(140deg,#241a27,#16121a)", + }, + { + id: "music-studio", + name: "Music Studio", + type: "studio", + category: "Creative", + version: "0.0.0", + description: "Compose, arrange, and generate audio on your hardware.", + tagline: "Web DAW with AI", + installed: false, + compat: "green", + studioState: "soon", + cover: "radial-gradient(120% 120% at 30% 25%,#2f4a3a,transparent 60%),linear-gradient(140deg,#16271d,#101a14)", + }, + { + id: "app-studio", + name: "App Studio", + type: "studio", + category: "Dev", + version: "0.0.0", + description: "Build and share new taOS apps, sandboxed and safe.", + tagline: "taOS app builder", + installed: false, + compat: "green", + studioState: "soon", + cover: "radial-gradient(120% 120% at 70% 25%,#3a4150,transparent 60%),linear-gradient(140deg,#1c1f28,#13151b)", + }, + { + id: "office-suite", + name: "Office Suite", + type: "studio", + category: "Productivity", + version: "0.0.0", + description: "Documents, spreadsheets, and presentations with AI.", + tagline: "Write, Calc, Slides", + installed: false, + compat: "green", + studioState: "soon", + cover: "radial-gradient(120% 120% at 35% 20%,#4a4632,transparent 60%),linear-gradient(140deg,#262216,#181610)", + }, + { + id: "web-studio", + name: "Web Studio", + type: "studio", + category: "Dev", + version: "0.0.0", + description: "Build sites with templates, host them on your LAN.", + tagline: "AI website builder", + installed: false, + compat: "green", + studioState: "soon", + cover: "radial-gradient(120% 120% at 65% 25%,#324a47,transparent 60%),linear-gradient(140deg,#152624,#0f1817)", + }, +]; + +/* ------------------------------------------------------------------ + Community studios (static mock) + ------------------------------------------------------------------ */ + +interface CommunityStudio { + id: string; + name: string; + badge: "Fork" | "Layout"; + parent: string; + description: string; + stars: number; + cover: string; +} + +const COMMUNITY_STUDIOS: CommunityStudio[] = [ + { + id: "pixel-art-studio", + name: "Pixel Art Studio", + badge: "Fork", + parent: "Images Studio fork", + description: "Palette-locked, grid-snapped sprite workflow.", + stars: 1200, + cover: "radial-gradient(120% 120% at 30% 20%,#3f3357,transparent 60%),linear-gradient(140deg,#221c30,#15111b)", + }, + { + id: "lofi-beats-kit", + name: "Lo-fi Beats Kit", + badge: "Layout", + parent: "Music Studio layout", + description: "Boom-bap drum rack, vinyl FX, swing presets.", + stars: 860, + cover: "radial-gradient(120% 120% at 70% 25%,#2f4a3a,transparent 60%),linear-gradient(140deg,#16271d,#101a14)", + }, + { + id: "api-forge", + name: "API Forge", + badge: "Layout", + parent: "Coding Studio layout", + description: "FastAPI scaffold, request runner, schema view.", + stars: 2400, + cover: "radial-gradient(120% 120% at 35% 25%,#3a4150,transparent 60%),linear-gradient(140deg,#1c1f28,#13151b)", + }, + { + id: "retro-fps-kit", + name: "Retro FPS Kit", + badge: "Fork", + parent: "Game Studio fork", + description: "Doom-style raycaster templates and assets.", + stars: 970, + cover: "radial-gradient(120% 120% at 65% 20%,#4a3340,transparent 60%),linear-gradient(140deg,#271620,#180f14)", + }, +]; + +/* ------------------------------------------------------------------ + Layout chips (static mock) + ------------------------------------------------------------------ */ + +interface LayoutChip { + id: string; + name: string; + parent: string; + author: string; + iconGradient: string; +} + +const LAYOUT_CHIPS: LayoutChip[] = [ + { + id: "photo-retoucher", + name: "Photo Retoucher", + parent: "Images Studio", + author: "@mara", + iconGradient: "linear-gradient(135deg,#6f7687,#474d5e)", + }, + { + id: "chiptune", + name: "Chiptune", + parent: "Music Studio", + author: "@ben", + iconGradient: "linear-gradient(135deg,#5f8a6f,#456f54)", + }, + { + id: "static-site-kit", + name: "Static Site Kit", + parent: "Coding Studio", + author: "@ivo", + iconGradient: "linear-gradient(135deg,#5d7a8a,#46606c)", + }, +]; + +function formatStars(n: number): string { + if (n >= 1000) return `${(n / 1000).toFixed(1)}k`; + return String(n); +} + +/* ------------------------------------------------------------------ + Hero card for the featured studio (Coding Studio) + ------------------------------------------------------------------ */ + +function StudioHero({ studio }: { studio: CatalogApp }) { + return ( +
+
+
+
+
+ Featured -- New Studio +
+

+ {studio.name} +

+

+ {studio.description} +

+
+ + + + {studio.category} -- runs on cluster +
+
+
+ ); +} + +/* ------------------------------------------------------------------ + Studio card (grid) + ------------------------------------------------------------------ */ + +function StudioCard({ studio }: { studio: CatalogApp }) { + const isSoon = studio.studioState === "soon"; + const isInstalled = studio.studioState === "installed"; + + return ( +
+ {/* Cover */} +
+ + {studio.category} + + {isSoon && ( + + Soon + + )} +
+ {/* Meta */} +
+
+
{studio.name}
+
{studio.tagline}
+
+

{studio.description}

+
+ {isInstalled ? ( + + Installed + + ) : isSoon ? ( + In design + ) : ( + New + )} + {isInstalled ? ( + + ) : isSoon ? ( + + ) : ( + + )} +
+
+
+ ); +} + +/* ------------------------------------------------------------------ + Community studio card (horizontal scroll row) + ------------------------------------------------------------------ */ + +function CommunityStudioCard({ item }: { item: CommunityStudio }) { + return ( +
+
+ + {item.badge} + +
+
+
+
{item.name}
+
{item.parent}
+
+

{item.description}

+
+ + + {formatStars(item.stars)} + + +
+
+
+ ); +} + +/* ------------------------------------------------------------------ + Layout chip + ------------------------------------------------------------------ */ + +function LayoutChipCard({ chip }: { chip: LayoutChip }) { + return ( +
+ + ); +} + +/* ------------------------------------------------------------------ + Section header + ------------------------------------------------------------------ */ + +function SectionHeader({ + title, + sub, + action, +}: { + title: string; + sub?: string; + action?: string; +}) { + return ( +
+
+

{title}

+ {sub && {sub}} +
+ {action && ( + + )} +
+ ); +} + +/* ------------------------------------------------------------------ + StudiosView + ------------------------------------------------------------------ */ + +export function StudiosView() { + const hero = STUDIOS.find((s) => s.id === "coding-studio")!; + + return ( +
+ {/* Hero */} + + + {/* taOS Studios grid */} +
+ +
+ {STUDIOS.map((s) => ( + + ))} +
+
+ + {/* Community Studios */} +
+ +
+ {COMMUNITY_STUDIOS.map((item) => ( + + ))} +
+
+ + {/* Studio layouts */} +
+ +
+ {LAYOUT_CHIPS.map((chip) => ( + + ))} +
+
+
+ ); +} + +export default StudiosView; diff --git a/desktop/src/apps/StoreApp/index.tsx b/desktop/src/apps/StoreApp/index.tsx index 92f4c6f5..7e2169cb 100644 --- a/desktop/src/apps/StoreApp/index.tsx +++ b/desktop/src/apps/StoreApp/index.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { Search, Download, Trash2, Check, Package, Loader2, Server, Compass, Grid2x2, Bot, Brain, Plug, Wrench, Star, Globe, - ArrowDownToLine, RefreshCw, Users, Cpu, + ArrowDownToLine, RefreshCw, Users, Cpu, Sparkles, } from "lucide-react"; import { Input } from "@/components/ui"; import { fetchLatestFrameworks, LatestVersion } from "@/lib/framework-api"; @@ -19,6 +19,7 @@ import { TaosAppsSection } from "./TaosAppsSection"; import { useIsMobile } from "@/hooks/use-is-mobile"; import { MobileStore } from "./MobileStore"; import { AppIcon, StoreCover } from "./AppIcon"; +import { StudiosView } from "./StudiosView"; /* ------------------------------------------------------------------ Nav sections @@ -27,6 +28,7 @@ import { AppIcon, StoreCover } from "./AppIcon"; type NavId = | "discover" | "apps" + | "studios" | "agents" | "models" | "services" @@ -46,6 +48,7 @@ interface NavItem { const NAV: NavItem[] = [ { id: "discover", label: "Discover", icon: }, { id: "apps", label: "Apps", icon: }, + { id: "studios", label: "Studios", icon: }, { id: "agents", label: "Agents", icon: }, { id: "models", label: "Models", icon: }, { id: "services", label: "Services", icon: }, @@ -957,6 +960,7 @@ export function StoreApp({ windowId: _windowId }: { windowId: string }) { const NAV_TYPE_MAP: Record = { discover: [], apps: ["streaming-app", "ai-app", "productivity", "home", "monitoring", "automation", "image-gen", "voice", "video-gen", "plugin"], + studios: ["studio"], agents: ["agent-framework"], models: ["model", "llm-runtime"], services: ["service", "infrastructure"], @@ -1027,9 +1031,9 @@ export function StoreApp({ windowId: _windowId }: { windowId: string }) { const profileSub = primaryTarget?.tier_id ? primaryTarget.tier_id.replace(/-/g, " ") : "Connect a device"; // When the user is searching, show the results grid even on the curated - // Discover/Community views (which otherwise ignore the search box). + // Discover/Community/Studios views (which otherwise ignore the search box). const searching = search.trim().length > 0; - const showGrid = searching || (activeNav !== "discover" && activeNav !== "community"); + const showGrid = searching || (activeNav !== "discover" && activeNav !== "community" && activeNav !== "studios"); // Mobile reads like the Apple App Store: bottom tab bar, full-width feed, // snap-scroll carousels and a full-screen search. Same data and install @@ -1131,6 +1135,8 @@ export function StoreApp({ windowId: _windowId }: { windowId: string }) {
) : activeNav === "community" && !searching ? ( + ) : activeNav === "studios" && !searching ? ( + ) : activeNav === "discover" && !searching ? ( ) : showGrid ? ( diff --git a/desktop/src/apps/StoreApp/types.ts b/desktop/src/apps/StoreApp/types.ts index 3dbe9b6e..a98fbd43 100644 --- a/desktop/src/apps/StoreApp/types.ts +++ b/desktop/src/apps/StoreApp/types.ts @@ -33,6 +33,8 @@ export interface CatalogApp { coverImage?: string; /** True when an installed app has a newer version available (drives Updates). */ update_available?: boolean; + /** Studios-specific lifecycle state. "soon" hides install and shows a badge. */ + studioState?: "installed" | "available" | "soon"; } export interface InstallTarget { From 7e03e5101e99303452c2763f288a869bf97b6421 Mon Sep 17 00:00:00 2001 From: jaylfc Date: Tue, 16 Jun 2026 19:35:17 +0100 Subject: [PATCH 2/7] feat(studios): add Coding Studio optional desktop app Adds CodingStudioApp as an optional platform app with three views: BuildView (file tree + syntax editor + build log panel), TemplatesView (hero prompt + 8 template cards), and PreviewView (URL bar + device toggle + simulated todo app + dev console). Registered in app-registry at launchpadOrder 13.25, optional: true. Includes 6 vitest tests covering titlebar, rail items, and view switching. --- desktop/src/apps/CodingStudioApp.test.tsx | 71 ++++ desktop/src/apps/CodingStudioApp.tsx | 74 ++++ desktop/src/apps/codingstudio/BuildView.tsx | 384 ++++++++++++++++++ desktop/src/apps/codingstudio/PreviewView.tsx | 316 ++++++++++++++ .../src/apps/codingstudio/TemplatesView.tsx | 158 +++++++ desktop/src/registry/app-registry.ts | 1 + 6 files changed, 1004 insertions(+) create mode 100644 desktop/src/apps/CodingStudioApp.test.tsx create mode 100644 desktop/src/apps/CodingStudioApp.tsx create mode 100644 desktop/src/apps/codingstudio/BuildView.tsx create mode 100644 desktop/src/apps/codingstudio/PreviewView.tsx create mode 100644 desktop/src/apps/codingstudio/TemplatesView.tsx diff --git a/desktop/src/apps/CodingStudioApp.test.tsx b/desktop/src/apps/CodingStudioApp.test.tsx new file mode 100644 index 00000000..d899bced --- /dev/null +++ b/desktop/src/apps/CodingStudioApp.test.tsx @@ -0,0 +1,71 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { CodingStudioApp } from "./CodingStudioApp"; + +function renderApp() { + return render(); +} + +describe("CodingStudioApp", () => { + it("renders the app titlebar", () => { + renderApp(); + expect(screen.getByText("Coding Studio")).toBeDefined(); + }); + + it("renders all rail items", () => { + renderApp(); + // Rail buttons use aria-label for exact matching via the nav element + const nav = screen.getByRole("navigation", { name: "Coding Studio views" }); + expect(nav).toBeDefined(); + expect(screen.getByRole("button", { name: "Code" })).toBeDefined(); + expect(screen.getByRole("button", { name: "Preview" })).toBeDefined(); + expect(screen.getByRole("button", { name: "Templates" })).toBeDefined(); + expect(screen.getByRole("button", { name: "Models" })).toBeDefined(); + }); + + it("shows Build view by default with Build rail item active", () => { + renderApp(); + // The rail Build button (inside nav) should be aria-current="page" + const nav = screen.getByRole("navigation", { name: "Coding Studio views" }); + const railBuildBtn = nav.querySelector('[aria-label="Build"]') as HTMLElement; + expect(railBuildBtn).toBeTruthy(); + expect(railBuildBtn.getAttribute("aria-current")).toBe("page"); + }); + + it("switches to Templates view on rail click", () => { + renderApp(); + fireEvent.click(screen.getByRole("button", { name: "Templates" })); + expect(screen.getByRole("button", { name: "Templates" }).getAttribute("aria-current")).toBe( + "page", + ); + expect(screen.getByText("Describe what you want to build.")).toBeDefined(); + }); + + it("Templates view shows all 8 template cards", () => { + renderApp(); + fireEvent.click(screen.getByRole("button", { name: "Templates" })); + const expectedNames = [ + "Web App", + "REST API", + "CLI Tool", + "Discord Bot", + "Static Site", + "Data Pipeline", + "Python Script", + "Browser Extension", + ]; + for (const name of expectedNames) { + expect(screen.getByText(name)).toBeDefined(); + } + }); + + it("switches to Preview view on rail click and shows preview header", () => { + renderApp(); + fireEvent.click(screen.getByRole("button", { name: "Preview" })); + expect(screen.getByRole("button", { name: "Preview" }).getAttribute("aria-current")).toBe( + "page", + ); + // Preview header h2 + expect(screen.getByRole("heading", { name: "Preview" })).toBeDefined(); + }); +}); diff --git a/desktop/src/apps/CodingStudioApp.tsx b/desktop/src/apps/CodingStudioApp.tsx new file mode 100644 index 00000000..7e7a35c2 --- /dev/null +++ b/desktop/src/apps/CodingStudioApp.tsx @@ -0,0 +1,74 @@ +import { useState } from "react"; +import { Sparkles, Code2, Play, LayoutGrid, Settings2 } from "lucide-react"; +import { BuildView } from "./codingstudio/BuildView"; +import { TemplatesView } from "./codingstudio/TemplatesView"; +import { PreviewView } from "./codingstudio/PreviewView"; + +type CodingView = "build" | "code" | "preview" | "templates"; + +const RAIL: { id: CodingView; label: string; icon: typeof Sparkles }[] = [ + { id: "build", label: "Build", icon: Sparkles }, + { id: "code", label: "Code", icon: Code2 }, + { id: "preview", label: "Preview", icon: Play }, + { id: "templates", label: "Templates", icon: LayoutGrid }, +]; + +export function CodingStudioApp({ windowId: _windowId }: { windowId: string }) { + const [view, setView] = useState("build"); + + return ( +
+ {/* title strip */} +
+ Coding Studio +
+ +
+ {/* left rail */} + + + {/* active surface */} +
+ {view === "build" && } + {view === "code" && } + {view === "preview" && } + {view === "templates" && } +
+
+
+ ); +} diff --git a/desktop/src/apps/codingstudio/BuildView.tsx b/desktop/src/apps/codingstudio/BuildView.tsx new file mode 100644 index 00000000..95088773 --- /dev/null +++ b/desktop/src/apps/codingstudio/BuildView.tsx @@ -0,0 +1,384 @@ +import { + Sparkles, + Folder, + FileText, + ClipboardCheck, + Check, + Circle, + ChevronDown, + Play, +} from "lucide-react"; + +const CODE_LINES = [ + { + n: 1, + jsx: ( + <> + import + {" { useTodos } "} + from + {" "} + {"'./useTodos'"} + + ), + }, + { + n: 2, + jsx: ( + <> + import + {" { TodoList } "} + from + {" "} + {"'./TodoList'"} + + ), + }, + { + n: 3, + jsx: ( + + {"// added by taOS - persists to localStorage"} + + ), + }, + { n: 4, jsx: <> }, + { + n: 5, + jsx: ( + <> + export default function + {" "} + App + {"() {"} + + ), + }, + { + n: 6, + jsx: ( + <> + {" "} + const + {" { todos, add, toggle, remaining } = "} + useTodos + {"()"} + + ), + }, + { + n: 7, + jsx: ( + <> + {" "} + return + {" ("} + + ), + }, + { + n: 8, + jsx: ( + <> + {" <"} + main + {" "} + className + {"="} + "app" + {">"} + + ), + }, + { + n: 9, + jsx: ( + <> + {" <"} + header + {">My Tasks <"} + small + {">{remaining} leftsmall + {">header + {">"} + + ), + }, + { + n: 10, + jsx: ( + <> + {" <"} + TodoList + {" "} + items + {"={todos} "} + onToggle + {"={toggle} />"} + + ), + }, + { + n: 11, + jsx: ( + <> + {" <"} + AddTodo + {" "} + onAdd + {"={add} />"} + + ), + }, + { + n: 12, + jsx: ( + <> + {" main + {">"} + + ), + }, + { n: 13, jsx: <>{" )"} }, + { n: 14, jsx: <>{"}"} }, +]; + +const BUILD_STEPS = [ + { status: "done" as const, title: "Scaffold Vite + React + TS", meta: "7 files - 1.4s" }, + { status: "done" as const, title: "Write TodoList + AddTodo", meta: "components/ - 2 files" }, + { status: "running" as const, title: "Add useTodos hook", meta: "wiring localStorage persistence..." }, + { status: "queued" as const, title: "Install dependencies", meta: "npm i - queued" }, + { status: "queued" as const, title: "Start dev server + preview", meta: "queued" }, +]; + +function StepIcon({ status }: { status: "done" | "running" | "queued" }) { + if (status === "done") { + return ( +
+ +
+ ); + } + if (status === "running") { + return ( +
+
+
+ ); + } + return ( +
+ +
+ ); +} + +export function BuildView() { + return ( + <> + + + {/* view header */} +
+

Build

+ + todo-app - Node + React - runs on fedora-gpu + +
+ + Chat + + + Diff + +
+
+ + {/* three-column body */} +
+ {/* column 1: file tree */} +
+
+ todo-app +
+ + {/* src folder */} +
+ + src +
+ + {/* App.tsx - active */} +
+ + App.tsx +
+
+ +
+ + TodoList.tsx +
+ +
+ + useTodos.ts +
+ +
+ + styles.css +
+ + {/* root files */} +
+ + index.html +
+ +
+ + package.json +
+ +
+ + vite.config.ts +
+
+ + {/* column 2: editor */} +
+ {/* tabs bar */} +
+
+ App.tsx + x +
+
+ useTodos.ts + x +
+
+ + {/* code area */} +
+ {CODE_LINES.map(({ n, jsx }) => ( +
+ + {n} + + + {jsx} + {n === 10 && ( + + )} + +
+ ))} +
+ + {/* terminal strip */} +
+
+ ~/todo-app + {" $ npm run dev"} +
+
+ VITE v5.4 + {" ready in 412 ms"} +
+
+ {"> Local: "} + http://todo-app.taos.local + {" "} + live +
+
+
+ + {/* column 3: build log */} +
+ {/* header */} +
+ + Build log +
+ + {/* steps */} +
+ {BUILD_STEPS.map((step) => ( +
+ +
+
{step.title}
+
+ {step.meta} +
+
+
+ ))} +
+ + {/* footer */} +
+ {/* model pill */} +
+
+
+
Qwen2.5-Coder 7B
+
local - fedora-gpu
+
+ +
+ + {/* open preview button */} +
+ + Open live preview +
+
+
+
+ + {/* prompt bar */} +
+