sugarcraft / sugar-crush-web
Browser UI for sugar-crush's WebSocket server mode, shipped as a pre-built Vite + Vue 3 bundle with a one-class PHP shim that locates it. `sugarcrush serve` serves it on the same port, so PHP users need no Node.
Package info
github.com/sugarcraft/sugar-crush-web
Language:TypeScript
pkg:composer/sugarcraft/sugar-crush-web
Requires
- php: ^8.3
Requires (Dev)
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-13 15:19:52 UTC
README
SugarCrushWeb
The browser UI for sugar-crush's WebSocket server mode: a multi-session dashboard with approvals, live tool output and settings, built with Vite + Vue 3 + TypeScript + Pinia + vue-router.
It ships as a Composer package. The pre-built bundle is committed under
dist/, and a one-class PHP shim tells sugarcrush serve where it is, so
PHP users get the UI from composer require alone and never need Node.
Status: multi-session. Everything a turn needs — the sessions sidebar, a virtualised transcript (Markdown, code, reasoning folds), tool cards with diffs, permission cards, a composer that queues, steers or interrupts, and a status bar — plus tabs and a tiled grid of live sessions, a server-wide approvals drawer, the schema-driven settings form, and the agent, todo, background, workflow and memory panels.
Install
composer require sugarcraft/sugar-crush-web
sugar-crush lists this package under suggest, not require, so headless
installs stay lean.
PHP API
use SugarCraft\CrushWeb\Assets; Assets::distPath(); // absolute, realpath()-resolved dist/ directory Assets::manifest(); // decoded Vite manifest: entry => {file, css, …} Assets::version(); // bundle version, stamped into dist/build-info.json
distPath() throws RuntimeException when dist/index.html is missing: a
broken install, never something the server should paper over.
What it does
sugarcrush serve prints a sign-in URL; open it. The page trades the one-time
code for an HttpOnly session cookie, then holds one WebSocket speaking
sugarcrush.v1 (sugar-crush's
docs/SERVER.md
documents the protocol and the UI).
- Sessions sidebar: newest first, filter, status, and a badge for every question a session is waiting on (also in the tab title, and as an opt-in desktop notification).
- New session in any project (servers started with
serve --allow-dir-browse): New session opens a folder picker over the server's browse root — breadcrumb, Up, child directories with a project badge, show hidden, a path box — and starts the session in the directory chosen (a workspace host of its own when it is not the server's root). Keyboard: ↑/↓, Enter/→ to open, Backspace/← for up, Esc to cancel. Without the flag, New session starts on the server's own root, as before. - Tabs and a grid: every session opened stays a tab (Alt+1…9 picks one; each shows its status and open questions). The grid tiles the open tabs, up to 3×3, each live — status, step, spend, the last few events, its questions and a line to prompt or steer it. The focused tile streams in full; the others are narrated by the server (a tail every 2 s) and catch up at once when focused. The layout is remembered per browser.
- Approvals drawer (the ⚠ count in the top bar): every question open in any session of the server, oldest first, grouped by session and answerable in place. A question arrives the moment it is put, whether or not this page follows its session; opt-in desktop notifications also say when a turn finishes out of sight.
- Transcript: virtualised (
@tanstack/vue-virtual); replies stream in as Markdown (markdown-it, raw HTML off, then DOMPurify — model and tool text are untrusted), fenced code coloured by a small highlighter loaded on first use; reasoning folds; a tool card per call with its arguments, output (the full text on request when the event was capped) and diff. - Permission cards: once / always (this session) / reject / reject & stop,
yanon a focused card. The first answer from any client wins. - Composer: Enter sends; while a turn runs, steer, queue or interrupt —
offered first is the server's
queueMode(server.hello'sdefaults.delivery;steerunless set, as the TUI's Enter does mid-turn); Stop or Esc Esc cancels;/completes server-runnable commands. - Status bar: activity and step, context used, spend, model, and the permission mode (changeable).
- Reconnects after 0.5 s doubling to 15 s (±30 % jitter) with a fresh
ticket, resuming every followed session from its gap-free
seqcursor. - Side panels (the panels button): Agents — the
session's delegated runs as a live tree, also under each
Taskcard, each opening into an agent view with the run's own transcript, a box to message it (or continue it once finished) and pause / resume / cancel, plus Background for a run the conversation itself delegated (the TUI'sCtrl+X b: the run moves to a background session); Todo — the agent's todo list, live; Background —/bgsessions with output, stop and send to this session; Workflows — run one, and the session's/workflowandWorkflowtool runs; Memory — notes per scope, search, add, edit, delete. - Command palette (Ctrl+K / ⌘K): sessions, slash commands, panels, new session, theme.
- Settings (the settings link in the top bar): a form generated from
settings.schema— your tier or a trusted project's local or committed shared file, each value's provenance, env and flag locks, apply-mode badges — with a diff preview of the file before anything is saved (settings.preview, thensettings.set).
Developing the UI
The TypeScript lives in src-web/ so it stays out of the PSR-4 src/ tree.
Node is pinned in .nvmrc.
npm ci npm run dev # Vite dev server; proxies /ws and /api to 127.0.0.1:7420 npm run typecheck # vue-tsc npm run lint # eslint npm test # vitest (happy-dom): protocol client, reducer, stores, components npm run build # writes dist/ npm run gen:protocol # regenerate src-web/protocol/generated.ts npm run e2e # Playwright (Chromium) against a real `sugarcrush serve`
For npm run dev, start the server with
sugarcrush serve --allowed-origin http://localhost:5173, so it accepts the
dev page's origin.
src-web/
protocol/ generated.ts (from the schema), client.ts (JSON-RPC over WS,
hello + resume, watchdog, reconnect), reconnect.ts (backoff),
cursor.ts (gap-free seq cursor), auth.ts (login, tickets)
stores/ connection, sessions, session (one per session id, the
reducer in reducer.ts), approvals, layout, settings (theme,
notifications), settings/ (the settings form: fields,
serverSettings), agents/ (agent tree, todos, background,
workflows, memory, panels)
components/ SessionSidebar, Transcript (virtualised), TranscriptItem,
MessageMarkdown, ReasoningFold, ToolCard, DiffView,
PermissionCard, Composer, QueueStrip, StatusBar,
ConnectionBanner; grid/ (tabs, tiles), approvals/ (drawer),
settings/ (SettingsView, fields, preview), agents/ (tree,
agent view), panels/ (side panels, command palette)
lib/ markdown, diff, format, highlight (lazy)
views/ DashboardView, SessionView, LoginView
dist/ is generated: never hand-edit it. Rebuild it with npm run build
and commit the result together with the source change. The web workflow
(.github/workflows/web.yml) rebuilds from a clean npm ci and fails when
the committed dist/ differs. The build is deterministic (content-hashed
file names, no timestamps), so a clean rebuild of unchanged source is
byte-identical.
src-web/protocol/generated.ts is generated too, by
scripts/gen-protocol.mjs from sugar-crush's
docs/protocol/sugarcrush.v1.schema.json (itself generated from the PHP
protocol classes). web.yml regenerates it and fails on a diff, so a server
protocol change the client was not updated for is caught in CI.
End-to-end tests
e2e/fixtures/server.ts starts php ../sugar-crush/bin/sugarcrush serve --port 0 over a throwaway git repository and HOME, on the offline echo
provider (no network, no keys; it needs ext-pcntl, ext-posix, ext-ffi).
A prompt of ::tool <Name> <json-object> lines makes the echo provider call
those tools for real, which is how the suite drives permission questions,
tool cards and diffs. Scenarios: the sign-in link and the token, a spent
code, send and see the echo, markup injection refused, a tool call with its
question and diff, a rejected call, a queued prompt, Stop, /clear through
command.exec, two tabs on one session (first answer wins), a tab opened
mid-turn, a dropped socket resuming from its cursor, and a server restart.
PHP_BINARY and SUGARCRUSH_BIN override where PHP and the server are.
Tests
composer install && vendor/bin/phpunit
tests/AssetsTest.php checks the committed bundle itself: the manifest's
files exist and are content-hashed, index.html loads the entry by a
relative URL, the version matches package.json, and no source map or
.env file leaked into dist/.
License
MIT