Two follow-up review findings:
- signals-dashboard: the async createServer callback had no top-level
error boundary, so a malformed %-encoded path, a stash write on a
read-only workshop, or a scan failure rejected a promise the server
never awaits — hanging the request and risking an unhandled-rejection
crash. Wrap the handler and return a controlled 500.
- workshop-create: Path A treated finding a marker (desks/, CAIRN.md,
etc.) as 'just use it', an early stop that left partially initialized
workshops incomplete. Make it detection-only and continue scaffolding
whatever is missing, per the 'only add what's missing' principle.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26faf13e-639c-4a21-ac05-c0dc2bff7c62
Second review round on the Cairn canvas extension:
- XSS via numeric fields: self-assessment scores, quality rating, and
token counts are read from unvalidated agent JSON and interpolated
into HTML/style/title. Coerce them at the source in scanSignals via
toScore (clamped 0..5) and toCount (finite nonnegative int), and
esc() the effort label, so a nonnumeric value can neither inject
markup nor break layout/width.
- CSRF: /api/stash, /api/restore, /api/open are state-changing loopback
POSTs that previously accepted any origin, so a web page that guessed
the port could mutate .desk-stash.json. Add isCrossSiteRequest() and
reject cross-site POST /api/* (Origin / Sec-Fetch-Site check), mirroring
the loopback protection in connector-namespaces/server.mjs.
- Accessibility: the 5s auto-refresh replaced the whole #content subtree,
dropping keyboard focus. Skip the swap when markup is unchanged and
restore focus to the same desk/action button when it does change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26faf13e-639c-4a21-ac05-c0dc2bff7c62
Address PR review findings on the Cairn canvas extension:
- XSS: replace inline onclick handlers (which used HTML-escape that
does not escape single quotes) with event delegation via
data-act/data-desk attributes and one document click listener that
survives the innerHTML auto-refresh.
- Path traversal: add isValidDeskName() and enforce it in every HTTP
and canvas-action handler that takes a desk name (reject empty, /,
\\, null byte, '.' and '..').
- Crash safety: String()-coerce in esc()/truncate() and guard
outcomeIssues with Array.isArray so a malformed signal cannot take
down the whole dashboard render.
- Honest UI: the per-desk button no longer claims to 'open' a desk;
it is relabeled 'path' and copies the desk's filesystem path to the
clipboard with an accurate toast built via textContent (not innerHTML).
- Correctness: outcome signals only pair with a signal when emitted at
or after it (within 1hr), and activeCount is computed by excluding
stashed desks instead of subtracting counts (no longer goes negative).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26faf13e-639c-4a21-ac05-c0dc2bff7c62
The dashboard is the centerpiece — real-time agent coordination
view showing desk status, signal types, intent text, outcome
pairing with honesty gap, token usage, and stash/restore controls.
Includes all updates from the-workshop PRs #3-#7:
- Empty state guidance for new users
- Token usage display per desk
- TA partnership signals + Cairn awareness
- Intent-as-text (execution signals use descriptive text)
- Outcome signal pairing with honesty gap calibration
- Open desk button
- Subtype labels (done/checkpoint/blocked/hands-up)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26faf13e-639c-4a21-ac05-c0dc2bff7c62