Repository navigation
Folio v0.2.0
A space can now be a public website. Folio AI can run on a key that a space or the whole instance provides, and visitors of a public site can ask Folio AI about it. The rest of the release is smaller, and there is a lot of it.
Publish a space as a public website. Open the space menu, choose "Publish as website" and make the space public. Anyone can then read it without an account: the page tree, search inside the space, documents, live read-only data tables, whiteboards as pictures, forms (view only), PDF and Office files, and downloads as Markdown, PDF or DOCX if you allow them. It is the same space, not a copy, so the site changes when the page changes. Three addresses: https://wiki.example.com/public/<space> works at once, <label>.<PUBLIC_SITES_DOMAIN> needs a domain of its own with wildcard DNS and a wildcard certificate, and a custom domain such as docs.example.com needs a CNAME and a proxy route. Pages with their own access restrictions, the .agent folder and templates are never shown.
How to try it: make a space public, then open /public/<space> in a private window. Operator steps for subdomains and custom domains: Public sites.
A Folio AI key for a space or the whole instance. People no longer need a key each. A space administrator sets a key for one space, an instance administrator sets one for everyone and can switch personal keys off. A run uses the first key that exists: personal, space, instance, then CURSOR_API_KEY. Shared keys are stored encrypted, never shown again and every change is audited. Cursor is still the only provider.
How to try it: space menu, "Folio AI key", or the "Shared key" tab of the Assistant analytics page for administrators.
Ask Folio AI on a public site. A space administrator can switch on "Allow visitors to ask Folio AI about this space". Visitors get an "Ask AI about this space" button and no account is needed. The assistant answers in Ask mode only, from the public pages of that one space, and cannot change anything. It runs on the key of the space or the instance, so the owner of that key pays, and questions are rate limited per address and per site.
How to try it: switch it on in the publishing settings of a public space that has a key, and ask a question on the site.
Not yet: Folio generates no robots.txt or sitemap for public sites, a page restriction is not inherited by child pages (lock each page that must stay closed), and the rate limits are counted per server process.
Also in this release
- Docs-as-code: front matter from Docusaurus, MkDocs, Hugo or Jekyll is kept byte for byte, a space bound to a folder such as
docs/writes no ids into your files, images can live in the repository next to the page, and moving a page rewrites its links in one commit. - OAuth 2.1 for MCP: claude.ai, Claude Desktop and ChatGPT connect to
/mcpwithout a pasted token. Newsearchandfetchtools (23 tools in all), typed output schemas and tool annotations. - Connect with GitHub: one click through a GitHub OAuth App, complete repository lists, the real default branch.
- Replace a PDF or Office page with a new version; every version stays in the history and can be restored.
- Assistant analytics: ratings, a "did it solve your question?" survey and the questions it could not answer. Space administrators see their own spaces.
- Status tags,
++underline++, copy that keeps formatting, emoji reactions and "Make heading" on whiteboards. - Install kits for ten platforms, a published image for amd64 and arm64,
TRUST_PROXY, and a demo mode for people who run their own demo. - Security: the assistant has no file or shell tools, raw files follow page access, copy and restore from the trash respect page access, and the open dependency advisories are closed.
Upgrading
Back up first, then update as usual (docs/INSTALL.md). Migrations 031 to 039 apply at startup (037 to 039 belong to the features above). They add tables, columns, indexes and triggers, widen two check constraints and create one system user, "Public visitor", which has no password, cannot sign in and is hidden from lists of people. Nothing is removed. New settings are all optional: PUBLIC_SITES_DOMAIN, PUBLIC_EXPORT_PER_MINUTE, PUBLIC_ASSISTANT_RUNS_PER_HOUR, PUBLIC_ASSISTANT_SPACE_RUNS_PER_HOUR, GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET. A space that is not public behaves exactly as before.
Try it without installing: https://demo.foliowiki.online (the Folio Handbook there is published as a website at https://demo.foliowiki.online/public/folio-handbook, and you can try "Publish as website" on it yourself; the instance is shared and resets every 24 hours). How it works and what to watch before you publish: https://foliowiki.online/blog/publish-wiki-space-as-public-website/. Everything that changed: CHANGELOG.md.
Full changelog for 0.2.0
Folio can now publish a space as a public website, pay for Folio AI with a key
shared by an instance or a space, and let visitors of a public site ask Folio
AI about it. Spaces that live in a product repository stay tidy: front matter
from other tools is kept, ids stay out of the files, images can live in the
repository, and moving a page keeps its links working. Also in this release:
OAuth 2.1 so that claude.ai, Claude Desktop and ChatGPT connect to /mcp,
search and fetch tools, assistant analytics, replacing a file page with a
new version, "Connect with GitHub", install kits for ten platforms and a
public-demo mode.
Upgrading from 0.1.0: back up first (see docs/INSTALL.md). Migrations 031 to
039 are applied automatically at startup. They add tables, columns, indexes and
triggers, widen two check constraints (the source of a saved git credential and
the visibility of a space) and create one system user, "Public visitor", the
identity behind Ask Folio AI on public sites; it has no password, cannot sign
in and is hidden from lists of people. Nothing is removed. Every new setting is
optional, and an unset one keeps its default or leaves its feature off:
PUBLIC_SITES_DOMAIN, PUBLIC_EXPORT_PER_MINUTE,
PUBLIC_ASSISTANT_RUNS_PER_HOUR, PUBLIC_ASSISTANT_SPACE_RUNS_PER_HOUR,
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, TRUST_PROXY, FOLIO_OG_*,
FOLIO_POSTHOG_KEY and FOLIO_DEMO_*. A space that is not public behaves
exactly as before.
Added
- Publish a space as a public website. A space or instance administrator
opens the space menu, chooses "Publish as website" and sets who can read the
space to "public": anyone on the internet then reads it without an account,
as a read-only site with the page tree, search inside the space, documents,
data tables (live, read-only), whiteboards as pictures, forms (view only) and
PDF and Office files. The site is served at<PUBLIC_URL>/public/<space>as
soon as the space is public; at<label>.<PUBLIC_SITES_DOMAIN>when the
operator setsPUBLIC_SITES_DOMAIN(wildcard DNS and a wildcard certificate
on the reverse proxy) and the administrator picks a label; and at a custom
domain such asdocs.example.com(a CNAME to the instance and a proxy route
for that host). The administrator can also let visitors download a page as
Markdown, PDF or DOCX (PUBLIC_EXPORT_PER_MINUTEcaps PDF and DOCX per
address, default 6). Pages with their own access restrictions, the.agent
folder and templates are never published (a restriction is not inherited by
child pages, so lock each page that must stay closed), and visitors are not
sent the names or cursors of the people editing. People who are signed in
read a public space exactly as they read a space open to the whole instance.
The publishing settings live in the database, not in Git, and only a browser
session can change them: not an API token, and not the public demo. The
access matrix and the spaces list show the new visibility, and switching a
space to public asks for confirmation. Folio does not generaterobots.txt
or a sitemap for these sites yet. Adds migration 038. Seedocs/INSTALL.md
anddocs/LIMITATIONS.md. - Shared Folio AI key. Folio AI no longer needs a key per person. An
instance administrator can set one Cursor key for the whole instance (the
"Shared key" tab of the Assistant analytics page) and a space administrator
can set one for a single space (space menu, "Folio AI key"); whoever owns the
key pays. A run uses the first key that exists, in this order: the person's
own key, the key of the space the question is asked in, the key of the
instance, thenCURSOR_API_KEYfrom the environment. An instance
administrator can also turn personal keys off so that everyone uses the
shared key: a stored personal key is then ignored and the form for adding one
is hidden. The assistant settings show whose key is in use. Shared keys are
stored encrypted withFOLIO_SECRETand are never sent back to the browser
after they are saved (only the key's name, the Cursor account, who set it and
when); every change is written to the audit log, and the public demo cannot
change them. Adds migration 037. Cursor remains the only provider. - Ask Folio AI on a public site. In the publishing settings a space
administrator can switch on "Allow visitors to ask Folio AI about this space"
(off by default). Visitors then get an "Ask AI about this space" button and
need no account. The assistant runs in Ask mode only, answers from the public
pages of that one space, cannot change anything, cannot read page history,
and is told it is talking to an anonymous visitor. It uses the key of the
space, else the key of the instance, elseCURSOR_API_KEY(a visitor has no
personal key), so the owner of the key pays; with no key the panel says Folio
AI is not set up for the site. A conversation belongs to the browser that
started it (a random token whose hash is stored), and nobody else can
continue or read it. Questions are rate limited per address
(PUBLIC_ASSISTANT_RUNS_PER_HOUR, default 20 an hour across all public
sites) and per site (PUBLIC_ASSISTANT_SPACE_RUNS_PER_HOUR, default 200 an
hour); the counters live in the memory of the server process. Public
conversations appear in the assistant analytics as "Public visitor". The
rules in the space's.agentfolder still apply to these runs, so keep
secrets out of them. Adds migration 039. - Front matter from other tools is kept. Folio edits only its own keys
(id,order,status,icon,cover) inside an existing front matter
block. Keys written by Docusaurus, MkDocs, Hugo or Jekyll (title,
sidebar_position,slug,tags,description, ...), comments, lists and
quoting stay byte for byte through a scan, a save, a change of icon or order,
a rename, a copy and a duplicate. A block that cannot be parsed is left
exactly as it is. - A light footprint in a product repository. A space bound to a folder of a
repository (a root path such asdocs/) no longer gets anidwritten into
its files, neither on the first scan nor when a page is created or edited.
Ids live in Folio's index and survive moves, renames, copies and restores
from the trash, and the space's.foliometadata file goes into a dotfile
inside that folder (docs/.<slug>.folio) instead of the repository root,
where site generators ignore it. Spaces that already carry ids in their
files, or a<slug>.folioin the root, keep working as they are, with no
migration. - Images in the repository. A space administrator can turn on "Store images
in the repository" (space menu, "Image storage"). New pasted or dropped
images are then saved inassets/of the space's folder (docs/assets/for
a space with a root path), committed with the page and linked by a relative
path (../assets/diagram.png), so they also show on GitHub. Off by default;
images already in pages do not move. - Moving or renaming a page keeps its links working. Relative links and
images inside the moved page and its subtree are recalculated for the new
location, and links to it from other pages are rewritten. The move and every
rewrite are one commit (docs: move <old> -> <new>,docs: rename ...for a
slug), and Undo restores the old place the same way. Only[text](path)
links and images are rewritten, not HTML tags or reference-style links. - Make heading on a whiteboard. Select a free text block that has a first
line and a body, in Edit mode, and a "Make heading" button appears beside the
selection: the first line becomes a larger heading of its own, grouped with
the body, in one undo step. (Excalidraw has no bold inside a text element, so
this is how a heading is made.) Text inside a shape or a sticky note is not
offered it. - Connect with GitHub. "Git access" can link a github.com account through
a GitHub OAuth App instead of a pasted token: one click, GitHub's consent
screen, and the token is saved for github.com (it replaces a token saved
there earlier). It appears only whenGITHUB_CLIENT_IDand
GITHUB_CLIENT_SECRETare set, and is also offered in the "create space"
and "connect git" dialogs. The token form now names the scopes it needs and
links to the page where the token is created. Seedocs/INSTALL.md. - MCP tools declare their result shape. All 23 tools now publish an
outputSchemaand returnstructuredContentnext to the JSON text they
always returned, so clients and catalogs that read structured output get
typed results (lists are wrapped in an object:{"spaces": [...]},
{"hits": [...]}, ...; the text form is unchanged). Every tool parameter
now has a description, includinglimit/offsetoffolio_table_query
and the column fields offolio_table_add_column,folio_table_create
andcreate_page. Tool names are unchanged. Seedocs/MCP.md. - Replace a file page with a new version. A PDF, Word, Excel or PowerPoint
page can now take a new file without becoming a new page: it keeps its
link, its place in the tree, its icon, its access rules and its stars. Use
"Replace with new version…" in the page's tree menu, the button in the
page header (editors only), or drag a file from the desktop onto the open
page and confirm. After a replace a toast offers "Undo". The new file may
have a different type (a deck that is now a PDF): the file is renamed and
every link to it from other pages is rewritten, the same way "Change slug"
does. A file that is byte for byte the current one changes nothing. The
usual checks apply (.pdf,.docx,.xlsx,.pptx, and the bytes must
look like the claimed format), and share links cannot replace anything. - Version history for file pages, with restore. Every replace is its own
Git commit, and whatever was still waiting for the quiet-period commit is
committed first, so the version you replaced is always in the history. The
history panel of a file page lists the versions; each shows the file name
and size it had then, a download of exactly that version and, for a PDF,
a preview. "Restore this version" puts that file back (and its type) as one
more commit, so nothing is rewritten and the version you restored over stays
in the history. New endpoints:POST /api/pages/:id/file(replace) and
GET /api/pages/:id/history/:sha/file(the file at a commit). The MCP
tool for a page at a revision now answers for a file page with its path,
extension and size; the bytes are not exposed over MCP. Search still finds
file pages by title only. - Status tags. A short coloured badge inside a sentence, like the status
lozenge in Confluence::status[Done]{color=green}. Six colours (grey, blue,
green, yellow, red, purple); grey is the default and is also what a missing
or unknown colour falls back to. The text is stored as typed and shown in
capitals, so search, export and copy keep your own spelling. In Live edit the
tag is one badge that the caret skips over; click it for a popover with a
text field and the six colours, and insert one from the toolbar button, the
selection bar or the/menu (with text selected it wraps the selection).
Tags also work inside table cells. Outside Folio the source reads as plain
:status[Done]. Search finds a tag by its text, never by its colour. The
Confluence import turns status macros into these tags (both the rendered
lozenge and the storage-format macro), PDF and DOCX export draw them as
badges, and the assistant knows the syntax. - Underline is written
++text++. Underline used to be an HTML tag pair
(<ins>…</ins>), and a pair like that could cross a**pair and produce
text nothing could render.++text++is a delimiter pair like~~and
**, so it nests with bold, italic, strike-through and highlight in any
order and can never cross them. Exactly two plus signs:C++anda + b
stay text. The toolbar button and the hotkey write it, paste and the
Confluence import write it, and the assistant and MCP descriptions tell
agents to use it. Existing<ins>and<u>on pages still render
underlined, and the underline button reads them too (pressing it on such a
span removes it). The conversion is also available as a function for anyone
who wants to rewrite old pages in bulk: it is conservative, leaves any pair
whose rewrite would not parse exactly as it was, and counts what it skipped. - Copy and cut in Live edit keep inline formatting. Live edit folds the
markers (**,==,++, link brackets) away, so copying the visible word
used to put bare text on the clipboard. The selection is now mapped back to
its Markdown source, and the clipboard gets two flavours: Markdown as plain
text, and the rendered result as HTML, so Slack, Google Docs and mail keep
the formatting. Pasting that HTML back into Folio (or HTML copied from
Reading) converts highlights, status tags and strike-through to Folio's own
Markdown. - Live edit lists look like Reading. Bullets are drawn by depth (disc,
circle, square), nested items indent one level at a time with a hanging
indent, ordered numbers stay real text, and checklist items drop the dash in
favour of the checkbox. Source mode is unchanged. - Emoji reactions on whiteboard shapes. Select a shape in Edit mode and a
small reaction button appears at the lower right corner of the selection
frame; it opens a palette of eleven
emoji (✅ ❌ 🔧 for yes, no and in progress, then 👍 ❤️ 😂 🎉 🤔 👀 🔥 👏), each
with a name on hover. A reaction shows up as a chip with a count just below
the shape; click the chip to add or remove yours, and point at it to see who
reacted. Reactions are stored in their own shared map of the board's live
document, one entry per shape, emoji and person, so two people reacting to the
same shape at the same moment both keep their reaction, and they appear in
every open tab at once. They do not touch the drawing: no scene edit, no undo
step, nothing in the board's SVG file or in Git. People who may only view a
board, and anyone in View mode, see the chips but cannot react. - Optional analytics hook for a demo you run (off by default). Folio sends
no telemetry: with no key set, the browser never loads an analytics library
and the server never opens a connection to anyone. An operator who runs a
public demo can setFOLIO_POSTHOG_KEY(andFOLIO_POSTHOG_HOST) to send
anonymous events to a PostHog project of their own: the kind of page opened
or edited, the space slug and the steps of connecting an AI app, never page
text, titles, search words, names or e-mail addresses. Visitors are not
identified and the visit id lives in the tab's session storage, not in a
cookie; the browser's Do Not Track setting turns it off. See
docs/INSTALL.md. - Link previews. Links to the instance's front page now carry Open Graph
and Twitter card tags, so LinkedIn, Slack, Telegram and the like show a title,
a description and a 1200x630 picture. A plain installation gets neutral
defaults and a Folio card; the public-demo mode has its own text and card.
FOLIO_OG_TITLE,FOLIO_OG_DESCRIPTIONandFOLIO_OG_IMAGE(a path under
the site or an absolute address) override any of the three. Share links keep
showing the shared page's own title and excerpt. searchandfetchMCP tools for ChatGPT deep research and company
knowledge. ChatGPT only accepts two read-only tools of exactly these
names.search(query)returns{results: [{id, title, url}]}and
fetch(id)returns{id, title, text, url, metadata}(the page as
Markdown; a data table as a Markdown table, a board as its labels and
connections, a PDF or Office file as a note). They apply the same access
rules assearch_pagesandread_page: a page you may not see is "page not
found", the same answer as for one that does not exist. The server now has
23 tools.integrations/: ready-made setups for AI tools. Config files and
instructions for connecting Folio over MCP: a Claude Code plugin (with a
folio-wikiskill and a marketplace file, soclaude plugin marketplace add evergreen-it-dev/folioworks), Cursor (mcp.jsonand a rule), VS Code,
Codex, Open WebUI, two importable n8n workflows (a new GitHub issue becomes a
table row; a weekly summary page), steps for ChatGPT and Claude (OAuth), and a
description of an onboarding setup. Placeholders only, no tokens.- OAuth 2.1 for MCP: connect Folio from claude.ai, Claude Desktop and
ChatGPT./mcpnow answers an unauthenticated request with401and a
WWW-Authenticateheader pointing at the protected-resource metadata, and
Folio is its own authorization server: discovery documents
(/.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server),
dynamic client registration and Client ID Metadata Documents, authorization
code with PKCE (S256 only), a consent screen on top of the normal sign-in
(read, or read and write), one-hour access tokens with rotating 30-day
refresh tokens, audience binding to/mcp(RFC 8707) and revocation
(/oauth/revoke, and "Connected apps" under "API tokens"). Tokens act with
the rights of the person who approved them; areadconnection cannot run
write tools; everything is stored as hashes. Personal access tokens work
unchanged. Adds migration 034 (five tables). - MCP tool annotations and server instructions. All 21 tools now declare
title,readOnlyHint,destructiveHint,idempotentHintand
openWorldHintexplicitly, so clients can skip confirmation for reads and
ask for it on replacing or deleting tools. The server also sends a short
instructionstext oninitialize. - Install kits and a ready-made image. A workflow publishes the image
(ghcr.io/evergreen-it-dev/folio, amd64 and arm64), anddeploy/carries
kits for a plain Linux server (install.sh), Coolify, Railway, Render,
DigitalOcean, Dokploy, CapRover, Easypanel, Portainer and Fly.io, plus a
smoke check for a fresh installation (deploy/smoke.mjs). See
docs/INSTALL.md. - Public-demo mode (for running your own demo). Off unless
FOLIO_DEMO_MODE=1; an ordinary instance never serves any of it. When on,
the sign-in screen offers one-click demo accounts and a thin banner shows in
the app. For everyone in that mode the features that could be abused on a
shared instance are refused with 403: API tokens, the assistant and AI keys,
git and Confluence endpoints that connect to a host the caller chooses,
creating spaces, share links, invitations, changing name or username,
changing a space's visibility or publishing it, and setting shared AI keys;
uploads are capped (FOLIO_DEMO_MAX_UPLOAD_MB, default 5), and a demo
account cannot change passwords. See.env.dev.example.
Demo accounts may be space administrators (to edit a space's.agent
rules) without being able to break the demo for others: deleting or
renaming a space, members and roles, git sync, page permissions, emptying
the trash and removing the.agentfolder itself answer 403./mcpand the
OAuth credential endpoints are rate limited per address and size capped
(FOLIO_DEMO_MCP_RPM,FOLIO_DEMO_MCP_WRITES_PER_HOUR). TRUST_PROXY. Behind nginx, Traefik or Caddy every visitor used to
share the proxy's address, so the sign-in rate limit was one bucket for
everybody.TRUST_PROXY=truetakes the client address from
X-Forwarded-For, but only from a proxy on a loopback or private address
and only the entry that proxy appended. Off by default; a bare hop count is
refused.- Assistant feedback and analytics. Every saved assistant answer can be
rated 👍 or 👎, the panel asks "Did the assistant solve your question?" after
every third answer, and the assistant records questions it could not answer
or was unsure about. Instance administrators get an "Assistant analytics"
page (/admin/assistant) with the conversations, ratings, survey results and
a "Questions without an answer" list, filtered by space, user and date.
Opening someone's conversation is recorded in the audit log. Adds migration
032. - Assistant analytics for space administrators. A person who administers a
space now sees the "Assistant analytics" entry in the account menu, limited
to their own spaces: only conversations that ran in those spaces, and inside
them only the messages of those runs. Counts, first question and dates are
computed over that visible part, a filter by someone else's space is
refused, and the page says how many messages are hidden. Instance
administrators still see everything; a personal access token is still
refused. - "Who opened this conversation". The page of a conversation in the
assistant analytics lists who opened it and when (the latest 50, then "and N
earlier openings"), read from the audit log. Adds migration 033, a partial
index only. - Paste a link over selected text. Select some words and paste a single
address: the words become a link to it instead of being replaced. Works in
the page text and inside table cells. A selection that is already a link or
an address, a selection inside code, or a clipboard holding anything other
than one address is replaced as before. - Search in the space switcher. Long space lists scroll inside the menu,
while the action for creating a new space stays visible. - Markdown tables have three labelled width modes: narrow, medium and full
viewport. In narrow and full-width mode the highlighted right edge can be
dragged to set an exact width. A full-width table may grow past the viewport
and scroll horizontally inside its own container. - A welcome wizard for a fresh installation. Instead of an empty screen
with one button, someone who has no space yet gets three steps: what to
create first (a document, a whiteboard or a data table), what Folio can do,
and what the assistant is. Then the first space is created with the chosen
page in it, and a first document opens ready to type in. "Skip" goes
straight to that result. The tour can be reopened from the account menu. - Duplicate in the page menu of the sidebar. The copy appears next to
the original together with all of its child pages, is named
"Title (copy)" and opens at once. Works for documents, whiteboards, data
tables, forms and file pages; "Copy to…" remains for a copy in another
place or another space.
Changed
- The assistant links the pages behind an unanswered question. The "what
is missing" text of a question it could not answer now names the gap briefly
and links the pages the assistant actually read for it, so an administrator
can jump straight to the page that should have held the answer. Pages it did
not open are never linked. - The space administrator's note shows space names. The line saying which
conversations a space administrator sees now lists the names of their
spaces instead of their short addresses. - In "Questions without an answer" the person's own question is now the main
line, with the assistant's restatement below it as "Assistant's wording";
older reports that were saved without a run show only the restatement. The
space filter and the space columns of the assistant analytics show space
names instead of slugs. - Interface languages are picked up from the bundle files that exist, so
adding a language means adding files, not editing code. English is the
fallback when the browser asks for a language there is no bundle for. - Texts the server writes into content — the name of the first view of a new
table, the heading of the link list on an imported whiteboard — follow the
language of the person who triggers them. - The assistant's instructions are in English; it answers and writes pages in
the language it is addressed in. - Code comments, tests, test data and server error messages are in English.
- In the share menu the checkbox under an existing link is always labelled
"Include child pages": the tick alone says whether it is on. - Share links can be listed, created, changed and revoked only from the
browser. A personal access token, whatever its scope, is refused: the link
holds an access key. Agents still read a page through a link they already
have. - A share link "with child pages" leaves out every page that has its own
access restrictions, and everything below such a page. The set is checked on
every visit, so restricting a page later also takes it out of links that
already exist. A restricted page shared by a link of its own is still shared
together with its unrestricted children. - Raw files (
/files/…) follow page access. A page file is served under the
rules of that page; any other file is served to a signed-in user unless
every page that references it is hidden from them, and to a share guest
only when a page of the share references it. A file no page references
stays open to the members of the space. - Uploaded attachments and repository files in an unusual format are
downloaded instead of being opened in the browser; SVG still opens, but
without scripts or network access. Common raster images and PDF open as
before. In particular, a link from a page to a.html,.txt,.md,
.jsonor.csvfile, or to a video or audio file, now downloads it
instead of opening it in the tab. - URL paths that are not in canonical form (
//,/./, backslashes, an
encoded..) now answer 403. - Running outside Docker needs Node.js 22 (the image already uses it).
- The assistant checks the
spaceandpageIdsent with a request. A space
the user cannot read answers404 space not found, exactly as a space that
does not exist; an unknown or inaccessible page answers404 page not found; apageIdwithoutspaceis400. Earlier an unknown page id was
silently ignored. An instance admin who is not a member of a space can no
longer start an assistant run in it.
Fixed
- Repository lists from GitHub and GitLab are complete, and say why they
fail. The "create space" dialog follows the provider's pagination (up to
1,000 repositories over 10 pages, never leaving the original host), and a
provider failure (invalid token, rate limit, SAML single sign-on required,
forbidden, unreachable) shows a specific message instead of an empty list.
Creating a space from a repository without naming a branch asks the remote
for its real default branch instead of assuming one. - A link to a page that sorts later is no longer stored as broken. The
first scan of a space classified each page's links while it walked, so a link
to a page further down the alphabet was recorded as broken and never counted
as a backlink. Links are now indexed after the whole walk, and a rescan
re-indexes unchanged pages that held broken links. - Formatting keeps its blanks outside the markers. Every inline formatting
button leaves a selection made only of blanks alone and keeps leading and
trailing blanks outside the markers. Pairs are read with the CommonMark
flanking rules, so an asterisk after a space is not an italic, and pressing
the button on a broken pair such as*word *moves the markers onto the text
(*word*). - Headings show plain text in the outline, notes and page title.
Highlights, status tags, emphasis, links and escapes are resolved, so the
outline, the notes panel and a page's title (the first#heading) no longer
show raw markers such as==text=={.green}. Heading anchors are unchanged,
so saved links keep working. - A new sticky note opens its text field after Escape. On a whiteboard,
after pressing Escape the next new sticky note used to open the previous
note's text (or nothing). The new note is now the only selection and opens
for typing. - The built-in assistant no longer gets the MCP tools
searchandfetch.
They exist only because ChatGPT deep research accepts tools with exactly
those names, and they duplicatesearch_pagesandread_page(same access
checks), so the model saw both reading tools twice, and a barefetchlooks like a
web fetch to a model that has no way to open a URL. The assistant now sees
the other 21 MCP tools in Agent mode and the read-only ones in Ask mode. The
external/mcpendpoint is unchanged and still offers all 23 tools. The
pageMetatest no longer depends on the environment variables of the
shell that runs it. docker-compose.ymlpasses every documented setting to the app.
TRUST_PROXY,FOLIO_DEMO_MODEand the otherFOLIO_DEMO_*settings,
FOLIO_DEFAULT_REPO_URLand the assistant deadlines
(CURSOR_AGENT_TIMEOUT_MS,CURSOR_AGENT_TIMEOUT_MS_AGENT,
CURSOR_AGENT_IDLE_TIMEOUT_MS) were read from.envby nobody, so under
Compose they silently did nothing. They now reach the container; left empty
they keep their built-in defaults..env.exampleanddocs/INSTALL.md
describe them.- Reading: a loose list keeps its number and its text on one line. A list
with blank lines between items wraps each item in a paragraph, and with the
marker inside the item the number sat alone on the line above the text.
Markers now hang in a gutter of their own at every level, which also gives
wrapped lines a hanging indent. Checklists and lists inside table cells keep
their layout. - Confluence import no longer overwrites the space's home page. Importing
into an existing space with the default target wrote the imported root page
over the space's ownindex.md; the tree never lists that file (the space
home has its own row), so the imported page vanished from the tree and the
old home was gone. The same happened for a chosen folder that already had a
page of its own. The root now takesindex.mdonly where nothing would be
replaced or hidden, and otherwise goes into its own folder; importing the
same tree again updates that folder in place. - Formatting inside a table cell is no longer erased on the first
keystroke. Cells draw nested formatting (bold inside underline, highlight
inside bold) instead of showing raw tags and markers, and typing into a
formatted cell no longer drops its underline and highlight. - Someone else's selection no longer hides your text. When a collaborator
selected a range, everyone else saw the selected lines turn into a blank gap
after a few seconds of the collaborator being idle. The text itself was
intact in the editor, the shared document and on the server; only its
drawing was hidden by the fade that dims idle collaborators. Folio now draws
remote selections itself, as a coloured mark that never touches the text's
visibility, and the fade dims only the colour and the caret. Mixed versions
keep working, since the presence data is unchanged. - The reaction button on a whiteboard no longer follows the pointer. The
button used to pop up at whatever shape the pointer crossed, which got in the
way of drawing and selecting. It now belongs to the selection: select exactly
one shape in Edit mode and it appears at the lower right corner of the
selection frame; it stays hidden while you drag, resize, rotate, type or draw,
and the palette closes when the selection goes away. In View mode, and for
people who may only view the board, the chips are still shown but cannot be
clicked. - Typed text can no longer be lost between Live edit and Reading. One
error raised while the browser saved a local copy of the page used to switch
the editor's connection to the shared document off for good: from then on
the editor kept text on screen that the document never received, and it was
gone when you moved to Reading or reloaded. The connection between the
editor and the shared text now never switches itself off: after every change
it checks that both sides agree and repairs any difference, and when the
editor closes it hands over whatever only it had. The local copy is reopened
if its storage connection breaks, and it is deleted only after the server has
confirmed it holds your edits (checked against the server's own state, not
the socket's "synced" flag). If the server does not take your edits the
header shows "Not saved", and closing the tab with unconfirmed edits asks
first. A page you may only view (page access) now opens read-only instead of
an editor whose typing was silently dropped. - The server keeps what a live page holds when a file write fails. A failed
write of a page file is retried with growing pauses and the room's snapshot
stays in the database; the next open continues from the newer snapshot instead
of the older file. Page files are written atomically (a temporary file, then a
rename), and the temporary files are excluded from Git commits and cleaned up
if a crash leaves one behind. Carriage returns (Windows line ends) are
normalized wherever text enters a live page, which also stops two clients from
rewriting the same span and doubling it. When the server itself replaces all
of a live page's text (a file changed outside while the page was closed, an
API or MCP body write, "Take the version from Git"), the replaced text is
first saved in a backup table that is pruned by age. Adds migration 035. - A single
-under a paragraph stays a dash. Typing-on the line right
after a paragraph used to turn the whole paragraph into a heading in the
editor, because Markdown treats a line of dashes under text as an
underline-style heading. The editor no longer recognises that form while you
type;#headings, lists, quotes, bold and rules work as before, and Reading
mode still renders underline-style headings in existing or imported text. - The "Ask" and "Agent" mode labels in the assistant panel and on the first-run
screen are now translated into Ukrainian instead of staying in English. - Asking the same unanswered question twice inside one assistant run no longer
creates a second report; the text of the first one is kept (or completed, if
it was empty). The limit of three reports per run is unchanged. - The "what is missing" text of an unanswered question in the assistant
analytics is now rendered as Markdown (links open in a new tab; raw HTML and
javascript:links are dropped) instead of showing the markup as text. - The trash page scrolls. A long list of deleted items no longer runs
past the bottom of the window with no way to reach the rest. - The sidebar page tree now updates by itself when someone else changes it.
A page created, renamed, moved, deleted or restored by another user, an API or
MCP client, the assistant, an import or a Git sync used to stay invisible in an
already open sidebar until the page was reloaded. It now appears within about a
second. Only people who can read the space are told, and only that something
changed; the list itself is fetched through the usual access checks, so
restricted pages stay hidden. During a long import the tree refreshes at a
relaxed pace instead of after every page. - The personal "Undo" history of a deleted space no longer shows up in a new space
created with the same name. - Choosing "New space" in the Confluence import dialog now starts with an empty
name field instead of the address of the current space. - Text typed on the line right under a table no longer joins the table. The
first character there — a letter or an emoji — used to become one more table
row and turn the grid back into raw Markdown; a blank line now stays between
the table and the new text. - Emoji from the system emoji panel, dead keys and phone keyboards (anything
typed through an input method) no longer break tables: in an empty cell the
text was entered twice, and on the line under a table it turned the grid
into raw Markdown. Enter, Tab, Escape and the arrow keys pressed while an
input method is composing stay with it instead of moving between cells. - A whiteboard box no longer looks empty in the picture of a board. When a
board was created or updated by the assistant, or imported, the label of a
box could be drawn underneath the box in the preview used by a document that
embeds the board, by the version history and by the.excalidraw.svgfile
in Git, while the canvas itself was fine. The label is now always drawn
directly above its box, and the stacking order the board was built in is
kept once the board has been opened and saved. A board that already has the
problem is corrected by its next save; the server does not rewrite a board's
file just because the board was opened. - The AI assistant starts again in the Docker image. After the Cursor SDK
update, its native parts (built for glibc) could not be loaded by the
Alpine-based image, so the first question failed with "Error loading shared
library" on both x86-64 and ARM (Apple Silicon). The image now carries the
compatibility layer they need, and the build checks that these parts load,
so an image in which the assistant cannot start fails the build instead of
failing for the first user. - Opening an invitation while already signed in now offers a primary
Continue action that accepts the invitation with the current account.
Signing out remains available as a secondary action. - A space typed at the edge of formatted text no longer breaks the
formatting. With the caret right before a bold, italic, struck-through,
highlighted or underlined phrase, or before a link, the space now goes in
front of the hidden marker instead of after it, where it used to turn
**bold**into plain text with the asterisks showing. - The MCP server now reports the product version when a client connects,
instead of a fixed1.0.0. The MCP documentation now matches the code: the
create_pagetool takes the text of a new document (REST creation does
not), and the data table tools are split into three that read and five that
need thewritescope. - A change pushed to Git (for example from an IDE) now reaches a page that is
open in the editor right after the sync. Before, the open page did not know
about it: the next keystroke or closing the tab wrote the old version over
the file, and the change was silently lost. This holds for documents, data
tables and whiteboards. Text typed a moment before the sync is merged with
the change from Git instead of being dropped. - When the same passage was changed in Folio and in Git, the conflict markers
now show up in the document text and the page stays in the conflict state
until it is resolved. A data table or whiteboard with a conflict is not
overwritten until "Take the version from Git" is chosen. - "Take the version from Git" on a whiteboard now really restores the
version from Git: the open canvas switches to it, and closing the tab no
longer writes the discarded version back to the repository. - Two small fixes in the MCP server found while an agent used it. A document
made withcreate_pagefrom text that does not start with a heading now
keeps the title you asked for (it used to be named after the file, for
example "release-notes"): the heading is added above the text, and a
leading heading you write yourself is kept and becomes the title. And
folio_table_insertnow stores an unticked checkbox asfalsein a column
you did not fill, as adding a row by hand does, instead ofnull.
Security
- The request log no longer records the query string of the Google and GitHub
sign-in callbacks (it carries a one-time code), nor the guest token of a
public-site Ask Folio AI conversation. - The assistant no longer has file or shell tools. The Cursor agent runs
without an operating-system sandbox, and its built-in read, search and shell
tools were not confined to the conversation workspace, so they could open
the Git repositories of spaces the person cannot read. A run now offers the
model only Folio's own tools (the MCP tool set, the built-in tool that
records unanswered questions, andread_skill, which returns the shipped
working instructions by name); access control is therefore the one the
tools apply to the signed-in person, not the file system's. - Asking for a space or a page the caller has no role in at all now gets the
same "space not found" / "page not found" as for one that does not exist,
in the assistant and over MCP, so private space slugs and page ids cannot
be probed. A caller with too low a role (a viewer asking to write) is still
told so. - Page search over MCP and in the assistant returns nothing for a disabled
user, as every other read already did. - Dependency updates close four high-severity advisories.
puppeteer-coreis
now 25.x (used only to render PDFs with the system Chromium); its browser
downloader no longer pulls inextract-zip(symlink path traversal, no fixed
version) orbasic-ftp. Thenanoidcopy bundled through the Mermaid import
of whiteboards is raised to 5.1.11 or later.npm auditreports no known
vulnerabilities. Running outside Docker now needs Node.js 22.12 or newer. - A second round of dependency updates closes the seven advisories that were
still open.@modelcontextprotocol/sdkis 1.31 or later (the OAuth client now
binds stored credentials to the authorization server that issued them; Folio
itself only uses the SDK's server side and in-process client, so nothing
changes for it).@fastify/busboy, behind multipart uploads, is 3.2.2 (CRLF
injection through the filename inContent-Disposition),source-map-jsis
1.2.2, and the Express copy ofproxy-addrthat the SDK brings in is 2.0.8.
The development-onlyshell-quote(viaconcurrently) is raised with an
override, and so iskatex(via Mermaid, 0.18.2 or later).sprintf-jshas
no fixed version; it was only reachable through the command-line tool of
js-yaml3 (insidegray-matter), which Folio never runs, so an override now
gives that tool a dependency-freeargparseand the package is gone from the
lock file.npm audit --omit=devreports no known vulnerabilities. A reminder
for operators:TRUST_PROXYtakes address lists as written, so write IPv4
proxies in plain form (10.0.0.0/8), not as a short IPv4-mapped IPv6 prefix. - "Copy to…" and "Duplicate" no longer carry over what the person copying
cannot open. Before, an editor (or a viewer who could edit another space)
could copy a page together with its children and read, in the copy, a child
hidden from them by page access, with its files; copying the root page of a
space into another space also took the.agentfolder along. Now a page
hidden from the person copying is left out together with everything below it
and the files only such pages use, the.agentfolder is not copied unless
the person administers the space, and dot files and folders are never
copied. The copy of a page that has its own access rules is private to the
person who made it; sharing it again is a deliberate step. - A page restricted by page access that was deleted and then restored from the
trash used to come back open to everyone in the space, and so did restricted
pages inside a restored folder or a restored space. The trash now keeps who
could open each restricted page and puts that back together with the page, in
the same step that makes it visible, narrowed to people who are still members
of the space. An administrator who was never let into a restricted page no
longer sees its title or path in the trash. Items that were already in the
trash before the fix carry no record of their access and are restored as
before. - Raw files of a space can no longer be read around page access, and a share
link to one page no longer opens other files of its space (other pages'
Markdown and tables, repository internals). Hidden paths (anything starting
with a dot, such as.git), metadata files and symbolic links are never
served. - A share link "with child pages" no longer publishes pages that the person
who made the link cannot read themselves. - Share-link management is closed to API tokens, so a read-only token can no
longer obtain the address of an edit link. - Uploaded attachments are served by their detected content type rather than
the type the uploader claimed. Verified raster images and PDF are shown
inline, SVG inline under a policy that blocks scripts and network access,
other known file types as downloads, anything else as an opaque download;
every response carriesX-Content-Type-Options: nosniff. Responses a
browser has already cached keep their earlier headers until the cache
expires. - Files served straight from a space's git repository (
/files/…) get the
same safe headers as uploaded attachments: the type comes from the content,
SVG opens under a policy that blocks scripts and network access, and HTML,
XML, scripts and unknown types are downloads. Anyone who could commit a file
to a space used to be able to run a script in a visitor's session this way. - The assistant no longer loads the
.agentrules of a space the user cannot
read. Rules restricted by page access are skipped for people they are hidden
from. Rules of a space remain part of every member's run, as documented.
Turns an earlier conversation already contains are not rewritten. - PDF and DOCX export no longer lets page content or an SVG make the server's
rendering browser request external or internal addresses. The page is
rendered with scripts off, every request except inline data blocked, a
content policy inside the document and a dead network proxy. Remote images
in a PDF remain possible only where the operator explicitly allows them;
that setting never applies to SVG rendering for DOCX. - Updated dependencies with known advisories: Fastify, the Cursor SDK (which
also drops an oldundici),undici,fast-uri,hono,qs,
ip-address,brace-expansionanddompurify. - Upgraded
@fastify/staticto 10.x, which closes a route-guard bypass through
encoded separators, a path traversal and a bypass through non-canonical URL
paths.