# Changelog All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Knotebook follows Keep a Changelog conventions: unreleased work accumulates under [Unreleased] and moves into a dated version section once it ships. The first release is v0.1.0. ## [Unreleased] ### Upgrade notes - **Everyone has to sign in again once after upgrading.** Signing in now records a session on the server, and cookies from before the upgrade don't name one, so they count as signed out. API tokens and connected apps keep working. **Migration 0038** only adds the `sessions` table; rolling back leaves it in place, and the previous version doesn't read it. See [Self-hosting](docs/self-hosting.md#upgrading-and-rolling-back). - **Migration 0039 (site limits) only adds two tables**, `site_limits` and `site_limit_changes`, and changes no existing row. Every limit starts at the value it had before. **`NOTE_MAX_BYTES`, `PARSE_WORKERS`, `PARSE_WORKER_MEMORY_MB`, `PARSE_TIMEOUT_MS` and `PARSE_QUEUE_WAIT_MS` still work and still win**: each one locks its row in Site admin → Site limits, with its range unchanged; to manage one on the page, remove it from `.env` and restart the server. The startup line `parse worker settings in effect`: its `from` entries can also be `admin`, and the number of workers and the longest wait for a worker now report `auto` instead of `default` when they are automatic; `workersSource` can also be `admin`. Rolling back leaves the tables in place and the previous version doesn't read them, so its limits return to the environment variables and built-in values; after upgrading again, the saved values apply again. See [Self-hosting](docs/self-hosting.md#upgrading-and-rolling-back). - **The `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID` and `OIDC_CLIENT_SECRET` variables are no longer imported.** A site that goes straight from v0.5 or earlier to this version gets no "SSO" sign-in service from them: upgrade to 0.15.x first so they are imported (all three must be set on that first start and valid: an issuer starting with lowercase `http://` or `https://`, without a username or password, and an issuer and client ID of at most 512 characters each, as the 0.15 documentation describes; otherwise nothing is imported and they are never read again), or set the identity provider up in Site admin → Sign-in after upgrading. The three lines in `.env` can be deleted; if they are left there they are ignored and the server starts as usual. Sign-in services that were already imported are not touched, and keep their old callback URL. - **Breaking for MCP clients: `create_bulk_transfer_token` is now called `open_notes_as_files`.** It takes the same arguments and answers the same way; the old name is gone, with no alias, so calling it gets the SDK's own "Tool create_bulk_transfer_token not found". Prompts or scripts that name the old tool must use `open_notes_as_files`. Restart your MCP clients so they list the new name. The `401` messages of the `/api/transfer/*` endpoints that pointed at the tool now name `open_notes_as_files` too. See [MCP](docs/mcp.md#open_notes_as_files) (#417). ### Added - **Site admins can adjust 17 limits in the browser, without restarting the server** (#397). **Site admin → Site limits** has the upload size limit, the note size limit, the four parse worker settings and the nine per-person request rates (API and MCP reads and writes, note content reads and writes, searches, uploads and copies, AI actions, bulk downloads, bulk write-backs); **Site admin → Sign-in → Session and tokens** has how long a web sign-in lasts (1 hour to 30 days, 7 days by default) and the number of active API tokens one person can hold. A limit can be lowered from its default (the number of parse workers, the memory per worker and the sign-in period can also be raised); the parse time limit goes down to 4 seconds and the memory per worker to 512 MiB, and the page says what lowering them can break. A change applies to the next request, upload or write; a new sign-in period only to people who sign in afterwards, and a lower token limit doesn't revoke any token. The page lists the 50 most recent changes with who made them, and shows a setting that an environment variable sets as locked. `GET /api/admin/limits` and `PATCH /api/admin/limits` are the API behind it, `GET /api/auth/config` now includes `maxUploadBytes`, and the editor checks files against the site's upload limit instead of a fixed 10 MiB. See [Self-hosting](docs/self-hosting.md#site-limits). - **Drag blocks side by side.** Drag a block by its ⋮⋮ to the left or right edge of another block to make a row of two columns, or onto the edge of a block in a row of fewer than four columns to add a column next to it. When every column of the resulting row holds a single picture, the columns are sized so the pictures come out the same height. One Ctrl+Z undoes the drop. See [Side-by-side columns](docs/columns.md) and [Known limitations](docs/known-limitations.md#side-by-side-columns). - **Resize columns by dragging the divider between them, or with ← / → on the focused divider** (5% of the row per press; quick presses undo together). Widening an image in a column past the column's width now widens the column and squeezes the other columns, down to 200 pixels, or 96 for a column that holds only images. The ⋮⋮ inside a column is 16 pixels wide, and the keyboard shortcut list has an entry for the divider keys. A file dropped on a divider is added next to the nearest block, as anywhere else in the gap. - **The API, MCP, bulk transfer and the in-app AI accept hex colors.** Text and background colors can be written as `#rgb` or `#rrggbb`, for example ``, besides the ten color names; they are stored and read back as lowercase `#rrggbb` (#378). - **Custom colors in the editor.** The text and background color menus in the formatting toolbar and the drag-handle menu have a Custom… item: pick a color in the square or type a hex code, then Apply, and the last eight custom colors you applied are kept on this device (#378). - **Table cells can be colored in the editor.** Hovering over a cell shows a small menu button whose menu has Colors, and the row and column handle menus have Colors too, which apply to the whole row or column; both offer the ten colors and Custom… (#378). ### Changed - **Dragging a block into an empty column replaces the column's empty paragraph.** Before, the empty paragraph stayed next to the block you dropped. - **MCP's instructions now say when to use `open_notes_as_files`.** Where the tool is offered — to a token or app credential, on a deployment with the collaboration component — a credential with `notes:write` is told to use it, if it has a shell, to change much of a note or many notes, and a read-only one to read many or whole notes. To make room, the instructions no longer give how many notes `list_notes` and `search_notes` return per call; each tool's `limit` still says. The tool's descriptions now open by saying it reads and writes (or downloads) one or many notes as files, and the read-write one also mentions the command that fetches one note. Its `next` now says that to work on one note you can fetch it without the download, and, with `notes:write`, that a file answered with `413 content_too_large` should be changed with `edit_note` instead — a note fetched on its own carries no `tooLargeToSend` mark (#417). - **A refused bulk download now says how long to wait.** The two `429`s from `GET /api/transfer/export` keep their `too_many_requests` code. When your hourly downloads are used up, the message gives the wait in minutes, says the limit is per person so a new token doesn't reset it, and says that fetching single notes with the note command doesn't count toward it; when another export is running, it gives the wait in seconds. `open_notes_as_files`'s `next` no longer tells the assistant to read `Retry-After`, which its `curl` commands don't print: it now says what to do for each `429` from its message — fetch the notes it needs with `note.curl` (with where to find their ids) or wait the minutes given, wait the seconds given when another export is running, otherwise wait a minute — and to fetch later changes with `note.curl` instead of downloading again. - **Removing a member asks for confirmation.** The trash button in a group's member table and in a note's share panel used to remove the person at once; now it first shows a confirmation with their email. In a group, removing yourself says you will lose access and can't add yourself back, and takes you out of the group's panel the way **Leave group** does. The last Admin's button stays disabled (#395). - **Signing out signs out only that browser, and its cookie stops working on the server.** Before, signing out only cleared the cookie in the browser, so a copy of it kept working for up to 7 days, and the only way to end a session was to change the password, which signed out every browser. Now each sign-in is a session the server keeps, and signing out (or signing in to the same account again in the same browser) deletes it: the account's other browsers stay signed in, and a note the signed-out browser still had open stops accepting its edits within seconds. Changing or adding a password still signs out every other browser, resetting it with a link signs out every other browser too, and disabling an account ends all of its sessions. Expired sessions are deleted as people sign in. - The `unsupported_color` message now suggests a built-in color name or a hex color, and the built-in AI's message for it changed the same way (#378). ### Removed - The import of `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID` and `OIDC_CLIENT_SECRET` on first start, a stopgap left over from the sign-in providers of v0.6 (#187). ### Fixed - **Dropping into the gap between two columns works.** A file dragged from the desktop into the gap could fail to be added, a block dragged there by its ⋮⋮ didn't drop, and a file dropped on a column's ⋮⋮ opened in the browser and left the note. Now a file lands next to the nearest block, and a block goes into one of the columns. - **Releasing a ⋮⋮ drag over the formatting toolbar now drops the block.** The toolbar briefly appears above the dragged block when the drag starts and took the drop, so a quick drag released over it did nothing. - When a public link is revoked while it is being presented and the page changes to "This link doesn't exist or is no longer active", the address no longer keeps `?present` and the slide part (`#/…`); they are removed without adding a history entry (#234). - **The move-into-group confirmation now says the note's version history is cleared, and the copy confirmations say it isn't copied.** Both the single-note and the multi-select dialogs, in English and Chinese (#246). - The remaining failure logs of the SSO callback (code exchange, userinfo, account lookup retry, unexpected errors) and of a failed first-start administrator setup no longer write the whole error, which for a database error could include the user's email, subject or password hash; they log a short summary instead (#211). One of them, the account lookup retry that still fails, now logs at error level instead of warn. The self-hosting guide now says how to leave the quoted values (`DETAIL`) out of PostgreSQL's own error log. - **Pasting several paragraphs into the middle of a paragraph no longer converts the ordinary links to notes that were already in it.** Only the links you paste become links to notes; an ordinary link to a note's address after the cursor, which ends up in the second half of the split paragraph, now stays an ordinary link. The same goes for pasting copied table cells into a table: ordinary links to notes in cells the paste didn't fill stay ordinary links (#353). ## [0.15.0] - 2026-11-10 ### Upgrade notes - **Migration 0036 (MCP bulk transfer) only adds tables and columns and changes checks**: `transfer_tokens` gets `access` and `range` and its `note_id` may now be empty, two tables are new (`transfer_bases` and `transfer_creates`), and the AI edit history allows the `op` `file_sync`. Existing rows are left as they are. Back up the database first as usual. See [Self-hosting](docs/self-hosting.md#upgrading-and-rolling-back). - **Migration 0037 (MCP bulk transfer) only adds one column**: `transfer_bases` gets `applied_edit_ids`, the AI edit history rows a downloaded copy wrote when it was sent back, so that a file whose rows were reverted is refused with `409 base_unavailable` (`reason: "reverted"`). Existing rows are left empty, which counts as nothing reverted. See [Self-hosting](docs/self-hosting.md#upgrading-and-rolling-back). - **Restart your MCP clients** to see `create_bulk_transfer_token`: a token or app credential gets it on a deployment with the collaboration component. No credential's scope changes. - ⚠ **Rolling back to v0.14:** **Don't revert the AI edit history rows of files sent back over MCP while v0.14 runs.** In v0.14, a `file_sync` row for a part a file rewrote or added can show a **Revert** button, and reverting it reports success but puts nothing back; the row is then marked as reverted, so it can't be reverted after upgrading again either. To undo such a write, upgrade again first, then revert it. See [Self-hosting](docs/self-hosting.md#upgrading-and-rolling-back) and [Known limitations](docs/known-limitations.md#sending-notes-back-as-files-mcp-bulk-transfer). - **After `APP_SECRET` changes, notes downloaded as files have to be downloaded again**: sending back a copy downloaded before the change answers `409 base_unavailable`. - **Breaking for MCP clients: `edit_note`'s `rename` now needs `if_match`**, the fingerprint of the title it replaces, which `read_note_outline` now returns as `titleFingerprint` (a successful rename returns the new title's). A rename without `if_match` — until now the only form accepted — is refused with `invalid_body`; one whose `if_match` isn't the current title's fingerprint, because someone changed the title after the client read it, is refused with `fingerprint_mismatch` and leaves that title as it is. Before, an assistant's rename silently overwrote a title someone else had just changed. The content operations' `if_match` is unaffected: a title change doesn't change any content fingerprint. Renaming a note in the web app works as before. See [MCP](docs/mcp.md#rename) (#381). - **Breaking for MCP clients and the API: Markdown with a `