[feat] Add local web dashboard - #77
Conversation
Co-Authored-By: AI Context Agent <noreply@anthropic.com>
Co-Authored-By: AI Context Agent <noreply@anthropic.com>
Co-Authored-By: AI Context Agent <noreply@anthropic.com>
…interface Co-Authored-By: AI Context Agent <noreply@anthropic.com>
Co-Authored-By: AI Context Agent <noreply@anthropic.com>
Co-Authored-By: AI Context Agent <noreply@anthropic.com>
- Added icons to navigation items in the Layout component for better visual representation. - Updated sidebar title to include a mark for branding. - Introduced new common components: CopyButton, DownloadButton, ToolBadge, and SearchInput for enhanced functionality. - Implemented front matter handling in markdown files for better content management. - Enhanced various views (Agents, Docs, Skills, Session, Workflow) with new UI components and improved search functionality. - Updated CSS variables for a more cohesive design and improved scrollbar styling. - Added support for displaying tool information in session and workflow views.
There was a problem hiding this comment.
Pull request overview
Adds a new local web dashboard surface to dotcontext: a src/web Node HTTP/SSE adapter over the harness runtime plus a bundled web-ui/ React+Vite SPA, wired into the CLI (dotcontext web), packaging, smoke tests, and docs.
Changes:
- Introduces
src/web(HTTP REST + SSE + static SPA serving) and thedotcontext webCLI command with dev-friendly flags. - Adds
web-ui/workspace (Vite + React router UI) to browse docs/skills/agents/sessions/workflow, with SSE-driven refetch. - Updates packaging/smoke validation to bundle
web-ui/dist, plus documentation and architecture boundary enforcement.
Reviewed changes
Copilot reviewed 72 out of 78 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| web-ui/vite.config.ts | Configures Vite dev server and /api proxy to the local backend. |
| web-ui/tsconfig.node.json | Adds TS config for Vite config/node tooling. |
| web-ui/tsconfig.json | Adds TS project references for app + node configs. |
| web-ui/tsconfig.app.json | Adds browser/React TS config for the SPA source. |
| web-ui/src/views/WorkflowView.tsx | Renders workflow summary, PREVC phase tracker, harness binding, and linked plan details. |
| web-ui/src/views/SkillsView.tsx | Adds skills browsing UI with search + content panel and export actions. |
| web-ui/src/views/SessionView.tsx | Adds session browsing UI with trace/artifact/checkpoint detail panes. |
| web-ui/src/views/DocsView.tsx | Adds docs browsing UI with search + markdown rendering and export actions. |
| web-ui/src/views/AgentsView.tsx | Adds agents browsing UI and exposes agent info/docs in the dashboard. |
| web-ui/src/types/api.ts | Defines the UI’s typed contract for /api/* endpoints and SSE payloads. |
| web-ui/src/main.tsx | Boots the React app with router + StrictMode. |
| web-ui/src/lib/markdown.ts | Builds a downloadable “frontmatter + markdown” representation for docs/skills. |
| web-ui/src/lib/api.ts | Implements fetch wrapper for { data } / { error } API envelope. |
| web-ui/src/index.css | Adds base UI styles and design tokens. |
| web-ui/src/hooks/useEventStream.ts | Adds a shared/ref-counted SSE client that bumps a version counter on events. |
| web-ui/src/hooks/useApi.ts | Adds generic REST hooks that refetch on selection changes and SSE bumps. |
| web-ui/src/components/Layout.tsx | Adds dashboard shell layout, navigation, and SSE connection badge. |
| web-ui/src/components/icons.tsx | Adds shared inline SVG icon components. |
| web-ui/src/components/common.tsx | Adds reusable UI primitives (panels, pills, markdown, copy/download, etc.). |
| web-ui/src/App.tsx | Defines SPA routes for docs/skills/agents/session/workflow. |
| web-ui/src/App.css | Adds comprehensive styles for shell, lists, workflow tracker, etc. |
| web-ui/README.md | Documents dev workflow, production build, and package validation for the dashboard. |
| web-ui/public/favicon.svg | Adds dashboard favicon asset. |
| web-ui/package.json | Defines SPA dependencies and scripts (dev/build/lint/preview). |
| web-ui/index.html | Adds SPA HTML entrypoint and favicon wiring. |
| web-ui/.oxlintrc.json | Adds web-ui lint config for React + TS hooks/export rules. |
| web-ui/.gitignore | Adds web-ui workspace ignores. |
| templates/packages/cli.README.md | Documents that the published CLI bundle includes the web dashboard assets. |
| src/web/server.ts | Implements Node HTTP server: routes /api/*, serves web-ui/dist, SPA fallback, bind warning. |
| src/web/routes/workflow.ts | Adds workflow REST routes backed by harness workflow services. |
| src/web/routes/skills.ts | Adds skills REST routes backed by harness skill action service. |
| src/web/routes/sessions.ts | Adds sessions REST routes backed by harness runtime state service. |
| src/web/routes/events.ts | Adds SSE route emitting hello + debounced runtime-change events. |
| src/web/routes/docs.ts | Adds docs REST routes backed by the new harness docs service. |
| src/web/routes/agents.ts | Adds agents REST routes backed by harness agent action service. |
| src/web/router.ts | Adds small route table + (method, pathname) matcher for src/web. |
| src/web/response.ts | Adds { data } / { error: { message } } envelope helpers. |
| src/web/index.ts | Exposes web boundary exports (server + defaults). |
| src/web/events/runtimeWatcher.ts | Adds chokidar-based watcher to coalesce .context/runtime/** writes. |
| src/web/events/tests/runtimeWatcher.test.ts | Adds unit tests for watcher batching + relative path formatting + close behavior. |
| src/web/tests/server.test.ts | Adds integration tests for REST endpoints, static SPA fallback, and SSE hello event. |
| src/web/tests/router.test.ts | Adds router unit tests (matching + decoding + error handling). |
| src/tests/architecture/tests/boundaries.test.ts | Enforces src/web boundary independence from src/cli and src/mcp. |
| src/harness/index.ts | Re-exports new harness services/types used by the web adapter. |
| src/harness/domain/workflow/plans/planLinker.ts | Exposes agent content retrieval for dashboard agent inspection. |
| src/harness/application/workflow/index.ts | Exports guide service/types via workflow application index. |
| src/harness/application/index.ts | Exports the new docs application module. |
| src/harness/application/docs/index.ts | Adds docs module barrel export. |
| src/harness/application/docs/HarnessDocsService.ts | Adds a harness service to list/get .context/docs/*.md with frontmatter parsing. |
| src/harness/application/docs/tests/HarnessDocsService.test.ts | Adds unit tests for docs listing/content parsing + traversal rejection. |
| src/harness/application/agents/agentsService.ts | Includes agent playbook content in agent info responses (for dashboard). |
| src/bin/dotcontext.ts | Adds dotcontext web CLI command + browser auto-open + dist checks. |
| src/tests/cli.test.ts | Ensures CLI help exposes web command and flags. |
| scripts/smoke-package-bundles.js | Adds smoke assertion that CLI bundle includes web-ui/dist/index.html. |
| scripts/dev-web.js | Adds combined dev runner for web API + Vite UI. |
| scripts/build-package-bundles.js | Copies web-ui/dist into CLI bundle and adds runtime deps (incl. chokidar). |
| README.md | Documents the new dashboard surface and how to run it. |
| package.json | Adds dev/build scripts for web dashboard and bundles web-ui/dist for publish. |
| package-lock.json | Adds chokidar + transitive deps to lockfile. |
| docs/src/content/docs/pt-br/reference/cli-commands.md | Documents web CLI command and flags (pt-BR). |
| docs/src/content/docs/pt-br/index.mdx | Adds dashboard link card (pt-BR). |
| docs/src/content/docs/pt-br/guides/web-dashboard.md | Adds full dashboard guide (pt-BR). |
| docs/src/content/docs/pt-br/guides/using-the-cli.md | Adds dashboard usage section (pt-BR). |
| docs/src/content/docs/en/reference/cli-commands.md | Documents web CLI command and flags (en). |
| docs/src/content/docs/en/index.mdx | Adds dashboard link card (en). |
| docs/src/content/docs/en/guides/web-dashboard.md | Adds full dashboard guide (en). |
| docs/src/content/docs/en/guides/using-the-cli.md | Adds dashboard usage section (en). |
| CONTRIBUTING.md | Adds contributor workflow for dashboard dev/build/package validation. |
| CHANGELOG.md | Notes new dashboard and packaging changes in Unreleased. |
| ARCHITECTURE.md | Updates architecture to include web + web-ui surfaces. |
| .context/docs/web-interface-architecture.md | Adds ADRs and authoritative API/SSE contract for the dashboard. |
| .context/docs/tooling.md | Documents new scripts for web dashboard development/building. |
| .context/docs/README.md | Adds web interface architecture doc to .context index. |
| .context/docs/development-workflow.md | Adds dashboard dev loop and build guidance. |
| .claude/settings.json | Adds Claude hook dispatch configuration (related to runtime visibility in dashboard). |
Files not reviewed (2)
- pnpm-lock.yaml: Generated file
- web-ui/pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| const session = await runtimeStateService(ctx).getSession(params.id); | ||
| sendData(res, 200, session); |
There was a problem hiding this comment.
Verified this against the branch — the finding is real, and the same root cause covers all four session-route comments (getSession, listTraces, listArtifacts, listCheckpoints):
matchRouteinsrc/web/router.tscallsdecodeURIComponent()on:paramsegments, soGET /api/sessions/..%2F..%2F..%2Fx/tracesstill matches the 4-segment pattern and hands the handlerparams.id === "../../../x".- Nothing validates the id from there down:
HarnessRuntimeStateServicedelegates toresolveRuntimeLayout(src/shared/fs/pathHelpers.ts), which raw-joins —sessionDir: (id) => path.join(sessionsDir, id).
Impact is bounded, but not theoretical. The trailing filename is fixed (session.json, trace.jsonl, artifacts/*.json), so this doesn't give arbitrary file read — but it does allow probing/enumerating directories anywhere on disk and reading any JSON file with those names (e.g. sessions from other dotcontext repos on the same machine). And since the API has no auth and doesn't validate the Host header, "localhost-only" doesn't fully neutralize it: a DNS-rebinding page can reach these endpoints from a browser, and --host explicitly supports non-localhost binds.
The fix is cheap because session ids are always randomUUID() (see createSession in runtimeStateService.ts): reject any params.id that doesn't match a strict allowlist (e.g. /^[0-9a-fA-F-]{1,64}$/) with a 400 before calling the service.
Two notes on where to put the guard:
HarnessDocsService.getContentalready resolves/api/docs/:namethroughPathValidatorspecifically so traversal names can't escape.context/docs— so the sessions routes are the one param surface that reaches apath.joinunguarded. Reusing the same pattern keeps the adapter consistent.- Beyond the web adapter, I'd also consider a defense-in-depth check inside
resolveRuntimeLayout'ssessionDir/sessionFile/sessionTraceFile/sessionArtifactsDirhelpers (reject ids containing/,\,.., or absolute paths). That protects every current and future adapter (MCP, hooks) at the shared layer instead of relying on each transport to remember the guard.
There was a problem hiding this comment.
Confirmed as high relevance for dotcontext. The new web surface accepts an explicit non-local --host while this decoded value crosses from HTTP into the filesystem-backed runtime without an identifier invariant. Please make this a single shared/session-boundary validation (not four route-local patches) and reject it before resolving any runtime path. This should block merge.
| try { | ||
| const traces = await runtimeStateService(ctx).listTraces(params.id); | ||
| sendData(res, 200, traces); | ||
| } catch (error) { |
There was a problem hiding this comment.
This is the same boundary flaw as the session-detail route and should be fixed by the shared session-ID guard. Add a regression request with encoded traversal/separators to prove listTraces rejects it before HarnessRuntimeStateService receives an ID.
| try { | ||
| const artifacts = await runtimeStateService(ctx).listArtifacts(params.id); | ||
| sendData(res, 200, artifacts); | ||
| } catch (error) { |
There was a problem hiding this comment.
Agreed. This endpoint makes the impact especially clear because it can enumerate JSON artifacts after the decoded parameter reaches the path layout. Cover it through the shared guard, including %2e%2e, encoded /, and \\, rather than maintaining a route-specific blacklist.
| try { | ||
| const checkpoints = await runtimeStateService(ctx).listCheckpoints(params.id); | ||
| sendData(res, 200, checkpoints); | ||
| } catch (error) { |
There was a problem hiding this comment.
Agreed. Treat all four sessions/:id routes as one merge-blocking contract: only a valid session identifier may cross into the runtime-state service. The regression suite should cover each endpoint and both rejected dependency outcomes (404/400 as chosen by the API contract).
Summary
Adds a bundled local web dashboard for dotcontext and wires it into the CLI, development workflow, packaging, smoke checks, and documentation.
What changed
dotcontext webCLI surface, including--port,--host,--no-open, and--api-onlyfor local Vite development.dev:web,dev:web-api,dev:web-ui, andbuild:web-ui.web-ui/distinto the generated@dotcontext/clibundle and makes package smoke tests fail if the dashboard assets are missing.Impact
Developers can run the dashboard locally with
npm run dev:web, validate the packaged version withnode dist/index.js web --no-open, and publish CLI bundles that include the built dashboard UI.Users can run the published CLI dashboard with
dotcontext webornpx -y @dotcontext/cli@latest web.Validation
npm run buildnpm run build:web-uinpm test -- --runInBandnpm run build:packagesnpm run smoke:packagesnpm --prefix docs run build.release/packages/cli:node dist/index.js web --port 4527 --no-open, then fetched/and/api/docsNote: docs build completed with existing duplicate-ID warnings for localized pages.