diff --git a/README.md b/README.md index 8530f4a..ad60548 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,9 @@ git clone cd dune corepack enable make deploy -make run +make dev ``` -Open [http://localhost:3100](http://localhost:3100). - `make deploy` installs dependencies, creates `.env` from `.env.example` if needed, and builds all packages. Before agents can actually respond, configure a model provider in `Settings > Model`. ## Common Commands @@ -40,12 +38,8 @@ Open [http://localhost:3100](http://localhost:3100). | `make build` | Build shared, backend, frontend, and Electron packages. | | `make test` | Run the backend test suite. | | `make check` | Run the pre-PR validation gate: build plus backend tests. | -| `make run` | Start the app with the built frontend assets. | | `make dev` | Start backend, frontend, and Electron together for local development. | -| `make electron-dev` | Launch Electron only when backend and frontend dev servers are already running. | -| `make electron-pack` | Build an unpacked Electron package for local testing. | | `make package` | Build an installable Electron app for the current platform. | -| `pnpm --filter @dune/frontend e2e:electron` | Run the opt-in Electron smoke E2E against the built app. | | `make clean` | Remove build and dev artifacts while keeping local runtime data. | ## Development Mode @@ -81,12 +75,6 @@ pnpm --filter @dune/frontend dev -- --host localhost --port 4173 4. Open [http://localhost:4173](http://localhost:4173). -5. If you want Electron on top of those existing dev servers, run in a third terminal: - -```bash -make electron-dev -``` - ## Monorepo Layout - `packages/frontend`: Lit-based SPA, workspace shell, agent views, sandbox UI, and apps UI. @@ -101,11 +89,10 @@ make electron-dev - BoxLite state lives at `data/boxlite/`. - `PORT` defaults to `3100`. - `ADMIN_PORT` defaults to `PORT + 1` and binds the admin plane to `127.0.0.1`. -- `FRONTEND_DIST_PATH` defaults to `./packages/frontend/dist`; `make run` serves the built SPA from there. +- `FRONTEND_DIST_PATH` defaults to `./packages/frontend/dist`. - Local tool state in `.claude/` and `.codex/`, runtime data in `data/`, and generated artifacts such as `dist/`, `test-results/`, `coverage/`, `.release/`, and `packages/backend/.port` are intentionally local-only and git-ignored. - If you want an isolated run for manual checks or demos, point `DATA_DIR` at another ignored path such as `./test-results/manual-checks/data`. ## Further Reading - [Sandboxes UI Manual Verification Checklist](docs/sandboxes-ui-manual-checklist.md) -- [Deployment Function UI Design](docs/deployment-function-ui-design.md) diff --git a/docs/deployment-function-ui-design.md b/docs/deployment-function-ui-design.md deleted file mode 100644 index 60984ec..0000000 --- a/docs/deployment-function-ui-design.md +++ /dev/null @@ -1,302 +0,0 @@ -# Deployment Function + UI Design (v2, Directory-Based) - -## Goals - -- Agent provides a directory to deploy. -- Support start/stop deployment actions. -- Keep Vercel-like semantics, but simpler. -- Every deployment run uses a brand new sandbox. - -## Why This Design - -Using `repoUrl/branch` is unnecessary in this product. The deploy unit is the current filesystem content prepared by the agent. - -So the system should deploy from a directory snapshot, not from git. - -## Core Model - -### DeploymentSpec - -```ts -export type DeploymentTarget = 'preview' | 'production' - -export type DeploymentSpec = { - id: string - agentId: string - name: string - sourceDir: string // absolute path inside source agent sandbox, e.g. /config/miniapps/my-app - installCommand: string // optional, default: "" - buildCommand: string // required for most apps - startCommand: string // required - runtimePort: number // default: 3000 - env: Record - targetDefault: DeploymentTarget - createdAt: number - updatedAt: number -} -``` - -### SourceSnapshot - -```ts -export type SourceSnapshot = { - id: string - agentId: string - specId: string - sourceDir: string - artifactPath: string // host path, content-addressed tarball - contentHash: string // sha256 of file tree content - fileCount: number - sizeBytes: number - createdAt: number -} -``` - -### DeploymentRun - -```ts -export type DeploymentStatus = - | 'queued' - | 'capturing' - | 'provisioning' - | 'building' - | 'starting' - | 'ready' - | 'failed' - | 'stopping' - | 'stopped' - -export type DeploymentRun = { - id: string - agentId: string - specId: string - specSnapshot: DeploymentSpec - snapshotId: string - sandboxId: string - status: DeploymentStatus - target: DeploymentTarget - url: string | null - createdAt: number - updatedAt: number - stoppedAt?: number - error?: string -} -``` - -## Hard Constraints - -- `start` MUST create a new `DeploymentRun` and new `sandboxId`. -- No in-place restart for a run. -- `stop` only stops one run. -- A run always references an immutable `snapshotId`. - -## Deployment Pipeline - -### 1) Capture Source - -Input: `sourceDir` from spec. - -- Validate `sourceDir` exists in source agent sandbox. -- Create a tarball snapshot from `sourceDir`. -- Save tarball to host artifact store. -- Compute `contentHash`. -- Create `SourceSnapshot` record. - -### 2) Provision Fresh Sandbox - -- Create a new deployment sandbox (never reused). -- Copy/extract snapshot artifact into sandbox work dir (e.g. `/workspace/app`). - -### 3) Build + Start - -- Run install/build/start commands in order. -- On success, expose run URL and mark `ready`. -- On failure, mark `failed` and persist logs. - -### 4) Stop - -- Stop target run sandbox. -- Mark run `stopped`. - -## Better Than "copy out + copy in" (but compatible) - -The system still does "copy out then copy in", but through a first-class snapshot artifact layer. - -Benefits: -- Reproducible runs (each run tied to exact snapshot). -- Auditable (`contentHash`, file count, snapshot time). -- Easy future rollback (redeploy older snapshot to new sandbox). -- Better UX for run history. - -## Snapshot Mechanics (Implementation Detail) - -Artifact store: -- Host path: `data/deployments/artifacts//.tar.zst` -- Metadata store tracks `snapshotId -> contentHash -> artifactPath`. - -Capture strategy: -- Fast path: if `sourceDir` is backed by a known host mount, package directly on host. -- Fallback path: create archive inside source sandbox, write to shared staging mount, then move to artifact store. -- Both paths produce the same `contentHash` and `SourceSnapshot`. - -Suggested ignore defaults while capturing: -- `.git/` -- `node_modules/` -- `.next/cache/` -- `dist/` -- `build/` -- `*.log` - -Safety and scale guards: -- Max snapshot size (v1): `1 GiB` compressed. -- Max file count (v1): `100,000`. -- Reject symlinks escaping source root. -- Per-agent deploy concurrency: 1 active start pipeline at a time. - -## API / Function Contract - -### Spec APIs - -1. `GET /api/agents/:id/deployments/spec` -- Returns current deployment spec. - -2. `PUT /api/agents/:id/deployments/spec` -- Upserts spec. -- Required: `name`, `sourceDir`, `buildCommand`, `startCommand`. - -### Run APIs - -3. `GET /api/agents/:id/deployments` -- List runs (newest first). - -4. `POST /api/agents/:id/deployments/start` -- Body: - -```json -{ - "target": "preview", - "note": "optional message" -} -``` - -- Behavior: - - load spec, - - capture source snapshot, - - create run, - - provision fresh sandbox, - - build/start. - -5. `POST /api/agents/:id/deployments/:runId/stop` -- Stop one run. - -6. `GET /api/agents/:id/deployments/:runId/logs` -- Get logs for details panel. - -7. `GET /api/agents/:id/deployments/snapshots` -- Optional in v1.1; useful for debugging and future rollback UX. - -## Validation Rules - -- `sourceDir` must be absolute sandbox path. -- `sourceDir` must not contain path traversal. -- `sourceDir` must be under allowed roots (for safety), for example: - - `/config/miniapps` - - `/config/memory/projects` -- `startCommand` required. -- `buildCommand` required (can be `"true"` for static/no-build apps). -- `runtimePort` must be in safe range (1024-65535). - -## State Machine - -```text -queued -> capturing -> provisioning -> building -> starting -> ready -queued -> capturing -> provisioning -> building -> failed -queued -> capturing -> failed -ready -> stopping -> stopped -failed -> stopping -> stopped -``` - -## UI Design - -### Location - -- Add `Deployments` tab to agent profile modal. -- Tabs: `Profile`, `Deployments`, `Computer`. - -### Deployments Tab Sections - -1. Spec Header -- Spec status chip (`Configured` / `Missing`). -- `Edit spec` action. -- `Start deployment` primary action. - -2. Runs List (left on desktop, top on mobile) -- Run status, target, created time. -- Snapshot hash short id. -- Sandbox short id. - -3. Run Details (right on desktop, bottom on mobile) -- URL (when ready). -- Commands used (from spec snapshot). -- Live logs. -- `Stop` button for active runs. - -### Spec Editor Fields - -- Name -- Source directory (required) -- Install command -- Build command -- Start command -- Runtime port -- Target default (`preview`/`production`) -- Env vars key/value - -Footer: -- `Save spec` -- `Save and deploy` - -### Wireframe - -```text -┌────────────────────────────────────────────────────────────────────┐ -│ Spec: Configured (/config/miniapps/foo) [Edit spec] [Start] │ -├───────────────────────────────┬────────────────────────────────────┤ -│ Runs │ Run details │ -│ [ready] preview 2m │ Status: ready │ -│ snapshot: sha_7a12 │ URL: https://... │ -│ sandbox: sbx_92f1 │ Snapshot: sha_7a12 │ -│ │ Sandbox: sbx_92f1 │ -│ [building] production 20s │ Logs │ -│ snapshot: sha_b3d4 │ > capturing source... │ -│ sandbox: sbx_ab18 │ > provisioning sandbox... │ -│ │ > pnpm build │ -│ │ │ -│ │ [Stop] │ -└───────────────────────────────┴────────────────────────────────────┘ -``` - -## Realtime Events - -Add websocket events: - -- `deployment:run:new` -- `deployment:run:update` -- `deployment:log:append` -- `deployment:snapshot:created` (optional) - -## v1 Scope - -- One spec per agent. -- Start/stop only. -- No rollback UI yet. -- No custom domains. -- Always new sandbox per run. -- Deploy source is always a directory path. - -## Future Extensions - -- "Redeploy this snapshot" button. -- Diff between two snapshots. -- Auto-detect commands (`npm`, `pnpm`, static build). -- Promote preview run to production alias. diff --git a/packages/backend/package.json b/packages/backend/package.json index a98bfaa..0400289 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -18,14 +18,12 @@ "hono": "^4.6.0", "mime-types": "^3.0.2", "nanoid": "^5.1.6", - "uuid": "^11.0.0", "ws": "^8.18.0" }, "devDependencies": { "@types/better-sqlite3": "^7.6.0", "@types/mime-types": "^2.1.4", "@types/node": "^20.0.0", - "@types/uuid": "^10.0.0", "@types/ws": "^8.5.0", "esbuild": "^0.27.4", "tsx": "^4.19.0", diff --git a/packages/backend/src/storage/database.ts b/packages/backend/src/storage/database.ts index d4ca797..c75c3a8 100644 --- a/packages/backend/src/storage/database.ts +++ b/packages/backend/src/storage/database.ts @@ -163,43 +163,6 @@ function initSchema(db: Database.Database) { CREATE INDEX IF NOT EXISTS idx_agent_runtime_mounts_agent ON agent_runtime_mounts(agent_id, created_at ASC); - CREATE TABLE IF NOT EXISTS deployment_configs ( - agent_id TEXT PRIMARY KEY, - source_path TEXT NOT NULL, - build_command TEXT NOT NULL, - start_command TEXT NOT NULL, - updated_at INTEGER NOT NULL, - FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS deployment_runs ( - id TEXT PRIMARY KEY, - agent_id TEXT NOT NULL, - initiator TEXT NOT NULL, - status TEXT NOT NULL, - sandbox_id TEXT, - error TEXT, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - stopped_at INTEGER, - FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS deployment_logs ( - run_id TEXT NOT NULL, - seq INTEGER NOT NULL, - timestamp INTEGER NOT NULL, - line TEXT NOT NULL, - PRIMARY KEY (run_id, seq), - FOREIGN KEY (run_id) REFERENCES deployment_runs(id) ON DELETE CASCADE - ); - - CREATE INDEX IF NOT EXISTS idx_deployment_runs_agent_created - ON deployment_runs(agent_id, created_at DESC); - - CREATE INDEX IF NOT EXISTS idx_deployment_logs_run_seq - ON deployment_logs(run_id, seq); - CREATE TABLE IF NOT EXISTS sandboxes ( id TEXT PRIMARY KEY, name TEXT, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 5b3a33b..9929cf6 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -19,6 +19,7 @@ "dompurify": "^3.3.0", "katex": "^0.16.38", "lit": "^3.3.0", + "lucide": "^0.577.0", "marked": "^17.0.0", "mermaid": "^11.13.0", "nanoid": "^5.1.6", diff --git a/packages/frontend/src/app-shell.css.ts b/packages/frontend/src/app-shell.css.ts index c36c0d2..eeb2417 100644 --- a/packages/frontend/src/app-shell.css.ts +++ b/packages/frontend/src/app-shell.css.ts @@ -545,10 +545,6 @@ export const appShellStyles = css` } @media (max-width: 980px) { - .sidebar-toggle-overlay { - display: none; - } - .pane-toolbar { min-height: var(--toolbar-height-compact); padding: 0 14px; @@ -561,20 +557,41 @@ export const appShellStyles = css` .workspace, .workspace.with-sidebar-resizer, - .workspace.settings-mode, .workspace.collapsed, .workspace.with-sidebar-resizer.collapsed { - grid-template-columns: minmax(0, 1fr); - grid-template-rows: minmax(188px, 34vh) minmax(0, 1fr); + grid-template-columns: 0 minmax(0, 1fr); + } + + .workspace.with-sidebar-resizer, + .workspace.with-sidebar-resizer.collapsed { + grid-template-columns: 0 0 minmax(0, 1fr); } .workspace.settings-mode { - grid-template-rows: minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr); } .sidebar-wrap { border-right: none; - border-bottom: 1px solid var(--pane-divider); + overflow: hidden; + } + + .sidebar-resizer { + display: none; + } + + .sidebar-toggle-overlay { + left: 16px; + color: var(--text-muted); + } + + .sidebar-toggle-overlay:hover { + background: var(--bg-hover); + color: var(--text-primary); + } + + .pane-toolbar.hidden-sidebar { + padding-left: calc(16px + var(--control-height) + 10px); } .footer-strip { diff --git a/packages/frontend/src/app-shell.ts b/packages/frontend/src/app-shell.ts index a30e4e8..6dac8b8 100644 --- a/packages/frontend/src/app-shell.ts +++ b/packages/frontend/src/app-shell.ts @@ -16,7 +16,7 @@ import type { ChannelDetailsPanel } from './components/channels/details-panel.js import './components/apps/apps-view.js' const ADMIN_USER_ID = 'admin' -const DESKTOP_FULL_HIDE_SIDEBAR_QUERY = '(min-width: 981px)' +const FULL_HIDE_SIDEBAR_QUERY = '(min-width: 1px)' const DEFAULT_SIDEBAR_WIDTH_PX = 320 const SIDEBAR_MIN_WIDTH_PX = 240 const SIDEBAR_MAX_WIDTH_PX = 520 @@ -682,7 +682,7 @@ export class AppShell extends LitElement { } private supportsFullHideSidebar() { - return window.matchMedia(DESKTOP_FULL_HIDE_SIDEBAR_QUERY).matches + return window.matchMedia(FULL_HIDE_SIDEBAR_QUERY).matches } private syncSidebarWidthFromPreferences() { @@ -786,12 +786,18 @@ export class AppShell extends LitElement { this.persistSidebarWidth() } + private isNarrowViewport() { + return !window.matchMedia('(min-width: 981px)').matches + } + private shouldFullyHideSidebar() { - return uiPreferences.sidebarCollapsed && this.supportsFullHideSidebar() && this.activeSurface !== 'settings' + if (this.activeSurface === 'settings') return false + if (this.isNarrowViewport()) return true + return uiPreferences.sidebarCollapsed && this.supportsFullHideSidebar() } private shouldRenderSidebarResizer() { - return this.activeSurface !== 'settings' && !this.shouldFullyHideSidebar() && this.supportsFullHideSidebar() + return this.activeSurface !== 'settings' && !this.shouldFullyHideSidebar() && !this.isNarrowViewport() } private openCreateChannelDialog() { diff --git a/packages/frontend/src/utils/icons.ts b/packages/frontend/src/utils/icons.ts index 1c14b43..9d9787b 100644 --- a/packages/frontend/src/utils/icons.ts +++ b/packages/frontend/src/utils/icons.ts @@ -13,7 +13,7 @@ import { Plus, Minus, Square, X, PanelLeft, Settings, Box, LayoutGrid, Users, Info, Trash2, FileText, Play, Pause, ChevronLeft, ChevronRight, ChevronDown, Menu, Link, ArrowRight, Camera, Wrench, XCircle, - AlertTriangle, RefreshCw, Search, Maximize2, Minimize2, CircleStop, Files, + AlertTriangle, RefreshCw, Search, Maximize2, Minimize2, CircleStop, } from 'lucide'; /* ── Types ────────────────────────────────────────────────────────────── */ @@ -84,7 +84,6 @@ export const iconMinimize = (o?: IconOptions) => renderIcon(Minimize2, o); // Content & files export const iconFileText = (o?: IconOptions) => renderIcon(FileText, o); -export const iconFiles = (o?: IconOptions) => renderIcon(Files, o); export const iconBox = (o?: IconOptions) => renderIcon(Box, o); export const iconLayoutGrid = (o?: IconOptions) => renderIcon(LayoutGrid, o); export const iconCamera = (o?: IconOptions) => renderIcon(Camera, o); diff --git a/packages/shared/src/schemas/deployment.ts b/packages/shared/src/schemas/deployment.ts deleted file mode 100644 index 6261065..0000000 --- a/packages/shared/src/schemas/deployment.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Type, Static } from '@sinclair/typebox' - -export const DeploymentInitiator = Type.Union([ - Type.Literal('human'), - Type.Literal('agent'), -]) - -export const DeploymentStatus = Type.Union([ - Type.Literal('preparing'), - Type.Literal('building'), - Type.Literal('starting'), - Type.Literal('ready'), - Type.Literal('failed'), - Type.Literal('stopping'), - Type.Literal('stopped'), -]) - -export const DeploymentConfigSchema = Type.Object({ - agentId: Type.String(), - sourcePath: Type.String(), - buildCommand: Type.String(), - startCommand: Type.String(), - updatedAt: Type.Number(), -}) - -export const DeploymentRunSchema = Type.Object({ - id: Type.String(), - agentId: Type.String(), - initiator: DeploymentInitiator, - status: DeploymentStatus, - sandboxId: Type.Union([Type.String(), Type.Null()]), - url: Type.Union([Type.String(), Type.Null()]), - error: Type.Union([Type.String(), Type.Null()]), - createdAt: Type.Number(), - updatedAt: Type.Number(), - stoppedAt: Type.Union([Type.Number(), Type.Null()]), -}) - -export const DeploymentLogLineSchema = Type.Object({ - runId: Type.String(), - seq: Type.Number(), - timestamp: Type.Number(), - line: Type.String(), -}) - -export const DeploymentSummarySchema = Type.Object({ - config: Type.Union([DeploymentConfigSchema, Type.Null()]), - activeRun: Type.Union([DeploymentRunSchema, Type.Null()]), - runs: Type.Array(DeploymentRunSchema), -}) - -export type DeploymentInitiatorType = Static -export type DeploymentStatusType = Static -export type DeploymentConfig = Static -export type DeploymentRun = Static -export type DeploymentLogLine = Static -export type DeploymentSummary = Static diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 974a0ef..0bf6a39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,9 +48,6 @@ importers: nanoid: specifier: ^5.1.6 version: 5.1.6 - uuid: - specifier: ^11.0.0 - version: 11.1.0 ws: specifier: ^8.18.0 version: 8.19.0 @@ -64,9 +61,6 @@ importers: '@types/node': specifier: ^20.0.0 version: 20.19.37 - '@types/uuid': - specifier: ^10.0.0 - version: 10.0.0 '@types/ws': specifier: ^8.5.0 version: 8.18.1 @@ -121,6 +115,9 @@ importers: lit: specifier: ^3.3.0 version: 3.3.2 + lucide: + specifier: ^0.577.0 + version: 0.577.0 marked: specifier: ^17.0.0 version: 17.0.1 @@ -966,9 +963,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - '@types/verror@1.10.11': resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} @@ -2058,6 +2052,9 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lucide@0.577.0: + resolution: {integrity: sha512-PpC/m5eOItp/WU/GlQPFBXDOhq6HibL73KzYP37OX3LM7VmzWQF8voEj8QRWUFvy9FIKfeDQkWYoyS1D/MdWFA==} + make-fetch-happen@10.2.1: resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -3621,8 +3618,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/uuid@10.0.0': {} - '@types/verror@1.10.11': optional: true @@ -4934,6 +4929,8 @@ snapshots: lru-cache@7.18.3: {} + lucide@0.577.0: {} + make-fetch-happen@10.2.1: dependencies: agentkeepalive: 4.6.0