Skip to content

Changelog

What changed in notdocs, from the CHANGELOG in the repository.

DocumentationChangelog
On this page

Follow it in a feed reader: RSS.

notdocs runs as the hosted service and as the free local daemon, under the notdocs software licence. Entries before 1.6.0 that mention a self-hosted image or an MIT licence describe an edition that is no longer offered; the licence is at /legal/software-licence.

Unreleased

  • A table cell with more tags than fit shows the ones that do and one chip for the rest, which names them on hover and to a screen reader; the extra tags were cut off at the cell's edge with nothing to say they were there.
  • The import page's "3 of 12 imported" carries a ring that fills as documents land.
  • A query table shows the keys it answers to, in a row under it that appears while the pointer or the caret is in the table.
  • The sidebar's tree fades at its bottom edge when there is more below it to scroll to.
  • Narrowing a list view to one status folds the other rows shut and slides the rest up, and a gallery card fades out and back in; both redrew.
  • The home page offers the one-line prompt that has a coding agent set notdocs up, with a button that copies it, under the hero's actions; it was only on the agents page.
  • The security page says organizations and projects are deleted from their settings and that a project's version texts go with it; it said nothing could be deleted without writing in and that version texts were kept.
  • The Obsidian comparison says wikilinks work as written, lists the calendar and gallery views and names sharing by link and published sites; the Notion comparison gained a row on sharing outside the team.
  • The home page says Claude connects to the MCP server by signing in over OAuth and that a project token is for an agent that runs unattended; it described tokens alone, and the hero's commit graph no longer labels a commit as made via the assistant.
  • The tiles on the home page and the cards on the features page lead with what each feature does for you, then how it works.
  • A tile on the home page shows publishing: visibility: public written into a folder's index document, then the page, its markdown and its llms.txt served with no account.
  • The home page names the workspace assistant, the Notion and Obsidian imports, SKILL.md and the link and markdown checks that gate CI, in a band of its own after the page tiles; it left them to the product pages.
  • The sidebar of the site's product pages, the sidebar of a published site and the table of contents beside an article branch each section from a trunk and trace a line to the page or heading shown; a section whose head is not a page folds.
  • The tiles on the home page light their edges as the pointer nears them, light up inside under it, and send a wave along their edges from a click.

1.7.1 (2026-10-15)

  • The notdocs package on npm no longer names a bug tracker and repository on GitHub that only the maintainers can open; it points at support@notdocs.dev.
  • The notdocs CLI and local daemon are distributed under the notdocs software licence, published at /legal/software-licence, rather than MIT; versions already on npm keep the licence they were published with.

