# Web Board > **Default since v0.2.0.** `skill-central board` now opens a local Hono dashboard instead of printing a terminal table. Use `board --cli` (or `--no-web`) for the v0.1.0 behaviour. The web board is a single-page vanilla-JS dashboard served from your machine. It does **not** require any external service, build step, or network access beyond `127.0.0.1`. ## Quick start ```bash # Launch on default port npx @bobcgn/skill-central board # Custom port npx @bobcgn/skill-central board --port 8080 # Auto-fallback to terminal npx @bobcgn/skill-central board --cli ``` Output: ``` ✓ skill-central web board http://127.0.0.1:5417/ Press Ctrl+C to stop. ``` The browser shows a sidebar grouped by layer (01-global → 04-tech-stack) and a detail pane with the full prompt body on click. ## What you can do | Action | UI affordance | |---|---| | Browse skills | Click a skill in the sidebar | | Read the prompt | Detail pane renders `
` with `white-space: pre-wrap` |
| Edit a skill | Click **Edit** → textarea opens with raw YAML → **Save** |
| See backups | Click **Backups** → list of `.bak.` siblings with restore buttons |
| Audit conflicts | Concurrent edits are caught by sha256 mismatch |

## Edit flow with sha256-conflict detection

```
1. GET /api/skills/:id         → returns rawYaml + sha256
2. User edits textarea
3. PUT /api/skills/:id         body: { rawYaml, expectedSha256 }
   - parse YAML → validate → reject if id changed → backup existing file → write
   - return { ok: true, sha256 }
4. If expectedSha256 doesn't match current file → 409 + currentRawYaml
   - frontend shows "File changed on disk since you loaded it"
   - both versions are presented for manual merge
```

Each successful save moves the previous content to `.bak.`. Backups are **never** auto-deleted; `doctor` lists them and you remove them by hand.

## Security model

- **Bind address.** Default `127.0.0.1`. The `--host` flag accepts any address, but **non-loopback hosts require `--i-understand-nonlocal`** — a footgun guard. The web board has no authentication: anyone with network access to the port can edit your skills.
- **No auth, no CORS.** Same-origin only. If you bind to `0.0.0.0` (against recommendation) you must opt in with the explicit flag.
- **Static asset path traversal.** Every `GET /*` resolves under `dist/web/`; `..` and absolute paths return 404.
- **Skill id pattern.** `:id` in routes is regex-checked to `[a-z0-9]+(-[a-z0-9]+)*`. Anything else returns 400.
- **Write scope.** `PUT /api/skills/:id` only writes to the resolved source path that `GET` reported. A skill can never be moved across layers via the web UI (use `remove` + `add` for that, which makes the move auditable).

## HTTP API reference

| Method | Path | Body | Response |
|---|---|---|---|
| `GET` | `/api/health` | — | `{ ok, version, skills }` |
| `GET` | `/api/layers` | — | `[{ name, path, priority, fileCount }]` |
| `GET` | `/api/skills` | — | `[SkillDto]` sorted by layer priority then id |
| `GET` | `/api/skills/:id` | — | `SkillDto & { rawYaml, sha256 }` |
| `PUT` | `/api/skills/:id` | `{ rawYaml, expectedSha256? }` | `{ ok, sha256 }` / `409` on conflict |
| `GET` | `/api/skills/:id/backups` | — | `[{ file, createdAt, size }]` |
| `POST` | `/api/skills/:id/restore` | `{ backupFile }` | `{ ok }` |

`SkillDto` fields: `id, name, description, type, tags, layer, priority, source`.

## Implementation notes

- Frontend: vanilla JS + a single `index.html` + `style.css` + `app.js`. No build step. Assets are bundled at `dist/web/` by `npm run build:web` (just `cp -R`).
- Server: Hono 4 with `@hono/node-server`. Static middleware is hand-rolled to avoid `hono/serve-static`'s `getContent` requirement on this Hono version.
- Backup convention: `.bak.` (e.g. `…/foo.yaml.bak.2026-06-15T09-11-08-835Z`).