The Desk
One bundled page per workspace where people see what routines found, decide, and tune settings. Pages, routes, publishing and how it is built.
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:
<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 | #/today |
What needs you now |
| Inbox | #/inbox |
Messages, gated approvals, read and done |
| Suggestions | #/suggestions |
Learned proposals: Accept, Reject, Snooze |
| Browse and records | #/browse/<type>, #/entity/<type>/<slug> |
Lists, filters, one record’s page |
| Views | #/views |
Saved and proposed views |
| Runs | #/runs, #/runs/<id> |
Health, run history, run detail |
| 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 (
#/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);#/suggestionsstill works and#/runsredirects 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,gplus the first letter of a type’s plural (when no other type or page key uses it),/search,ffilters,nnotifications,[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 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 runbundle;bundle --checkcatches drift. - Seeding a real Desk for a test. Use synthetic data (
templates/jobs-watch/demo/generate.py) on a separate artifact.