Skip to the content.

Development Guide

Setup

git clone https://github.com/pgedeon/openclaw-project-webos.git
cd openclaw-project-webos
npm install
cp .env.example .env
# Edit .env, then start
node task-server.js

Architecture

The dashboard is a single-page application with a Win11 desktop shell.

SPA Entry Point

index.html — loads CSS and the shell module:

<script type="module" src="/src/shell/shell-main.mjs"></script>

Shell System (src/shell/)

Views (src/shell/native-views/)

Each view is an ES module exporting a class:

export class MyView {
  constructor(container, apiClient, options) { ... }
  async render() { ... }
  destroy() { ... }
}

Views are lazy-loaded when their window is opened.

Widgets (src/shell/widgets/)

Offline (src/offline/)

API Server (task-server.js)

Express-like HTTP server providing REST endpoints. Key routes:

Storage (storage/asana.js)

PostgreSQL storage layer with parameterized queries. All mutations write to an audit log.

Security (src/security/)

Making Changes

Adding a View

  1. Create src/shell/native-views/your-view.mjs exporting a class with render() and destroy()
  2. Add entry to src/shell/app-registry.mjs in the apps array
  3. The shell automatically adds it to the start menu and taskbar

Adding a Widget

  1. Create src/shell/widgets/widgets/your-widget.mjs
  2. Register in src/shell/widgets/widget-registry.mjs

Adding an API Endpoint

  1. Add route handler in task-server.js
  2. Add storage methods in storage/asana.js if needed
  3. Add migration in schema/migrations/ if schema changes
  4. Add tests in tests/
  5. Update docs/api.md

Adding a Database Migration

  1. Create schema/migrations/NNN_description.sql
  2. Test against local database
  3. Document in CHANGELOG.md

Documentation Site

The docs site (GitHub Pages, cayman theme) is generated from docs/*.md by scripts/build-docs-index.mjs. Each run writes docs/index.md (landing-page link index), stamps missing Jekyll front matter onto new markdown files, and emits docs/search-index.json — the corpus consumed by the client-side search page docs/search.html (vanilla JS, no dependencies). After adding or changing any doc under docs/, re-run:

node scripts/build-docs-index.mjs          # regenerate index.md + search-index.json
node scripts/build-docs-index.mjs --check  # drift check — exit 1 if either output is stale

Both generated files are committed, so the Pages workflow stays pure-Jekyll.

Testing

# API validation (server must be running)
node scripts/dashboard-validation.js

# Comprehensive test suite
node tests/comprehensive-test.mjs

# Individual test files
node tests/test-workflow-approvals-api.js
node tests/test-saved-views-api.js

# Python security tests
pytest tests/test_secrets.py

# DB-free unit/integration suite (CI verify job)
node scripts/ci-db-free-tests.js

# E2E with Playwright
npx playwright test

E2E locally against a DB-free server (CI e2e job equivalent)

STORAGE_TYPE=json_snapshot HOST=127.0.0.1 PORT=13890 DASHBOARD_AUTH_TOKEN=dev-e2e-token \
  node task-server.js > task-server-e2e.log 2>&1 &
E2E_BASE_URL=http://127.0.0.1:13890 E2E_AUTH_TOKEN=dev-e2e-token npx playwright test --project=chromium

Coverage philosophy for API-level flows: test what the mode honestly allows against the live server, and drive full write-path semantics over a real HTTP harness when storage is required. The one-click actions suite (test.describe('One-click actions API') in tests/e2e.spec.ts) shows both layers: degradation-boundary tests pin the live json_snapshot behavior (400 unknown-kind validation ordering, 503 {available:false} audit-first execute refusal, 200 read-contract /api/actions/recent), while the latch pipeline (happy-path executed receipt, idempotent replay with exactly-one executor invocation, 409 stale_retry) runs against tests/fixtures/actions-harness.js — the real Router + registerActionRoutes over an ephemeral http.Server with an in-memory receipt pool, since json_snapshot ships pool=null and cannot back the PostgreSQL latch.

The same philosophy extends to DB-free end-to-end flow tests that need no Playwright browsers at all: tests/test-e2e-mcp-snapshot-flows.js (registered in scripts/ci-db-free-tests.js) drives two shipped features against real servers — the MCP stdio server runs as a real child process (initializetools/list → executed tools/call) pointed at tests/fixtures/snapshot-harness.js, which serves the real snapshot routes over an ephemeral http.Server with json_snapshot parity (pool: null), and the snapshot/restore flow runs over real HTTP against the same harness (create/preview/apply degradation boundary, registry listing + byte-identical download of a seeded artifact, integrity-before-database ordering, redaction invariant on the shipped bytes). One transport lesson is pinned in the test’s own harness comment: the MCP session must be driven with async spawn, never spawnSync — a synchronous wait blocks this process’ event loop, freezing the in-process backend mid-request and deadlocking any tool call against it.

CI

GitHub Actions workflow .github/workflows/ci.yml runs on every push/PR to main:

The audit level starts at critical because the current prod tree carries 5 known-open HIGH advisories (ws 8.20.0 direct dep; extract-zip 2.0.1 via puppeteer-core 24.x). Once ws >= 8.20.2 and puppeteer[-core] >= 25 land, tighten to --audit-level=high (path documented in the workflow comment).

Performance Notes

Static facts only — no synthetic benchmarks. Measured 2026-08-25 by walking the static import graph of src/shell/shell-main.mjs (dynamic import() calls excluded).

Boot module count

List virtualization

Shared window math lives in src/shell/list-window.mjs (pure, DOM-free, covered by tests/test-list-window.js):

Debugging

Style Guide