discover · compare · collections · requirements · contribute · feed · watching · stats · help with your tokens
grinev/boardown · source
Snapshot 0 · 2026-11-04T12:47:28Z · TypeScript · MIT
Content fingerprint
5d596eff601ed62e152aa0987798314773741636727c9144ace01e26f6fc5b54GitHub description at capture
A local-first, git-native task board that lives in your repo as Markdown. Tasks, epics and releases as .md files — no cloud, no account. VS Code extension, native desktop app or CLI for AI agents
README at capture
# boardown
[](https://github.com/grinev/boardown/actions/workflows/ci.yml)
[](./LICENSE)
[](https://pnpm.io)
[](https://nodejs.org)
A local-first task board that stores its data as plain markdown files inside
your project's git repo. Releases, epics and tasks live in `.boardown/` next
to your code, so they version, branch and diff with the rest of the project —
no cloud, no server, no account.
boardown ships as a **VS Code extension** that reads `.boardown/` from the open
workspace and a **standalone desktop app** (Windows / macOS / Linux) that opens
any project folder — both reuse the same board UI and read the same markdown
files. A headless **CLI** (`@grinev/boardown-cli`) rounds it out for scripts and
AI agents, driving the same `.boardown/` files from the command line.
**AI-agent friendly.** The board *is* plain markdown in your repo, so an AI
coding agent (Claude Code, Cursor, …) already sees it next to your code. The CLI
gives it a first-class way to drive that board: every command speaks JSON, so an
agent can read the backlog, pick up the current release, and add or move tasks —
the very same board you see in the editor. Plan with your agent, watch the board
update live.
<p align="center">
<img src="./assets/Board.png" alt="boardown board view" width="80%" />
</p>
<p align="center">
<img src="./assets/Task.png" alt="boardown task details" width="80%" />
</p>
<p align="center">
<img src="./assets/Backlog.png" alt="boardown backlog view" width="80%" />
</p>
See [PRODUCT.md](./PRODUCT.md) for the full spec. What's planned next lives on
boardown's own board in [`.boardown/`](./.boardown/).
## Installation
boardown comes as a VS Code extension, a desktop app, and a command-line tool —
pick whichever fits your workflow. The extension is published to the VS Code
Marketplace and Open VSX; the desktop app is attached to each
[GitHub Release](https://github.com/grinev/boardown/releases); the CLI is on
npm. All of them read and write the same `.boardown/` board.
### VS Code extension
Install **boardown** from either registry:
- [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=grinev.boardown)
- [Open VSX](https://open-vsx.org/extension/grinev/boardown) — for VSCodium, Cursor, Gitpod, Windsurf and other VS Code forks
Open the **Extensions** view (`Ctrl+Shift+X` / `Cmd+Shift+X`), search for
`boardown`, and click **Install**. Or from the command line:
```sh
code --install-extension grinev.boardown
```
Prefer to install from a `.vsix`? Each
[GitHub Release](https://github.com/grinev/boardown/releases) attaches one:
1. Download `boardown-<version>.vsix` from the latest release's **Assets**.
2. Open VS Code and go to the **Extensions** view (`Ctrl+Shift+X` /
`Cmd+Shift+X`).
3. Click the **`…`** menu at the top of the Extensions panel and choose
**Install from VSIX…**.
4. Select the downloaded `.vsix` file.
Once installed, open your project folder and click the board icon in the
top-right corner of the editor, or run **Boardown: Open Board** from the
Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`). If the workspace has no
`.boardown/` folder yet, an onboarding screen walks you through creating the
board.
The open board refreshes itself when its `.boardown/` files change on disk —
switching branches, pulling, editing a file, or running the CLI all update the
board in place, without the Reload button. Turn it off with the
`boardown.autoRefresh` setting.
### Desktop app
Each [GitHub Release](https://github.com/grinev/boardown/releases) also ships a
standalone desktop app (Electron). Grab the file for your OS from the release's
**Assets**:
- **Windows** → `boardown-<version>-win-setup.exe` (installer) or the portable
`boardown-<version>-win.zip` (unzip and run `boardown.exe`).
- **macOS** → `.dmg` (drag to Applications) or `.zip`. Pick the `arm64` build
for Apple Silicon or the `x64` build for Intel Macs.
- **Linux** → `.AppImage` (`chmod +x boardown-*.AppImage && ./boardown-*.AppImage`)
or `.deb` (`sudo dpkg -i boardown-*-linux-amd64.deb`).
The desktop builds are **not code-signed yet**, so the OS warns on first launch.
To run anyway:
- **Windows** — on the SmartScreen prompt, click **More info → Run anyway**.
- **macOS** — right-click the app → **Open** (then confirm), or clear the
quarantine flag: `xattr -dr com.apple.quarantine /Applications/boardown.app`.
- **Linux** — no prompt; just make the `.AppImage` executable as shown above.
On launch the app shows recent project folders and an **Open Folder…** button;
pick a folder and the board loads from its `.boardown/`.
The board refreshes itself when those files change on disk — from git, an
editor, or the CLI — updating in place without the Reload button. Toggle it
under **Settings → Auto-refresh on file changes** in the sidebar.
### Command-line interface (CLI)
A headless `boardown` command for scripts and AI agents, published to npm as
[`@grinev/boardown-cli`](https://www.npmjs.com/package/@grinev/boardown-cli). It
reads and writes the same `.boardown/` markdown files as the apps, so every
change is a reviewable git diff.
Install it globally, or run it on demand with `npx`:
```sh
npm i -g @grinev/boardown-cli # installs the `boardown` command
boardown --help
npx @grinev/boardown-cli release current # or run without installing
```
It finds the board by walking up from the current directory to a `.boardown/`
folder (like git finds `.git`), or takes `--data-dir <path>`. Output is a stable
JSON envelope when piped (or with `--json`) and human-readable in a terminal,
which makes it a good surface for automation. It reads the way the app does —
`release current`, `backlog` and `archive` mirror the three tabs and list tasks
as compact summaries; `task get <id>…` is the full drill-down. See the
[CLI README](./packages/cli/README.md) for the full command list and the
machine-readable `schema` contract.
It also carries an agent skill, the page that teaches a coding agent the whole
CLI. Install it where your agent looks for skills:
```sh
boardown skill install claude # Claude Code: .claude/skills/ in this project
boardown skill install codex opencode # the shared .agents/skills/ (Cursor, Copilot, Gemini CLI, Amp too)
boardown skill install claude --global # under your home directory, for every project
```
### Local server (`boardown-web`)
A local HTTP server that serves the board UI in an ordinary browser tab, for one
project or for several at once. It is **not published to npm**: you build a
tarball from a checkout and install that, which is the whole of its distribution
story.
```sh
pnpm install
pnpm --filter @boardown/web build
cd packages/web && npm pack # → boardown-web-<version>.tgz
npm i -g ./boardown-web-<version>.tgz
```
With no argument it serves your **registry** of projects — the list of boards you
keep on this machine. Point it at a single board instead with `--data-dir`, or at
another registry file with `--registry`:
```sh
boardown-web # your registry of projects
boardown-web --data-dir /path/to/project/.boardown
boardown-web --registry /home/me/projects.yaml
boardown-web --port 7777 # otherwise the OS picks the port
boardown-web --no-watch # do not watch; Reload only
boardown-web --config-dir /home/me/studio-config # one theme for every board
```
The default registry file lives where your OS keeps user configuration:
| OS | File |
| --- | --- |
| Windows | `%APPDATA%\boardown-web\projects.yaml` |
| macOS | `~/Library/Application Support/boardown-web/projects.yaml` |
| Linux and the rest | `${XDG_CONFIG_HOME:-~/.config}/boardown-web/projects.yaml` |
It prints the address it is listening on, and the registry file it opened. It
binds the loopback interface only and refuses a request from anywhere else, so it
is a board on your machine, not a board on your network.
An open board refreshes itself when its files change on disk — from git, an
editor, or the CLI — updating in place without the Reload button, as in the
VS Code and desktop shells. Pass `--no-watch` to turn that off for the run.
Nothing is created for you up front: until that file exists the server starts
anyway and `/` lists no projects — the file, and the folder above it, are written
the first time you add a project. A file that is there but does not parse refuses
the start instead, naming the reason — the same as one you passed with
`--registry`.
The registry is a list of projects, whether it is the default file or one you
name with `--registry`:
```yaml
# projects.yaml
projects:
boardown: /home/me/code/boardown
shop: /home/me/work/shop
```
Each key is the id that appears in the URL — lowercase letters, digits and dashes
— and each value is an absolute path to the **project** folder, whose board is
the `.boardown/` inside it. The boards are then at `http://127.0.0.1:<port>/b/boardown/`
and `/b/shop/`, and `/` lists them with each project's name taken from its own
`config.yaml`. A project whose board cannot be read still gets a row, with the
reason on it, so one bad line never costs the others. Editing the file is enough:
a project added to it shows up without a restart, and one removed from it stops
being served, and reloading `/` is what re-renders the list.
You do not have to edit it by hand, though. The list page keeps it for you: every
row has a **Remove** button, and under the list an **Add project** button opens a
dialog taking a path and an id, filling the id in from the folder's name as you
type. A folder
with no board yet is fine — it is registered like any other, and opens onboarding
when you click it. Removing a project takes it out of the list and nothing else;
your files stay exactly where they are. Both write one line of the registry file
and leave the rest of it, comments included, alone — so the file is still yours to
edit, and a project you add by hand and one you add from the page look the same.
To show a board inside another app's window that already names the project, open
it with `?embed` — `http://127.0.0.1:<port>/b/shop/?embed`, or `/?embed` with
`--data-dir`. The project-name row above the tabs is left out and the tab bar
becomes the top of the page, 8px from its edge; everything else stays. It belongs to that page only:
the same board opened without it shows its title as usual.
To jump to a task, add `#task=<id>` — `http://127.0.0.1:<port>/b/shop/?embed#task=SHOP-12`.
The page opens that task's dialog once the board has loaded, or in place when the
fragment changes in a page already open, then drops the fragment, so setting the
same one again opens the task again. It works with or without `?embed`; an id no
task has opens nothing.
The app hosting those pages can also own their look. `--config-dir <path>`, in
either mode, names a **shared configuration directory** outside every board:
```
studio-config/
├── config.yaml # theme: studio (or light / dark)
└── themes/
└── studio.json
```
```json
{
"name": "Studio",
"base": "dark",
"fontFamily": "\"Inter\", system-ui, sans-serif",
"tokens": { "--accent": "#7c5cff", "--bg": "#14141a" }
}
```
The selected theme is shown on every board the server serves, whatever each
board's own `theme` says, and Settings shows it as read-only. Nothing is written
into the boards.
- **`base`** is required. Every token left out, and the font when `fontFamily` is
absent, comes from that built-in palette.
- **`tokens`** are the palette's own CSS variables (see
`packages/ui/src/theme/theme.css`), each checked as a colour or a shadow.
- **Names.** The theme name must match the file name exactly, case included.
- **Unknown content.** Fields and token names a build does not know are ignored.
- **Errors.** A broken file refuses the start. While watching, an edit shows up on
every open board at once; a broken edit keeps the last valid theme and shows the
reason on each board. Remove the `theme` key to give every board its own theme
back.
## Custom task statuses (beta)
> **Beta.** Statuses are declared by hand in `config.yaml` and there is no UI for
> managing them yet. The on-disk format may still change before 1.0 — expect to
> edit your config when it does.
A board can replace `todo` / `in-progress` / `done` with its own columns. Declare
them in `.boardown/config.yaml`:
```yaml
statuses:
- key: backlog
label: Not started # optional — the key is shown when absent
- key: dev
- key: review
- key: shipped
```
It is all-or-nothing: absent keeps the three built-ins, present replaces the whole
set. Between 2 and 8 entries; `key` follows the same rule as a custom field's —
1–40 characters, starts with a letter, then letters, digits, `_` or `-` — and keys
must be unique.
The meaning is **positional**. The **first** status is the one a new task takes,
and — unless `statusOutsideActiveRelease` is on — the only one a task may be
created with outside an active release. The **last** is the
terminal one: a link to a task in it is struck through, and completing a release
counts everything else as unfinished. The columns **between** are what the WIP
limit caps — one number in `wipLimits`, applied to each of them independently.
Colours follow the same order, so the first column stays grey and the last stays
green whatever you call them.
Editing the list under an existing board never rewrites your files. Tasks whose
status you dropped keep it and gather in a read-only **Unknown** column at the end
of the board, which you can drag them out of; the CLI answers `USAGE` naming the
board's own list if you try to *set* a status it does not declare, and `boardown
schema` reports the list so an agent reads it up front.
## Custom task types (beta)
> **Beta.** Types are declared by hand in `config.yaml` and there is no UI for
> managing them yet. The on-disk format may still change before 1.0 — expect to
> edit your config when it does.
A board can turn the four built-in types on and off, and add its own. Declare
them in `.boardown/config.yaml`:
```yaml
taskTypes: # optional; names base types and flips them on or off
- key: tech
disabled: true
customTaskTypes: # optional; the board's own types
- key: ops
label: Ops # optional — the key is prettified when absent
icon: server # optional — a lucide name; `boardown schema` lists them
color: '#0EA5E9' # optional — 6-digit hex
commitPrefix: ops # optional — used by Copy commit message; the key when absent
```
`taskTypes` is an override list, not a replacement: a base type it does not
mention stays as the product shipped it (today, all four on). `customTaskTypes`
keys follow the same rule as a custom field's, must be unique, and may not reuse
a base type's key (`bug`, `feature`, `docs`, `tech`). At least one type must end
up enabled. A bad declaration makes the config invalid — the app shows its
config error screen and the CLI returns `BOARD_INVALID`.
A disabled type is not offered in pickers, filters or `--type`. A task that
already carries one still loads and still shows it. `task add` without `--type`
uses `feature`, or the first enabled type when `feature` is off. `boardown
schema` reports the enabled types and the accepted icon names.
## Custom task fields (beta)
> **Beta.** This is the first slice of a larger customization story: only the
> `string` type exists, fields are declared by hand in `config.yaml`, and there is
> no UI for managing them yet. The on-disk format and the CLI flag may still
> change before 1.0 — expect to edit your config when they do.
A board can carry extra per-task fields that boardown itself knows nothing
about — who reported a bug, which environment it reproduces on, a ticket number
in another system. Declare them in `.boardown/config.yaml`:
```yaml
idPrefix: BD
nextId: 47
projectName: My Board
customFields:
- key: reporter # the frontmatter key, and the CLI's --field name
label: Reporter # optional — the key is shown when absent
type: string # the only type today
- key: env
type: string
```
`key` is 1–40 characters, starts with a letter and continues with letters,
digits, `_` or `-`. Keys must be unique, and may not reuse a name task
frontmatter already has (`id`, `type`, `priority`, `status`, `epic`, `order`,
`checklist`, `notes`, `links`, `labels`). A bad declaration makes the config invalid — the app shows its
config error screen and the CLI returns `BOARD_INVALID`, rather than ignoring the
line.
Every declared field becomes a row in the task dialog's **Details** panel, under
Type / Priority / Epic / Release, edited in place like everything else there. Values land in
the task's own frontmatter as ordinary keys:
```markdown
## Fix flaky sort order
---
id: BD-12
type: bug
status: in-progress
order: 300
reporter: alice
env: staging
---
```
A value that mentions another task (`BD-12`), a doc page
(`[[guides/release-process]]`) or a web address (`https://example.com`) renders
those as links, the same way the task description does — clicking one opens the
task, the doc page, or the URL in the system browser. Typing `[[` while editing a
field offers the doc-page suggestion list.
From the CLI, set them with a repeatable `--field`, and ask `schema` which fields
a board declares:
```sh
boardown task edit BD-12 --field reporter=alice --field env=staging
boardown task edit BD-12 --field reporter= # clear it
boardown task add "Fix login" --type bug --field env=prod
boardown schema --json # includes the board's customFields
```
Two things to know while this is beta. Values are stored as plain top-level keys,
so **removing a field from `customFields` drops its stored values** the next time
each of those files is written — git is the recovery path. And a board that
declares nothing sees no change at all: no new rows, no new keys on disk, no new
CLI output.
## Working with branches
The board is text in your repo, so it merges like any other file — no server
arbitrates writes. Two habits keep merges clean.
**On a branch, touch only the task that branch is about** — its status, title,
description, checklist, notes. That is one task block in one file, and a status
change is a two-line diff: task sections never move around, so nothing else in
the file shifts. Leave the neighbouring tasks alone even if you spot something
worth fixing; note it down for later. While **Track update date** is on (the
default), every change also rewrites the task's `updatedAt` line — a status change
is three lines, and two branches that change the *same* task conflict on that
line. Turn the setting off in Settings (`trackUpdatedAt: false`) if your branches
often share tasks.
**Reshape the board on the main branch:** creating tasks (a new task bumps
`nextId` in `config.yaml` and two branches end up with the same ID, which nothing
detects yet), dragging cards, moving a task between the backlog and a release
(a delete in one file and an insert in another — merged badly, the task
lands in both), and the release lifecycle.
Skim the `.boardown/` diff before merging: a duplicated task block is easy to
spot in markdown and far cheaper to fix there. Conflict markers left inside a
task's frontmatter only drop that task — the parser flags it and the rest of the
board keeps working.
## Building the `.vsix` from sources
To build an installable `.vsix` yourself instead of downloading it:
```sh
pnpm install
pnpm --filter boardown package
```
This produces `packages/vscode/build/boardown-<version>.vsix`, which you can
install with the steps above.
## Try it from sources
Install dependencies once:
```sh
pnpm install
```
Start boardown against this repo's sample `.boardown/`:
```sh
pnpm dev
```
Or open another project by pointing `--data-dir` at that project's `.boardown/`
directory:
```sh
pnpm dev -- --data-dir /path/to/project/.boardown
```
Each run writes a log file to `logs/` at the repo root (gitignored), named for the
run's start time — `logs/web-2026-07-19T14-32-08-123Z.log`. At the default `info`
level it records every action taken in the app (with its arguments), every write
to the board, and every failure on either side, which makes it the thing to attach
to a bug report. `BOARDOWN_LOG_LEVEL=debug` adds each individual read/list/stat;
`warn` or `error` narrows it to problems. The folder keeps the 10 most recent runs.
Only this dev shell writes logs — the VS Code, Electron and CLI builds a user
installs write none.
Then open `http://localhost:5173` in a browser. In VS Code, run
**Simple Browser: Show** from the Command Palette, enter
`http://localhost:5173`, and pin the tab if you want it to behave like a local
board panel.
If the selected `.boardown/` has no `config.yaml`, the web shell creates the
default structure automatically with `idPrefix: TASK`. Create `config.yaml`
manually before first launch if you want a different prefix.
## Development
Requirements:
- Node.js **>= 20** (the repo pins `20` via `.nvmrc`; Node 22 also works)
- pnpm **10+** (`npm install -g pnpm` or via `corepack`)
Install dependencies once if you skipped the quick start above:
```sh
pnpm install
```
The repo is a pnpm workspace with six packages:
- [`packages/core`](./packages/core) — platform-agnostic logic (schemas,
parser, board operations). Pure TypeScript, runs in Node.
- [`packages/ui`](./packages/ui) — the React app: components, Zustand store,
UI flow. Takes an `FsAdapter` as input, knows nothing about the host.
Source-only (consumed directly by the shell's bundler).
- [`packages/web`](./packages/web) — the browser shell, in two roles over one set
of endpoints: the Vite dev app that mounts `@boardown/ui` over a middleware
serving a local `.boardown/` data directory, used for iterating on the UI from
sources, and `boardown-web`, a local server for one board or a registry of
several. Nothing here is published to a registry.
- [`packages/vscode`](./packages/vscode) — the primary MVP distribution target,
a VS Code extension shell next to `web` (extension host via esbuild + webview
via Vite), reusing `@boardown/ui` unchanged. Packages into an installable
`.vsix` (see [Building the `.vsix` from sources](#building-the-vsix-from-sources) above).
- [`packages/electron`](./packages/electron) — a cross-platform desktop shell
(macOS / Windows / Linux): an Electron main process + preload behind the same
`FsAdapter`, with a Vite-built renderer that reuses `@boardown/ui`. See
[Desktop app (Electron)](#desktop-app-electron) below.
- [`packages/cli`](./packages/cli) — a headless command-line / agent-facing
shell: it does not mount `@boardown/ui`, mapping commands onto `@boardown/core`
board operations over a Node `FsAdapter`, with machine-readable JSON output.
Bundled with esbuild and published to npm as `@grinev/boardown-cli`. See
[Command-line interface (CLI)](#command-line-interface-cli) above.
### Common scripts (run from the repo root)
| Command | What it does |
|--------------------|-----------------------------------------------------------|
| `pnpm dev` | Start the web dev server against this repo's `.boardown/` (Vite, `http://localhost:5173`) |
| `pnpm dev:sandbox` | Start the web dev server against a throwaway copy of the test fixture (`http://localhost:5199`) — see [Browser testing](#browser-testing) |
| `pnpm build` | Build the shells that have a `build` script (web → Vite bundle, vscode → host + webview); `core` and `ui` are source-only and skipped |
| `pnpm test` | Run Vitest across all packages |
| `pnpm typecheck` | Run `tsc --noEmit` in every package |
| `pnpm lint` | Run ESLint over the workspace |
| `pnpm format` | Apply Prettier in-place |
| `pnpm format:check`| Check Prettier formatting without writing |
| `pnpm icons` | Regenerate every shell's app icon from `assets/brand/boardown.svg` (run after changing the logo) |
### Running a single package
Use pnpm's `--filter`:
```sh
pnpm --filter @boardown/web dev # only the web dev server
pnpm --filter @boardown/core build # only the core build
pnpm --filter @boardown/core test # only core tests
pnpm --filter @boardown/ui test # only ui tests
pnpm --filter @grinev/boardown-cli test # only cli tests
```
The dev server runs in any modern browser — it talks to the selected
`.boardown/` over a local Vite middleware, so no File System Access API or
Chromium-only feature is involved.
To open another boardown data directory from sources, pass `--data-dir`. The
path must point to the `.boardown` directory itself, not to the project root:
```sh
pnpm dev -- --data-dir /path/to/project/.boardown
```
If `--data-dir` is omitted, boardown uses this repository's `.boardown/`, same
as before. Relative `--data-dir` paths are resolved from the directory where
you run the command.
A shared configuration directory (see
[Local server](#local-server-boardown-web)) is passed the same way, as
`--config-dir <path>` or the `BOARDOWN_CONFIG_DIR` environment variable. The flag
wins, and both resolve a relative path from where you run the command. It works
with the sandbox too:
`BOARDOWN_CONFIG_DIR=/path/to/config pnpm dev:sandbox`.
### Desktop app (Electron)
`packages/electron` is a cross-platform desktop build (macOS / Windows / Linux).
It reuses `@boardown/ui` unchanged behind an Electron `FsAdapter` and boots to a
sidebar of recent project folders — pick one, or **Open Folder…**, and the board
loads from that folder's `.boardown/`.
Run it from sources in dev (Vite HMR for the renderer, esbuild watch for the main
process and preload):
```sh
pnpm --filter @boardown/electron dev
# open a specific folder on launch:
pnpm --filter @boardown/electron dev -- /path/to/project
```
Bundle and package it:
```sh
pnpm --filter @boardown/electron build # main + preload (esbuild) + renderer (Vite) → dist/
pnpm --filter @boardown/electron dist # package for the current OS via electron-builder → release/
```
`pnpm install` downloads the Electron binary automatically (it is allow-listed in
the root `pnpm.onlyBuiltDependencies`).
**Per OS** — `electron-builder` packages for the **host OS**, so run `dist` on the
OS you're targeting (or in a CI matrix, one runner per OS):
- **macOS** → `.dmg` + `.zip`
- **Windows** → a `Setup .exe` installer (NSIS) + a portable `.zip`
- **Linux** → `.AppImage` (run directly) + `.deb`
Cross-building from another OS is fiddly (Windows would need Wine), so the
[`Release`](./.github/workflows/release.yml) workflow runs a per-OS matrix to
build all three and attach them to each GitHub Release (see
[Releasing](#releasing)). Signed / notarized artifacts (Apple notarization,
Windows code-signing) need certificates and are deferred, so distributed builds
are unsigned for now — end users see a SmartScreen (Windows) / Gatekeeper
(macOS) warning on first launch ([how to bypass it](#desktop-app)).
### App icons
Every shell's app icon derives from a single master, `assets/brand/boardown.svg`.
`pnpm icons` rasterizes it (via `sharp` + `png2icons`) into the per-shell binaries
each build expects — the VS Code Marketplace `icon.png`, and Electron's
`build/icon.{ico,icns,png}` (Windows / macOS / Linux) — and also writes the VS Code
command/tab button mark `packages/vscode/media/board.svg` as a small colored copy of
the master, so it never drifts from the app icon. Those outputs are committed, so
normal builds and CI never need the rasterizer. To change the logo, replace the one
SVG and re-run `pnpm icons`.
### Sample board for the dev server
The repo ships a `.boardown/` folder at the root with a minimal config and a
couple of empty releases / epics. `pnpm dev` reads the selected data directory
via a small Vite middleware that exposes `/api/fs/{read,list,stat,write}` over
HTTP, and `@boardown/ui` mounts on top of an `HttpFsAdapter` that talks to
those endpoints — the same pair `boardown-web` serves. This is the working environment for UI development and local
use from sources; a production browser deployment (folder picker, FS Access
API or otherwise) is not in the MVP scope.
When the selected data directory has no `config.yaml`, the shell does **not**
seed a config or a starter release. Instead `@boardown/ui` shows an onboarding
modal that collects the project name and ID prefix and writes
`.boardown/config.yaml` on submit (`nextId` starts at `1`). After onboarding the
board starts empty and opens on the Backlog tab — create your first release from
the UI. The web dev shell only ensures the board root directory exists.
### Browser testing
There is no e2e suite. UI changes are exercised by driving a real browser —
either by hand or by an AI agent through the [Playwright MCP](https://github.com/microsoft/playwright-mcp)
server configured in `.mcp.json` (first run needs `npx playwright install chromium`).
```sh
pnpm dev:sandbox
```
This copies `tests/fixtures/board/.boardown/` into a fresh temp directory, prints
its path, and serves it at `http://localhost:5199`. The copy is what makes the
board safe to poke at: every click writes markdown through `/api/fs/write`, so
driving the repo's own `.boardown/` would corrupt the real board (and loading a
board can rewrite `config.yaml` on its own, via the `nextId` check). Nothing is
cleaned up on exit — the temp copy stays around so you can inspect what the UI
actually wrote.
The fixture covers the interesting states: a finished, a current and a future
release, tasks in every status and of every type, backlog tasks with and without an epic,
a task with a checklist and notes, and a pair of linked tasks.
Board columns, task cards, backlog rows and sections carry `data-testid`
attributes because they have no accessible name of their own; everything else
(dialogs, form fields, buttons) is reachable through roles and labels.
## Releasing
The whole monorepo ships under **one lockstep version**: the same number lives
in every `package.json`, with the **root `package.json` as the single source of
truth**. Each release attaches the VS Code `.vsix` plus the Electron desktop
installers for all three OSes (Windows / macOS / Linux), and publishes the CLI
to npm; future shells (web, JetBrains) will release together under the same
version.
Releases are driven by a version bump on `main`, not by pushing tags by hand:
1. Bump the version:
```sh
pnpm release:prepare patch # or minor / major / an explicit 0.3.0
pnpm release:rc # cut a 0.3.0-rc.1 prerelease
```
This updates the root version and mirrors it into every package, including
`minCompatibleVersion`. If the on-disk format files
(`packages/core/src/schemas.ts`, `serializer.ts`, `parser.ts`, `loader.ts`,
`layout.ts`)
changed since the previous release tag, it moves `minCompatibleVersion` to
the version being released. For a **stable** version it also seeds
`docs/release-notes/vX.Y.Z.md` with a draft (the same notes the workflow
would auto-generate). It does **not** commit and does **not** tag.
2. Curate the release docs, then commit them together:
- Rewrite `docs/release-notes/vX.Y.Z.md` into user-facing notes — the
workflow publishes it verbatim as the GitHub Release body (and falls back to
generating from the commit log if the file is absent; RC prereleases always
generate).
- Add a `## X.Y.Z` section to `packages/vscode/CHANGELOG.md` and, if the
release adds a headline capability, a bullet to the extension README's
Features list — these are the VS Code Marketplace listing.
- Run the gates (`pnpm lint && pnpm typecheck && pnpm build && pnpm test`),
then stage the version bump plus those docs and commit as a single
`chore(release): vX.Y.Z` (this scope is excluded from generated notes).
3. Push to `main`:
```sh
git push origin main
```
4. The [`Release`](./.github/workflows/release.yml) workflow notices that the
tag `vX.Y.Z` for the current version does not exist yet. It runs in three
stages: a `determine` job decides whether the bump is releasable; a `build`
matrix then runs the checks and builds the `.vsix` once on Linux and the
desktop installers on one runner per OS (electron-builder packages only for
its host); finally a `release` job gathers every artifact, resolves the
release notes (the committed `docs/release-notes/vX.Y.Z.md` if present,
otherwise generated from the commit log), creates and pushes the tag, and
publishes a GitHub Release with the `.vsix` and the desktop installers in
**Assets**. If the tag already exists (no version bump), the workflow skips
the release.
5. Once the GitHub Release exists, `Release` calls the reusable
[`Publish to Marketplace`](./.github/workflows/publish-marketplace.yml)
workflow, which downloads the released `.vsix` and pushes it to the VS Code
Marketplace (`vsce publish`). It needs a `VSCE_PAT` repository secret (an
Azure DevOps Personal Access Token with the *Marketplace → Manage* scope for
the `grinev` publisher); prerelease (`-rc.N`) versions and versions already on
the Marketplace are skipped. If the store publish fails after the release is
cut, re-run it on its own from **Actions → Publish to Marketplace → Run
workflow** against the same tag — no rebuild needed.
6. `Release` then also calls the reusable
[`Publish to Open VSX`](./.github/workflows/publish-openvsx.yml) workflow,
which publishes the same released `.vsix` to the
[Open VSX registry](https://open-vsx.org) (`ovsx publish`) — the open
marketplace used by VSCodium, Gitpod, Cursor and others. It needs an
`OVSX_PAT` repository secret (an Open VSX access token whose owner owns the
`grinev` namespace, created once via `ovsx create-namespace grinev`);
prerelease (`-rc.N`) versions and versions already on Open VSX are skipped.
It can likewise be re-run on its own from **Actions → Publish to Open VSX →
Run workflow** against any tag.
7. `Release` finally calls the reusable
[`Publish to npm`](./.github/workflows/publish-npm.yml) workflow, which builds
and publishes the CLI package (`@grinev/boardown-cli`, the `boardown` command)
to the [npm registry](https://www.npmjs.com/package/@grinev/boardown-cli).
Auth is **tokenless** via [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers)
(OIDC) — the repo is registered as a trusted publisher for the package, so no
npm secret is needed; the workflow only mints an `id-token` for auth and build
provenance. Stable versions publish under the `latest` dist-tag and RC
prereleases under `next`; versions already on npm are skipped. Re-runnable on
its own from **Actions → Publish to npm → Run workflow** against any tag.
boardown tracks its own work on a board stored in `.boardown/`. Commits that
only touch that board data use the `chore(board): …` scope and are excluded
from the generated release notes (just like the `chore(release): …` bump
commit), so dog-fooding the board never clutters a user-facing changelog.
Preview the notes that would be generated for the current version with:
```sh
pnpm release:notes:preview
```
This is also the starting point curated notes are seeded from: the preview and
`docs/release-notes/vX.Y.Z.md` share the same generator.
Every push and pull request to `main` also runs the
[`CI`](./.github/workflows/ci.yml) workflow (lint, typecheck, build, test).
## License
[MIT](./LICENSE)