Repository navigation
Pad v0.18.0
Highlights
1. Agents and you in the same doc.
An agent can now change part of an item instead of rewriting the whole body: pad item patch takes exact old/new text, and each old must match once (PLAN-3578, #2055). If the item is open in someone's editor, the patch lands in the live document as a replace of only the blocks it changes, so the person typing elsewhere keeps typing and keeps their cursor (#2056, #2082). pad item patch --undo reverses a patch as long as its new text is still there (#2058, #2089).
2. Library updates for built-in conventions and playbooks.
Conventions and playbooks made from Pad's library now remember where they came from. When Pad ships a better version, the Library page badges "Update available" (your copy is unedited) or "Library changed" (you edited it), and the item pane opens a review: what the library changed, what you changed, and which settings accepting would replace (TASK-3462, #1852, #1873). From a terminal, pad library diff <ref> shows the same and pad library update <ref> takes the library's text, guarded against a concurrent edit (#1875). Copies made before this release are matched once by exact title (#1878). The playbook editor offers the same review (BUG-3852, #2209). The Library now has its own link in the sidebar's Agent section and the mobile sheet (TASK-2258, #2013).
3. The Pad pane in Claude Code.
The pad plugin now includes a side panel, opened with /pad-pane: Now, Next, Browse, Find and Activity views of your workspace, and an item view where you can set status, comment, or tell Claude to work on it (TASK-3486, #1883). If Pad isn't set up yet, the pane's Get started view offers three paths: Pad Cloud, Pad on this machine, or your own server (PLAN-3490, #1888). One plugin install gives the skills, the pane and notifications; the plugin follows stable releases (TASK-3487, #1890), and pad init points Claude Code users at it (TASK-3488, #1898). The pane needs Claude Code 2.1.287 or later.
4. Reference collections.
A collection can be marked as reference material instead of work, with "Track as work" in the collection editor, --tracks-work=false in the CLI, or tracks_work on MCP (PLAN-3535, #1996). Reference items stop counting toward parent progress, no longer block a parent from closing, and drop out of Insights, dashboard open-work counts and pad project next/standup (#2000). On a parent they are listed in their own References group (#2005). Existing Conventions and Playbooks become reference; everything else, Docs included, stays work until you change it. New template Docs collections start as reference.
5. Board and search.
- Needs Attention comes first on the dashboard when it has anything in it (TASK-3588, #2064).
- Set an item's priority from its card (TASK-2214, #1956).
- A parent filter on any collection, built from the parents its items actually have (TASK-2215, #1958).
- A filtered view says so: the active view's name, a one-line filter summary with Clear, and "12 of 84" counts (TASK-3592, #2072).
- The command palette has a persistent scope control, and "All" searches every workspace you can see, including ones you haven't opened yet (TASK-3604, #2085).
Also new
Agent writes.
- Agent body writes are version-checked, phase 1: an unversioned write is counted and warned about (PLAN-3555, #2054).
- Idempotency keys for item create, patch writes, comments, notes, decisions, copy/move and push, so a retried request does not repeat (TASK-3585, #2062, #2065, #2068, #2070, #2071).
pad item context/pad_item action=context/GET /items/{ref}/contextreturn one brief for working an item; the skill and ship playbook load it (PLAN-3598, #2094, #2099, #2104, #2107).- Leases default to the agent's name,
pad project next/readyskip items another agent holds, and leases are held by an opaque worker id so two agents under one name don't share one (TASK-1809 #1900, TASK-3613 #2102). Remote /mcp writes are agent writes named by the client (BUG-2772, BUG-3496, #1903). - MCP gains
pad_itemreminders/unremind and link-pr/unlink-pr, and typed field writes now work over local stdio too (TASK-3611).
Typing is not lost.
- Closing an item, signing out, or saving a changed body asks first when typing may not have reached the server (BUG-3556, BUG-3571, BUG-3540, TASK-3548; #2037, #2041, #2011, #2025).
- A tab that dies with unconfirmed edits offers them on the next open (TASK-3606, #2086), and restoring a kept draft applies only your changes, so a newer version is kept (TASK-3607, #2093).
- Restoring a kept draft no longer reports a false conflict when the stored body ends in a newline. A kept draft whose starting point may be missing edits the server already holds (a collaborator's, or your own that reached the server before the tab died) is not applied as a patch: the notice says why and offers Copy, so restore never duplicates text (BUG-3850, TASK-3856; #2207, #2213).
- Op-log recovery refuses an incomplete replay and sets aside the whole log on a schema rebuild; dormant op-logs compact into one snapshot (TASK-3574, TASK-3531). A peer that drops loses its caret at once, and the Live badge reflects a dead connection and catches up on reconnect (TASK-2206 #1932, TASK-2197 #1895).
- A kept draft you are looking at stays put: newer kept text from another tab cannot replace it before you choose (BUG-3863, #2215).
- Markdown round-trip fixes:
<Button>andPromise<T>stay text, bold/italic/strike serialize back,[[REF]]from an agent is a link (BUG-3583, BUG-3582; #2057, #2052).
Onboarding and getting started.
/pad onboardon an existing project asks where its open work lives (TODO files, GitHub issues) and offers to bring it in, after confirming (TASK-3454, #1853). Onboarding says "Agent activity detected" rather than claiming a connection (TASK-3596, #2077).- In-app tutorials on Pad Cloud, from a first-time card and a Tutorials entry in the user menu; self-hosted instances link to getpad.dev/learn instead (TASK-3452, #1839).
- Workspace import reports what it carried, marks a partial import until its owner keeps or deletes it, and a guide walks a self-hosted workspace to Pad Cloud (TASK-3612, TASK-896; #2100, #1923).
pad workspace link <slug> --url <server>re-points a directory to another server and keeps.pad.toml's other keys (#2103).
Collections and schema.
- Schema edits say what they will do to existing items, and removing an option is never a silent rename (TASK-2187, TASK-2188, #1877).
- Archived collections can be listed, restored and undone; the archive confirm shows how many items it holds (TASK-2189, #1874).
Web UI.
- Quick capture keeps your place (TASK-3590, #2067); the mobile view sheet lists saved views and "All" (TASK-3589, #2066); list group headers get the board lane's bulk actions (TASK-2222, #2012); the dashboard's attention and plan lists are capped with a Show-all toggle (TASK-2210, #1896).
- Someone whose only access to an item is a direct share now sees it leave when it is archived, instead of keeping a live copy until they refresh (BUG-3796, #2212).
- Other people's creations are batched into one toast, and only their events count as unread (TASK-3593, #2073).
- Add Relationship offers both directions and says which way each points (TASK-2217, #1957). The playbook editor and collection dialogs keep a draft (TASK-2191, #1882).
- Search takes
include_archivedand anis:archivedtoken (TASK-2864, #2001). Activity's collection and actor filters run on the server (TASK-2219, #1948). Insights buckets days in your time zone (TASK-3524, #1955). - The sign-in pages follow your theme (TASK-3509, #1924), with the new Pad mark across the app's icons, header and link card (TASK-3514, #1926).
- One app dialog replaces native confirm/alert; the palette and quick-add are real dialogs; focus is taken, kept and returned; icon-only buttons and fields have names (TASK-2221, TASK-2235, TASK-2240, TASK-3515).
- Board lanes mount a window of cards, the item editor loads on demand, and lane reorder is one all-or-nothing request (TASK-2230, TASK-2226, TASK-3517; #1953, #1935, #1933). The server gzips responses (TASK-2225, #1934).
- Creating an API key, you choose Read only or Read and write, and the key list shows each key's access, including older keys (TASK-3866, #2216). A Rotate button on API tokens (TASK-3505, #1915). Quick-add is
c, and one switch turns every single-key shortcut off (BUG-3465, #1851). pad auth 2fa setupanddisable(TASK-403, #1927); the CLI sign-in page has Deny and shows what is asking (TASK-2253).
Webhooks.
- Owner webhooks keep per-endpoint delivery state, and 408/425/429 responses are retried (TASK-3409, #1809).
Security / operators
-
Self-hosters who expose Pad directly (no reverse proxy) should upgrade. /mcp middleware refusals now get the response write deadline (BUG-3834, #2199), and every non-stream response gets a rolling write deadline (BUG-3821, #2189).
-
MCP streams (/mcp listen and SSE) keep alive, take a stream admission slot, and end when their credential does: a revoked session, PAT or OAuth token, or a deleted or disabled user ends the stream at the next check (BUG-3835 #2197, BUG-3837 #2201). Streams whose client stops reading now end instead of pinning a handler (BUG-3808 #2182, BUG-3637 #2172). A collab awareness frame's entry count is bounded by its bytes (BUG-3772, #2204).
-
History: the History tab could return a 500 for an item with a damaged version row. Version rows now record their body hash and the walk refuses what it cannot verify (BUG-3820, #2192); version writers no longer diff text that is not valid UTF-8 (BUG-3839, #2200).
-
Import drops duplicate scalar unique-field values and reports them instead of failing; a hand-edited archive with a composite slug collision is still refused (BUG-3825, #2194).
-
Accounts and sign-in: two concurrent OAuth unlinks can no longer remove a user's last sign-in method (BUG-3816, #2191). An admin invitation resend revokes and re-creates in one transaction, so it cannot mint an invitation for a just-deleted workspace (BUG-3817, #2193). A password change kills outstanding reset links (BUG-3679, #2114); reset-password, register and bootstrap refuse a cross-site form (BUG-3680, #2146); the instance always keeps an active admin (BUG-3811, #2178).
-
Sharing: a share link serves nothing once its item or collection is archived (BUG-3635, #2167); shared bodies are sanitized with the markdown allowlist and the SPA CSP restricts form-action (BUG-3669 #2145, BUG-3799 #2162).
-
CLI: a workspace or ref string cannot re-route a request, and PAD_TOKEN is not sent to a server that only
.pad.tomlnamed (BUG-3645, BUG-3646, #2127). credentials.json is replaced atomically at 0600 and updated under a lock (BUG-3648, BUG-3649, #2170), including on macOS and Windows (BUG-3838, #2203). -
Concurrency: checks that read before a lock now re-check under it: unique_scope values, bulk move/tag, moves, legacy adoption, bulk status/priority updates (BUG-3812, BUG-3813, BUG-3665, BUG-3806, BUG-3831 #2202).
-
Network: the outbound network policy blocks Azure WireServer (BUG-3660, #2155).
-
Releases: release.yml smoke-starts each shipped binary and fails the release before publishing if one does not start (TASK-2035, #1980, #1983).
-
Workspaces: a link between items is used only when both items live in the link's workspace, in every list, count, graph, progress and backlink reader (BUG-3776, #2208). A workspace slug is slugified whichever door supplies the name, including import with a name override; migrate-to-pg keeps existing slugs (BUG-3769, #2205).
-
Access changes reach live connections: revoking access, or any access or credential change, re-checks live connections at once (#1744, #1746). A read-only OAuth token's write gets 403 insufficient_scope, and the /mcp challenge names the scope (TASK-2308, #1986).
-
Also hardened in this cycle, listed in the full changelog: an authorization and tenancy sweep (#1723-#1743, including the outbound network policy #1738), OAuth fixes (BUG-3338, BUG-3398, BUG-3400, BUG-3678), BUG-3336, BUG-3350, BUG-3361, BUG-3768, and migration repairs (BUG-3663, BUG-3664, BUG-3751, BUG-3752).
Behavior changes
- Lists that used to return everything (item lists, children, starred, shared collections, /changes) now return at most 1000 rows and page by cursor. Scripts calling the API directly should follow the cursor (BUG-3704, BUG-3789-3792; #2143, #2151, #2153).
- Agent body writes with no version token are counted and warned about (PLAN-3555 phase 1). A future release will refuse them.
- An OAuth or social sign-in whose provider answer has no stable account id is refused (BUG-3671, BUG-3797).
- Inviting an existing user now creates a pending invitation they accept or decline, from the web "+" menu, the invite link, or
pad workspace invitations|accept|decline. It no longer adds them directly (BUG-2136, #1831, #1833). - A pending invitation is accepted only while its inviter can still grant that access. If the inviter was removed, was demoted, or was restricted to some collections, the invite page says so and offers no Accept, and the invitation drops off the invitee's list (BUG-3770, #2214). Someone who is already a member and follows such a link is let in with the role they have (BUG-3870, #2217).
POST /api/v1/auth/tokensrefuses ascopesvalue it does not recognize, including null,""and[], with 400. Omittingscopesstill creates a full-access key (TASK-3866, #2216).- Leases default to the agent's name, and
pad project next/readyskip items another agent holds (TASK-1809, #1900). - Existing Conventions and Playbooks collections become reference collections on upgrade (PLAN-3535, #1996).
- A collection schema that repeats or empties a field key or option is refused (TASK-3539, #2009); an item list filter naming no field is refused (BUG-3480, #1880).
- Quick-add moved to
c(BUG-3465, #1851). - On Pad Cloud, attachment storage is enforced at upload: Free 100 MB per workspace, Pro 1 GB pooled across the workspaces you own; imports and cross-workspace copies count. A refusal names the limit. Self-hosted stays unlimited (TASK-3619, BUG-3653, TASK-3625, TASK-3601; #2110, #2117, #2119, #2080).
- The workspace export download is shown only to an owner with unrestricted access (BUG-3614, #2105).
- The Claude Code plugin no longer starts an event monitor at session start; watching is opt-in (TASK-3513, #1925).
Changelog
Features
- 3631139: feat(acceptance): one Go reading of a body's acceptance criteria (PLAN-3598 slice (a)) (#2091) (@xarmian)
- 4226e8c: feat(activity): the collection and actor filters run on the server, and the web/CLI badge says which (TASK-2219) (#1948) (@xarmian)
- 28872c2: feat(admin): the MCP audit page names users and connections instead of truncated UUIDs (TASK-2255 C110) (#2032) (@xarmian)
- d5c2ecf: feat(apps): 'via ' attribution on items, comments and history, and the members Apps section (TASK-3413, SPEC-6 U9c UI) (#1793) (@xarmian)
- ede0c22: feat(apps): Settings → Apps: install review, install-code handoff, and lifecycle controls (TASK-3413, SPEC-6 U9a) (#1785) (@xarmian)
- 37066f9: feat(apps): app attachment routes: item-scoped upload, metadata, and a streamed download re-admitted before its first byte (TASK-3401, SPEC-6 U6c) (#1776) (@xarmian)
- 213ecc0: feat(apps): app install lifecycle: two-phase disable and rotate, re-enable, uninstall with the tombstone (TASK-3397, SPEC-6 U8c) (#1775) (@xarmian)
- 52dc41b: feat(apps): app install provisioning, install codes and redeem (TASK-3397, SPEC-6 U8b) (#1772) (@xarmian)
- ca158c2: feat(apps): app install staging: apps/1 manifest, fetch policy, pending reservations, preview (TASK-3397, U8a) (#1770) (@xarmian)
- 82c45f2: feat(apps): app item actions and context codes (TASK-3414, SPEC-6 U11) (#1784) (@xarmian)
- bec3448: feat(apps): app upgrade: staged preview with a classed diff, one-transaction confirm that re-derives it (TASK-3397, SPEC-6 U8b2) (#1777) (@xarmian)
- 68e306c: feat(apps): app webhook delivery behind an in-flight fence, signature v2 (TASK-3408, SPEC-6 U10b) (#1780) (@xarmian)
- e87ace0: feat(apps): app webhook events name the install that wrote them (TASK-3411, SPEC-6 U10d) (#1781) (@xarmian)
- 5e2accd: feat(apps): app write routes: items create/update, comments create/update/delete, the write rule, SSE and watch parity (TASK-3401, SPEC-6 U6b) (#1773) (@xarmian)
- aadf3c0: feat(apps): app-owned webhooks, held until redeem hands out the secret (TASK-3408, SPEC-6 U10a) (#1778) (@xarmian)
- 41be1bf: feat(apps): human-facing JSON names the app that wrote an item, comment or version (TASK-3413, SPEC-6 U9c) (#1787) (@xarmian)
- 8b8124a: feat(apps): item-pane link-out for installed apps' item actions (TASK-3413, SPEC-6 U9d) (#1794) (@xarmian)
- be54575: feat(apps): per-endpoint webhook delivery state, a per-install rate cap, and a 24 h drop (TASK-3408, SPEC-6 U10c) (#1783) (@xarmian)
- b0dc7d5: feat(apps): the app API admits a person's delegated token (TASK-3399, SPEC-6 U5b-2) (#1782) (@xarmian)
- 282a42f: feat(apps): the installed-app API door, ceiling and read routes (TASK-3401, SPEC-6 U6a) (#1771) (@xarmian)
- 9ae4a0b: feat(apps): upgrade review and draft activation in Settings → Apps (TASK-3413, SPEC-6 U9b) (#1789) (@xarmian)
- 85c49cf: feat(appstore): FencedTx, the appstore skeleton, and the boundary and write-capture guards (TASK-3388) (#1764) (@xarmian)
- 526001c: feat(appstore): app attachment upload, read rule and caps behind FencedTx (TASK-3396, U7a) (#1768) (@xarmian)
- 35e4843: feat(appstore): app comment create, edit and delete inside FencedTx (TASK-3391) (#1767) (@xarmian)
- a17e2c4: feat(appstore): app item create and field update inside the fence (TASK-3390) (#1765) (@xarmian)
- 55db86f: feat(auth): Deny and request context on the CLI sign-in approval page (TASK-2253) (#1943) (@xarmian)
- 70c57f8: feat(auth): a first Cloud signup lands in its new workspace; OAuth comes first (TASK-3595) (#2075) (@xarmian)
- cc85c2f: feat(auth): social claim of a never-verified account, provider subjects, cloud-only sidecar routes (TASK-3351) (#1760) (@xarmian)
- b572f07: feat(auth): the bot principal for installed apps: users.kind, kind gates on every sign-in door and credential, people-only counts (TASK-3392) (#1766) (@xarmian)
- d6a45bf: feat(billing): billing availability and prices come from the sidecar; PAD_BILLING=auto|off (TASK-3577) (#2048) (@xarmian)
- f3f90d0: feat(billing): the billing page says what Pro covers and who pays (TASK-3603) (#2084) (@xarmian)
- e8d680b: feat(brand): the Pad mark is the brand: one vector master, the app's icons, header and link card from it (TASK-3514) (#1926) (@xarmian)
- d15b310: feat(chatgpt): a reset-and-verify script for the reviewer's demo workspace (TASK-3321 G8) (#1718) (@xarmian)
- 1339916: feat(chatgpt): serve OpenAI's domain challenge at /.well-known/openai-apps-challenge (TASK-3321 G4) (#1717) (@xarmian)
- 809d497: feat(chatgpt): the Pad plugin package for OpenAI's plugin directory (TASK-3321 U4) (#1712) (@xarmian)
- a55d15d: feat(ci): an advisory dependency-age check (cmd/depages) (TASK-1391) (#2014) (@xarmian)
- a896edf: feat(cli): pad auth 2fa setup and disable; disable asks for the password, or a code on an account without one (TASK-403) (#1927) (@xarmian)
- 1d4f060: feat(cli): pad init points Claude Code users at the pad plugin (TASK-3488) (#1898) (@xarmian)
- 99aac16: feat(cli): pad library diff and pad library update for built-in conventions and playbooks (TASK-3462 U3c) (#1875) (@xarmian)
- fbba819: feat(cli,mcp): pad item context and pad_item action=context (PLAN-3598 slice (c)) (#2099) (@xarmian)
- 8eebefd: feat(cloud): GET /admin/billing-users pages the users the reconcile sweep re-syncs (TASK-3564, pad half) (#2042) (@xarmian)
- 3201342: feat(cloud): a Stripe event marker is a claim with a lease, so a handler that dies does not lose the event (BUG-3561, pad half) (#2043) (@xarmian)
- 772c1a8: feat(cloud): the Stripe customer link is compare-and-set, and readable by user (TASK-3549) (#2027) (@xarmian)
- bc3e23d: feat(collab): a patch to an item open in an editor applies as a narrowed replace (PLAN-3578 step 2) (#2056) (@xarmian)
- f678612: feat(collab): a peer that drops without saying goodbye loses its caret at once (TASK-2206) (#1932) (@xarmian)
- 1a80389: feat(collab): compact a dormant op-log into one Yjs snapshot (TASK-3531 U1+U2) (#1984) (@xarmian)
- 4f6d087: feat(collab): count and log watermark stamps that cover content-bearing rows (TASK-3541 step 0) (#2023) (@xarmian)
- 3e8bf36: feat(collab): count failed op-log appends (TASK-3574) (#2051) (@xarmian)
- 9a99f06: feat(collab): count resumes and force_refresh on resume, to measure offline editing (TASK-3501) (#1908) (@xarmian)
- 23c8e4f: feat(collab): the GC compacts dormant op-logs and a covered resume merges (TASK-3531 U3-U5) (#1985) (@xarmian)
- 4532eec: feat(decision): conventions ask about the item without its comment trail, 12 questions a call (TASK-3119 U2a) (#1871) (@xarmian)
- deec622: feat(decision): executable conventions, the server half (TASK-3119 U1a) (#1859) (@xarmian)
- cc1aa69: feat(decision): log each evaluation's spend (TASK-3119) (#1861) (@xarmian)
- 8eb4519: feat(editor): a tab that dies with unconfirmed edits offers them on the next open (TASK-3606) (#2086) (@xarmian)
- d94a389: feat(import): a bundle import reports what it carried next to what arrived, and a guide walks a self-hosted workspace to Pad Cloud (TASK-3612) (#2100) (@xarmian)
- 136fac6: feat(import): a partly imported workspace is marked until its owner keeps or deletes it (TASK-896) (#1923) (@xarmian)
- 82395bd: feat(import): mark rows a workspace import wrote, author not verified (BUG-3379) (#1755) (@xarmian)
- 0929f65: feat(invitations): a mailbox-only proof on invitation emails verifies the invitee (TASK-3352) (#1757) (@xarmian)
- bc329fb: feat(items): agent body writes are version-checked by default, phase 1: count, warn, replace (PLAN-3555) (#2054) (@xarmian)
- 0cc033e: feat(items): content-anchored patch writes on an item with no live editor (PLAN-3578 step 1) (#2055) (@xarmian)
- 493498c: feat(items): idempotency keys for comments, notes and decisions; idempotency_target_gone is structured (TASK-3585) (#2068) (@xarmian)
- a05197c: feat(items): idempotency keys for cross-workspace copy and move (TASK-3585) (#2070) (@xarmian)
- c46ecc4: feat(items): idempotency keys for item create (TASK-3585) (#2065) (@xarmian)
- 7ec0191: feat(items): idempotency keys for patch writes (TASK-3585) (#2062) (@xarmian)
- 78729db: feat(items): pad item patch --undo, anchored on each edit's context (PLAN-3578 step 3) (#2058) (@xarmian)
- 43d7e05: feat(items): restoring a kept draft applies only your changes, so a newer version is kept (TASK-3607) (#2093) (@xarmian)
- a6f9610: feat(lease): an opaque worker id holds item leases, so two agents under one name do not share one (TASK-3613) (#2102) (@xarmian)
- 2dc5ac7: feat(mcp): ChatGPT item_history names who made each change; email-shaped names are masked (TASK-3321) (#1716) (@xarmian)
- d3b1209: feat(mcp): ChatGPT responses are minimized through per-tool allow-lists (TASK-3321 U3) (#1713) (@xarmian)
- 26fd8a6: feat(mcp): ChatGPT tools declare their OAuth scope; a missing scope answers the sign-in challenge (TASK-3321 U2c) (#1720) (@xarmian)
- 0b0398b: feat(mcp): count admitted MCP requests by method and client (TASK-2307) (#2003) (@xarmian)
- 6118f70: feat(mcp): mount the ChatGPT catalog at /mcp/chatgpt, OAuth-only, off by default (TASK-3321 U2b) (#1719) (@xarmian)
- 1204a31: feat(mcp): pad_item link-pr links a PR from supplied metadata, and unlink-pr removes it (TASK-3611) (#2098) (@xarmian)
- bcb8d93: feat(mcp): pad_item reminders lists an item's reminders and unremind disarms one (TASK-3611) (#2097) (@xarmian)
- 0c0874d: feat(mcp): pad_item.history rows name who made the change (ToolSurface 0.63) (#1714) (@xarmian)
- eb987af: feat(mcp): pad_workspace destructive + open-world (invite emails), pad_playbook open-world (TASK-3321 U0b, ToolSurface 0.61) (#1707) (@xarmian)
- 58be45b: feat(mcp): remote /mcp writes are agent writes named by clientInfo; refused claims say lease_held (BUG-2772, BUG-3496) (#1903) (@xarmian)
- add4b62: feat(mcp): scope in the /mcp challenge, and a 403 insufficient_scope for read-only OAuth writes (TASK-2308) (#1986) (@xarmian)
- f2a1ca7: feat(mcp): structured and typed field values write over local stdio too (TASK-3611) (#2096) (@xarmian)
- 39a5259: feat(mcp): the ChatGPT catalog, a one-tool-per-operation projection of /mcp (TASK-3321 U1, stacked on #1707) (#1708) (@xarmian)
- ed7219e: feat(oauth): RFC 9207 iss on every authorize redirect; JSON 404 for unowned /.well-known (TASK-3321 U0a) (#1706) (@xarmian)
- 5c53aae: feat(oauth): a second canonical resource, with every token bound to one (TASK-3321 U2a) (#1715) (@xarmian)
- 6817804: feat(oauth): count and log requests that omit the RFC 8707 resource (TASK-3363 phase 1) (#1748) (@xarmian)
- 7396ec4: feat(oauth): delegated sign-in for installed apps through the issuance barrier, shipped closed (TASK-3399, SPEC-6 U5b-1) (#1779) (@xarmian)
- e526216: feat(oauth): install clients, service tokens and the issuance barrier for installed apps (TASK-3394, SPEC-6 U5a) (#1769) (@xarmian)
- 6066d42: feat(outbox): freeze an app-projection block into item and comment events (TASK-3389) (#1763) (@xarmian)
- 5c598a1: feat(plugin): no event monitor at session start; an opted-in session's watch starts from the mod, and an unarmed one shows nothing (TASK-3513) (#1925) (@xarmian)
- b66af1e: feat(plugin): the Pad pane ships in the pad plugin (/pad-pane) (TASK-3486) (#1883) (@xarmian)
- 09135d1: feat(plugin): the pane's Get started view: Pad Cloud, this machine, or your own server (PLAN-3490) (#1888) (@xarmian)
- ccd915f: feat(push): idempotency keys for push, and the monitor drops a re-published push (TASK-3585) (#2071) (@xarmian)
- 0bdf539: feat(release): the Claude Code plugin follows stable releases; the release workflow pins the marketplace (TASK-3487) (#1890) (@xarmian)
- 480c5d3: feat(report): Insights buckets days in the viewer's time zone (TASK-3524) (#1955) (@xarmian)
- f61a07f: feat(search): include_archived on /search, and an is:archived search token (TASK-2864) (#2001) (@xarmian)
- aa7bdd4: feat(server): GET /items/{ref}/context, one deterministic brief for working an item (PLAN-3598 slice (b)) (#2094) (@xarmian)
- 5c9c650: feat(server): a read-scoped token may run a playbook (TASK-2863) (#1987) (@xarmian)
- 56922e7: feat(server): every access and credential change re-checks live connections (#1746) (@xarmian)
- 95046c9: feat(server): gzip what the client accepts, except streams, upgrades, ranges and secrets (TASK-2225) (#1934) (@xarmian)
- 560ab27: feat(server): reference items stop counting as open work (PLAN-3535 PR 2) (#2000) (@xarmian)
- 484ca66: feat(server): revoking access re-checks live connections immediately (#1744) (@xarmian)
- 312a73c: feat(server): the health probe gets its own per-address rate-limit tier, and the search burst is 30 (TASK-3620 PR B) (#2108) (@xarmian)
- 559d9d1: feat(server): the item brief carries only the decisions Pad would surface, and counts the rest (TASK-3618) (#2107) (@xarmian)
- ff6d280: feat(skill): the skill and the ship playbook load an item's brief with pad item context (PLAN-3598 slice (d)) (#2104) (@xarmian)
- aa49db3: feat(storage): enforce plan storage limits at upload; Pro 1 GB pooled per account, Free 100 MB (TASK-3619) (#2110) (@xarmian)
- dab3acd: feat(store): a read-only diagnostic counts version tie groups migration 054 may have misordered (BUG-3751) (#2136) (@xarmian)
- bdea49a: feat(store): copy a convention's role field into its role column at startup (PLAN-3598) (#2092) (@xarmian)
- 10f722e: feat(web): "Possibly breaks CONVE-N" chips on the item page (TASK-3119 U1b) (#1860) (@xarmian)
- b831e9f: feat(web): + badge, Decline, and an accept/decline card on invitation links (BUG-2136 U2) (#1833) (@xarmian)
- d0ee9af: feat(web): Add Relationship offers both directions and says which way each points (TASK-2217) (#1957) (@xarmian)
- 697ce2d: feat(web): Needs Attention comes first on the dashboard when it has anything (TASK-3588) (#2064) (@xarmian)
- c78037a: feat(web): a Library link in the sidebar's Agent section and the mobile sheet (TASK-2258) (#2013) (@xarmian)
- fac299d: feat(web): a Rotate button on API tokens (TASK-3505) (#1915) (@xarmian)
- edb20ae: feat(web): a filtered collection view says it is filtered (TASK-3592) (#2072) (@xarmian)
- 8efce99: feat(web): a parent filter on any collection, from the parents its items have (TASK-2215) (#1958) (@xarmian)
- 910a1ae: feat(web): batch other actors' creation toasts; only their events count as unread (TASK-3593) (#2073) (@xarmian)
- 6735b43: feat(web): cap the dashboard's attention and plan lists, with a Show-all toggle (TASK-2210) (#1896) (@xarmian)
- b901ec6: feat(web): choose an API key's access when creating it, and see every key's access (TASK-3866) (#2216) (@xarmian)
- 6903eee: feat(web): choose monthly or annual Pad Pro at checkout (TASK-3468) (#1856) (@xarmian)
- 4ac7f16: feat(web): gapped sort keys, so a reorder is one write (TASK-3525) (#1975) (@xarmian)
- 77b2c25: feat(web): list group headers get the board lane's bulk actions (TASK-2222) (#2012) (@xarmian)
- c62b2b8: feat(web): quick capture keeps your place (TASK-3590) (#2067) (@xarmian)
- 88a26af: feat(web): reference children are listed apart and leave progress (PLAN-3535 PR 3) (#2005) (@xarmian)
- 2850115: feat(web): review and accept built-in updates from the Library page and the item pane (TASK-3462 U3b) (#1873) (@xarmian)
- b034236: feat(web): set an item's priority from its card (TASK-2214) (#1956) (@xarmian)
- 439bda2: feat(web): the auth pages follow the theme, with a toggle that survives sign-in, the Pad grid mark and a slim self-hosted header (TASK-3509) (#1924) (@xarmian)
- ebb2936: feat(web): the mobile view sheet lists saved views and "All" (TASK-3589) (#2066) (@xarmian)
- 1ed71b0: feat(web): the playbook editor and collection dialogs keep a draft; Save stays; the arguments section is edited in place (TASK-2191) (#1882) (@xarmian)
- f6938b3: feat(webhooks): owner webhooks keep per-endpoint delivery state; 408/425/429 are retried (TASK-3409) (#1809) (@xarmian)
- f5a00d5: feat: a collection can be reference material, not work (PLAN-3535 PR 1) (#1996) (@xarmian)
- cff306e: feat: a plan-limit refusal is a persistent prompt that names the limit and who can lift it (TASK-3601) (#2080) (@xarmian)
- 9d2572e: feat: adopt pre-origin conventions and playbooks as built-ins, once, by exact title (TASK-3462 U4) (#1878) (@xarmian)
- 15c9f76: feat: archived collections can be listed, restored and undone; the archive confirm shows the count (TASK-2189) (#1874) (@xarmian)
- a23edba: feat: built-in update count in the bootstrap, and accepted updates record replaced fields (TASK-3462 U3a) (#1868) (@xarmian)
- c178654: feat: built-ins record their origin; an item made from one can be offered Pad's current text (TASK-3462 U1+U2) (#1852) (@xarmian)
- 5adbe04: feat: bulk sort_order: one all-or-nothing request per lane reorder, one activity row per item collapsed per batch (TASK-3517) (#1933) (@xarmian)
- 79922d4: feat: conventions checked on each comment alone, with the chip on its card (TASK-3119 U2b) (#1872) (@xarmian)
- bc2a982: feat: every content change through the ChatGPT door is versioned first (TASK-3321 U1b) (#1711) (@xarmian)
- 4fdc6ed: feat: in-app tutorials on Pad Cloud, links to getpad.dev/learn elsewhere (TASK-3452) (#1839) (@xarmian)
- 8d02818: feat: leases default to the agent's name, and next/ready leave out others' leased items (TASK-1809) (#1900) (@xarmian)
- 1c23a1b: feat: onboarding asks where an existing project's open work lives and offers to bring it in (TASK-3454) (#1853) (@xarmian)
- 3ffe879: feat: predictable palette search scope; "All" searches every workspace you can see (TASK-3604) (#2085) (@xarmian)
- 7b68125: feat: schema edits say what they do to items; removing an option is never a rename (TASK-2188, TASK-2187) (#1877) (@xarmian)
Bug fixes
- 3bcef0e: fix(access): system collections are ordinary collections for restricted members (TASK-3376) (#1756) (@xarmian)
- 3d21213: fix(account): account deletion needs an interactive, recently signed-in session (BUG-3336) (#1733) (@xarmian)
- ff9b9f3: fix(account): account export reads run inside the deadline, from one snapshot per workspace (BUG-3659) (#2160) (@xarmian)
- 3a49a84: fix(activity): the feed and dashboard count only rows the caller may see (BUG-3687) (#2169) (@xarmian)
- 1800059: fix(admin): the instance always keeps an active admin; demote, disable and self-delete share one locked guard (BUG-3811) (#2178) (@xarmian)
- 9bd1910: fix(api): /changes is bounded at 1000 and continues by a cursor on the write order (BUG-3791) (#2151) (@xarmian)
- 30ba2ed: fix(api): body-supplied IDs must name something in the authorized workspace (BUG-3346) (#1728) (@xarmian)
- fc9ceae: fix(api): children, starred and shared-collection lists are bounded at 1000 and paged; MCP starred caps at 300 (BUG-3789, BUG-3790, BUG-3792) (#2153) (@xarmian)
- 858bd85: fix(api): item lists are bounded at 1000 with has-more paging, and filter-key probing is bounded (BUG-3704, BUG-3703) (#2143) (@xarmian)
- 2246ea6: fix(apps): a collection-restricted owner cannot grant an app whole-workspace access (BUG-3768) (#2123) (@xarmian)
- 4f277f8: fix(apps): an app upload is charged against the plan's storage cap (BUG-3653 part B) (#2119) (@xarmian)
- 95ae3ad: fix(apps): an installed app learns its workspace from the redeem, an unscoped /me and every webhook (BUG-3416) (#1797) (@xarmian)
- a04e35a: fix(apps): every list in an apps JSON response is a list, never null (BUG-3417) (#1796) (@xarmian)
- 83ca50f: fix(apps): the apps UI tolerates null lists from the server (BUG-3417, client half) (#1795) (@xarmian)
- 17cbfa2: fix(attachments): a refused upload, transform or import reclaims the blob it wrote (BUG-3652) (#2118) (@xarmian)
- 4e39883: fix(attachments): a thumbnail takes its original's binding at insert time (BUG-3385) (#1762) (@xarmian)
- 67d06fd: fix(auth): a CLI session is never a browser cookie, and sign-in refuses a cross-site form (BUG-3350) (#1747) (@xarmian)
- 15ffd05: fix(auth): a password change kills outstanding reset links (BUG-3679) (#2114) (@xarmian)
- 241dd31: fix(auth): a social sign-in without a provider subject is refused on an account with one bound (BUG-3671) [deploy BEFORE pad-cloud #80] (#2144) (@xarmian)
- 551a338: fix(auth): a user-less legacy workspace token reaches only its own workspace (BUG-3353) (#1734) (@xarmian)
- 98871c7: fix(auth): a verification link verifies only for its account's session; the mailbox owner can claim (BUG-3382) (#1758) (@xarmian)
- 7b41a02: fix(auth): account deletion's 2FA code goes through login's limiter and single-use claim (BUG-3482) (#1881) (@xarmian)
- ec5b606: fix(auth): an OAuth sign-in without a provider subject is refused, bound or not (BUG-3797) [deploy AFTER pad-cloud #81] (#2152) (@xarmian)
- 66b4a9d: fix(auth): disabling an account ends every credential, and a disabled account gets no new one (BUG-3349) (#1741) (@xarmian)
- 2bd0862: fix(auth): platform administration refuses an API token (BUG-3361) (#1742) (@xarmian)
- d4e69a5: fix(auth): reset-password, register and bootstrap refuse a cross-site form, as login does (BUG-3680) (#2146) (@xarmian)
- 1f391d9: fix(auth): store errors on access decisions fail closed (BUG-3334) (#1730) (@xarmian)
- db62001: fix(billing): a plan past its expiry entitles nothing, and Stripe plan writes are ordered (BUG-3356) (#1753) (@xarmian)
- fb71a91: fix(billing): account deletion proceeds past a sidecar 400 only for the old sidecar's empty-customer refusal (BUG-3560, retro of #2036) (#2076) (@xarmian)
- 5d344d2: fix(billing): admin stats count upgrades by when the account became Pro (BUG-3591) (#2069) (@xarmian)
- 8206452: fix(brand): name the entity as Perpetual Software LLC everywhere (TASK-3321) (#1722) (@xarmian)
- ac5f66f: fix(changes): an archived granted item reaches its grantee through /changes (BUG-3796) (#2212) (@xarmian)
- ad6cfc2: fix(cli): a bundle import shows progress, abandons a stalled upload, and resolves a lost answer (BUG-3476) (#1902) (@xarmian)
- a630f86: fix(cli): a credentials save stamps its own file's mtime before it truncates, so the empty-file memo holds off Linux (BUG-3838) (#2203) (@xarmian)
- 22dcce6: fix(cli): a failed stat fails the credentials write instead of skipping the truncate (BUG-3810) (#2181) (@xarmian)
- 1d08594: fix(cli): a locked credentials load re-reads an empty file before believing it (BUG-3829) (#2195) (@xarmian)
- 223c973: fix(cli): a workspace or ref string cannot re-route a request, and PAD_TOKEN is not sent to a server only .pad.toml named (BUG-3645, BUG-3646) (#2127) (@xarmian)
- e17f17f: fix(cli): an empty credentials file read without the lock is unknown, and UpdateStore will not save over it (BUG-3828) (#2187) (@xarmian)
- 90a3047: fix(cli): copy --dry-run previews a big number with every digit (BUG-3456) (#1858) (@xarmian)
- a99a040: fix(cli): credentials.json is replaced atomically at 0600 and updated under a lock (BUG-3648, BUG-3649) (#2170) (@xarmian)
- c204dd3: fix(cli): json fields print as JSON, metadata labelled for the item's kind; MCP 0.68 (BUG-3448) (#1838) (@xarmian)
- 833e309: fix(cli): whoami says what a failed check established, and exits non-zero (BUG-2706) (#1901) (@xarmian)
- 47fe81a: fix(cli,mcp): explicit-limit callers read every page or say they are truncated (BUG-3793) (#2154) (@xarmian)
- 0ecec82: fix(cli,server): a rate-limited health probe no longer starts a second server, and a start that loses the port touches nothing (TASK-3620 PR A) (#2106) (@xarmian)
- ba35908: fix(cli,web): link re-points a directory to another server and keeps .pad.toml's other keys; the move-to-Cloud guide is browser-only (TASK-3612) (#2103) (@xarmian)
- 9f54830: fix(cloud): a failed read of a Stripe event row is not a claim (BUG-3561) (#2083) (@xarmian)
- e3e76da: fix(cloud): account deletion asks the billing sidecar by user, so every Stripe customer the user owns is cancelled (BUG-3560) (#2036) (@xarmian)
- 5caf11f: fix(collab): a REST content replacement fences tabs seeded from the old body, as a restore does (BUG-3746) (#2134) (@xarmian)
- 134d0a4: fix(collab): a compaction snapshot covers only its own generation's cursors (BUG-3747) (#2142) (@xarmian)
- e64abac: fix(collab): a delayed browser snapshot cannot overwrite a body built from more ops (BUG-3744) (#2132) (@xarmian)
- 6ed3c09: fix(collab): a markdown applier request that timed out is not re-sent to another tab (BUG-3748) (#2133) (@xarmian)
- 16a0243: fix(collab): a multi-block patch into an open editor leaves the unchanged blocks between edits untouched (PLAN-3578) (#2082) (@xarmian)
- ae52c96: fix(collab): a patch whose applier times out is not re-sent to another tab (BUG-3586) (#2059) (@xarmian)
- db6f0cd: fix(collab): a schema rebuild sets aside the WHOLE op-log, so set-aside edits can be replayed (TASK-3574 (4)) (#2047) (@xarmian)
- 602ce10: fix(collab): a websocket peer that stops reading or answering is closed within a bounded time (BUG-3771, BUG-3636) (#2124) (@xarmian)
- 64e6dfb: fix(collab): an applier ack reports persisted only when its bracket held no failed append (BUG-3749) (#2131) (@xarmian)
- c8a1eeb: fix(collab): an awareness frame's entry count is bounded by the bytes that carry it (BUG-3772) (#2204) (@xarmian)
- 5960cf1: fix(collab): an open tab refuses a content write over typing the server has not stored (BUG-3542) (#2016) (@xarmian)
- 2898bff: fix(collab): close a peer the op bus had to drop an op for (TASK-1273) (#1981) (@xarmian)
- d90ac1e: fix(collab): persist a typed burst's waiting frames in one transaction (BUG-3253) (#1909) (@xarmian)
- 78ec772: fix(collab): recovery refuses an incomplete op-log replay, and a failed compaction snapshot keeps the log (TASK-3574 (1) and (3)) (#2045) (@xarmian)
- d93a94a: fix(collab): recovery replays SyncStep2 rows, which can hold the only copy of an edit (TASK-3574 (2)) (#2046) (@xarmian)
- 416aea2: fix(collab): stop appending the whole document to the op-log on every reconnect (BUG-3523) (#1965) (@xarmian)
- a5409ed: fix(collab): the editor is read-only through a force_refresh recovery, so no typing is lost (BUG-3697) (#2128) (@xarmian)
- 33e7a50: fix(dashboard): a task under two active plans no longer pins the skeleton (BUG-3538) (#2002) (@xarmian)
- 1fe9a15: fix(deploy): Kubernetes keeps attachments on a PVC, and uploads get a writable /tmp (BUG-3714) (#2141) (@xarmian)
- 6fd5962: fix(deploy): the Kubernetes manifests run one Pad replica with Recreate and no autoscaler, since collaboration needs a single writer (BUG-3573) (#2039) (@xarmian)
- 09ecc5e: fix(diff): version writers never diff text that is not valid UTF-8 (BUG-3839) (#2200) (@xarmian)
- daf9461: fix(documents): restore only a document of the URL's workspace (BUG-3335) (#1724) (@xarmian)
- 08a5f1c: fix(editor): a block marker after a hard break stays text (BUG-3584) (#2060) (@xarmian)
- 6d3842b: fix(editor): a line-start marker followed directly by marked text keeps its form (BUG-3600) (#2079) (@xarmian)
- 8d910dc: fix(editor): bold, italic and strike always serialize to markdown that reads back (BUG-3583) (#2057) (@xarmian)
- cfc5391: fix(events): a corrupted event sequence counter heals visibly into a new id space; the id is read back, not rendered through a double (BUG-2744) (#1906) (@xarmian)
- 828b656: fix(export): an export reads one snapshot, so a concurrent write cannot leave an item without its collection (BUG-3654) (#2122) (@xarmian)
- 3b27a13: fix(grants): an item grant never opens a deleted collection (BUG-3333) (#1725) (@xarmian)
- 0978b9d: fix(import): a bundle is not complete until its gzip footer verifies (BUG-3657) (#2158) (@xarmian)
- 3fe40ae: fix(import): bound the work a single bundle import can do (#1735) (@xarmian)
- 8710100: fix(import): bundle import remaps attachment references in version history, and on a kept partial import (BUG-3655, BUG-3656) (#2156) (@xarmian)
- 9ef3462: fix(import): refuse duplicate source ids and attach by source id (#1740) (@xarmian)
- 77e1b38: fix(import): relation defaults in imported schemas point at the imported items (BUG-3658) (#2159) (@xarmian)
- d51aba9: fix(invitations): a member accepting a dead invitation is already in, not told to get re-invited (BUG-3870) (#2217) (@xarmian)
- 7e7bb61: fix(invitations): a pending invitation dies with its inviter's authority (BUG-3770) (#2214) (@xarmian)
- 6dbe15e: fix(items): a keyed create claims its key before the plan-limit check (TASK-3585) (#2088) (@xarmian)
- b15a483: fix(items): a keyed replay of an archived-source move re-checks the destination (TASK-3585) (#2087) (@xarmian)
- d509d75: fix(items): a move uses the item as it stands under the lock (BUG-3665) (#2165) (@xarmian)
- 199fd20: fix(items): undo of an emptied line; a patch that changes nothing is refused (PLAN-3578 retro fixes 5+6) (#2089) (@xarmian)
- 68a7e27: fix(leases): bind item leases to the claiming account (#1731) (@xarmian)
- 9ef94ba: fix(links,store): wiki links stay on their item through a reference-shaped rename and legacy renumbering (BUG-3693, BUG-3750) (#2140) (@xarmian)
- 4ed3b5e: fix(materialize): a job's deadline scales with its op-log (BUG-3521) (#1946) (@xarmian)
- 202f53d: fix(mcp): MCP's long-lived streams end with their credential (BUG-3837) (#2201) (@xarmian)
- 7825f3a: fix(mcp): MCP's long-lived streams keep alive and take a stream admission slot (BUG-3835) (#2197) (@xarmian)
- fdcf086: fix(mcp): a /mcp stream whose client stops reading ends instead of pinning its handler (BUG-3808) (#2182) (@xarmian)
- 23a0360: fix(mcp): a connection row decides workspace access, not the legacy token list (BUG-3337) (#1749) (@xarmian)
- f9937c7: fix(mcp): cloud refuses to start without an https origin for MCP OAuth; a blocked self-hosted MCP says so (TASK-1069) (#1920) (@xarmian)
- d2337fa: fix(mcp): escape the workspace and ref in pad_playbook.run's path (TASK-2863) (#1991) (@xarmian)
- 16edb94: fix(mcp): free tools/list headroom: reflow and condense descriptions (TASK-3536) (#2004) (@xarmian)
- 1d752e0: fix(mcp): remote pad_item.import keeps its artifact (BUG-3533) (#1992) (@xarmian)
- 3901101: fix(mcp): remote pad_set_workspace embeds the workspace bootstrap (BUG-3326, ToolSurface 0.62) (#1710) (@xarmian)
- 0f932b5: fix(mcp): remote routes answer a missing workspace with no_workspace and the list (TASK-2314) (#1846) (@xarmian)
- 59772ff: fix(members): a collection-restricted owner cannot grant beyond its scope through invitations or role changes (BUG-3634) (#2116) (@xarmian)
- d212eb6: fix(members): keep a workspace's owner and at least one owner member (#1737) (@xarmian)
- c0e66c6: fix(models): the fallback terminal lists cover every "won't fix" spelling (BUG-3546) (#2033) (@xarmian)
- fc64d92: fix(netpolicy): block Azure WireServer, and pin the cloud metadata census (BUG-3660) (#2155) (@xarmian)
- c52f64e: fix(netpolicy): one outbound address policy for URL import and webhooks (#1738) (@xarmian)
- 6365ac0: fix(oauth): a revoked grant mints nothing, and one refresh token rotates once (BUG-3678) (#2121) (@xarmian)
- 7b95dae: fix(oauth): another client's replay of a spent refresh token or code revokes nothing (BUG-3400) (#1807) (@xarmian)
- 5749671: fix(oauth): include_future_workspaces is no longer a control; limit to current is an action (BUG-3338) (#1751) (@xarmian)
- 2ef63e3: fix(oauth): introspection describes only the caller's own client's access tokens (BUG-3398) (#1791) (@xarmian)
- a2e1c5d: fix(oauth): narrow current-only wildcard connections to their current workspaces (BUG-3338) (#1752) (@xarmian)
- 6f6af0c: fix(oauth): the consent screen names Pad, the instance, the user's workspaces and the return host (TASK-1071) (#2017) (@xarmian)
- b950f31: fix(playbooks): the shipped ship playbook loads conventions with --full and guards its status updates with expected_seq (TASK-3575) (#2040) (@xarmian)
- e774268: fix(plugin): drop the pinned version so installs follow the commit (BUG-3463) (#1848) (@xarmian)
- 9f9304b: fix(plugin): the pane's copy link points at the server it talks to, not always Pad Cloud (BUG-3484) (#1899) (@xarmian)
- 505cac1: fix(ratelimit): retention covers the full refill window, so an idle drained bucket is not reset (BUG-3638) (#2115) (@xarmian)
- 1d20ae5: fix(release): the release smoke stops the server it boots, and checks that it did (TASK-2035) (#1983) (@xarmian)
- 9e6791d: fix(scripts): install-refresh asks git in the caller's checkout, not the server's cwd (BUG-3403) (#1790) (@xarmian)
- 38c5916: fix(search): scope workspace search to visible items (#1723) (@xarmian)
- 08a362e: fix(server): /mcp middleware refusals get the response write deadline (BUG-3834) (#2199) (@xarmian)
- 698fe2f: fix(server): a collab revalidation tick that errors makes the conn read-only (BUG-3474) (#1866) (@xarmian)
- 8a1aaf6: fix(server): a failed flush reaches the handler through chi's wrappers (BUG-3827) (#2186) (@xarmian)
- f3b753e: fix(server): a non-cloud boot no longer makes a Pad Cloud database's free users unlimited (TASK-3551) (#2028) (@xarmian)
- a53160e: fix(server): a shared collection lists items in the owner's arranged order (BUG-3530) (#1990) (@xarmian)
- 4d0944c: fix(server): a watch stream resolves each workspace slug once per tick, not per notification (BUG-3773) (#2210) (@xarmian)
- f17aaf2: fix(server): a workspace invite in the setup window answers setup_required, not 500 (BUG-3544) (#2034) (@xarmian)
- 9da6714: fix(server): access_epoch hashes the set the item doors filter by (BUG-3347) (#1812) (@xarmian)
- b6f2d41: fix(server): an account export that times out or fails says so, instead of arriving short (BUG-3626) (#2109) (@xarmian)
- fd63cc5: fix(server): an event stream whose client stops reading ends instead of pinning its handler (BUG-3637) (#2172) (@xarmian)
- 250b5c2: fix(server): an interrupted import rolls back, and its outcome can be asked for (BUG-3475) (#1867) (@xarmian)
- b3cf4f5: fix(server): an ordinary open backlog fills Up next and
pad project ready(BUG-3453) (#1854) (@xarmian) - 4d6ebd4: fix(server): attribution comes from the request, never the body (BUG-3372) (#1754) (@xarmian)
- 0328b8d: fix(server): bulk field update leaves coercion-only keys out of its patch and keeps any locked auto-date value (BUG-3831) (#2202) (@xarmian)
- 037e616: fix(server): bulk move and bulk tag re-check what they read before the lock (BUG-3813) (#2188) (@xarmian)
- f9de187: fix(server): cap the MCP request body at 7 MiB, answering 413 (BUG-3534) (#1988) (@xarmian)
- ed9de58: fix(server): every non-stream response write gets a rolling write deadline (BUG-3821) (#2189) (@xarmian)
- 4d017dd: fix(server): item context fills its budget past a part that does not fit, orders conventions by relevance, and widens implement to commit and PR rules (PLAN-3598) (#2101) (@xarmian)
- 0f5b567: fix(server): lineage and closure name only items the caller may see (#1743) (@xarmian)
- 4b53142: fix(server): match accounts by email only once the address is verified (#1732) (@xarmian)
- 47ca9e5: fix(server): refuse a collection schema that repeats or empties a field key or option (TASK-3539, PR B) (#2009) (@xarmian)
- f74f626: fix(server): register-with-code claims the invitation atomically; a gone invitation refuses and rolls the account back (BUG-3438) (#1834) (@xarmian)
- daed6bb: fix(server): removeUnusableWorkspace reports a failed delete as kept (BUG-3479) (#1870) (@xarmian)
- 119823d: fix(server): role-board reorder refuses another workspace's item and counts its rows (BUG-3342) (#1750) (@xarmian)
- fe94213: fix(server): the /me summary mirrors the admin bypass; every admin route proven open to an admin session, closed to an admin PAT (BUG-1926) (#1918) (@xarmian)
- e3668f0: fix(server): the item-grant filter keeps an app request's read ceiling (BUG-3424) (#1825) (@xarmian)
- a1c36fc: fix(server): the upgrade diff states what a widened delegated access grants (BUG-3427) (#1817) (@xarmian)
- d75db30: fix(server,store): unique_scope values are checked again inside the write, under its lock (BUG-3812) (#2184) (@xarmian)
- cd6dfdb: fix(settings): Conventions and Playbooks start unchecked when a member is restricted (TASK-3384) (#1759) (@xarmian)
- 5578aeb: fix(settings): a late member-access response cannot fill or save another member's panel (BUG-3698) (#2126) (@xarmian)
- 76e029f: fix(share): a share link serves nothing once its item or collection is archived (BUG-3635) (#2167) (@xarmian)
- 896b556: fix(share): a shared body keeps only the classes and inputs the markdown renderer emits (BUG-3799) (#2162) (@xarmian)
- bf37b59: fix(share): a shared item body is sanitized with the markdown allowlist, and the SPA CSP restricts form-action (BUG-3669) (#2145) (@xarmian)
- 6e30a54: fix(share-links): a link stops resolving while its workspace is deleted (#1726) (@xarmian)
- 1f982df: fix(sse): an item event without a collection is not workspace-level (BUG-3343) (#1729) (@xarmian)
- aae955c: fix(storage): bundle import and cross-workspace copy are charged against the storage cap (BUG-3653, TASK-3625) (#2117) (@xarmian)
- c87587c: fix(store): PG migration 054 breaks same-second version ties by id, not ctid (BUG-3751) (#2183) (@xarmian)
- 0aec8ed: fix(store): Postgres migration 004 drops the old doc_type CHECK before renaming phase-plan rows (BUG-3752) (#2130) (@xarmian)
- aa89757: fix(store): a body rewritten without a version rebases the newest version's patch, so History stays exact (BUG-3745) (#2139) (@xarmian)
- 623652c: fix(store): a link row counts only when both endpoints live in its workspace (BUG-3776) (#2208) (@xarmian)
- d95f908: fix(store): a role change or disable of a vanished user is 404, and the unguarded DisableUser is gone (BUG-3826) (#2196) (@xarmian)
- eac9279: fix(store): a workspace slug is slugified whichever door supplies it; migrate-to-pg keeps existing slugs (BUG-3769) (#2205) (@xarmian)
- 5279c49: fix(store): account deletion and workspace purge lock comments root first, and retry a residual deadlock (BUG-3395) (#1792) (@xarmian)
- 61f07d3: fix(store): an API token mint or rotation reads its row back inside its own transaction, never panics on a concurrent delete (BUG-3405) (#1774) (@xarmian)
- 0c68bbc: fix(store): an admin resend revokes and re-creates in one transaction that re-reads the workspace (BUG-3817) (#2193) (@xarmian)
- fdce754: fix(store): an item list with a search term keeps include_archived and sort, and the tag filter matches exactly (BUG-3686, BUG-3706) (#2147) (@xarmian)
- 812f689: fix(store): an unlink re-checks that a sign-in method remains under the account lock (BUG-3816) (#2191) (@xarmian)
- eff70d7: fix(store): compaction deletes exactly the rows it read (TASK-3532) (#1995) (@xarmian)
- a27df26: fix(store): concurrent re-invites no longer report a successful replace as an error (BUG-3460) (#1840) (@xarmian)
- 6770818: fix(store): every write that reads its row back does so inside its transaction (TASK-3406) (#1805) (@xarmian)
- 0280623: fix(store): import drops a unique value an earlier archive item holds, and says so (BUG-3825) (#2194) (@xarmian)
- aa74802: fix(store): import indexes wiki-links, so an imported workspace has backlinks without waiting for a restart (BUG-3627) (#2111) (@xarmian)
- b9db178: fix(store): index api_tokens.token_hash on both backends (BUG-3668) (#2157) (@xarmian)
- 0479bf6: fix(store): index status_transitions(seq) on both backends (BUG-3667) (#2164) (@xarmian)
- 107c382: fix(store): legacy built-in adoption re-reads each item under the workspace lock (BUG-3806) (#2179) (@xarmian)
- be61537: fix(store): legacy document version doors resolve from the body read in the same snapshot (BUG-3833) (#2198) (@xarmian)
- c97a054: fix(store): legacy per-collection item numbering is converted even after migration 056 dropped its marker (BUG-3664) (#2129) (@xarmian)
- 4c7774d: fix(store): log why a workspace import renumbered its items (TASK-2923) (#1994) (@xarmian)
- 8658db7: fix(store): migration 025 rebuilds documents with foreign keys off, so a database with versions can upgrade (BUG-3663) (#2120) (@xarmian)
- 1082c4a: fix(store): reads exclude soft-deleted collections' items (BUG-3425) (#1820) (@xarmian)
- 1e75d19: fix(store): rebindQuery skips SQL comments and quoted identifiers (BUG-3430) (#1822) (@xarmian)
- f9d79ae: fix(store): relationship reads exclude soft-deleted collections (BUG-3434) (#1828) (@xarmian)
- a7f98c0: fix(store): search, tags, children and progress exclude soft-deleted collections (BUG-3428, phase 1) (#1824) (@xarmian)
- 0d28c48: fix(store): startup secret encryption passes compare-and-set on the value read (BUG-3805) (#2177) (@xarmian)
- c912c2c: fix(store): the wiki-link backfill re-reads each item under the workspace lock (BUG-3666) (#2168) (@xarmian)
- 6f75e25: fix(store): version doors resolve the patch chain from the body read in the same snapshot (BUG-3814) (#2190) (@xarmian)
- 0102163: fix(store): version rows record their body's hash, and the walk refuses what it cannot verify (BUG-3820) (#2192) (@xarmian)
- b3e6815: fix(store): wiki backlinks exclude sources in soft-deleted collections (BUG-3344) (#1814) (@xarmian)
- db8fb3a: fix(store,server): an item write re-checks its fields under the seq lock when the schema it was validated against has moved (BUG-3407) (#1806) (@xarmian)
- 48c3ca0: fix(test): test temp dirs stop leaking into /tmp (BUG-3412) (#1786) (@xarmian)
- 2f3cf40: fix(timeline): 'commented on update' only for a comment an update carried (BUG-3437) (#1832) (@xarmian)
- 8bb4616: fix(urlimport): marshal OpenAPI examples with libopenapi's own yaml package (TASK-3422) (#1818) (@xarmian)
- d5ca8a7: fix(versions): an applier-path body write leaves a version row (BUG-3327) (#1709) (@xarmian)
- 69d2be8: fix(versions): restoring or diffing a version whose patch failed is refused with 409 (BUG-3815) (#2180) (@xarmian)
- 540925c: fix(watchevents): a frame that wins the receive select after Close records and reports nothing (BUG-2741) (#1905) (@xarmian)
- 0663b05: fix(web): Activity, Tags, a tag's items, Starred, Members and the Library say when they failed to load (TASK-2203) (#1930) (@xarmian)
- f7b2558: fix(web): ESC closes the pane before its ItemDetail has loaded (BUG-3148) (#2053) (@xarmian)
- 52d90ea: fix(web): Insights throughput labels read as dates, and hours in local time (TASK-2220) (#1951) (@xarmian)
- 03a3617: fix(web): Library, Playbooks, Conventions and the playbook editor find their collections by trait (BUG-3481) (#1876) (@xarmian)
- 56f8a49: fix(web): SSE bursts coalesce their reconcile and collections reload; the timeline ignores other items' comments (TASK-2224) (#1844) (@xarmian)
- b87f63a: fix(web): a failed logout always lets items save again, and a sign-out discards what it stands down (BUG-3571, retro review of #2041) (#2074) (@xarmian)
- 3377637: fix(web): a failed version restore says so, restore needs edit permission, and a child reorder that does not land says why (TASK-2205) (#1929) (@xarmian)
- 71f21dd: fix(web): a json field keeps its type in the schema editor (TASK-2194) (#1971) (@xarmian)
- c0faa52: fix(web): a kept draft with a stale base says it can't be applied and offers only Copy (TASK-3856) (#2213) (@xarmian)
- 9e886dd: fix(web): a link that opens elsewhere never keeps window.opener (BUG-3359) (#1739) (@xarmian)
- 791f25a: fix(web): a palette search that fails says so, with Retry, instead of "No results" (TASK-3587) (#2063) (@xarmian)
- edf7e22: fix(web): a raw markdown save carries its base and asks, not replaces, over a changed body (BUG-3540) (#2011) (@xarmian)
- 56eecea: fix(web): a reorder settles to what the server stored and never leaves an unconfirmed order looking settled (BUG-3699, BUG-3700) (#2150) (@xarmian)
- 1fc60d7: fix(web): a save refused for an open tab's unconfirmed typing asks, and Overwrite lands (TASK-3548) (#2025) (@xarmian)
- b5df3f9: fix(web): a share link's sign-in comes back to it, and a gone or unreachable link says so (TASK-2249) (#1940) (@xarmian)
- fc2e768: fix(web): a shared ?view=table link opens the table, and the remembered view is per workspace (TASK-2212) (#1947) (@xarmian)
- c086457: fix(web): a shown kept draft is held, so newer kept text cannot replace it (BUG-3863) (#2215) (@xarmian)
- 6647ca5: fix(web): a split-pane switch makes each request once (TASK-2228) (#1937) (@xarmian)
- 89d9298: fix(web): a tag filter matching nothing says No matches; tag pages get Show completed (TASK-2211) (#1913) (@xarmian)
- 92b472f: fix(web): a teardown flush over the keepalive budget goes as an ordinary request (BUG-3522) (#1949) (@xarmian)
- eb3f8f0: fix(web): a workspace opens with its requests in parallel; the dashboard poll pauses when hidden or live (TASK-2229, TASK-2227) (#1912) (@xarmian)
- 20690ae: fix(web): admin user rows, settings tabs and the console nav work from the keyboard (TASK-2238) (#1962) (@xarmian)
- e5997ae: fix(web): an agent's [[REF]] applied to an open tab is a link, not bracket text (BUG-3582) (#2052) (@xarmian)
- 34d17e4: fix(web): an item load no longer waits for the index to catch up (BUG-3473) (#1887) (@xarmian)
- 7aa5fa6: fix(web): an open mobile sheet stops page pull-to-refresh, and a pull from its list top closes it (BUG-3386) (#1830) (@xarmian)
- 721c959: fix(web): an unknown invitation code says so, the header names the workspace, and the login link keeps the code (TASK-2251) (#1917) (@xarmian)
- 1eecf71: fix(web): angle-bracketed prose survives the editor:
<Button>,Promise<T>, comments and unknown tags are text, not dropped markup (BUG-3557) (#2035) (@xarmian) - 1a76ca3: fix(web): app dialog and toasts replace native confirm/alert; sticky save and sync status (TASK-2221) (#2015) (@xarmian)
- cbf90f4: fix(web): cards, sidebar rows and table headers stop nesting controls (TASK-2237) (#1968) (@xarmian)
- c0beb4d: fix(web): closing or leaving an item asks first when typing may not have reached the server (BUG-3556) (#2037) (@xarmian)
- 80bfd26: fix(web): conventions, playbooks and the library say what their markers mean (TASK-2257 C72) (#1963) (@xarmian)
- 203a0b8: fix(web): deleting a token or revoking a share asks first and says when it fails; a new token has a Copy button (TASK-2193) (#1910) (@xarmian)
- 35ca07b: fix(web): drop the ⌘] binding that cancelled browser Forward for nothing (BUG-2666) (#1849) (@xarmian)
- 7222505: fix(web): every field shows a focus ring, and the sidebar add buttons reach 3:1 (TASK-2262) (#1843) (@xarmian)
- 41692af: fix(web): every filter select and search input has a name; the share-link password gate is labelled, focused and announced (TASK-2236) (#1921) (@xarmian)
- 8384adc: fix(web): every text field has a name, not just a placeholder; an axe check proves it rendered (TASK-3515) (#1931) (@xarmian)
- 93b906b: fix(web): field editor polish: a non-number says so and sends nothing, the select announces its highlight, the date clear is a sibling (TASK-2216) (#1919) (@xarmian)
- 999c4c8: fix(web): four retro-review findings on #2063, #2067 and #2072 (TASK-3599) (#2078) (@xarmian)
- b0f325e: fix(web): hand back an edit lost with its socket on force_refresh (BUG-3526) (#1973) (@xarmian)
- a867a8f: fix(web): icon-only buttons carry accessible names (TASK-2240) (#1974) (@xarmian)
- 46490b0: fix(web): keep unsaved text when the session ends under an open item (BUG-3571, part 2) (#2044) (@xarmian)
- f8faed6: fix(web): keyed lists render repeated keys instead of blanking the view (TASK-3539, PR A) (#2006) (@xarmian)
- c435457: fix(web): keys typed before the slash or [[ menu opens are its query (BUG-3547) (#2024) (@xarmian)
- 5ea7fde: fix(web): keys typed into an editor frozen by force_refresh recovery stay in the editor (BUG-3786) (#2138) (@xarmian)
- e95135c: fix(web): links to the /-/r/ resolver open their target (TASK-3119) (#1862) (@xarmian)
- 25dca94: fix(web): list and table j/k step the rows on screen (BUG-3492) (#1894) (@xarmian)
- 9b7ffdf: fix(web): long Active Plans titles stay inside their card at every width (BUG-3440) (#1855) (@xarmian)
- 6c4b549: fix(web): looking at the template list no longer picks startup, and a chosen template stays named (TASK-2254) (#1961) (@xarmian)
- f323d81: fix(web): manual order breaks a sort_order tie oldest first, as the server does (BUG-3527) (#1969) (@xarmian)
- 92305fb: fix(web): marks never sit on inline atoms, so a tab's editor equals its Y.Doc (BUG-3568) (#2038) (@xarmian)
- f392847: fix(web): no field suppresses its focus ring any more (TASK-3461) (#1845) (@xarmian)
- fe838f1: fix(web): notifications, docked sheets and the Insights popover take, keep and return focus (TASK-2235 U1) (#1945) (@xarmian)
- f2a3a28: fix(web): offline edits are protected honestly: the badge says they live in this tab, closing or leaving asks, a discarding reload hands them back (TASK-2199) (#1892) (@xarmian)
- 75e1aa6: fix(web): on HTTP/1.1 a browser holds at most three workspace streams; other tabs poll (BUG-3320) (#1904) (@xarmian)
- 5e914e7: fix(web): onboarding says "Agent activity detected"; live signals say "connected" and "ready to receive" (TASK-3596) (#2077) (@xarmian)
- 9a90368: fix(web): one connection-recovered signal heals failed loads, and the dashboard and activity show stream status (TASK-2201) (#1960) (@xarmian)
- 35e5f48: fix(web): playbooks say how they run, and the library keeps its tab (TASK-2256) (#1966) (@xarmian)
- 13fa0c9: fix(web): quick-add is
c, and one switch turns every single-key shortcut off (BUG-3465) (#1851) (@xarmian) - 64398c9: fix(web): rich crash drafts anchor on the editor's confirmed snapshot; patch restore waits for the open editor (BUG-3850) (#2207) (@xarmian)
- 9103aae: fix(web): section tab titles, one Mod+\ chrome state, a reachable and complete shortcuts sheet (TASK-2261) (#1972) (@xarmian)
- 0015f49: fix(web): settings say whose they are, roles and grants explain themselves, webhooks say where they live (TASK-2255 C59/C60/C109) (#2030) (@xarmian)
- 2b0652d: fix(web): signing out asks first while an open item holds edits the server may not have (BUG-3571, part 1) (#2041) (@xarmian)
- 22a32ef: fix(web): skipped board cards keep their placeholder height, so a scroll to a far card lands on it (BUG-3553) (#2050) (@xarmian)
- 4c73346: fix(web): space between the pad wordmark and the first workspace tab (TASK-3545) (#2021) (@xarmian)
- 94d60f5: fix(web): state the password rule on every password field, and show its refusal there (TASK-2260) (#1941) (@xarmian)
- 022b23e: fix(web): the auth pages are real forms, with labels, announced errors and a focused first field (TASK-2259, TASK-2236) (#1911) (@xarmian)
- ede34e9: fix(web): the balanced fields grid follows the item body, not the viewport (TASK-2209) (#1897) (@xarmian)
- 69fbbe6: fix(web): the collection page's search ranks every match, so a filter cannot hide one past the cap (BUG-3710) (#2149) (@xarmian)
- 6071bf3: fix(web): the collection search box writes the address on a debounce (TASK-2232) (#1939) (@xarmian)
- 5c3ed1b: fix(web): the command palette and quick-add are real dialogs (TASK-2235 U2) (#1950) (@xarmian)
- a7c559e: fix(web): the command palette is an accessible combobox, axe-clean in both themes (TASK-2234) (#1847) (@xarmian)
- 4d90f45: fix(web): the conventions bulk toggle, the roles board and workspace create show what failed (TASK-2204) (#1928) (@xarmian)
- 3595a3c: fix(web): the copy and push dialogs send an idempotency key, one per opening (TASK-3597) (#2090) (@xarmian)
- 1d02204: fix(web): the field editor's advanced-section sync no longer depends on the Svelte runtime to stay alive (TASK-2186) (#1841) (@xarmian)
- c1d7988: fix(web): the graph follows the light theme: readable labels, a glow that shows, completed items that recede (TASK-2239) (#1936) (@xarmian)
- b8c9b0e: fix(web): the import dialog shows progress, stops on a stall, and says what was kept (BUG-3475) (#1869) (@xarmian)
- 84ddb3f: fix(web): the list cursor no longer snaps back while the pane follows j/k (BUG-3204) (#1863) (@xarmian)
- 82f8ecc: fix(web): the page behind the palette, a docked sheet or the notification panel is inert (TASK-3520) (#1954) (@xarmian)
- 2e9bfc9: fix(web): the palette's server searches keep a coll: qualifier as the collection filter (BUG-3709) (#2148) (@xarmian)
- 6ca32c2: fix(web): the playbook editor offers a built-in's library update (BUG-3852) (#2209) (@xarmian)
- 186ffdb: fix(web): the remaining native confirm() dialogs ask through the shared ConfirmDialog (TASK-3543) (#2018) (@xarmian)
- bd74960: fix(web): the role board reloads when the route's workspace changes (BUG-3701) (#2175) (@xarmian)
- d39cb9f: fix(web): the selected tab is marked by an accent, not a background step (BUG-3420) (#1829) (@xarmian)
- 9d69d4e: fix(web): the slash menu and [[ picker open where they can be seen, and [[ has a way in (TASK-2218) (#1952) (@xarmian)
- e091bcc: fix(web): the workspace export download is shown only to whom the server lets export, an owner with unrestricted access (BUG-3614) (#2105) (@xarmian)
- bb38641: fix(web): toasts are announced, errors linger, hover holds them, and an unreachable server says so (TASK-2202) (#1914) (@xarmian)
- fa66181: fix(webhooks): a DNS failure is retried, and a redirect may not turn the POST into a bodyless GET (BUG-3661, BUG-3662) (#2163) (@xarmian)
- 7ee0bdd: fix(webhooks): whole-workspace access to manage; no delivery for a deleted workspace (#1727) (@xarmian)
- f6f588b: fix: a convention's stored metadata copy follows its ordinary fields (TASK-2257) (#1964) (@xarmian)
- 05c039f: fix: agents load items with --agent; --format markdown stays body-verbatim and names an empty body (TASK-3451) (#1842) (@xarmian)
- 8c8deee: fix: an invite asks an existing user instead of adding them; decline; CLI invitations (BUG-2136 U1) (#1831) (@xarmian)
- a91fcce: fix: blank workspaces accept the library's own triggers and scopes (BUG-3446) (#1836) (@xarmian)
- ee288cb: fix: item lists refuse a filter key that names no field (BUG-3480) (#1880) (@xarmian)
- c81b5c0: fix: owners cannot lock themselves out by mistake; accounts with no password can set one and turn 2FA off (TASK-2190) (#1879) (@xarmian)
- 3aff8e3: fix: pad keeps an edited /pad skill and never downgrades a newer one (BUG-3466) (#1857) (@xarmian)
- 63e2830: fix: the Live badge reflects a dead SSE connection, and the reconnect catches up (TASK-2197) (#1895) (@xarmian)
- 10a1683: fix: the access epoch tracks the live collection set; deleted collections leave the local index; the open-children guard ignores them (BUG-3428 phase 2) (#1826) (@xarmian)
- 1d94d03: fix: the first-run launchpad follows an onboarding agent live (BUG-3447) (#1837) (@xarmian)
Performance
- d9978cf: perf(web): a board lane mounts a window of its cards (TASK-2230) (#1953) (@xarmian)
- f9713c9: perf(web): tag and starred pages render from the local index (TASK-2231) (#1938) (@xarmian)
- 4acd224: perf(web): the collection page loads the item pane's editor on demand, not before first paint (TASK-2226) (#1935) (@xarmian)
- 1b78add: perf(web): the editor coalesces its markdown, not its dirty signal (TASK-2232) (#1942) (@xarmian)
Refactors
- 01fba76: refactor(cli): examples move from Long to cobra's Example field (TASK-2865, part 1) (#2020) (@xarmian)
- cf3cdef: refactor(web): drop the dead terminalStatuses prop chain (TASK-3537) (#2007) (@xarmian)
Other changes
- 5614980: Make database restore preserve live data on failure (#1703) (@mattfaltyn)
- 9a7b38e: build(deploy): docker compose runs the released, signed image; k8s pins a release (TASK-2034) (#2031) (@xarmian)
- 2c1706f: build(release): a shipped binary that does not start fails the release before it publishes (TASK-2035) (#1980) (@xarmian)
- e303c16: build(web): jsdom 30 (TASK-2586) (#2019) (@xarmian)
- b454f17: build(web): move the SvelteKit and Svelte config into vite.config.ts (TASK-3423, PR A) (#1823) (@xarmian)
- 41456fc: build: go1.26.9 and x/net v0.60.0 for twelve new standard-library advisories (#1922) (@xarmian)