1.6.0 (2026-10-15)

  • /sign-in and /sign-up on notdocs.dev open the workspace, where the sign-in dialog is; they answered 404.
  • The hosted service's /api/health asks the database and answers 503 when it does not answer; it said ok whatever the database did.
  • The site, the pricing page and the docs no longer describe a self-hosted edition or an MIT licence; notdocs runs hosted and as the local daemon.
  • The editor keeps tabs in code blocks inside list items; it turned them into spaces.
  • The editor reads a setext heading (text underlined with ---) whose text holds an autolink line as the heading; it split it into a paragraph and a rule.
  • The editor keeps two lists that follow each other apart (- a then * b), and keeps the continuation lines of checkbox items in a list that also has plain items.
  • The editor keeps bold, italic and links around inline HTML, a line break or a footnote reference as written; it closed the mark before them.
  • The editor keeps a paragraph that holds only a no-break space ( , as spacer lines in many READMEs); it dropped it.
  • The editor reads a line starting with an address such as <press@example.com> as the next line of the paragraph above, as the file does; it started a new paragraph.
  • The editor keeps a line holding a tag (</role>) that continues the text of a list item in that text; it moved it into a block of its own.
  • The editor keeps the formatting of text after a code block inside a list item (it showed its backticks and asterisks), keeps a task item's continuation line in the item (it split it into a paragraph of its own), and keeps the checkbox of an item in a list that mixes checkbox and plain items.
  • The editor keeps a <br> or <br/> in prose as written; it turned it into a backslash line break.
  • The editor keeps inline code that wraps onto the next line as it is written; it joined the two lines.
  • Published pages and the site keep reference links and images ([text][ref]), footnotes (listed after the text and linked both ways), the text of inline directives (:abbr[HTML]{title} as an abbreviation) and MDD page and section breaks (as a rule); they left the words of a reference link, a footnote reference and an inline directive out of the page.
  • The editor draws inline HTML: the text between a pair of formatting tags (<kbd>, <sub>, <sup>, <mark>, <abbr title> and the like) in that element and an inline <img> as the image, sanitised as raw HTML blocks are; the tags stay visible as quiet markers. It showed the tags as text and the words between them plain.
  • The editor draws :::info, :::warn, :::error and :::danger as callouts, as the site does, and heads a callout with its label (:::note[Heads up]) instead of its kind.
  • Obsidian-style callouts are callouts in the editor and on published pages: any kind (> [!example], > [!danger]), in any case (> [!note]), with a fold marker (> [!tip]-) and a title after the marker (> [!NOTE] Before you start). They showed as plain quotes with the marker as text.
  • Documents with Windows line endings (CRLF) open in the editor. The canonical form now writes every line ending as a line feed; it wrote a CRLF file with both, line feeds between blocks and CRLF inside paragraphs and code.
  • The editor shows and keeps single-line directives such as MDD's ::page-break and ::section-break or ::embed[…]{src="…"}, drawn as a labelled dashed rule, and keeps the attributes of a text directive (:abbr[HTML]{title="…"}). Business documents with a page break opened in the source view only.
  • The editor shows and keeps footnotes: a reference (text[^1]) as a small raised label and its definition ([^1]: note, indented paragraphs included) as a note block; it wrote them back as a link (text[^1](note)).
  • The editor keeps reference-style links and images ([text][ref], [ref][], [ref], ![alt][ref]) and their definitions ([ref]: https://…), showing a definition as its source line; it rewrote them as inline links.
  • The editor opens documents with empty list items (1. with nothing after it) or an empty blockquote (>), which it could not open.
  • The editor reads code blocks, tables and continuation lines inside numbered list items without an extra space of indent, keeps a numbered list that starts at 0, and keeps the checkboxes of numbered task items (1. [ ] a).
  • The editor keeps an autolink with a port (<http://localhost:3000>), and shows bold between a less-than and a greater-than sign (disk <80% … **WARN:** … >90%) as bold rather than as asterisks.
  • The editor keeps bold, italic, strikethrough and links nested the way the file has them (**_x_**, **[a](x)**, ****x****), keeps the link around a linked image ([![badge](…)](…), which it dropped), and reads ~one~ and MDD subscript (H~2~O) as text instead of strikethrough. Such documents opened in the source view only.
  • The editor opens documents with inline code inside bold, italic or strikethrough (**x**); they could not be opened in the editor or a shared session.
  • The editor reads a paragraph that opens with a word and a period, such as "No. Saves go through…" or "I. M. Pei", as prose; it showed it as a numbered list and would have saved "1." in its place.
  • The editor no longer puts a space before three colons inside a line, as in `:::note` written as code, which also sent such a page to the source view.
  • The editor shows every container directive, not only callouts: :::files, ::::tabs with :::tab[Label], ::::steps, ::::cards, ::::accordions and MDD blocks such as :::letterhead are framed blocks headed by their label or name, a file tree drawn as one, and their text is edited in place. A document holding them opened in the source view only ("the editor would rewrite line …"), and showed the fence as text with the list after it.
  • The command palette (⌘K) offers "Switch organization: " for each of your other organizations, which opens its home.
  • The app has a user menu: your monogram in the sidebar footer and in the header of an organization's home opens a panel with your name and address, your organizations (the current one checked, and your role where you are a member or a guest), Settings, and Sign out. Choosing an organization opens its home; signing out ends the session for every organization and lands on notdocs.dev. A local workspace shows its fixed local identity and has no sign out. On /w the menu replaces the organization dropdown.
  • Choosing an organization with no projects yet in the sidebar's workspace switcher opens that organization's home; it did nothing.
  • A project whose repository the organization's GitHub connection no longer reaches shows one notice at the top instead of the same error in every panel: it says whether the organization lost its GitHub account or holds another one than the repository's owner (an organization connects to one GitHub account), gives owners a Reconnect GitHub button and links the organization's home; members are told an owner has to reconnect. Settings have a GitHub section for owners with the connected account, a link to the app's permissions on GitHub, a way to reconnect or change the account, and the projects the account does not reach.
  • A shared session no longer shows "Unsaved changes" for a moment after a change that leaves the file as it is, such as an agent's edit folded in or a value set to what it already was.
  • The documentation is a notdocs site now, published by notdocs from its own repository (github.com/entro314-labs/notdocs-docs) at https://entro314.notdocs.dev/notdocs-docs/p, with an llms.txt and a search box. /docs and /docs/<page> on notdocs.dev redirect permanently to the same page there, and the site's Docs links, footer and llms.txt point there. A Publishing page is new, and the Hosted page says who authors a commit and how to limit a token to folders.
  • An agent's edit to a document people have open in a shared session lands in the session instead of under it: docs.update (from MCP, the CLI, the SDK or the API, with nothing new asked of the agent) is merged character by character with what the people are typing, on the version the agent read, so an edit to the same paragraph no longer loses to theirs. The people's unsaved typing is saved as theirs first, and the agent's edit is a commit of its own, authored by the agent. While it writes, the agent shows in the presence row as a square chip (docs-bot · agent) and as a caret over what it changed, for a few seconds. A session open on another instance of the hosted service is not reached; the agent's edit then goes to the file and the session merges it by line, as before, naming the agent.
  • Other people's carets in a shared session are drawn in their colour again; they were drawn transparent, since the editor only draws a #rrggbb colour.
  • In a shared editing session, a change made from outside it (an agent's or a person's write through the API, MCP or CLI, or an edit to the file) is announced to everybody in the document: the top bar says who made it and which sections (docs-bot@agent changed Setup and Usage) and those sections are tinted for a few seconds. When such a change collides with lines being edited, the notice names whose version is in the History.
  • A docs.update written on the version it was read from (every save of a shared session, and most agents' edits) is committed on GitHub as the person or the agent who made it, as a merged one already was; it was committed with no author of its own.
  • The home page shows two isometric loops: under the three moves, a markdown file rendering as a page whose card, dragged from doing to review, rewrites status: in the file and lands one commit; in the agents section, in place of the contract diagram, notdocs taken apart into repository, core, store and contract, with the web app, CLI, MCP server and assistant generated from the contract. Each plays only while on screen, pauses when pressed, shows a still frame under reduced motion, and is hidden on phone widths.
  • Every page of the site carries breadcrumb structured data, and the pricing page describes notdocs as a software application with the Free, Solo and Team prices as offers, read from the same plan catalogue the page shows.
  • The site's pages have titles that say what each is about in a search result or a tab (Run locally: CLI and daemon on any markdown folder · notdocs rather than Local · notdocs); the menus and headings keep their short names. The docs, local setup, import, CLI and Notion comparison pages have fuller descriptions.
  • The changelog page has one title again. CHANGELOG.md's frontmatter showed at its top as a rule and a heading of the file's id, followed by a second "Changelog" heading.
  • Published pages are served as markdown too. A document's address with .md after it (/handbook/p/guides/setup.md, index.md for the home, <id>.md for a document shared by link), or a request whose Accept header prefers text/markdown, answers the file as written, frontmatter included; the page names that address in its head (<link rel="alternate" type="text/markdown">) and under its title. A document shared by link is answered to whoever holds its address and marked for no index, as its page is.
  • Every published project answers llms.txt (/handbook/p/llms.txt, docs.acme.com/handbook/llms.txt): the site's name, what its home says of it, and each listed page by the address of its markdown. A custom domain answers one for the organization, naming each project's.
  • Published sites have a search box, over the listed pages alone, through /api/public/search?org=&project=&q=, which an agent can call as well. A visitor's search is lexical: a query is never embedded, since that would be spend on the deployment's credentials with no credential to meter it against. A document shared by link is never found.
  • A browser that offers WebMCP gets three read-only tools on a published page: the site's pages, a page as markdown and the search, through the same public routes a visitor's page uses, so the agent reads exactly what its reader can.
  • Commits on GitHub name who made the change as the git author: a person by their name and email address, an agent by its token's name as <name>@agent, as the History already did. The committer stays notdocs. A person's address is now in the repository's history, as with any commit they make themselves.
  • A commit an app connected over OAuth makes names the app in a Via: trailer of its message, so the repository's history tells a person's own change from one Claude made as them. A token's folders are matched without case, as links resolve, so a token limited to docs writes under a Docs folder too.
  • Agent tokens can be limited to folders: tokens.create takes paths (folders from the repository root, such as docs/changelog), notdocs token create --path <folder> (repeatable) and "Folders it may write" in the project's token dialog. Such a token reads the whole project and writes only under its folders: documents, their comments, the images it uploads or imports (which live under <docsRoot>/assets/), and moves that start and end there and rewrite no link elsewhere. markdown.fix, index.rebuild and docs.moveMany are refused to it. tokens.list, notdocs token list and the settings show the folders.
  • Published pages show images written as raw HTML (<img src="…">) and Obsidian embeds (![[diagram.png]], found by name as Obsidian finds it), an embedded document as a link to its page, and a card's href pointing at the published page; a card or embed of a document visitors may not read keeps its words without the address.
  • Published pages are kept per project and read again only when a document, the repository's config or the organization changes, so a visit costs a few small queries; unpublishing a document takes effect on the next request. Visitors are limited to 300 requests a minute per address (429 with Retry-After).
  • A custom domain becomes the canonical address of published pages as soon as Vercel reports it verified, checked on a visit at most every ten minutes; before, it waited for somebody to open the domain settings.
  • Publishing: a document can be read without an account. Its visibility is private, link (anyone with its address; not listed, not indexed) or public (listed on the project's published site and in its sitemap). A document without one takes it from the nearest folder whose index.md sets one, then from visibility in notdocs.config.json, then from the organization's default, which is private. The project and organization defaults cover the documents under the docs root, not the root files or markdown read through include globs, and templates are never published. Set it from the globe button on a document (it shows the address to send), from "Who can read this…" on a folder in the sidebar (which writes the folder's index.md, creating it if needed), from Publishing in the settings for the project and the organization, with notdocs publish <path> <public|link|private|inherit> (--project-default, --org-default), or with docs.visibility, docs.update, config.update and orgs.update over the API and MCP.
  • Published sites: a project's published documents are at <org>.notdocs.dev/<project>/p/<path>, with a sidebar of the published pages, a table of contents and the docs' callouts, tabs, steps, code and diagrams; a document shared by link is at /p/<id> and is never listed. Links and wikilinks to documents a visitor may not read show their words without the address, images come from the documents that show them, and a query block lists the published documents it finds. Visitors read the default branch. Solo, Team and Enterprise publish; on Free a document set to public is shared by link.
  • Custom domains: an organization's own domain now serves its published documents (docs.acme.com/<project>/<path>, with its own sitemap and robots.txt) instead of the workspace, which stays on the organization's subdomain, where people sign in.
  • The organization subdomain's sitemap.xml lists the projects' published pages, and its robots.txt names it.
  • GFM alerts (> [!NOTE], [!TIP], [!IMPORTANT], [!WARNING], [!CAUTION]) render as callouts on the site's pages and on published pages, as they do in the editor.
  • orgs.update takes a visibility, and its slug is optional. The plan's limits carry publicSite, shown in the Plan settings and notdocs org plan.
  • A menu or panel that grows after it opened is fitted to the window again rather than running off its bottom.
  • Notices look the same everywhere. The bar over a document (deleted, read-only, changed on disk, unsaved text to restore), the assistant's errors, an import's warnings and the messages about a deployment that is missing its GitHub, Clerk or domain settings were each drawn by hand, some with an icon and an edge and some without. They are one bar now: the icon sits on the first line of the message, and the buttons that answer it stay on that line and drop under it on a narrow screen. The read-only MDX notice no longer touches the edges of a phone.
  • Type: the two heading sizes carry their own tightening, so every page title and document title is set the same, and a link's underline sits at the position the typeface names and breaks around descenders.
  • Cards share one layout. A card's title bar, body and footer now use the same padding and the same hairline rules wherever they appear: a query block in a document, a code listing, the assistant's task list and its recommendations. A card's actions sit in a tinted footer, as a dialog's do: the organization's docs repository card and the recommendation card gained one, and Alternatives there is a button like any other. A project on the organization's page lifts under the pointer as gallery cards do.
  • Buttons are pills. A labelled button has round ends and a little more room at its sides; icon buttons keep their square, so a toolbar still reads as one strip. A press shrinks a button slightly more than before, except one that opens a menu or a popover, which holds still so the panel does not jump. A destructive button (Delete project, Disconnect) rests on a red tint and fills with red under the pointer, where it used to be a white button with red text.
  • Cards, menus, dialogs and buttons sit on softer shadows. Each surface's shadow is now a stack of faint layers that widen as they fade, behind the same hairline edge, so a card rests on a wide soft ground and a menu or dialog floats clearly above it without a hard dark edge. The light theme changes; the dark theme and high contrast keep their edges as they were.
  • Only operations that change something take an Idempotency-Key. members.list, tokens.list, connections.list, domains.status and assets.grant need admin or write scope but change nothing, so they now ignore the key like any read: the SDK and the web app no longer send one with them, which saves a database write per call, and a repeated domains.status can no longer be answered a stored "pending" after the domain verified. Their MCP tools (and the WebMCP tool of assets.grant) take no idempotencyKey and are marked read-only, so a client runs them without asking, and GET /api/ops lists them as not idempotent.
  • A document the MDD tools wrote is read as the business document it is. Such a file carries MDD's document-type and no type, so a letter (document-type: business-letter) was stamped type: page and shown and validated as a plain page. A file without a type now gets the notdocs type its document-type names: business-letter a letter, terms-of-service terms, privacy-policy privacy, and proposal, invoice, contract, memo, report and nda their own. MDD's generic agreement and service-agreement, which a DPA and an SLA borrow for want of their own, and any other document-type still make a page, and a type already there is never changed. Stamping adds only the type line. A file that lacks a field its type requires (an invoice without invoice-number) is still listed and searched, and its issues show when it is validated, as for any document whose frontmatter does not match its type; a save to it is refused until the field is added.
  • History records a comment when it is made. A comment, reply or resolve was recorded only when the local daemon's watcher reported the comments file, and the watcher can miss one of several quick writes, so History lacked that change until the file changed again; on GitHub projects, where a push names no hash for the file, comment changes had stopped reaching History at all. The operation now records the change itself, naming who made it, and the watcher's or the push's later report of the same file records nothing.
  • pnpm --filter web dev:local keeps the daemon's index in its workspace (.dev-workspace/.notdocs/index), as notdocs serve does, so a restart keeps the project id, a bookmarked /w/<id> address and every document's History. The index was in memory, and each restart made a new project whose History started from the files on disk. --fresh still starts over. notdocs serve --data-dir also accepts a folder whose parents do not exist yet.

1.5.0 (2026-09-30)

  • Print preview: Print or save as PDF in a document's Export menu, or in the command palette, shows the document on A4 sheets as it prints, with its letterhead, header, footer and page breaks, and prints exactly that page, so the browser's print dialog saves it as a PDF. It replaces the tab the export used to open. The printed page also got right what it had wrong: the header and footer sat over the first and last lines of every page and numbered each one page 1; they now print in the page margin with the real page number, as mdd-convert does, in Chrome and Edge (Firefox and Safari print the page without them). The lines of a letterhead, contact block and signature print on lines of their own instead of running together. notdocs docs export --format html says the file prints to PDF from a browser, and --format pdf explains how to get one.
  • A write retried after its answer was lost is not made twice. Every operation that changes something takes an Idempotency-Key header, and a repeat with the same key and input is answered what the first attempt answered: a docs.create retried used to fail with conflict because its own first attempt had made the file, and a comments.add retried started a second thread. The SDK and the web app send a key with every write, MCP tools take an idempotencyKey argument, and the CLI has --idempotency-key. Keys last 24 hours; a key sent again with another input is refused.
  • Documents: .mdd, .mdc and .mdx files are read as documents, not only .md. An MDD letter (.mdd) and an .mdc are listed, searched, linked and saved like .md, byte for byte; Cursor's rules in .cursor/ stay behind agentFiles. An .mdx is read-only: it is listed, indexed, searched and shown with its JSX and import lines as written, but not saved, since the markdown serializer would break them; the editor says so, and a write through the API, the CLI or an agent is refused with the reason. A move keeps a document's extension, [[offer]] finds offer.mdd, and the sidebar marks a document that is not .md with its extension. Markdown elsewhere and notdocs config survey now list only .markdown as a format the workspace does not read.
  • Local daemon: a web UI pointed at a daemon started with notdocs serve --token offers its tools to the browser's agent (WebMCP). The page read the list of operations without the token, the daemon refused it and the page registered no tools without saying so; a list it cannot read is now reported in the browser console.
  • History no longer repeats a comment. The local daemon's watcher often reports one write to a document's comments file more than once, and each report was recorded as another change; the comments file now gets an entry only when its content changed, as a document does.
  • History: an agent's write to a file keeps its own entry when a save reaches the file before the change feed has recorded it, as a shared session's save often does. The save merges it as before, and History shows the agent's change and then the merged save; before, the agent's version was folded into the save and had no entry of its own.
  • GitHub projects: a repository too large for GitHub to list in one answer (past 100,000 files or 7 MB of listing) is listed folder by folder, so every document in it is indexed. Before, the documents past GitHub's cut were missing from the workspace. A repository with one folder too large to list even alone is refused with a message rather than indexed in part.
  • Deleting an organization removes it and all of its projects in one step. A deletion that stopped part-way, on a database error say, could leave the organization in place with some of its projects already gone; now it is all or nothing, and trying again finishes one that stopped after the custom domain was detached.
  • MCP: an agent connected with a project token, to the hosted endpoint or through notdocs mcp against a hosted workspace, is no longer offered the organization tools (orgs_list, orgs_get, github_installations, github_repos, billing_status) that refuse every token. An app a person connected over OAuth still gets them.
  • History and the project's recent changes name who made a change by email, and an agent by its token's name (deploy-bot@agent), as comments do. They showed the internal id (usr_01…, token:tok_01…). changes.since and changes.tail answer the name as actorName beside actor.
  • Importing a Word document into a project whose images are kept in the repository makes one commit for the document and its pictures. It made one commit per picture and one more for the text.
  • A list or panel that could not load has a Retry beside the message: comments, history (and a change's diff), links, search, boards and query views, the project home, the sidebar's documents, and the settings sections for members, tokens, the custom domain, the plan, connected apps, GitHub repositories and markdown found elsewhere. Before, the message stayed until the page was reloaded.
  • Assistant: when the page cannot reach the server to ask which models it has, the panel and Settings say so, with a Retry. Any failed request used to show the setup for a missing AI Gateway key, as if the deployment had none.
  • Assistant: once Approve or Deny is pressed on a proposed write, the other button waits with it (and Run or Skip on an external tool's call), so a second press cannot send the opposite answer while the first is on its way.
  • Boards: ⌥← or ⌥→ on a card whose next column the filter hides leaves the card where it is and says so, with Show all in the toast. It used to move the card into the hidden column, where it and the focus disappeared without a word.
  • Sidebar: every drag has a keyboard route. A pinned document moves with ⌥↑ and ⌥↓; a document's menu lists Make it a task (and each other type in the type chips), as a drop on the chip does; Add files here… in a row's menu and Add files in the Documents header pick markdown files and images, as a drop from the desktop does. The type change that meets a changed file now points to that menu rather than the properties, where the type cannot be set.
  • Documents: Discard and Restore are undone from their toast, as a delete is, with no confirmation first. Restoring a version from the History offers Undo, which writes back the version it replaced (not offered when the restore was merged with a change made meanwhile, and refused if the document changed after it). Discarding the unsaved text kept from an earlier visit, or the edits held on the branch the switcher left, offers Undo that puts them back.
  • The workspace fits a phone. Under 768px the sidebar is hidden until the top bar's toggle opens it as a sheet over the page, which a pick, a tap beside it or Escape puts away; a document's top bar keeps the file name, the save state, Save, comments and the assistant, with Read, Source, Pin and the exports in one More menu; the side panel always floats as a sheet that its button closes (a panel pinned on a wider window does not open over a phone's page by itself, and is a column again there); the page margins narrow, the project home's view tabs take their own row, and the notices above a document wrap. Nothing scrolls sideways at 390px.

1.4.0 (2026-09-29)

  • Editing together: when the document of a shared session is deleted, what the session held is kept as a draft on each person's device and offered when the document is there again (the delete undone, or the file put back). Before, it was gone with the session.
  • Comments: a thread whose heading was renamed, or that an agent anchored to a heading the document never had, shows as a comment on the document, with where it was anchored beside it. It used to keep the badge of a section that is not there.
  • The API takes the bearer token as its only credential. It also took the sign-in cookie, which a browser attaches to any request whatever page makes it, so only the cookie's own settings stood between another site and an operation run as you. The web app, the CLI and every connected app already send the token.
  • Custom domains: a change shows at once. Which organization a domain is served as is kept in the K/V store every instance reads, and renaming the organization, setting or removing the domain, or a change of plan drops what is kept; before, each instance and the CDN remembered the old answer for up to ten minutes, so a renamed organization's domain led nowhere meanwhile. Without the K/V store an instance remembers for one minute.
  • Plans: a custom domain is served while the organization's plan includes one. When the plan ends the domain stays on the organization, is not served, and Settings says why; it serves again on a plan that includes one, with nothing to set up.
  • Billing: an organization whose subscription payment is overdue cannot be deleted or taken through a second checkout until the subscription is cancelled or paid, however long its paid period has been over. The subscription's status is kept from the provider's webhook for this.
  • A file whose id is not a ULID (id: intro, a Zettelkasten id: 202109231200) is stamped a ULID when it is indexed and keeps the old value under aliases, where a [[wikilink]] by that name still finds it; an import already did this. Before, a name was taken as the document's identity, which comments then refused, and a number was replaced without being kept. A document indexed under such a name gets its ULID the next time its file changes, or on notdocs reindex.

1.2.0 (2026-09-29)

  • The change feed names a section by its heading as it reads: a heading holding a link or an entity was named by its markdown (see-docsx), so the sections an event listed did not match the anchors comments use on the page.
  • Local daemon: a document can be renamed to a name that differs by case alone (Plan.md to plan.md) on a disk that does not tell case apart, which answered "already exists". The daemon also reads the checked-out branch from the repository's HEAD file instead of running git for every operation and every file event.
  • Local daemon: the web UI sends the daemon's token, so notdocs serve --token and the web UI work together. Set NOTDOCS_DAEMON_TOKEN for pnpm --filter web dev:local, or NEXT_PUBLIC_NOTDOCS_DAEMON_TOKEN for a web UI pointed at a daemon started elsewhere.
  • Connected apps: a refresh token that was already replaced and is presented again ends the connection, since a token used twice was copied; the app is connected again from its own side. Before, the copy that got there first kept working.
  • Connecting an app by its client ID metadata document: the document is read from the very address its host was checked at, so a host name that answers a public address to the check and a private one a moment later is not followed inside the network; multicast, reserved, NAT64 and 6to4 addresses are refused with the private ones.
  • Assistant: the answer a chat is streaming can be resumed or stopped only by the person who asked for it; before, anybody who could open the project and knew the chat's id could.
  • notdocs assets upload and notdocs import send an image of any size the plan allows: where the deployment keeps uploads in an object store, the CLI now sends the file straight there, as the web app does, instead of refusing what does not fit in one request. The SDK has it as client.uploadAsset.
  • A project opened by its address while signed out offers the sign-in, as an organization's address already did, instead of saying the project will not open.
  • On a phone-width window the sidebar starts closed, so the document has the screen; the toggle and ⌘\ open it as before, and what you chose stays.
  • History shows a document's own changes however busy the branch has been since: it was the document's share of the branch's newest 2,000 events, so an older document on a branch agents commit to showed "No recorded changes". changes.tail takes a docId for that, and answers more and oldest so a reader some events are withheld from can page back past a short page; notdocs changes --tail and the MCP tool have both.
  • Editing alone: the page no longer says "This file changed on disk" about its own save when you kept typing through it; a checkbox or a status changed while a save is on its way stays changed and is saved next, rather than snapping back; and a document deleted under your unsaved text says it was deleted instead of saying it changed.
  • Renaming a document to a name another file already has is refused before anything changes; before, the title changed and the file kept its old name. A move that fails now says "Could not move" or "Could not rename" in plain words.
  • A change the type's schema refuses now names the property at fault and what it expects, wherever it is made (the document page, the sidebar, a board or a table cell), instead of only saying the frontmatter does not match. The sidebar says "document" where it said "page", and a deployment without sign-in says so in words under the button.
  • Saving a document that did not change writes nothing: no commit with an empty diff on GitHub, no rewrite of the file on disk. The docs.update operation and the assistant's tool now say that frontmatter replaces the whole frontmatter, so an agent sends every key to keep.
  • GitHub projects: a move, an image upload or a batch of writes no longer fails with "main moved while committing" because somebody else committed in the same moment; the commit is built again on the new head, and still refuses when one of its own files changed. A markdown file larger than one megabyte is read and indexed instead of failing the push that carried it.
  • Create docs repo can be tried again: a repository that exists already (an attempt that stopped half-way, or one made by hand) is taken as it is and given only the starter files it lacks. And a project whose first indexing fails is no longer left registered and half indexed behind the error.
  • An organization that is no longer connected to a repository's owner on GitHub (the app was uninstalled, or the organization connected another account) no longer reads that repository's change feed, its remembered versions or its live events, and a push to the repository is no longer indexed for it. Before, only the document operations refused.
  • A document added from a board lands in the same folder the New document dialog would use for its type (an nda under contracts/, a page at the docs root); before, a board used the type's plural.
  • Editing alone: what you type while a save is on its way is no longer lost when you leave the document before the save answers; it is kept on this device and offered when the document is opened again, like a save that failed. And Keep my version after a branch switch writes to the branch the conflict came from.
  • A guest who may see only some documents no longer learns of the others through the link check, the markdown check or the repository's own templates: those answer for the documents the guest may read, and the built-in templates alone.
  • notdocs serve --token now protects the daemon as documented; the flag was taken as the project token and the daemon started unprotected (NOTDOCS_DAEMON_TOKEN was unaffected). notdocs mcp against a hosted workspace lists the same tools as the hosted endpoint, without the admin tools a project token cannot run, and notdocs op docs.validate no longer reports an invalid document as a conflict.
  • Editing together: when the server cannot write the shared session back to the file (the repository refused the commit, the branch was switched under the daemon, a property the type does not allow), the page now says so, and the session keeps what was typed until the write lands rather than dropping it half a minute after the last person leaves.
  • Deleting your account (a Clerk user) now disconnects the apps you had connected over OAuth, such as Claude. Before, a connected app kept its access after the account was deleted, and would have acted as whoever next signed up with the same address.
  • Self-hosting: the published image builds again. The image build fetched the dependencies before it had copied the patches/ folder, so it stopped at the patched Sentry package and no image has been published since the patches were added.
  • Claude: add notdocs to Claude on claude.ai, in the desktop and mobile apps or in Claude Code as a connector at https://notdocs.dev/api/mcp, with no token to copy. Claude sends you to notdocs to sign in and choose read, or read and write; it then works as you in every workspace you can open, and never manages organizations, people, billing or tokens. Any MCP client that supports sign-in connects the same way: notdocs is its own OAuth authorization server, with dynamic client registration, client ID metadata documents and PKCE, access tokens that last an hour and refresh tokens replaced on every use. Settings › Connected apps lists the apps you connected and disconnects them, which ends their access at once; the same is the connections.list and connections.revoke operations.
  • MCP tools have titles and say whether they only read and whether they can overwrite or remove something, so a client like Claude runs the reads without asking and confirms before a delete. The hosted endpoint no longer lists the admin tools, which no token could run.
  • Tables, trees and diagrams: Settings › Tables, trees and diagrams, notdocs lint and the markdown.check operation find the markdown that renders as text instead of what it is: pipe rows GFM does not read as a table because the delimiter row is wider or narrower than the header (or missing), a file tree outside a code block, and Mermaid source written as text or in a code fence with no language. Fix (notdocs lint --fix, markdown.fix) repairs them in one commit, changing only those lines: the delimiter row is made as wide as the header, a tree gets a tree fence and a diagram a mermaid one. A code block that holds the start of another, which makes every fence after it pair up wrong, is reported for you to fix. notdocs lint exits 1 while there are problems, for CI.
  • Pasting: a table, a file tree or a Mermaid diagram pasted into a document as plain text, from a terminal, a model's answer or VS Code, becomes a table, a tree block or a diagram, with a mismatched delimiter row repaired, where before each line became a paragraph of its own.
  • Review after organizing: once documents are organized, the dialog offers a review by an agent, in the assistant (Ask the assistant fills its box) or as the MCP server's new review-organization prompt. The agent fixes the links the move left broken, with your approval, and suggests document types and folder names without changing them. The assistant can now check the branch's links itself.
  • Organize: markdown read through include moves under the docs root from the sidebar's Markdown elsewhere row, from Settings › Markdown elsewhere, or with notdocs docs organize --apply. Each document keeps its path below the root, its id, history and comments, and the links to and from it are rewritten in the same commit; the include globs the move emptied are dropped, and the links are checked before and after so the result says whether the move broke any, apart from those broken already.
  • Links: Settings › Links, notdocs links and the links.check operation list every link, image and wikilink on the branch that reaches nothing (a file that is not there, a path that leaves the repository, a wikilink no document answers to), with its document and line. notdocs links exits 1 when there are any, for CI.
  • Sidebar: markdown read through include, outside the docs root, is kept out of sight so the sidebar shows the docs root's structure: a Markdown elsewhere row counts it and Show lists it in its folders, and the eye button hides it again. The choice is each person's, per project; the open document is always listed. Search, the palette, boards and the project home list it all along.
  • notdocs init lists the markdown the workspace would not read: --include reads it where it is, and --move shows how it would move into docs/, which --move --apply does, keeping each file's path below docs/ and rewriting the links to and from the moved files, wikilinks aside since a move keeps the name they find a file by. The coding agents' files never move, and a checkout with uncommitted changes is refused.
  • Markdown elsewhere: Settings lists the markdown in the repository that the workspace does not read, by top-level folder, with the coding agents' files (skills, plugins, .claude/, instructions below the root) apart and unticked, and an owner reads the folders they tick where they are, through include. An empty project and an empty sidebar say how many markdown files are not read and where most of them are, with a link there. notdocs config survey prints the same (--apply reads them), and it is the config.survey operation for the API, the SDK and MCP.
  • notdocs config shows the repository's notdocs.config.json, and notdocs config set changes the docs root, the include globs, whether agent files are read and the ignore globs, then re-indexes the branch; the same is the config.update operation for the API, the SDK and MCP (admins only).
  • Config: include in notdocs.config.json takes globs of markdown to read where it is, besides the docs root, so a repository that keeps its documents in projects/ or INDEX.md is read without moving them; they can be edited, created, moved and linked like the rest, and the sidebar shows them under Repository, in their folders. The files coding agents keep (a CLAUDE.md or AGENTS.md below the root, .claude/, .cursor/, Copilot's prompts, skills and plugins) are left out unless agentFiles is set; the root CLAUDE.md and AGENTS.md are read as before. Under a docs root of "", those agent files were read until now: the next index removes them from the workspace, and the change feed records each as deleted, while the files stay in the repository.
  • GitHub projects: the ignore globs in notdocs.config.json now apply to a repository on GitHub as they did to a local one, and a push that touches markdown outside the workspace (outside the docs root and not a root file) no longer indexes it, where before such a file showed up in the workspace until the next full index and could be given an id.
  • Docs root: a repository whose notdocs.config.json sets "docsRoot": "" (or ".") to read all of its markdown now works in the web app. New pages and folders from the sidebar, cards added on a board, dropped and imported files and tasks from the assistant are created at the path shown, where before they got a leading slash and were refused; the sidebar counts and classifies the documents again, and the move popover indents folders the same as under docs/. A project with nothing under its docs root says where it looked and how to widen it, where before it only said there were no documents, although a repository that keeps its markdown outside docs/ has plenty.
  • Accounts: a notdocs account now follows its Clerk user through Clerk's webhooks. A changed primary address or name is copied over, and a Clerk user who is deleted and signs up again with the same address gets their account and organizations back, where before sign-in was refused because the address "belongs to another notdocs account". Self-hosted instances turn this on with CLERK_WEBHOOK_SIGNING_SECRET.
  • Boards: a card is dragged the way a sidebar row is. It lifts into the same floating label, a touch screen drags it after a short hold instead of scrolling, the column under it lights up and says Move here (or Already here in its own column), and it can be dropped on a column of another board on the page. The move itself is unchanged, the same write as ⌥← and ⌥→.
  • Docs: on a phone or a narrow window, a Documentation disclosure above the page lists every docs page, with the one shown marked, where before only the breadcrumb led back to the overview.
  • Workspace: the hairline under the top bar appears once the page scrolls under it, as on the site's header, instead of always cutting the bar from a page at rest.
  • Home page: the animated tiles keep the layout work of their scenes to themselves, so the typing in the editing-together tile no longer lays out the rest of the page at every character.
  • Lists: the highlight that follows the pointer or the arrow keys through a menu, the palette or the sidebar appears on the first item it lights instead of growing out of the list's top edge.
  • Touch targets: a tag's remove button, a sidebar folder's chevron and the assistant's button that takes the document off a question each take a 24px press, although they still look as small as before.
  • Deleting a document no longer asks first: from the sidebar's menu, the palette or ⌘⌫ on a sidebar row it is deleted at once, and the toast that follows offers Undo for eight seconds, which puts the file back as it was. A bare Backspace on a row no longer deletes, since nothing asks any more; the shortcut is ⌘⌫ (Ctrl+Backspace or Ctrl+Delete). Deleting a project or an organization still asks.
  • Menus: a menu that opens above its button for want of room below now grows upward out of the button, instead of scaling from its far edge.
  • Theme: switching between light and dark cross-fades the page over a fifth of a second instead of flashing from one brightness to the other, where the browser supports view transitions; with reduced motion asked for it still switches at once.
  • Theme: the theme switch's thumb slides to the new choice again; the freeze that keeps a theme change to one repaint also stopped the thumb. Changing the ground or the contrast repaints the page at once too, where before each control faded to its new colour on its own clock.
  • Sidebar: collapsing and expanding it animates the rows with the panel (they narrow and their labels fade) instead of swapping to the rail's rows at once, and ⌘\ pressed from a row keeps the focus on it.
  • Notifications: a toast's timer stops while the pointer or the keyboard focus is on it, so an Undo no longer disappears from under the hand reaching for it, and a toast sinks back out the way it rose in instead of vanishing.
  • Assistant: a prompt typed and not sent is still there when the panel is opened again after a click outside closed it, and pinning or unpinning the panel no longer clears it or drops the focus.
  • Command palette: the first search shows the branch's documents whose title or path matches while the server's answer is on its way, instead of a loader, and Enter opens what is shown. Before, Enter pressed during the loader opened the first recent document, whatever had been typed.
  • Reduced motion: jumping to a comment's block and to the end of the assistant's answer no longer scrolls smoothly when less motion is asked for.
  • Tables: a right-click on a column's edge no longer starts resizing it, and a column dragged wider stops at the 1200px its handle announces.
  • Tabs: the arrow keys, Home and End move focus to the tab they select; before, focus stayed on the tab just left, which was no longer tabbable, so the focus ring sat on the wrong tab.
  • Workspace: sidebar rows, menu items and list options darken the moment they are pressed, so a tap on a touch screen, where there is no hover, shows it landed.
  • Docs: the inline 'On this page' list turns its chevron when opened, and a code listing's Copy button says when copying failed (and announces the result to screen readers) instead of doing nothing.
  • Reduced motion: staggered entrances on the site and in the app (the hero, progress steps) show at once instead of staying invisible for their delay and then snapping in. On the home page the document-type chips and the Resolve button keep their look instead of rendering as bare text, and the token tile's status reads revoked, as its steps do.
  • Site header: a menu the mouse opened stays open when its button is then clicked (before, the click closed it), and a tap on a tablet no longer opens and closes it in one go. Each menu grows out of the button that opened it, the page shown is marked in the bar, its menu and the phone sheet, and a Skip to content link is the first stop for the keyboard. The phone sheet slides in from the screen edge and back out the same way, reverses if closed mid-slide, and the page under it no longer scrolls. With reduced transparency or more contrast asked for, the header is solid instead of translucent.
  • Assistant allowance: an answer or a rewrite stopped before it finished is counted against the organization's allowance by what the AI Gateway says the stopped call cost; before, only finished calls were counted, so stopping every answer shortly before its end used model spend nobody recorded. A failed write to the allowance ledger is reported to error tracking, and every write finishes even when the response ended first.
  • History: any earlier version of a document can be restored from its History tab, and with notdocs docs restore <ref> <hash>; the old text is written over the current one as a new change, so the restore is undone from the History too. The web page asks for unsaved edits to be saved first, and a reader without write access sees the action disabled. notdocs changes now ends each line with the version's hash.
  • Editor: edits that are not saved no longer vanish when a document is left. Closing or reloading the tab while editing alone with unsaved edits or an open conflict asks first, and when the save sent on the way out of a document answers with a conflict or fails, the text is kept on this device, with a note saying so, and offered when the document is opened again (Restore or Discard). Before, the conflict answered a page that was gone, so no dialog showed and the text was lost.
  • Change feed: changes.since no longer skips an event when two documents are saved at once on the hosted service. A project's events now commit in the order of their numbers, so a reader never moves its cursor past one still being written, and an empty page keeps the cursor where the caller was instead of jumping to the latest event; changes.tail returns only events up to the cursor it hands back.
  • GitHub projects: a branch other than the default is indexed whole the first time an operation addresses it, so picking it in the branch switcher lists all its documents; before, only the files pushed to it since the project was added were listed, and every other document answered not_found. A push that creates a branch, is forced, or moves the head without a commit re-reads the branch whole, so a document a force-push removed or reverted leaves the index and search; a deleted branch's documents leave the index. A branch the repository does not have answers not_found, and tag pushes are ignored.
  • Indexing: re-indexing a branch (a moved head without a webhook, a config change, a branch switch, the daemon starting) reads only the files whose content differs from what is indexed, or whose embedding is missing, and a save no longer reads the file back from the store after writing it; on a GitHub project that was two or three API requests per document per re-index and one per save. index.rebuild still reads and re-derives every file.
  • Indexing: a file copied with its frontmatter (so with another document's id) gets an id of its own when it is indexed, and the document already indexed keeps its id (on a first index, the first file in path order does); before, the two shared one entry in the index that followed whichever file changed last, so one of them was missing from lists and search, and comments, shared sessions and backlinks jumped between them.
  • Indexing: stamping an id into a file that already has frontmatter adds the missing id, type and title lines at the top of its block and leaves the rest as written; before, the whole block was written out again, so YAML comments, anchors, quoting, flow lists and the order of the keys were lost on a note's first index and committed.
  • Import: a note whose id is not a ULID (id: intro, a Zettelkasten id: 202109231200), and a Notion row with such an ID property, imports: the value is added to the document's aliases, so a wikilink by it still finds the document, and the document gets a ULID. Before, a string id was kept and docs.create refused the note (the web import failed it, the CLI stopped), and a numeric one was replaced and lost.
  • Import: a Notion task database imports. Its properties are fitted to the task's fields (Urgent, High, Medium and Low priorities become p0 to p3, several assignees are joined, a number or date is written as text) and a value that still does not fit is kept as notion<Name>; before, a Priority column, a second assignee or a numeric Due made the row fail the task schema, so it was refused. Two pages whose names match once Notion's ids are stripped become Name.md and Name-2.md, with their subpages and the links to them; before, the second was refused as already existing. notdocs import reports a file that fails, imports the rest and prints what was created, what existed and what failed in plain and --json output, exiting non-zero when any failed; before, it stopped at the first failure, and --json printed nothing about the files already written.
  • CLI: notdocs import notion and notdocs import obsidian upload the images of an export or a vault (through assets.grant and assets.upload, as notdocs assets upload does) and point the pages at them, as the import page does; --dry-run lists them. Before, the CLI read only markdown and CSV, so a Notion page's images were broken links and an Obsidian embed ![[pic.png]] became the word "pic.png". The archive logic moved into @notdocs/core, so the web and the CLI share one implementation, and a Notion image is found under the folder its page lands in once the page ids are stripped.
  • Query tables: a cell is edited where it stands. A click, Enter or F2 opens the property's own control (the menu of a status, a date, tags, a number, text, the title), Enter or leaving the cell saves it to the document's frontmatter and Escape drops it; the arrow keys, Home and End move between cells, and ⌘/Ctrl+Enter or a double click opens the document. The id, type and path columns, checkbox lines from inside other documents, and tables in a document being read or seen by a guest stay read-only. A board card moved to the "No value" column now removes the key instead of writing it empty, as a row dropped from the sidebar already did.
  • Query blocks: the block's header switches between board, table, list, calendar and gallery, and the switch is written into the block's view: line, so the file says how it is shown; before, calendar and gallery could only be reached by typing the YAML. Project home offers the same five views for tasks, and the slash menu's Query block names them.
  • Moves: a [[wikilink]] to a moved document is rewritten to the shortest name that still finds it, adding folders ([[notes/roadmap]]) when its new file name is one another document answers to first; before, the bare file name was written and the link could point at the other document. A folder-qualified wikilink ([[a/index]]) finds docs/a/index.md in a vault rooted at docs/ rather than whichever index came first, documents sharing a name resolve in path order everywhere, and a markdown link written in other case than the file (notes/Plan.md) is rewritten on a move instead of being left pointing at nothing.
  • Undo: the Undo of a rename, a type change, a reorder or a board placement says whether it worked: a document changed meanwhile stops the undo with a note naming it, and a finished one says Undone. Before, the change-meanwhile answer was not read, so Undo silently did nothing, and a reorder was put back halfway.
  • Boards: a card dragged to another column follows the document again once the move is saved, so a later change of its status by an agent or another person shows; before, the board kept the card in the column it was dragged to until the view was left.
  • Editor: text typed while a save was on its way survives a save the server merged with somebody else's edit: it is folded over the merged file and stays unsaved, where before the editor was replaced by the merge and the typing was lost under a toast saying both sets of changes were kept. A merge folded into unsaved edits no longer adds a blank line at the top of the body.
  • Branches: switching branch while a document is open shows that document on the new branch; unsaved edits made on the old branch keep the page on it, with a note offering to save them there or discard them, and a save always goes to the branch its text was read from. Before, the page kept the old branch's text: in a shared session the editor typed into a session that had closed while the top bar said Saved, and alone the unsaved edits were saved onto the other branch. A change to the same document on another branch no longer shows as a change on disk.
  • Editing together: a tab whose connection comes back after its session closed (a laptop asleep for more than half a minute, a daemon restart, an expired stored session) no longer merges its copy into the fresh session, which put the whole document in twice and wrote that to the file; it ends the session and edits alone with what it held, unsaved, so a save merges it with the file as any save does. A session that ends for good mid-edit (refused, or its access gone) does the same instead of showing "Reconnecting…" while keystrokes and ⌘S went nowhere, and a server error or a busy answer (5xx, 429) is retried as a lost connection rather than taken as a refusal.
  • Sign-in: a signed-out visitor on an organization's address is offered sign-in again, and so is a person whose GitHub connection step fails for want of a session; since error messages stopped carrying their code, both showed the bare error instead.
  • Custom domains: replacing an organization's domain attaches the new one before detaching the old, so a domain Vercel refuses (one held by another Vercel account) leaves the old domain serving; before, the old one was detached first and the organization was left with a dead domain.
  • Billing: a subscription event for a product the deployment does not map (an Enterprise plan set up in Polar without POLAR_PRODUCT_ID_ENTERPRISE, a product whose id changed) is ignored and logged; before, it was applied as Free and took a paying organization's plan away.
  • CLI: notdocs token create --scope and notdocs invite --role refuse a value that is not one of theirs (exit 2); before, a mistyped --scope minted a write token and a mistyped --role made a guest a member of the whole organization. notdocs op docs.update exits 5 when the answer is a conflict, as docs set and docs edit do; it exited 0.
  • Organizations: deleting an organization whose subscription was cancelled in the billing portal works at once, as the refusal told the owner it would; before, it stayed refused until the paid period had ended and three more days had passed.
  • Projects: a new project's row is reserved under a lock on its organization before anything else happens, so two projects created at once can no longer both pass the plan's project cap, and "Create docs repo" creates the repository on GitHub only once the project fits; before, a name clash or a concurrent create after the repository was made left it on GitHub, seeded, with an internal error and a retry that said it already existed. Two names that make the same address (my.repo and my-repo, or a repository called docs beside the Docs project) now get my-repo-2 instead of an internal error.
  • Sign-in: an invitation is taken only by a person whose address is verified with Clerk, addresses are matched without regard to case (an invitation to Bob@Acme.com reaches the person who signs in as bob@acme.com instead of leaving them with an empty second account), and an account already linked to one Clerk user is never moved to another. Before, a person whose Clerk account held the address unverified, or who was given an address that someone else's notdocs account still carried, took over that account with its organizations.
  • API: a guest invited to some documents reads only the images those documents show (in their body or as their gallery cover) from GET /api/assets; before, any image in the repository was readable by path. A document hidden from such a guest now answers exactly as a missing one (not_found, same message) on docs.get, docs.backlinks, docs.validate, docs.export and comments.list, so a guest can no longer tell which paths exist.
  • API: comment operations take only the id of a document the branch knows: an id is refused as invalid unless it is a ULID, and not_found unless the document exists. Before, any string was used as the sidecar's file name, so a write-scoped caller could create sidecars for documents that do not exist or, on a GitHub project, name a path outside .notdocs/comments/. docs.create now refuses a backslash anywhere in the path, as this changelog already said it did.
  • Editor: a document holding markdown the editor has no block for (a footnote, a leaf directive such as ::embed, an MDD subscript H~2~O, a reference-style link) opens in the editor read-only with a note, and is edited in the source view, which keeps it as written; no shared session opens on it, and a session whose file gains such markdown from an agent or a push stops writing and says why. Before, the editor read those as something it could write (a footnote became a link to a file named after the note, a subscript became strikethrough), and the first keystroke, or with a session open any change on disk with nobody typing, wrote that rewrite over the file.
  • Indexing: a file that starts with a byte-order mark, or with an empty ---/--- block, is read as having frontmatter; before, the indexer stamped a second block above the first with a new id, so the document lost its identity, its comments and its links by id, and the change was committed. A file whose frontmatter is not valid YAML, or is a list rather than keys and values, is skipped and logged, keeping its last good index; before, one such file stopped the whole branch from indexing, the local daemon from starting, a new project from being indexed, and failed the push it came with.
  • GitHub projects: an organization reaches a repository only while it is connected to the repository's GitHub account. Before, the connection was checked when the project was added and never again, so an organization that reconnected to another account, or whose account uninstalled the app, kept reading and committing to the repositories through whichever installation now covered them, another organization's included. Its projects now answer forbidden with a message to reconnect.
  • Uploads: on the hosted service an image pasted, dropped or imported in the web app goes from the browser straight to the store, so it is bound by the plan alone; before, a request through the API carried at most 4.5 MB, and an image over about 3 MB failed before the server saw it although the plans promised 10, 25 and 100 MB. A new assets.grant operation says how a file may be sent and refuses one the plan or the request cannot take before a byte goes over, assets.upload and a Word import name the actual limit when a request cannot carry the file, billing.status reports the request's cap and whether images bypass it, the Plan section shows the caps a person will meet, and notdocs assets upload asks first and says when a file is the web app's to send.
  • Import: the images of a Notion export or an Obsidian vault are rewritten by the path each page names, resolved from that page, so a page that mentions pic.png in its prose or code keeps its words, images/pic.png no longer becomes images/https://…, and two images of one name in different folders each show the right one; an Obsidian embed ![[pic.png]] of an image in the vault becomes that image instead of its name. Before, every occurrence of an image's file name anywhere in the text was replaced.
  • Editing together on the hosted service: a message posted to an instance other than the one holding the tab's stream is no longer answered with "no such session". The store names the room of every open session, the instance the message lands on handles it on its own copy of the room, and the replies travel over the room's channel to the instance with the stream.
  • Editing together: a session's access is checked again on every message and every keepalive of its stream, and the hosted change feed checks its socket every 25 seconds. A member who was removed, a guest narrowed to other documents or a token that was revoked is cut off within that time instead of keeping the stream, and a member narrowed to reading is told so and their edits stop counting. A session is usable only with the kind of credential that opened it: a person's session is no use to a project token, nor a token's to a person.
  • Editing together: a save that the store merged with a change on disk no longer erases what was typed while the save was on its way. The merged file is folded into the session over the text that was sent, so the keystrokes of the meantime are kept as unsaved edits and the next save writes both; where they collide the session's version is kept and the room says so.
  • Local daemon: notdocs serve listens on loopback only (127.0.0.1), refuses a request whose Host header is not localhost, 127.0.0.1 or [::1] before it looks anything up, and refuses a browser page, its websocket included, unless it comes from a loopback origin (http://localhost:<port>, http://127.0.0.1:<port>, http://[::1]:<port>). Before, it listened on every interface with a wildcard CORS rule, and the daemon answers as the folder's owner for every request, so anyone on the network and any web page could read and write the served folder. A new --token <token> flag (or NOTDOCS_DAEMON_TOKEN) makes the daemon ask every request that carries an Origin header, the websocket handshake and the editing stream included, for that token as a bearer token (?token= on the websocket), which keeps unrelated local pages out; the CLI and the MCP server send no origin and are not asked. The web UI does not pass the token yet, so pnpm --filter web dev:local runs without one.
  • Change feed: changes.since answers more, whether the page was full, and the SDK's subscribe walks the feed while it holds. Before, a page made up of events a restricted guest may not see came back empty, the walk stopped there and re-read the same page on every poll, and a visible event past it never arrived; and an event the socket delivered ahead of ones raised on another server instance moved the read past those, which were then skipped. The socket no longer moves where the feed is read from, and an event that arrives both ways is delivered once.
  • GitHub: connecting an installation to an organization now proves the installation is yours. The install link from the organization's home carries a signed state naming you and the organization, valid for ten minutes, and the setup page hands it back; the API then checks through the GitHub connection on your notdocs account that your GitHub account reaches the installation, and refuses otherwise. Before, any organization owner could attach any installation of the app by its id. Your account needs a GitHub connection for this step; the hosted docs say so.
  • Documents: docs.create refuses a path outside the docs root or the repository (../, an absolute path, a backslash, .notdocs/), as docs.move and docs.import already did, and the local daemon's store refuses any path that leaves its folder, through a symlinked folder included. Before, a create could write a file outside the repository.
  • Saving: a three-way merge whose result no type schema accepts (one side made a page a decision, the other gave it a task status) is answered as a conflict on the keys at fault, with both sides' values, instead of being written. Two saves against one version on the local daemon now let one through and refuse the other as stale, and a save lands whole or not at all: the file is replaced through a rename, never written in place.
  • Index: a document's row, its checkbox-line tasks and its change event are one transaction, so an embedding that fails (the gateway down) no longer leaves a document indexed without its event; the embedding is tried again the next time the document is indexed or the index rebuilt. Every way a file gets indexed (a save, the watcher, a rebuild) now runs one at a time per file.
  • GitHub: a push webhook is answered once its files are indexed, and with a failure when they are not, so a delivery that failed shows as failed on GitHub and can be sent again; before, it was acknowledged before the work. A batch write (stamping ids on a repository's first index, the link rewrites of a move) is checked against the very commit it lands on and computed again when a file changed underneath, instead of committing a stale text over a newer file.
  • Comments: two first comments on a document made at once both survive; before, the second could replace the first, and a GitHub refusal of a stale sidecar write was an error rather than the retry it is now.
  • Members: a change of role (members.update) is only for a person already in the organization, goes through the same seat check as an invitation, and two invitations racing for the last seat take one seat, not two. Before, a change of role could add anyone the deployment knew, past the plan's cap.
  • Billing: a subscription delivery is applied by when the provider last revised the subscription, so an older state arriving after a newer one (an "active" after a revocation, out of order) is ignored, and two deliveries at once cannot both win. Needs the orgs_subscription_revised_at migration.
  • Search: a guest limited to some documents is limited in the query itself, ahead of the ranking, so the document they may see is found however many they may not see rank above it.
  • Site: the home page has a section on editing together (two people typing into one document, an agent's change folded in, the one write the repository sees) and the bento is two bands, the workspace's tiles and then what a page can hold: three new tiles (fences that draw, backlinks, the calendar and gallery views) beside the documents that leave; the FAQ answers whether two people can edit at once and what it costs, which it still said was nothing. The docs gain an Editor page (the toolbar, Read and Source, images, the blocks that draw, appearance, keyboard) and an Editing together page (sessions, the server's writes, changes from disk, where sessions run and what they cost); Documents points at both. The features page no longer says there are no live cursors, and the Notion comparison no longer lists co-editing as what notdocs lacks.
  • Editing together: a document open in more than one browser is one shared session. Everybody's keystrokes, the title and the properties show on every screen as they are typed, other people's carets show in their colour with their name, and the top bar shows who else has the document open. The file stays the only truth: the server holds the session, writes it back after a short pause in the typing (1.5 s, 10 s at most, and when the last person leaves; ⌘S writes now), and folds a change that lands on disk meanwhile (an agent, a push, a checkout) into the session where the lines differ; where they collide the session's version is kept and written, the page says so, and the file's version stays in the History. The local daemon and the self-hosted image have sessions on; the hosted service shares them between its instances through the K/V store, and a deployment without one edits alone, as before. While a session is on, the source view shows the text but does not edit it. Under the hood the editor's schema and markdown reader live in @notdocs/editor, which the server loads headlessly; three things the schema now accepts that markdown always wrote: an image inside its paragraph, a list item that starts with a quote or a code block, and inline code inside a link. Inline HTML (a <kbd>key</kbd>) is kept as written on every side rather than read into marks by the browser alone, so a save no longer rewrites it.
  • Sidebar: hairlines separate the quick actions, Pinned and Recent, and the document tree. Every section count lines up at the right edge, with a section's buttons showing in its place on hover, and the type chips scroll in one row that fades at the edge instead of cutting a chip off.
  • Moving: when a document is moved or renamed, the markdown links to it in other documents are rewritten to its new path, and its own relative links and images are rewritten so they still point where they did; a [[wikilink]] is rewritten only when it would no longer resolve. The rewrite lands in the same commit as the move, spliced into the files byte for byte so nothing else in them changes; a linking document that changes while the move is computed makes the move a conflict rather than losing the edit. Before, a move left every link as written, which the changelog and docs.move said.
  • Links: a document knows what links to it. A Links tab in the rail, beside Comments and History, lists the documents whose [[wikilinks]] (by title, file name, path or alias) or markdown links point at it, and opens them; notdocs docs backlinks <doc> prints the same list, and docs.backlinks is the operation behind both, so the SDK and the docs_backlinks MCP tool have it. The index keeps each document's links as written and resolves them when asked, so a renamed target keeps its backlinks. It needs the docs_links migration, and a document indexed before it answers once it is re-indexed (notdocs reindex, or any edit).
  • Import: a Word document (.docx) becomes a page. The import page takes one or more .docx files beside the Notion and Obsidian archives, notdocs import docx <files...> --into docs does it from the CLI, and docs.import is the operation behind both (so the SDK and the docs_import MCP tool have it). Headings, text, lists, tables and links are converted through md-docx; the pictures inside the file are stored where an upload goes (the repository's assets/ folder, or the object store on the hosted service) and the page shows them from there, so no base64 lands in the markdown. The title is the first heading, or the file name; the plan's upload size caps the file.
  • Editor: a Source button in the top bar (⌘⇧U) shows the document's markdown as text, in the code font, and saves it back as markdown; the title and the properties stay as they are. It is the way out when the editor cannot write a block back (the banner now says so): fix or remove the block as text and save. The choice is remembered in this browser like Read is.
  • Editor and site: a block of raw HTML in a document is shown as what it draws, with scripts, event handlers, styles and unsafe links removed, instead of as its source (in the editor) or not at all (on the site). Edit HTML shows the source under it; a block with nothing left to draw shows its source with a line saying so. The file keeps the HTML byte for byte, and an Anasa block-id comment still renders as nothing but an anchor.
  • Sidebar: documents and folders can be put in a chosen order. An order number in the frontmatter places a document among its siblings (lower first; the rest follow alphabetically), a folder takes the order of its index.md, and Move up and Move down in a row's menu write it. notdocs docs set <doc> order=3 does the same from the CLI, and an agent writes the key like any other. Before, the order was always alphabetical.
  • Query blocks: two more views. view: calendar lays the documents out on a month grid by their date or due (or the key named in dateKey), opening on the current month or the fullest one, with a note for those that have no date; view: gallery shows them as cards, with a cover image and a description (or summary) when the frontmatter carries one, in one section per value of groupBy. Both open a document on a click, and the block header names the view as before.
  • Overview: every document type with a document on the branch has a list of its own beside the recent changes, not only decisions: pages, specs, meetings, the business documents, and a custom type from notdocs.config.json alike. Each list is headed by the type's tag in a colour of its own, with the count, and a row shows the document's status or its date. Tasks keep their board above.
  • Editor: the formatting toolbar has headings, bold, italic, strikethrough, inline code, a link, every block the / menu inserts (lists, a task list, a quotation, a code block, a callout, a divider, a table, an image), the assistant, a copy of the document's link, undo and redo. It floats over a selection as before, or its pin button keeps it above the text while you scroll; its × hides it, and the Toolbar choice in Settings or the sidebar's Appearance panel brings it back or moves it. The pressed states follow the caret now; before, the bar could show a mark as off while the caret was in it.
  • Editor: a Read button in the top bar (⌘⇧E) shows the document without editing it: the text, the title and the properties stay as they are, a link opens on a click, a drawn query, diagram or tree offers View source instead of Edit, and comments work as before. The choice is remembered in this browser from one document to the next; a document you create always opens for editing.
  • Editor: while editing, the block under the pointer takes a soft ground (code, tables, images and callouts a faint outline), so it is plain that the text can be changed; nothing lights up while reading.
  • Appearance: a Cursor choice, in Settings and the sidebar's Appearance panel, sets the shape of the text cursor in the editor: a line, or an underline or a block over the next character.
  • Images: paste an image into a page, drop one on it, or pick one with /image, and it is stored and shown where the caret was. The local daemon and a self-hosted instance commit it to the repository under <docsRoot>/assets/ (a hash of the content in the name, so the same image twice is one file) and the page references it by its path from the repository root, which GitHub and Obsidian show too; before, a deployment without BLOB_READ_WRITE_TOKEN, the local daemon's web UI included, refused every upload. The hosted service still keeps uploads in Vercel Blob. Images already in the repository show in the editor, by a path from the root or one relative to the document, served by GET /api/assets; an exported HTML or Word file carries them inside it. Uploading is the assets.upload operation now, so the SDK, the assets_upload MCP tool and notdocs assets upload <file> have it, and the /api/assets upload route is gone. Only images are stored: PNG, JPEG, GIF, WebP, AVIF and SVG. A request body larger than any plan's upload is refused before it is read.
  • Root files: a repository's readme.md, Readme.md or changelog.md is listed as a document, as README.md and CHANGELOG.md already were. The root files of notdocs.config.json are matched without regard to case, the way GitHub finds a readme; before, a name spelled in another case was left out of the workspace and refused as a path.
  • New document: the dialog is wider, so every template's description reads in full beside its name. On a narrow screen a long description now ends in an ellipsis; before, the template list was wider than the dialog, which cut the ends of the descriptions and the selected check mark off at its right edge.
  • Appearance: code, IDs and paths are set in Google Sans Code, and a Code font choice switches them to JetBrains Mono or the system's monospace. Settings has an Appearance section of its own: colour, type and comments in groups, each choice with a line on what it changes, the fonts as selects with a sample that shows the chosen interface, text and code faces and spacing together. The sidebar's panel stays for quick changes and scrolls when it is taller than the window.
  • Assistant: a question asked while a document is open is asked about that document, so "this document", "this" or "here" mean it and the assistant reads it before answering. A chip above the message field names the document; its × sends the next messages without it, until you open another document. Before, only /tasks knew which document was on screen.
  • Appearance: the interface font and the text font (the editor, rendered pages and the assistant's answers) are chosen apart. Text follows the interface unless it has a face of its own. Geologica and Google Sans join Schibsted Grotesk, Lexend, Atkinson Hyperlegible and OpenDyslexic for both; Literata is a choice for text. A face picked for dyslexia before stays picked for the interface and text; "Literata for reading" has to be picked again under Text font.
  • Editor: a fenced tree block, holding what the tree command prints, is drawn as a file tree with folders, files and their notes, as it already was on the site; Edit tree opens the source. A tree drawn with single-dash connectors (├─ ) is read too, and one off the grid shows as written with a note saying so.
  • Editor: a fenced mermaid block is drawn as its diagram, in the workspace's colours and redrawn when the theme changes, instead of showing as code. Edit diagram opens the source under it; while the source does not parse, the last drawing stays and the reason shows beneath it. The file keeps the Mermaid source byte for byte.
  • Organisation hosts: a workspace subdomain or custom domain answers /manifest.webmanifest with the manifest, /robots.txt with rules that leave it crawlable (every workspace page is noindex, which a crawler only reads on a page it may fetch) and /sitemap.xml with not found. Before, all three came back as the workspace page, and the browser logged a manifest syntax error on every load.
  • Editor and assistant: fenced code is syntax-highlighted, in the editor as you type and in the assistant's answers, for any language Shiki has a grammar for (Python, Go, Rust, SQL and the rest, not only the handful the public docs use). Each language is fetched the first time a block asks for it, and a page with no code on it loads none of the highlighter. The colours follow the light or dark theme you picked, and are drawn over the text rather than written into it, so the markdown saved is unchanged.
  • Dependencies: @types/node follows the runtime the project supports and deploys on (Node 24, engines >=24) instead of Node 26, so an API that only Node 26 has fails the typecheck rather than production; two overrides lift the transitive lodash-es and path-to-regexp pins to their patched releases, and pnpm audit is clean.
  • Web: a page of the workspace that fails to render shows its error inside the shell, with the sidebar and the way to every other document still there, instead of replacing the whole window.
  • Editor: linking a selection opens a small form under the paragraph (Enter links, Escape gives up) instead of the browser's prompt.
  • API: an agent token's "last used" is refreshed at most once a minute instead of on every request, which was one database write per call.
  • Uploads: an image or file is stored under its project, and uploading needs write access to the project, so a read-only guest can no longer upload on the organization's plan. Files uploaded before this change keep their addresses and are not removed with a project, since nothing says whose they were.
  • Deleting: an owner deletes a project, or the whole organization, from a Danger zone in Settings, after typing its slug back. projects.delete removes the project's index, change feed, guests, agent tokens, uploaded files and every remembered file version no other project still names; orgs.delete does that for each project and removes the organization with its people, custom domain and usage record, and is refused while a paid plan runs (cancel it in the billing portal first). Neither touches a repository or a file in it, so adding the repository again rebuilds the project. notdocs projects delete <slug> and notdocs org delete <slug> do the same; agent tokens cannot. The privacy policy, the data portability page and the DPA say so; deleting an account is still done on request.
  • Hosted: a production build on Vercel applies the pending migrations before it builds, so a deployment carries the schema its code needs instead of answering 500 until somebody remembered to run drizzle-kit migrate. A build whose migrations fail is never promoted. Preview builds do not migrate, since their database may be the production one; NOTDOCS_MIGRATE_ON_BOOT is unchanged.
  • Store: the failures a person can act on are typed and keep their sentence through the API — a notdocs.config.json that does not parse, a branch that is not the checked-out one, a move whose source is gone or whose target is taken, and a project source that is not owner/repo. Before, they were bare errors, which now read as "something went wrong on the server".
  • API: a failure nobody wrote a message for answers "something went wrong on the server" and the detail goes to the server log. Before, the message was whatever the error carried, which for a driver error is the failing statement and its parameters, and the browser showed that line where the document, the change feed or the sidebar should have been.
  • Web: an error is shown as the sentence the API wrote, without the internal: , not_found: machine code in front of it. A read that failed no longer reads as an empty project either: the document count in the project header and the counts beside Tasks and Decisions are left out rather than shown as zero, and the activity sparkline stops pulsing as a skeleton above the error that explains why the feed never arrived.
  • Sidebar: the filter row's document count no longer sits under the search button that overlaps its right edge.
  • Web: the import page's archive-type label is tied to its control, a task list's read-only checkbox in rendered markdown is no longer focusable while hidden from assistive technology, the pinned rail is an aside without a redundant role, and the GitHub setup page's way back is a client-side link. Lint: the React, jsx-a11y, Next.js and import rules run in CI (correctness and suspicious findings fail it; the React Compiler and effect-dependency rules stand at 134 warnings, a cap CI only lets fall).
  • Comments: every document summary carries openComments, the open threads of its sidecar as the index counts them, so the sidebar badge shows for a document nobody has opened this session and another person's new thread badges at once; before, the count came from the pages opened in this browser. docs.list, docs.get, queries and search answer it; the CLI and MCP see it; it needs the docs.open_comments migration.
  • Change feed: a changed event carries sections, the heading slugs whose text differs from the previous version ('' for the text before the first heading), when the index remembers that version. notdocs changes, MCP and the SDK see it; it needs the events.sections migration.
  • Editor: a change that arrives from disk while there are unsaved edits is folded into them at once when the two sides touch different lines (the same three-way merge a save does), with a note; when they touch the same lines the banner stays, and now names the sections that moved ("changed on disk in rollout, on lines you are editing too").
  • Editor: when a change from disk or a conflict choice replaces the document, the caret goes back to where it was in the text, moved by whatever the reload changed before it, instead of jumping to the top of the page.
  • Search: searchLanguage in notdocs.config.json names the language full-text search stems and drops stop words in (english unless set, simple for none, or any configuration Postgres ships such as german); before, every workspace was searched with the English dictionary.
  • Assistant: Stop ends the generation wherever it runs. On a hosted deployment the producer may live on another server instance; the stop is now relayed over the K/V store's pub/sub and that instance aborts the model call, where before the record was cleared but the generation ran, and was metered, to its end.
  • API: without CAPYDB_KV_REST_URL the rate limiter keeps its sliding windows in the process (per instance) instead of not existing; and when the K/V store cannot answer, the ai window refuses the request for five seconds rather than letting it through, since it is the one brake on model spend. This changes the documented fail-open policy for that window alone; ops and upload still fail open.
  • Assistant: a NOTDOCS_MCP_SERVERS that is not JSON or names a bad field is reported in the panel ("The external tools are off: … at 0.name …") instead of only in the server log, so a typo no longer costs every external tool in silence.
  • Editor: a body holding a node the serializer does not know (pasted content, a schema that moved under an upgrade) is reported in a banner naming the node, and the document is not saved until the block is removed; before, the failure was thrown from inside the editor's update and nothing said why the page misbehaved.
  • Comments: adding, replying and resolving re-read the sidecar and re-apply the change when somebody else's comment landed first, up to three times, instead of answering "comments changed concurrently, retry" on the first collision.
  • SDK: createClient takes a token function as well as a string, asked before every call and every socket connection; the web passes its session-token getter once, so a feed that has been open for hours reconnects and polls with the current token instead of the one it started with.
  • Local daemon: the change socket authorizes the caller to read the project and carries only the events of documents they may open, the same gate as the hosted socket; before, the daemon subscribed any socket to any project id.
  • Hosted: the boot line about a missing CAPYDB_KV_URL now also says that a change recorded on another server instance reaches this one's sockets only on the browser's thirty-second re-read; before, that consequence went unlogged.
  • Hosted: a deployment with the GitHub App but no GITHUB_WEBHOOK_SECRET polls every repository's default branch once a minute and re-indexes it when the head moved, and logs at boot that it is doing so. Before, such a repository indexed once and went stale in silence until notdocs reindex.
  • Comments: threads can be read on the page as well as in the panel. "Show on the page" at the top of the Comments panel (and in Appearance) offers On hover, where resting the pointer on a heading's or block's handle floats its open threads beside the text, and In the margin, where every open thread sits as a card beside its text, stacked so they never overlap, the way a document's margin reads; on a page too narrow for a margin column the cards float on hover instead. A card opens the panel on its anchor for replying and resolving; the handle's click does what it did.
  • Sidebar: the collapsed rail shows one glyph per section (Pinned, Recent, Documents, Root files, Templates) with the changed dot, instead of a column of bare type icons with no indent. The pointer resting on a glyph opens the section in full in a panel beside the rail, the sidebar's own width, and moves the panel to the next glyph it rests on; a press keeps it open, with focus inside, until Escape, an outside click or another press. The Documents panel carries the filter. The panel's rows drag, rename and open their menus as in the open sidebar. An anchored panel no longer closes when what it shows scrolls, or when a press lands in a menu it opened.
  • Sidebar: the collapsed rail no longer carries its own Expand button, which duplicated the toggle in the top bar (⌘\); the workspace monogram stays where every other glyph sits and opens the workspace switcher, so the rail keeps a way to another project.
  • Icons: the interface icons are Solar Icons (480 Design, CC BY 4.0) in the Linear style, in place of the hand-drawn set: one consistent 1.5px stroke on a 24px grid, and a glyph that reads as what it is where the old set borrowed one. Settings is a gear (it was a circle with four ticks), a launch a rocket, a decision a signpost, a letter or memo an envelope (also the Contact page), an invoice a receipt, an experiment a flask and the legal types a shield; before, those types borrowed the table, sparkle, @ and key glyphs. The set is generated from the icon data by pnpm --filter web icons; the credit is on the legal notice page and in humans.txt.
  • Appearance: the sidebar footer's theme menu becomes an Appearance panel, remembered in the browser. Background: Paper (the default), Plain, Sepia or Slate, each with its light and dark side. Contrast: follows the system's prefers-contrast unless set to Normal or High; high contrast is pure ink on a pure ground, every hairline a visible line, muted text at 7 and a thicker focus ring, over any background (⌘K "Toggle high contrast"). Font: the interface is set in Schibsted Grotesk and code in JetBrains Mono (Geist goes); reading text can switch to Literata, a serif with an optical-size axis, or the whole app to Lexend, Atkinson Hyperlegible Next or OpenDyslexic. Text spacing: Relaxed opens letters, words, lines and paragraphs of running text as the dyslexia style guides ask. Every choice applies before the first paint and follows a forced theme's colour scheme in native controls too.
  • Dependencies: AI SDK 7.0.105 (with @ai-sdk/react 4.0.108, @ai-sdk/gateway 4.0.85, @ai-sdk/mcp 2.0.52, devtools 1.0.22), resumable-stream 2.2.13 (a subscribe race in resumed assistant streams), @types/node 26.6.1 everywhere.
  • Settings: the organization form's button says "Change slug", which is what it does (the name is not editable). notdocs serve says when semantic search is off for want of AI_GATEWAY_API_KEY.
  • Accessibility: dialogs are named by their visible heading and described by their description; a menu item that can be the chosen one of its list is a radio item; tab lists take Home and End. The shortcut help lists ⌘↵, F2 and Delete, which were bound but unlisted.
  • Documents: the Export menu gains "Check the document", which runs docs.validate and reports the properties and, for a business document, the MDD structure in a toast; a failed export now says why instead of only stopping its spinner.
  • Web: when live updates cannot open a socket, the top bar shows a Polling badge that says changes are read every five seconds; a failed token read for the feed is logged instead of leaving the feed silently unsubscribed.
  • Web: a request that fails says so where its answer would have gone, instead of reading as empty: the organization list on /w, the project list in the workspace switcher, the document tree in the sidebar and the Decisions list on the home ("could not be read" with the reason), the agent tokens table in Settings and the template picker of New document. A comment or reply the API refused keeps its text in the box, with the reason in the toast.
  • People: a person's access can be changed and a person removed. members.update makes a member a guest of the project (of some documents or all of it) or a guest a member, and changes a guest's documents; members.remove takes a person out of the organization, membership and every guest access, and frees the seat. Owners are not changed from here. The settings People table has the two actions per row, notdocs members role <email> <member|guest> [--docs ids] and notdocs members remove <email> do the same, and MCP gains the matching tools. Inviting somebody who is already in the list now moves them to the role given instead of adding a second row.
  • API: the rate limiter keys every window by the credential the same way on every route: a person by their user id, an agent token by the token's; two tokens with one name no longer share a window.
  • Live feed: on a hosted deployment with the K/V store, change events cross between the API's server instances over its pub/sub, so a change made through an agent or a push webhook on one instance reaches a browser connected to another at once; the thirty-second re-read stays as the floor when the store is unset.
  • API: changes.tail answers with the newest events of a project, newest first, and the latest cursor, with before to page further back. The live feed seeds its cursor from it and the recent-changes views (project home, sidebar, palette, a document's History) read the newest 2,000 events from the end instead of walking the whole feed from the first event, so a long-lived project no longer costs dozens of requests to open. notdocs changes --tail <n> prints the newest events; MCP gains changes_tail.
  • API: docs.create asks the store for a write that must find no file at the path, so two creates of the same path at the same moment end with one document and one conflict, instead of the second overwriting the first (local) or failing as an internal error (GitHub).
  • Error tracking: Sentry 11 collects request bodies, cookies, query strings, stack-frame variables and model inputs by default; the app now turns each of those off explicitly, so a document, a token or a prompt never travels with an exception report.
  • Live feed: while the socket is open the SDK re-reads changes.since every thirty seconds, so a change made through another server instance of the hosted API (an agent's write over MCP, a push webhook) reaches the browser within that time instead of on the next reconnect. Hosted fan-out is per instance; this is the floor until events travel over the K/V store.
  • Editor: text typed while a save was on its way stays unsaved. The top bar said Saved once that save answered, the Save button went quiet and leaving the page lost those keystrokes; now the document stays dirty until the next save carries them, and a merge (the file had changed underneath) still replaces the editor's text, as its toast says.
  • Billing: a webhook that revokes a subscription the organization no longer holds is ignored. Polar's deliveries are not ordered, so the old subscription's revocation could arrive after a plan change in the portal had activated the new one and drop the organization to Free.
  • Organizations: "Create docs repo" checks the plan's project cap before it creates anything on GitHub; before, an organization at its cap got the repository created and then the plan refusal, leaving a repository to delete by hand.
  • API: docs.update takes as its merge base only a version the document itself has carried; a baseHash from another document or another project is treated as unknown and answers a plain conflict, so nothing of the other text can come back in it.
  • API: a guest invited to some documents of a project no longer sees the others through the side doors: comments.list and docs.validate on a document they may not open answer forbidden, and the change feed (changes.since and the live socket) carries only the events of their documents. Before, the comment threads (quotes included), the validation issues and every path, id and hash in the project were readable.
  • API: the hosted API refuses to register or open a project backed by a folder (kind: local); those belong to the local daemon, which serves the folder it was started on. Before, an organization owner could register any path of the server's own filesystem as a project and read, write and rename the markdown files under it.
  • Accent: the one interactive colour is now wine (#7a2a3a on light, a rose #e09aa6 on dark) instead of teal, in the app, the site, the mark, the favicon and app icons, the share image, the manifest theme colour and the letterhead rule of exported MDD documents. Danger, warning and success keep their hues.
  • Sidebar: the footer is four icon controls instead of three rows: Settings, Import, Theme (a menu of Light, Match the system and Dark) and Keyboard shortcuts, in a row when the sidebar is open and stacked on the collapsed rail. The theme pill and the shortcuts button leave the top bar, which keeps the sidebar toggle, the page's own controls and the rail. The repository path moves into the workspace switcher under the organization name, where a long local path no longer wraps over the footer.
  • Tooltips: a tip is positioned from its control's rectangle outside the page flow, so it is no longer cut off by the collapsed sidebar rail or the panel it sits in; it flips to the other side of the control rather than leaving the window, and hides on scroll, Escape or a press. A control wrapped in a tip no longer shows the browser's own title on top of it.
  • Local daemon: switching the checked-out branch (git checkout) re-indexes the workspace for the new branch, so the sidebar no longer shows "No documents on this branch yet" after a switch; the web refreshes its branch list when a change arrives from another branch.
  • Sidebar and boards: a document row dragged from the sidebar drops on a column of any board, the project home's or a query block's inside a document, and takes that column's value for the key the board groups by (status, or whatever groupBy names); a board scoped to one type gives a document of another type that type too, so it appears where it was dropped. The column shows a ring and "Move here" while a row is over it; a folder or a row already in the column says so instead. A multi-selection drops every selected document, one commit each, with one toast; Undo puts the previous values back. The board's own cards drag as before.
  • Sidebar: the row menu (the hover "…" and the right-click) is opaque and clickable again. It rendered inside the draggable row, so pressing a menu item started a drag of the row, which faded it (menu included) and swallowed the click. Anchored menus and pickers now render outside their anchor, flip above it when there is no room below, and stay inside the viewport.
  • Sidebar: renaming a document changes its title; the file is renamed after the new title only when it was named after the old one, so hand-named files keep their names.
  • Dialect: [[wikilinks]] ([[target]], [[target#heading|alias]], ![[embed]]) are read and written as they are, on every surface; a link resolves by title, file name, path or a frontmatter alias, the way Obsidian and Anasa resolve it, and opens the document from the editor. Raw HTML blocks survive the editor untouched.
  • Indexing: a file that lacks id, type or title gets only that frontmatter added; its body keeps its bytes. Checkbox lines inside any document (- [ ] Ship it @priority:high @due:2026-09-20, Anasa's grammar) are indexed as tasks (new inline_tasks table: run the migrations once per deployment, see the README) and returned by a type: task query as read-only rows that open their host document.
  • Comments: a thread can anchor to an Anasa block id (ID::<id>, the <!-- ID::… --> marker under a block). Such a block gets the margin comment handle a heading has, ⌘⇧M with the caret inside it starts a thread there, and the panel scrolls to the block.
  • Assistant: NOTDOCS_MCP_SERVERS names external MCP servers (a stdio command such as Anasa's anasa-mcp next to the daemon, or an HTTP endpoint with a token) whose tools the assistant may call; tools not marked read-only wait for approval.
  • Import: notdocs import obsidian --keep-wikilinks and the matching box on the import page leave wikilinks as written; the page and the switching guide say when an Obsidian or Anasa vault needs no import at all.
  • Site: a Features index linking every capability to the page that explains it; a Templates catalogue generated from the built-in templates, with the file each one starts; comparison pages against Notion and Obsidian in a new Compare column of the footer and the Product menu; /mcp redirects to the agents page, which now describes SKILL.md and llms.txt.
  • Legal: website terms of use are their own page (section 12 of the terms of service now points to it); a renewal and cancellation page says how a plan renews and how to cancel it online, and pricing and the terms link to it; a "Do not sell or share my personal information" page carries the US notice at collection, the Global Privacy Control position and how to make a request.
  • Agents: notdocs.dev/SKILL.md is an agent skill that installs the CLI, initialises a repository and connects the MCP server; llms.txt points to it and to the changelog feed. The repository has an AGENTS.md for contributors' agents.
  • Changelog: an RSS feed at /changelog/rss.xml, one entry per bullet, advertised from the changelog page.
  • Web: an error page for a page that fails to render and a global error page for a failing root layout, both reporting to Sentry and offering a retry.
  • Site: every page has a canonical URL, Open Graph URL, site name and locale, and the share image (with alt text) on every page rather than only the home; the home page has its own title and Organization and WebSite structured data, and docs and legal pages a breadcrumb trail. The docs templates page is now "Templates guide", so it no longer shares a title with the catalogue. Workspace pages and the GitHub setup step are marked noindex. The About page no longer says there is no pricing.
  • Web: every screen of the app names itself in the browser tab, most specific first: ● Roadmap · Handbook · Acme · notdocs for a document with unsaved edits, Conflict: or ● Changed on disk: when those apply, Settings, Importing 12 of 40…, the organization home, a non-default branch in brackets after the project, and the loading, sign-in, not-found and failure states (Opening project…, Sign in to acme, Document will not open, Page not found, Something went wrong). Previously every app page was titled "notdocs".
  • Site: robots.txt names AI answer engines and training crawlers in their own groups (both allowed), stops blocking /w so its noindex can be read, and keeps crawlers off the error-report tunnel; the sitemap gives each markdown page its last-revised date; llms.txt moves the legal pages under ## Optional; www.notdocs.dev redirects permanently to notdocs.dev.
  • Sidebar: the tree is the repository's folders. Sections (Pinned, Recent, Documents, Root files, Templates) collapse with a chevron, carry an icon, a count and a hover action (clear recent, new page, new folder, new template), and remember their state and the open folders per project. Folders have chevrons and indent guides, documents their type icon, the open one is highlighted, and rows show an accent dot when the document changed since it was last opened (rolled up onto closed folders and sections) and a badge with open comments for documents opened this session. The tree is a WAI-ARIA tree: one tab stop, arrows, Home and End, Enter opens, F2 renames, Delete asks, typing filters. A hover "…" and a right-click open the same menu: Open, Rename, Move to…, New page here, New folder here, Pin, Copy link, Delete. Renaming and creating are inline rows with the file name previewed and slashes and duplicates refused. Trees past 150 rows are windowed.
  • Sidebar: drag and drop. Documents and folders (and a Shift or ⌘ multi-selection) drag between folders, to the root, into Templates, onto a type chip (which changes the document's type) and onto Pinned; a collapsed folder opens after a moment's hover; the drop marker is indented to the folder it lands in. Sibling order stays alphabetical, so a drop beside a row of the same folder does nothing and says so. Every drop updates the tree at once and rolls back with a note if the write fails; a folder with many files shows a spinner while it moves. Pinned rows reorder by drag, the one manual order there is. "Move to…" is a folder picker under the row for the keyboard.
  • Sidebar: files dropped from the desktop onto a folder (or the Documents header) are imported: markdown becomes documents there (a foreign id is dropped), images are uploaded and get a page each; without BLOB_READ_WRITE_TOKEN images are refused with the upload route's message. Dropping onto the editor is not handled: the editor has no image-drop path.
  • Undo: moving, renaming, changing the type of and deleting a document each leave a toast with Undo for eight seconds; undoing a delete recreates the document with the same id.
  • Palette: on a document page ⌘K offers Rename document, Move to…, Pin or Unpin, Duplicate and Delete for that document, with the same inline rename, folder picker and confirm as the sidebar. The document top bar has a pin button.
  • API: docs.move and docs.moveMany give documents new repository paths (under the docs root or one of the root files) in one commit; the id, hash, history and comments stay with the document, the change feed records one moved event per file (with fromPath), and a target that exists is a conflict. Relative markdown links inside other documents are not rewritten; links by id keep working. docs.create keeps a supplied ULID id when it is free on the branch.
  • CLI: notdocs docs mv <ref> <newPath> and notdocs docs mkdir <folder> (git keeps no empty folders, so it creates <folder>/index.md). MCP gains the matching docs_move and docs_moveMany tools.
  • Store: WorkspaceStore.move; the local store renames on disk and prunes emptied folders, the GitHub store writes one commit through the git data API with the old paths deleted.
  • Plans: organizations are on Free, Solo, Team or Enterprise, sold per organization at a flat monthly price through Polar; the plan caps people, projects, uploads and custom domains, turns semantic search on, and includes a monthly assistant allowance measured as what the AI Gateway reports each call costs. A deployment without Polar (the local daemon, a self-hosted instance) treats every organization as unlimited.
  • Web: a Plan section in settings shows the plan, what is used, the way to a bigger plan and the billing portal; a pricing page on the site; the assistant answers 402 with the reason when the plan does not cover it, and a pasted gateway key always works.
  • API: billing.status, billing.checkout and billing.portal; plan_limit (402) and rate_limited (429) error codes; the Polar webhook at /api/webhooks/polar; per-caller rate limiting on the API, the assistant and uploads over CapyDB K/V when its REST variables are set; uploads carry a project and are capped by its plan; a Sentry hook for failures that are not the caller's.
  • CLI: notdocs org plan, notdocs org upgrade <solo|team> and notdocs org billing.
  • Legal: the terms describe plans, fees, cancellation and Polar as merchant of record; Polar and Sentry join the subprocessors.
  • A proposal marked sent, accepted or declined and a report marked review validate again: @markdownkit/remark-mdd 4.1 accepts those statuses, where 4.0 reported each as an invalid status.
  • Templates for the documents a product team writes, grouped by lifecycle stage on a new Templates docs page: problem, competitive, prd, persona, journey, pricing, research, accessibility, rfc, adr, threat-model, infrastructure, standards, battlecard, brief, playbook, oncall, disaster-recovery, user-guide, metrics, feedback, quarterly-review and deprecation. Templates are keyed by name, so several can start one type (prd and rfc are both specs), and a repository file in docs/_templates/ with a built-in's name now replaces it, as the docs always said; its description frontmatter is shown in the list.
  • Eight new built-in types for internal documents: runbook, postmortem (with severity and status), release (with version), experiment, persona, brief, slo (with service, objective, window) and playbook, each with a template, an icon, a sidebar group and a default folder.
  • Five new legal types that are MDD documents like the business types and export to Word and PDF: terms (MDD terms-of-service), privacy (privacy-policy), nda, dpa (MDD agreement) and sla (service-agreement), with templates carrying the letterhead, page furniture and signature blocks each requires.
  • Business documents. Six new built-in types, letter, proposal, invoice, contract, memo and report, are MDD documents: their frontmatter carries MDD's document-type, date and author, and their templates carry :::letterhead, :::header, :::footer, :::contact-info and :::signature-block blocks. docs.validate (and notdocs validate) checks a business document's structure as well as its frontmatter: required blocks, block counts, nesting and the MDD 2 spelling. A new docs.export operation, notdocs docs export --format docx|html, an MCP tool and an Export menu on the document page produce a Word file through @markdownkit/md-docx 4 (headers and footers are page furniture and {{page}} a live page number) or a self-contained HTML page with a print stylesheet for the browser to print to PDF. Structure validation is @markdownkit/remark-mdd 4's validator.
  • Core: the canonical pipeline reads and writes the MDD 3 dialect. Single tildes are subscripts (H~2~O) rather than strikethrough and are no longer escaped on the way out; ~~strike~~ is unchanged. A document written in MDD 3 round-trips through the editor byte for byte.
  • Web: a Business documents page in the docs, and the six new types in the type list.
  • Self-hosting: one image for every deployment. The Clerk publishable key and the root domain are read from the container's environment when a request is served (CLERK_PUBLISHABLE_KEY, NOTDOCS_ROOT_DOMAIN; the NEXT_PUBLIC_* names still work) instead of being inlined at build, so the published ghcr.io/entro314-labs/notdocs image signs people in and routes organization subdomains as soon as .env names them, and changing either is a restart rather than a rebuild. Outside Vercel every page is rendered on request.
  • Live changes without a WebSocket. After three refused connections the SDK's subscribe polls changes.since every five seconds and tries the socket again once a minute; an onStatus callback reports whether events arrive over the socket or by polling. Self-hosted, /api/ws answers 426 Upgrade Required instead of failing, so the browser and notdocs changes --follow see changes within five seconds where before they saw nothing until a reload.
  • Web: the document History shows what each change did. Every edit whose previous version the feed knows carries a +N −M badge, and pressing the row opens the diff itself; the newest twenty rows fetch their versions as the tab opens (four at a time), older rows when expanded. The delta runs over the canonical text with the frontmatter in it, so moving a status counts as an edit and hand-written serializer noise does not. docs.blob (read scope) returns a remembered file version by hash; only hashes recorded for the project answer, and the browser keeps them for good since a hash never changes.
  • CLI: on npm as notdocs: npm install -g notdocs, npx notdocs, and claude mcp add notdocs -- notdocs mcp --serve . work from a global install. The package is one tsdown bundle that inlines the private workspace packages and ships the SQL migrations next to it; the workspace package is now named notdocs (was @notdocs/cli), so it is pnpm --filter notdocs. A publish-cli workflow publishes on a cli-v* tag with provenance.
  • Self-hosting: a Dockerfile builds the web app and API as one standalone image, docker compose up -d runs it with Postgres (pgvector, pg_trgm) and Valkey, migrations run on the first request, and docs/self-hosting.md covers the keys, Clerk, the GitHub App, TLS, backups, upgrades and what stays hosted-only. A plain redis:// K/V URL now connects without TLS. A GitHub Actions workflow publishes ghcr.io/entro314-labs/notdocs on main and v* tags.
  • Hosted: uninstalling or suspending the GitHub App on an account disconnects every organization that used it: the webhook clears the organization's GitHub login, so its home offers "Connect GitHub" again, and drops the cached stores of its repositories, so their next operation says the app is not installed rather than failing on a stale token. Projects stay registered until the app is reconnected; unsuspending does not reconnect on its own. Repositories removed from an installation are logged.
  • Web: the site around the app. A sticky translucent header with Product and Company mega menus and a mobile sheet, a footer, and a home page with a word-by-word hero beside a self-drawing commit graph, a demo that types itself in, numbered steps joined by a drawn path, a seven-tile bento of looping product scenes (merge, board, feed, search, types, comments, tokens), a flowing contract diagram, local-or-hosted, an FAQ and a closing band, with every section rising in as it enters the viewport and sinking out as it leaves; docs, agents, CLI reference, changelog (rendered from this file), switch-from-Notion and switch-from-Obsidian, about, contact and security pages; a legal hub with the legal notice, terms, privacy and cookie policies, DPA, subprocessors, AI disclosure, data portability and accessibility statement, all authored as markdown under apps/web/content and rendered with the workspace's own pipeline; a 404 page; robots.txt, sitemap.xml, llms.txt, security.txt, humans.txt and lawyers.txt. Settings link the policies, and the assistant panel says it is a language model. notdocs has a mark: a page with a lane of commits and an agent's spark showing through it, drawn once in code and rendered as the favicon, the app icons, the Open Graph image and the header, with a brand page that offers the files; contact and about are built pages with mailbox cards and the decision timeline.
  • Web: documentation blocks in the site's markdown. Code listings are highlighted by Shiki at build time into CSS variables for both themes, with line numbers, {1,3-4} line highlights, a title="…" (or bare) filename and a copy button; a ```mermaid fence draws a diagram in the theme's colours on the client; directives modelled on fumadocs give callouts (:::note, :::tip, :::warning, :::caution), ::::tabs with :::tab[Label], ::::steps with :::step[Title], ::::cards with :::card[Title]{href icon}, ::::accordions with :::accordion[Title] and :::files from a nested list. Docs pages carry the documentation tree on the left and a table of contents that follows the scroll on the right, folded into a disclosure on phones. The docs are split into Hosted, Local, Documents, Query blocks, Comments, Branches and merges, Search and Import, and the agents page uses tabs per client.
  • Web: the landing page sells the product instead of describing it: a headline, one paragraph, then one document shown as the file it is and as the page it becomes, with the history a person and an agent wrote under it; six benefits; the two commands that give an agent the workspace; a closing sign-in. The sign-in, first-organization and error states are unchanged.
  • API: docs.get answers from the index (the row's hash and the remembered blob) instead of reading the store, so opening a document on a GitHub project no longer waits on the contents API; the default branch comes from the project row rather than a GET /repos round trip per operation (refreshed by branches.list and the push webhook); repo templates are cached per project and branch until a _templates/ file or the config changes; listings and queries no longer select the search text of every row.
  • API: every /api/ops/* answer carries Server-Timing: op;desc="<name>";dur=<ms> and Cache-Control: private, no-store.
  • Web: nothing looks stuck any more. Every segment under /w, /o and /github/setup has a route skeleton at the page's shape; a two-pixel bar at the top shows any route change; sidebar rows and palette picks show the item being opened; every button behind a write (save, comment, reply, resolve, approve, deny, new document, create organization, add project, invite, new token, revoke, rebuild, import, create docs repo, domain add/verify/remove) shows a ring while it runs; a board card shows its move until it is written; the status pill and property checkboxes save straight away and roll back with a note if the write fails; resolving a comment is optimistic.
  • Web: API answers are cached and revalidated in the background: a page seen before paints at once and refreshes quietly, identical in-flight requests are shared, and a write or a live change event refreshes only the answers it may have moved. The workspace asks for projects, organizations, branches, config and documents at once instead of one after another; the change feed is read once per branch and shared by the sidebar, the home page and the palette; board, table and list views and every filter chip switch without a refetch.
  • Hosted: migrations no longer run on every cold start in production; pnpm --filter @notdocs/api exec drizzle-kit migrate runs them, and NOTDOCS_MIGRATE_ON_BOOT overrides the default (on in development, off in production).
  • Hosted: projects are added from the organization home (acme.notdocs.dev/, or /w without subdomains) through a repo picker: install the notdocs GitHub App, which sends you back to /github/setup to attach the installation to the organization, then pick a repository. Needs GITHUB_APP_SLUG; without a GitHub App the dialog says so. An organization can only register repositories of the GitHub account it connected.
  • Hosted: "Create docs repo" on the organization home creates a private <login>/<slug>-docs repository through the GitHub App (Administration: write), seeds it with the notdocs init starter files in one commit and registers it as the "Docs" project.
  • CLI: notdocs link [--url] [--project <slug|id>] [--token] writes .notdocs/link.json (git-ignored) so every command and notdocs mcp point at a hosted workspace from that checkout; notdocs unlink removes it and notdocs status prints where commands point. Resolution order: flags, NOTDOCS_*, the link file, the local daemon.
  • Web: the landing creates an organization first; repositories are picked on its home.
  • Hosted: every organization gets its own subdomain (acme.notdocs.dev) when NEXT_PUBLIC_NOTDOCS_ROOT_DOMAIN is set; projects are addressed as /<projectSlug> on it and links, the workspace switcher, the landing redirect and copied document links follow. Org slugs are validated (3–40 lowercase characters, no reserved labels) and can be renamed.
  • Hosted: an organization can be served on a custom domain (docs.acme.com) registered through Vercel's Domains API; settings and the CLI (notdocs org domain set|status|remove, notdocs org slug) show the DNS records to create and poll verification. Needs VERCEL_API_TOKEN, VERCEL_PROJECT_ID and, for a team, VERCEL_TEAM_ID.
  • Tokens: agent tokens can be listed and revoked from settings, the CLI (notdocs token list|revoke) and MCP; secrets are still shown once.
  • CI: GitHub Actions runs install with a frozen lockfile, build, typecheck, lint, format check, tests and the web build.
  • Web: the assistant asks before an ambiguous write (which template, which folder, which of several documents) with a question card answered in the panel, and suggests two or three follow-up prompts under a settled answer.
  • Web: an assistant's update can be taken in part: each hunk of the diff toggles, and Apply writes the kept hunks from the browser and tells the agent.
  • Web: the conflict dialog leads with a recommendation (keep mine, take the file's, keep editing) graded from the diff, with the alternatives one drawer away.
  • Web: the home page shows changes per day for two weeks as a sparkline with this week against last, and switches the tasks section between board, table and list.
  • Web: tables and lists narrow by status chips, the sidebar narrows by document type, records tables summarise each column in the footer, the index rebuild shows progress rows, and the palette, model picker and sidebar filter share one search field with a clear button.
  • Web: streaming text blurs its newest characters behind the caret, every expand animates through one reveal, the running tool row shows its step number, hashes and keys are mono chips, the update card carries its +N −M badge, and links in answers and sources get a sweeping underline.
  • Web: assistant answers survive a reload or a dropped connection when CapyDB K/V is configured; the panel picks the stream up where it was and marks the answer as resumed, and Stop ends the generation on the server rather than just closing the connection.
  • Web: focus rings follow a control's own corners and are no longer clipped by the properties bar, list cards, the sidebar filter or scrollable code blocks in assistant answers; chips in the collapsed properties bar and settings keep their hairline edge.
  • Web: a document is only marked unsaved when its canonical text changes, so focusing or clicking in the editor no longer enables Save.
  • Import: Notion exports lose their Export-<uuid>/ root folder and hyphenated page ids.
  • Deploy: typed Vercel config installs with a frozen lockfile and builds the workspace before the app.
  • Web: the design system gains surface ramps, layered shadows, a radii scale and strong easings, with primitives (glide highlights, records table, chips, filter chips, height-animated reveal, code block, inspector rows, diff view, pixel loader, theme toggle) that every screen is rebuilt on.
  • Web: collapsible sidebar rail (⌘) with the workspace and branch switchers, documents grouped by type, recents from the change feed and an inline filter.
  • Web: command palette (⌘K) with card picks for recent documents and quick actions, sections, keyboard travel across cards and rows, and a semantic badge on server hits.
  • Web: home board with status chips, per-column add, drag polish and recent changes with actor monograms; query blocks render as a records table (sticky first column, resizable and sortable columns, row selection) or as rows.
  • Web: document page with one top bar (breadcrumb, save state, rail buttons), a collapsed/read-only/editable properties panel (⌘⇧P, E, Esc), comment handles in the gutter (⌘⇧M), a pinnable popover rail for comments, history and the assistant (⌘⇧C, ⌘⇧H, ⌘J, ⌘⇧.), and a conflict dialog that diffs the canonical text, so only real edits show.
  • Web: settings with records tables for people and session tokens, an assistant section for a personal gateway key; import with per-file progress rows; light and dark themes with a toggle; the shortcut help (?) lists every shortcut.
  • Web: assistant panel with per-project conversations, progress lines while tools run, @ mentions the agent reads first, /tasks structured extraction from the open document, a moveTask tool behind approval, and AI SDK DevTools in development.
  • Core: canonical markdown pipeline (CommonMark, GFM, YAML frontmatter, directives), typed doc schemas, query blocks, sidecar comments, three-way merge, templates, Notion and Obsidian importers.
  • Stores: local folder (filesystem watch) and GitHub App (commits via the contents API, push webhooks).
  • API: every operation from one contract, local daemon with PGlite, hosted mode on CapyDB, change feed over WebSocket, full-text plus embedding search, project API tokens, guests.
  • SDK: typed client generated from the contract.
  • MCP: server generated from the contract on MCP SDK v2, over stdio and streamable HTTP.
  • CLI: notdocs with init, serve, mcp, docs, query, search, changes, comments, import (Notion, Obsidian), tokens and members.