// The macOS desktop scene: menu bar, dock, draggable app windows // and their simulated contents (what Puck patrols). import * as React from "react"; import type { EffectId } from "../engine"; import { type Pos, useDrag } from "../lib/useDrag"; export interface WinState { claude: "running" | "done"; build: "running" | "done" | "fail"; mail: string[]; discordNoise: boolean; discordMention: boolean; calSoon: boolean; } export const initialWinState = (): WinState => ({ claude: "running", build: "running", mail: ["hf"], discordNoise: true, discordMention: false, calSoon: false, }); /** How an engine effect mutates the simulated windows. Pure; colocated with WinState. */ export function applyEffect(w: WinState, eff: EffectId): WinState { switch (eff) { case "claude_done": return { ...w, claude: "done" }; case "build_done": return { ...w, build: "done" }; case "build_fail": return { ...w, build: "fail" }; case "mail_unread": return { ...w, mail: Array.from(new Set([...w.mail, w.mail.includes("hf") ? "fin" : "hf"])) }; case "discord_mention": return { ...w, discordMention: true }; case "discord_noise": return { ...w, discordNoise: true }; case "calendar_soon": return { ...w, calSoon: true }; case "none": return w; } } // ---- menu bar --------------------------------------------------------------- export function MenuBar({ moodLabel, puckActive, badge, onPuck, clock, }: { moodLabel: string; puckActive: boolean; badge: number; onPuck: () => void; clock: string; }) { return (
Finder File Edit View Go Window
􀙇 􀛨 􀊫 {clock}
); } // ---- dock --------------------------------------------------------------------- export interface DockApp { id: string; name: string; glyph: string; running?: boolean; badge?: number; tint?: string; } export function Dock({ apps, onOpen }: { apps: DockApp[]; onOpen: (id: string) => void }) { return (
{apps.map((a) => (
onOpen(a.id)} style={a.tint ? { color: a.tint } : undefined} > {a.glyph} {(a.badge ?? 0) > 0 && {a.badge}}
))}
); } // ---- generic draggable window ------------------------------------------------- function AppWindow({ id, pos, onPos, w, h, title, icon, sub, attn, dimmed, onFocus, z, children, }: { id: string; pos: Pos; onPos: (id: string, p: Pos) => void; w: number; h: number; title: string; icon: string; sub?: string; attn: boolean; dimmed: boolean; onFocus: (id: string) => void; z: number; children: React.ReactNode; }) { const drag = useDrag(pos, (p) => onPos(id, p)); return (
onFocus(id)} >
{ onFocus(id); drag(e); }} >
{icon} {title}
{sub &&
{sub}
}
{children}
); } // ---- window bodies -------------------------------------------------------- function ClaudeBody({ st }: { st: WinState }) { const done = st.claude === "done"; return (
⏵ implement the memory garden panel
edit src/garden/Plot.tsx +48 −6
edit src/garden/seed.ts +12 −0
write src/garden/decay.ts new
{done ? : } {done ? "Memory Garden — completed · 7 files" : "Memory Garden — running…"} {done ? "done" : "····"}
); } function TermBody({ st }: { st: WinState }) { const s = st.build; return (
vu@workshop ~/puck $ npm run build
vite v5.3.1 building for production…
transforming modules ·····
{s === "running" && ( <>
bundling…
)} {s === "done" && ( <>
✓ built in 2m 14s — 0 errors
)} {s === "fail" && ( <>
✗ 3 tests failing — auth.test.ts
expected 200, received 401
)}
); } interface ListRow { from: string; sub: string; time: string; unread?: boolean; mention?: boolean; noise?: boolean; muted?: boolean; av: string; } function MsgList({ rows }: { rows: ListRow[] }) { return (
{rows.map((r) => (
{r.av}
{r.from} {r.time}
{r.sub}
))}
); } function MailBody({ st }: { st: WinState }) { return ( ); } function DiscordBody({ st }: { st: WinState }) { return ( ); } function CalBody({ st }: { st: WinState }) { return (
Today · Thu Jun 5
Demo sync
{st.calSoon ? "in 10 min · 11:00" : "11:00 – 11:30"}
Deep work — Puck UI
14:00 – 16:00
); } // ---- the set of windows ----------------------------------------------------- interface WinDef { id: string; title: string; icon: string; sub: string; w: number; h: number; Body: React.ComponentType<{ st: WinState }>; } export const WIN_DEFS: WinDef[] = [ { id: "claude", title: "Claude Code", icon: "✳", sub: "Desktop 2", w: 432, h: 296, Body: ClaudeBody }, { id: "terminal", title: "workshop — build", icon: "▦", sub: "zsh", w: 404, h: 224, Body: TermBody }, { id: "mail", title: "Mail", icon: "✉", sub: "Inbox", w: 364, h: 318, Body: MailBody }, { id: "discord", title: "Discord", icon: "◍", sub: "Social Bog", w: 332, h: 236, Body: DiscordBody }, { id: "calendar", title: "Calendar", icon: "◷", sub: "", w: 252, h: 184, Body: CalBody }, ]; // memo: SimApp re-renders every sprite flight; the window scene only depends on these props export const DesktopWindows = React.memo(function DesktopWindows({ positions, onPos, onFocus, focused, attnId, winState, }: { positions: Record; onPos: (id: string, p: Pos) => void; onFocus: (id: string) => void; focused: string | null; attnId: string | null; winState: WinState; }) { return ( <> {WIN_DEFS.map((d, i) => ( ))} ); });