Highlights
Pad v0.11.0 is mostly about a new in-app detail pane and item mini-browser, with board view, collaboration, and access-control hardening rounding it out.
In-app detail pane & item mini-browser
Open any item in a side pane docked next to your list instead of losing your place on a full-page navigation. Click a row to open it; the pane's ?item= ref lives in the URL, so a paned view is shareable and survives a refresh. From there the pane behaves like a browser: click a linked item, a child, a relationship, or a graph node's Open link and the pane re-targets in place with a back stack, navigated by an in-pane Back chevron, browser Back, or a depth-aware ESC that pops one level at a time. A structural parent breadcrumb shows where the current item lives, j/k follows your list selection until you drill in (after which the pane detaches), and you can drag the divider to resize or click the list's dead space to collapse the pane. The same pane also mounts on the full-page item view, where the item you started on freezes read-only while you peek at another, without tearing down its live collab session. On mobile the split collapses to a full-screen overlay with focus trapping and full ARIA-modal isolation.
Board view upgrades
New collections now default to a Board instead of a List, across the create modal, pad collection create, MCP collection create, and the runtime fallback for collections with no stored setting (IDEA-2274). Existing collections keep their stored default_view (no migration), and an explicit picker or --default-view choice is always respected. The board default is unconditional, so seed collections that don't suit a status board are set by hand: Contacts groups its board by relationship, and the Meeting Notes, Decisions, and OKRs presets stay List. A custom collection with no status field still defaults to Board and shows a single Uncategorized lane until you switch it to List (IDEA-2287). Items with no value for the grouping field now collect in that Uncategorized lane instead of disappearing from the Board (IDEA-2275). Board and List cards also show an abbreviated item age ("3h ago", "5d ago", then a short date) opposite the status, reusing the same relativeTime() the item-detail header uses (IDEA-2286).
Collaboration & version-history hardening
Real-time collaborative editing and version restore got materially more robust. Restoring an old version no longer lets a live collab editor silently overwrite the restored content: restore now prunes the collab op-log and reseeds every peer from the restored content, so a stale Y.Doc flush can't clobber it (BUG-2264). It also flushes the live editor before snapshotting the undo-point version, so an in-flight edit lands in version history instead of being lost when the op-log is pruned (BUG-2271). Concurrent field edits now merge instead of clobbering: the web editor sends a single-key fields_patch with expected_updated_at and retries on a 409 rather than replacing the whole fields blob (BUG-2273), and collection settings got the same optimistic-concurrency treatment (BUG-2265). Same-second version ordering is now deterministic thanks to a per-item monotonic tie-breaker (BUG-2270). Two narrow degraded-mode races in the restore path were also fenced, including reconciling a Postgres commit-ack-loss as a durable commit rather than a rollback (cloud/Postgres deployments only, BUG-2276).
Cross-workspace access hardening (cloud)
A batch of authorization fixes closes cross-workspace access gaps that only surface once a workspace has more than one member, so they matter most to multi-user and Pad Cloud deployments. Collaborative-editing WebSocket writes now require edit permission, so a workspace viewer can still join a live document and appear in presence but no longer persist changes to it (TASK-265). SSE event subscriptions now check workspace membership at connect time, so a non-member can no longer open a connection against another workspace's event stream (a connection-slot DoS and existence-oracle gap; the events themselves were already filtered) (TASK-264). Webhook and token mutations now verify both the object ID and its workspace ownership, closing cross-workspace IDOR paths (TASK-266). On the remote /mcp transport, an OAuth token's consented workspace allow-list now scopes workspace-global reads such as pad_workspace list, so a token scoped to one workspace can no longer enumerate the slugs of others (BUG-2102).
MCP & platform
Remote /mcp (the cloud transport) now serves the read-only resource set that was previously stdio-only: items, dashboard, collections, bootstrap, and bounded attachment images. It reuses the same auth and consent perimeter as the tool dispatchers (TASK-2101). The attachment image resource also got a hardened label and a 1 MiB download bound (#933). A new "unparented only" filter lands end to end. The item index and delta payloads carry an optional is_unparented bit for unrestricted callers (TASK-2096), and the web FilterBar exposes it as a chip that round-trips through URLs and saved views (TASK-2099). The toolchain moved to Vite 8.1 / SvelteKit 2.70, and the same PR fixed a pane pop-focus regression the SvelteKit bump surfaced (#1001).
Install
brew install perpetualsoftware/tap/pad
# or pull the container image
docker pull ghcr.io/perpetualsoftware/pad:0.11.0Verifying
Every archive is signed with cosign (keyless / Sigstore OIDC) and ships an SBOM + SLSA build provenance. The checksums signature ships as a single Sigstore bundle (checksums.txt.sigstore.json) — verify with:
cosign verify-blob \
--certificate-identity-regexp "^https://github.com/PerpetualSoftware/pad/.github/workflows/release.yml@.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--bundle ./checksums.txt.sigstore.json \
./checksums.txtChangelog
Features
- c72fe5a: feat(items): add unparented filtering contract (#926) (@xarmian)
- 8cdfb8e: feat(mcp): remote /mcp resource parity — wire read-only resources onto the cloud transport (TASK-2101) (#934) (@xarmian)
- b14953a: feat(web): Phase 1 depth-aware ESC — pop one level at depth>0 (TASK-2163) (#967) (@xarmian)
- 82994fc: feat(web): add ?item= URL-state plumbing to collection page (TASK-2110) (#939) (@xarmian)
- add3ffa: feat(web): adopt vite 8.1 / sveltekit 2.70 toolchain + fix pane pop focus (#1001) (@xarmian)
- deaced0: feat(web): children jump-badge in item header (IDEA-2133) (#957) (@xarmian)
- 4b96834: feat(web): click list-column dead space to collapse the item pane (IDEA-2148) (#960) (@xarmian)
- faf9b37: feat(web): default new collections to Board — schema-aware (IDEA-2274, IDEA-2287) (#1015) (@xarmian)
- fe04de0: feat(web): detach at depth>0 — clear list highlight + gate pane-snap (TASK-2161) (#965) (@xarmian)
- 62f2d0a: feat(web): drill the pane from relationship, child, and graph-Open anchors (TASK-2159) (#966) (@xarmian)
- af7cc11: feat(web): editor content-link pane interception via EditorLinkPopover (TASK-2160) (#968) (@xarmian)
- 48dfbe0: feat(web): expose ItemDetail resolved {id,ref,slug} identity via onIdentity (TASK-2173) (#979) (@xarmian)
- 57da084: feat(web): focus per hop for the detail-pane mini-browser (TASK-2162) (#972) (@xarmian)
- b447ce9: feat(web): focus-follows-editing (activePane) on the full-page pane host (TASK-2181) (#986) (@xarmian)
- a029932: feat(web): in-pane Back chevron for the pane chrome (TASK-2164) (#970) (@xarmian)
- 456a50f: feat(web): in-place "Add child" in the Children section (PLAN-2140) (#958) (@xarmian)
- be40818: feat(web): j/k pane-follow + ESC precedence chain (TASK-2119, TASK-2118) (#945) (@xarmian)
- 551762c: feat(web): mobile split-pane becomes a full-screen overlay (TASK-2121) (#952) (@xarmian)
- f268b66: feat(web): mount split-pane detail view + row-click open (TASK-2112, TASK-2111) (#942) (@xarmian)
- a0bb31d: feat(web): mount the detail pane on the full-page item host (TASK-2174) (#980) (@xarmian)
- a5620eb: feat(web): onOpenTarget(PaneTarget) callback contract + central resolution + same-item guard (#964) (@xarmian)
- 42e5562: feat(web): pane accessibility & focus management (TASK-2122) (#953) (@xarmian)
- 9c0280b: feat(web): pane header controls + resizable divider (TASK-2113, TASK-2114) (#944) (@xarmian)
- eaeeb09: feat(web): pane-navigation controller — depth/ownership state machine (TASK-2157) (#962) (@xarmian)
- 11c7e1e: feat(web): parameterize scroll-target in createScrollRestoration (TASK-2171) (#976) (@xarmian)
- acd9f3f: feat(web): retain-alive master freeze — peeking prop + complete mutation-path audit (TASK-2172) (#977) (@xarmian)
- b37797e: feat(web): settle popstate-driven pane mints to one teardown/mint (TASK-2166) (#971) (@xarmian)
- 14f624d: feat(web): show abbreviated item age on board & list cards (IDEA-2286) (#1014) (@xarmian)
- 1693a0d: feat(web): show an Uncategorized lane on the Board for items with no group value (IDEA-2275) (#1013) (@xarmian)
- d517659: feat(web): sticky pane header keeps item ID + controls visible while scrolling (IDEA-2134) (#956) (@xarmian)
- c956471: feat(web): un-hide structural parent breadcrumb in the embedded pane (TASK-2165) (#969) (@xarmian)
- 9ce52a8: feat(web): unparented filtering UI and persistence (TASK-2099) (#932) (@xarmian)
Bug fixes
- 0f48bce: fix(ci): bump advisory deps to restore green CI (BUG-2278) (#997) (@xarmian)
- dd79e83: fix(ci): narrow BUG-2278 web fix to advisory deps, revert toolchain slide (#999) (@xarmian)
- 9d18f12: fix(collab): flush live editor into items.content before version restore (BUG-2271) (@xarmian)
- 51cd6e8: fix(collab): op-id durable fence for restore-rollback vs applier-ack race (BUG-2276 residual 2) (@xarmian)
- e601f2b: fix(collab): reconcile Postgres commit-ack-loss on version restore instead of treating it as rollback (BUG-2276 residual 1) (@xarmian)
- 40f8805: fix(collab): version restore via prune+reseed (BUG-2264) (#990) (@xarmian)
- 34233a2: fix(e2e): de-flake pane j/k re-target test by opening the first row (BUG-2279) (#1003) (@xarmian)
- 475a70b: fix(mcp): harden attachment image resource label + download bound (#933) (@xarmian)
- bcef802: fix(security): gate collab WebSocket writes on editor role (TASK-265) (#938) (@xarmian)
- 2aa4f14: fix(security): require workspace membership on SSE subscriptions (TASK-264) (#937) (@xarmian)
- 9f6c1d8: fix(security): scope MCP workspace-global reads by OAuth consent allow-list (BUG-2102) (#935) (@xarmian)
- 8e16501: fix(security): scope webhook + token mutations by workspace to close cross-workspace IDOR (TASK-266) (#936) (@xarmian)
- 37ec77d: fix(store): monotonic tie-breaker for same-second item version ordering (BUG-2270) (@xarmian)
- 1dbe043: fix(store): optimistic concurrency + sibling broadcast for collection settings (BUG-2265) (#989) (@xarmian)
- af85d94: fix(web): board-view layout — lanes fill, thin gutters, snug scrollbar (BUG-2127) (@xarmian)
- 53252ee: fix(web): close BUG-2129 stale-fields gap in the collection-edit switch fence (#961) (@xarmian)
- b6d6a1c: fix(web): debounce the item link-search inputs to stop per-keystroke requests (#959) (@xarmian)
- b76abdd: fix(web): full ARIA-modal isolation for the mobile detail pane (TASK-2131) (#1007) (@xarmian)
- 528c3a0: fix(web): guard optimistic upsert with scope epoch (BUG-2098) (#931) (@xarmian)
- 5033bf6: fix(web): harden split-pane collab teardown flush + regression e2e (TASK-2117) (#947) (@xarmian)
- ccf7daf: fix(web): inert the collapsed sidebar so off-screen nav leaves the a11y tree (BUG-2282) (#1011) (@xarmian)
- 1f12b26: fix(web): item field edits use fields_patch + optimistic concurrency (BUG-2273) (#991) (@xarmian)
- 06d7e42: fix(web): keep the inline "New quick action" form open on click (BUG-2281) (#1005) (@xarmian)
- e2ec876: fix(web): make itemMatchesRef workspace-aware in ItemDetail (IDEA-2135) (#1008) (@xarmian)
- 29e49e4: fix(web): make the master/pane freeze invisible to the user (BUG-2263) (#987) (@xarmian)
- 1fb4437: fix(web): manual-sort tiebreak — new items float to the top of a lane (BUG-1941) (#948) (@xarmian)
- 5095748: fix(web): per-instance localDirty shadow + scalar-consumer audit (TASK-2156) (#963) (@xarmian)
- 68e10c8: fix(web): remove visualViewport listeners on Editor destroy (TASK-2109) (#940) (@xarmian)
- d14bceb: fix(web): render the Rich/Markdown mode toggle on the peeking side too (BUG-2263 follow-up) (#988) (@xarmian)
- 55233af: fix(web): reset rawMode on pane item-switch + document flush-on-close policy (TASK-2124) (#955) (@xarmian)
- 033e05f: fix(web): restore fixed-width board lanes + horizontal scroll (revert BUG-2127 lane sizing) (#949) (@xarmian)
- 3c4eee5: fix(web): retarget URL/breadcrumb on collection rename across tabs (BUG-2272) (@xarmian)
- 957ec7b: fix(web): scrub dead paned-item ?item= from last-route cache + other-surfaces audit (TASK-2123) (#954) (@xarmian)
- 8c710e1: fix(web): stop paneOverlay ref-count effect self-looping on mobile (BUG-2284) (#1009) (@xarmian)
- 6d08b72: fix(web): thin horizontal scrollbars — set ::-webkit-scrollbar height (BUG-2127 follow-up) (#950) (@xarmian)
- 316e25a: fix(web): trap focus in BottomSheet so ESC/Tab hit the sheet, not the layer under it (BUG-2130) (#1006) (@xarmian)
Performance
- 220be58: perf(web): cache workspace members/roles + server capabilities per switch (TASK-2120) (#951) (@xarmian)
Refactors
- 0c4a628: refactor(web): extract component with embedded prop (TASK-2106) (#941) (@xarmian)
- 04923c1: refactor(web): extract the pane controller + shell into paneHostController + PaneHost (TASK-2170) (#978) (@xarmian)
- 3525171: refactor(web): reactive master/pane freeze — drop the peeking-driven editor remount (TASK-2180) (#985) (@xarmian)
- 6fcc20f: refactor(web): remove dead EditorToolbar.svelte (#973) (@dchaudhari7177)
Other changes
- c07f6d4: Add bounded MCP image attachment resource (#930) (@jstar0)
- 96daff2: chore(deps): bump low-risk dev tooling + render libs (safe subset of #1002) (#1004) (@xarmian)
- 3725d96: test(web): Phase-2 CAPSTONE e2e for the full-page pane host (TASK-2175) (#981) (@xarmian)
- 2aff33d: test(web): R14 async-race capstone e2e suite for the pane mini-browser (TASK-2167) (#975) (@xarmian)
- 7291516: test(web): vitest for pane ?item= round-trip + click interception (TASK-2116) (#943) (@xarmian)