---
title: The Desk
description: One bundled page per workspace where people see what routines found, decide, and tune settings. Pages, routes, publishing and how it is built.
sidebar:
  order: 1
---

## What it is

The Desk is one bundled HTML page (`assets/desk/index.html`) shared by every routine in a workspace. It reads the workspace's store, renders its pages from the compiled `meta/definition`, and writes only the paths people own. Watch and interactive routines get one; serverless and approval-gated routines skip it unless asked.

## Why it exists

A routine that only writes reports leaves people reading and forgetting. The Desk gives them one place to check on a phone, keep or drop what a run found, approve what it wants to send, accept what it learned, and change its settings, with every decision stored where the next run reads it.

## How it works

### Getting one

```
python3 skills/routineos/scripts/routineos.py new hiring-signals --from-template jobs-watch
```

writes `definition.json` and `dashboard/index.html`, the Desk with the workspace name in its config block:

```html
<script type="application/json" id="desk-config">{"store": "db", "workspace": "hiring-signals"}</script>
```

Publish `dashboard/index.html` privately with the capabilities `{"db": {}, "user": {}, "downloads": true}`. Then seed:

```
python3 skills/routineos/scripts/routineos.py seed hiring-signals --out seed
```

and apply the batch files in order. Until the seed is applied the page shows the not-set-up state with that one command. Publishing is the person's decision.

### Pages and routes

| Page | Route | Does |
|---|---|---|
| [Today](/desk/today) | `#/today` | What needs you now |
| [Inbox](/desk/inbox) | `#/inbox` | Messages, gated approvals, read and done |
| [Suggestions](/desk/suggestions) | `#/suggestions` | Learned proposals: Accept, Reject, Snooze |
| [Browse and records](/desk/records) | `#/browse/<type>`, `#/entity/<type>/<slug>` | Lists, filters, one record's page |
| [Views](/desk/views) | `#/views` | Saved and proposed views |
| [Runs](/desk/history) | `#/runs`, `#/runs/<id>` | Health, run history, run detail |
| [Settings](/desk/settings) | `#/settings` | Values with cost notes, workspace options |

Unknown routes land on Today.

> Since 0.5.0: a new shell. Navigation becomes Today, Inbox, [Approvals](/desk/approvals) (`#/approvals`), one entry per record type (plural label) with its saved views nested, History (`#/history?mode=list|cal|health|activity`), and Settings. Suggestions becomes an Inbox tab (`#/inbox?tab=suggestions`); `#/suggestions` still works and `#/runs` redirects to History. The top bar holds search, a notifications bell, a theme toggle and a density toggle (comfortable or compact, kept in browser storage). A command palette opens with Cmd K or Ctrl K. Keys: `g t`, `g i`, `g a`, `g h`, `g s`, `g` plus the first letter of a type's plural (when no other type or page key uses it), `/` search, `f` filters, `n` notifications, `[` sidebar, `?` shortcuts, Esc. Below 900px wide a bottom nav holds Today, Inbox, the first type, History and Settings, with no sideways scroll at 390px.

### Notifications

> Since 0.5.0: the bell opens a 360px panel with a derived feed: new Inbox messages, gates waiting, gates under 6 hours from timing out, failed or late runs, and the run cap reached. It uses the viewer's read state and has All and Unread tabs and a Preferences link to Settings. Nothing is pushed from the page.

### How it is built

The source is plain modules under `assets/desk/src/` (`shell.js`, `router.js`, `store.js`, `actions.js`, `states.js`, `ui.js`, `icons.js`, `tokens.css`, `shell.css`, and one file per page under `pages/`), bundled into one file:

```
python3 skills/routineos/scripts/routineos.py bundle          # write the bundle
python3 skills/routineos/scripts/routineos.py bundle --check  # fail if it is stale
```

`store.js` is the only module that touches `claude.use`. Stored text is rendered as text, never as HTML: everything in the store was written by a run that read the web.

## Worked example

Hiring Signals' Desk after the [Quickstart](/introduction/quickstart) run: Today lists one `match` message and the last run per producer; Browse `#/browse/role` shows one active role, and `#/browse/role?status=all` adds the dropped graduate role (`not in title_keywords`); Runs shows `parsed 3 of 3 sources`.

## Common pitfalls

- **Publishing without `db`.** The page says "This page needs the db capability. Republish it with db declared."
- **Editing the bundle by hand.** Edit `src/` and run `bundle`; `bundle --check` catches drift.
- **Seeding a real Desk for a test.** Use synthetic data (`templates/jobs-watch/demo/generate.py`) on a separate artifact.

## Related

- [Capabilities and degraded modes](/desk/capabilities)
- [Add a Desk](/guides/add-a-desk)
- [Desk routes reference](/reference/desk-routes)
