singularity-forge/web
Mikael Hugo e50d96e1f8 chore(web): upgrade all dependencies to latest stable
- @hookform/resolvers 3.10.0 → 5.2.2
- @tailwindcss/postcss 4.2.1 → 4.3.0
- @types/node 24.12.2 → 25.6.2
- @uiw/codemirror-* 4.25.8 → 4.25.9
- autoprefixer 10.4.27 → 10.5.0
- esbuild 0.27.4 → 0.28.0
- eslint 9.39.4 → 9.x (pinned; eslint 10 incompatible with eslint-config-next)
- eslint-config-next 16.2.3 → 16.2.6
- lucide-react 0.564.0 → 1.14.0
- motion 12.36.0 → 12.38.0
- next 16.2.3 → 16.2.6
- postcss 8.5.8 → 8.5.14
- react/react-dom 19.2.4 → 19.2.6
- react-day-picker 9.13.2 → 10.0.0
- react-hook-form 7.71.2 → 7.75.0
- react-resizable-panels 2.1.9 → 4.11.0
- recharts 2.15.0 → 3.8.1
- sonner 1.7.4 → 2.0.7
- tailwindcss 4.2.1 → 4.3.0
- tw-animate-css 1.3.3 → 1.4.0
- typescript 5.7.3 → 6.0.3
- zod 3.25.76 → 4.4.3

Breaking changes fixed:
- react-resizable-panels v4: PanelGroup→Group, PanelResizeHandle→Separator
- react-day-picker v10: ClassNames.table renamed to month_grid
- recharts v3: TooltipContentProps/DefaultLegendContentProps type changes,
  DataKey type for key prop
- shiki: cast createHighlighter promise to local ShikiHighlighter type
- voice/route.ts: pass requestUrl through buildDigitsResponse
- pty-chat-parser.ts: declare _lastInputAt private field
- sf-workspace-store.tsx: fix stale pi-coding-agent import path,
  add import for locally-used workspace types

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 11:52:54 +02:00
..
app chore(web): upgrade all dependencies to latest stable 2026-05-10 11:52:54 +02:00
components chore(web): upgrade all dependencies to latest stable 2026-05-10 11:52:54 +02:00
hooks chore: commit current workspace state 2026-05-05 14:46:18 +02:00
lib chore(web): upgrade all dependencies to latest stable 2026-05-10 11:52:54 +02:00
public feat(web): browser-based web interface (#1717) 2026-03-21 12:16:54 -06:00
styles style: format repository with biome 2026-05-05 14:31:16 +02:00
.gitignore feat(web): browser-based web interface (#1717) 2026-03-21 12:16:54 -06:00
components.json style: format repository with biome 2026-05-05 14:31:16 +02:00
eslint.config.mjs style: format repository with biome 2026-05-05 14:31:16 +02:00
next-env.d.ts feat(web): browser-based web interface (#1717) 2026-03-21 12:16:54 -06:00
next.config.mjs style: format repository with biome 2026-05-05 14:31:16 +02:00
package-lock.json chore(web): upgrade all dependencies to latest stable 2026-05-10 11:52:54 +02:00
package.json chore(web): upgrade all dependencies to latest stable 2026-05-10 11:52:54 +02:00
postcss.config.mjs style: format repository with biome 2026-05-05 14:31:16 +02:00
proxy.ts chore: commit current workspace state 2026-05-05 14:46:18 +02:00
README.md feat(web): add error boundaries, expand test coverage, add README 2026-05-10 11:24:40 +02:00
tsconfig.json style: format repository with biome 2026-05-05 14:31:16 +02:00

sf web

Next.js 15 (App Router) frontend for sf --web. Ships as a standalone bundle baked into the sf release; can also be run from source for development.

What this is

The web UI is a browser workspace for sf. It connects to a bridge service (src/web/bridge-service.ts) that manages an sf subprocess per project CWD and proxies RPC commands over stdio. The page is a single-page app: no server-side rendering, client-only via dynamic(..., { ssr: false }).

How to run

Packaged (normal use)

sf --web              # launches Next.js standalone server and opens browser
sf --web --port 3000  # pick a specific port

Source dev mode (requires the repo checked out)

npm --prefix web run dev

The dev server needs these env vars (set automatically by sf --web; set manually for source dev):

Variable Description
SF_WEB_AUTH_TOKEN Bearer token for all API requests
SF_WEB_PROJECT_CWD Absolute path of the project being served
SF_WEB_HOST Host to bind (default 127.0.0.1)
SF_WEB_PORT Port to bind

Auth

On first page load the client reads the bearer token from the URL fragment (#token=…), stores it in localStorage under sf-auth-token, and strips the fragment from the URL.

All subsequent requests attach it:

  • Fetch / API routesAuthorization: Bearer <token> header (via authFetch / authHeaders in web/lib/auth.ts).
  • SSE routes?_token=<token> query parameter (EventSource doesn't support custom headers).

Architecture

Browser
  └─ page.tsx  (dynamic, ssr:false)
       └─ SFAppShell
            ├─ WorkspaceChrome  — layout chrome, sidebar, status bar
            │    └─ 7 views  (see below)
            └─ CommandSurface — slash-command palette

Next.js API routes  (web/app/api/**/route.ts)
  └─ delegate to *-service.ts files in src/web/
       └─ bridge-service.ts  — per-CWD singleton sf subprocess (RPC over stdio)

bridge-service.ts spawns sf as a child process, speaks JSON-RPC over stdio, and multiplexes all API routes onto that single bridge. Auth is enforced before requests reach the bridge via requireProjectCwd() (which validates the token and resolves the CWD from SF_WEB_PROJECT_CWD).

The 7 views

View key Component Purpose
dashboard Dashboard Live project status, metrics, quick-start panel
chat ChatMode Conversational agent interface
power DualTerminal Full-screen split terminal (agent + shell)
roadmap Roadmap Milestone and slice plan explorer
files FilesView Project file browser with syntax highlighting
activity ActivityView Event log and session history
visualize VisualizerView Dependency graph and architecture visualizer

Adding a new API route

  1. Create web/app/api/<name>/route.ts that calls requireProjectCwd(request) for auth/CWD resolution, then delegates to a service:
// web/app/api/my-feature/route.ts
import { requireProjectCwd } from "../../../../src/web/bridge-service.ts";
import { collectMyFeatureData } from "../../../../src/web/my-feature-service.ts";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export async function GET(request: Request): Promise<Response> {
  const projectCwd = requireProjectCwd(request);
  const data = await collectMyFeatureData(projectCwd);
  return Response.json(data, { headers: { "Cache-Control": "no-store" } });
}
  1. Implement src/web/my-feature-service.ts with the actual logic (may call the bridge or read disk directly).

Tests

Tests for web utilities live in web/lib/__tests__/ and run via Vitest:

npx vitest run web/lib --config vitest.config.ts

Note: co-located *.test.ts files inside web/ outside of __tests__/ subdirectories are silently skipped by the root Vitest config. Always place web tests under web/lib/__tests__/.