Edit the board in the browser
A published board is static by default: it shows the files as they were at build time, and a drag only stages a move. Three steps make the same page edit the board:
Name the server in docs.yaml
kanban.api in the site’s docs.yaml names the board server, and
agents.yaml configures it. See Two keys turn the board live.
Run the server
folio-board-server api runs beside the checkout that holds the board.
Open the link it prints
The link carries a token new on every start. See The server prints the link that opens the board.
The server writes the card file, checks the whole board, and commits. The files in git stay the source of truth; the page reads them back after every save.

Two keys turn the board live
The site that renders the board names the server. For the server on this
page, the URL is http://127.0.0.1:<port> or http://localhost:<port>, the
two hosts it answers. An https:// origin with no path names a hosted server
instead (board.server.hosted): the board then offers Sign in with
GitHub and keeps the session in the browser for 8 hours. Any other value
fails the build.
# docs.yaml
kanban:
source: board
routes:
public: true
api:
url: http://127.0.0.1:4350The project whose agents.yaml names the board configures the server:
# agents.yaml
board:
source: board
server:
bind: 127.0.0.1:4350 # the default; loopback only
origins: ["http://localhost:4340"] # the pages allowed to call it
read_only: false # true refuses every write| Key | Default | Meaning |
|---|---|---|
board.server.bind | 127.0.0.1:4350 | The address the server listens on. Use 127.0.0.1: the server answers only 127.0.0.1 and localhost in Host. |
board.server.origins | [] | Browser origins allowed to call the server, matched exactly. Empty, no page can call it. |
board.server.read_only | false | true answers every write with 403 read_only. |
A site built without kanban.api carries no server address and no editor
code. Removing the key and rebuilding returns the page to the static board.
The server prints the link that opens the board
# REPOSITORY is Folio Board's git URL. The build fetches Folio Docs from its
# public repository.
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install --locked \
--git "$REPOSITORY" folio-board-server
folio-board-server api --project-dir .board: /home/ana/widgets/board
api: http://127.0.0.1:4350
open: http://localhost:4340/kanban/#board-token=3f9c…
token: 3f9c…The token is a random 256-bit value, new on every start. Open the open:
link: the page reads the token from the address, keeps it in the tab’s
session storage, and removes it from the address bar. The part after # is
never sent to a server. A new tab or a restarted server needs the link again.
--token HEX fixes the value, for scripted demos.
The server never pushes
It commits in the board’s own checkout. Push the board branch as you would
after folio-agents board move --commit.
Every save is one commit
A live card has no edit mode. Each value in the dialog is its own control: click it, change it, and press Enter or leave the field. The value shows at once with a mark while the commit runs; a refused save rolls back and says why under the control.
| Gesture | What the card file gets | Commit message |
|---|---|---|
| Drag a card to another column | status: | board: <id> <from> -> <to> |
| Edit the title, milestone, track, type, tags, priority, size, assignee, parent, blocked by or created | that frontmatter field | board: update <id> |
| Edit the description | the body before the first machine heading | board: describe <id> |
| Tick or untick a criterion | its checkbox | board: check criterion on <id> |
| Rewrite a criterion | its text | board: update criterion on <id> |
| Add a criterion | one open line under ## Acceptance criteria | board: add criterion on <id> |
| Add a comment | one line at the end of ## Comments | board: comment on <id> |
| Add a trail note | one line at the end of ## Trail | board: trail <id> |
| Add an artifact | one artifacts: entry | board: attach <kind> to <id> |
| Remove an artifact | that artifacts: entry | board: detach <kind> from <id> |
Comments and trail lines carry today’s date and the server’s actor, the
slugified git user.name of the board checkout. No request names an actor.
A move shows Undo, which commits the move back.
The artifact tile takes a URL, #42 for a pull request, or the path of a
file the board already carries, from the card’s directory or as
board/cards/<id>/…. A file elsewhere in the project is refused; attach it
with folio-agents board attach.
Only entries listed under artifacts: can be removed; a file in the card’s
directory stays until the file goes.

The description opens in a rich editor
Edit description opens the card’s description in place. Selecting text
shows a bar with bold, italic, inline code, a link, two heading levels, two
kinds of list and a quote; / at the start of a line inserts a block, such as
a code block or a divider. Cmd or Ctrl+Enter saves. The server serves the
editor itself, at /editor/v1.js, so a static site never loads it.

The editor opens as plain Markdown source instead, with a line that says why, in three cases:
- the description uses a component, such as
<Tabs>; - the rich editor would rewrite part of the description;
- the editor did not load.
Only the description reaches the editor. The frontmatter, the criteria, the
comments and the trail keep their own controls, and the server refuses a
description that would change them, such as one that adds a ## Trail
heading. Pasted plain
text joins its lines into one paragraph line, as
board fmt writes them.
A card that changed is refused, not merged
Every edit except a comment, a trail note and an attached artifact carries
the hash of the card the page last read. When the card file changed since,
the save is refused with 412 precondition_failed and the card as it is now.
The page shows the current card; make the edit again. A comment, a trail note
and an attached artifact need no hash: each adds a dated line or an entry and
changes no text the page showed. Adding a criterion needs the hash, like every
other edit.
A commit to another card is not a conflict. The server re-reads the board
and retries up to three times, then answers 409 stale.
Each save must leave a valid board, and a refused save writes nothing. A
parent that makes a cycle gets 400 invalid_input. A description that would
change another section, such as one that adds a ## Trail heading, gets
422 board_invalid.
Without the server the board stays static
When the server does not answer, or the tab has no token, the filter bar
shows read-only. The board then works as a static board: moves are staged
in the browser and Export moves prints the folio-agents board move
commands.
The server answers loopback only
These rules hold for the local server. A hosted server answers its public host and takes a GitHub session instead; the Board API lists its routes.
- It listens on a loopback address; any other
bindis a config error. - A request whose
Hostis not127.0.0.1:<port>orlocalhost:<port>gets421 misdirected, which stops DNS rebinding. - Only the pages in
originspass CORS, and no cookies are used. - Every route except
/editor/v1.jsneedsAuthorization: Bearer <token>. - A request body is at most 256 KB.
What the browser cannot do
Not available in this release
Creating, deleting and archiving cards and editing board.yaml. Use
folio-agents board add and a hand edit.
The routes the page calls are listed in the Board API.