zudo-text

検索したい単語を入力

いつでも検索バーを開ける

Kanban Board

The kanban board renders a directory as an interactive board with drag-and-drop columns. Each card is its own .md file in the directory, and a special manifest file (KANBAN.md) drives the board structure. This design makes every card independently editable in any text editor, git-diffable, and sync-friendly.

For the full architecture specification, see Kanban Directory Model.

What makes a directory a kanban board

A directory is a kanban board if and only if it contains a file named KANBAN.md. Without that file, it is simply a workspace directory and is not shown as a board. Adding KANBAN.md converts it to a board; deleting it reverts.

my-project/                  ← the board directory
├── KANBAN.md                ← manifest: board props + per-column card link-lists
├── refine-the-spec.md       ← a card
├── ship-the-adapter.md      ← a card
└── write-the-tests.md       ← a card

Creating a board

There are four ways to create a new kanban board:

Kanban Setup Wizard

The primary flow. Set a leaf to the Empty provider (or open the empty-leaf picker) and choose "Kanban Board" — the wizard renders as the full body of the leaf and walks through four steps: Location (New — name a folder and optionally pick where in your workspace it goes; or Existing — pick one of the boards already in your workspace, or browse for a folder), Details (board title and comma-separated column names), Layout (List, Now, or Calendar), and Confirm, which writes KANBAN.md and opens the board. Picking an already-initialized folder (one that already has KANBAN.md) skips straight to opening it.

"Set up this board" CTA

If a Kanban Board frame is opened pointing at a directory that has no KANBAN.md yet (for example, a pin aimed at a freshly created folder), the frame shows a "Set up this board" form in place of the board view. Fill in the title and columns and click the button — it writes KANBAN.md into that directory and the board view appears in place.

New Kanban Board…

Open the command palette (Cmd+K) and run "New Kanban Board…". The command opens the workspace folder picker so you can choose a folder, then opens it as a kanban frame. This command is always available from the palette.

Picking a folder that has no KANBAN.md writes a fresh, empty manifest and opens the new board. Picking a folder that already has a KANBAN.md leaves the existing manifest untouched and simply opens that board — the command never overwrites an existing board.


The manifest: KANBAN.md

KANBAN.md is the single source of truth for column membership and card order. It has two parts: YAML frontmatter carrying the board properties, and a body of ## Column headings each followed by an ordered markdown link-list of the cards in that column.

Frontmatter

---
type: kanban
title: My Project
columns: [Backlog, In Progress, Done]
collapsed: [Done]
labelDefs: [{name: bug, color: "#e11d48"}, {name: feature, color: "#2563eb"}]
cardWidth: 280
cardMode: bigger
---
FieldDescription
typeAlways kanban. Marks this file as a board manifest. Written first.
titleBoard title displayed at the top of the frame.
columnsAuthoritative column set and left-to-right display order.
collapsedColumn names rendered collapsed on open.
labelDefsBoard-level label definitions (name and color). Color-only labels (no name) are allowed as visual indicators.
groupByGroup cards into swimlanes by priority or labels. Can also be toggled from the toolbar.
viewsSaved view configurations with optional filters and groupBy overrides.
cardWidthCanonical Base width for List cards in CSS px. It is stored independently from cardMode; an absent value stays absent.
cardModeList-card presentation: normal, bigger, or biggest. An absent value behaves as normal without writing a default.

Body

The body lists one ## ColumnName heading per column, each followed by a markdown link-list of the cards in that column in order:

## Backlog

- [Refine the spec](./refine-the-spec.md)
- [Write the tests](./write-the-tests.md)

## In Progress

- [Ship the adapter](./ship-the-adapter.md)

## Done

Each list item is a standard markdown link: - [<title>](./<card-file>.md). List position is card order within the column; heading name is each card's status. A column heading with no list items below it is a valid empty column.

Moving a card to another column or reordering it is a single write to KANBAN.md — no card file is touched.


Card files

Each card is its own .md file in the board directory: YAML frontmatter for metadata, then a markdown body.

Frontmatter

---
title: Ship the adapter
idStable: k7m2p9q4rs3t
priority: high
due: 2026-06-10
labels: [backend, p1]
imageAttachments: ["architecture diagram.png", "demo.avif"]
---
FieldDescription
titleCard title. Fallback chain when absent: first H1 in the body, then the filename slug.
idStableDurable 12-character ID generated on first save. Used as the stable identity for the due-time annotation — survives renames.
priorityurgent | high | medium | low
dueDue date in YYYY-MM-DD format.
labelsLabel names matching labelDefs in the manifest.
imageAttachmentsOrdered bare filenames of images saved in the workspace assets/ directory. This is separate from images referenced in the Markdown body.
notifyDue-time annotation, settable with the same notify: grammar as todo items.
(any other key)Preserved verbatim across saves. Unknown keys (created_at, updated_at, custom fields) are round-tripped untouched.

status and order are NOT stored in card frontmatter. They are derived from the card's position in KANBAN.md. Moving or reordering a card writes only the manifest; card files are not touched for positional changes.

Filenames and renames

A card's filename is a creation-time slug (e.g., ship-the-adapter.md) that never changes even when you rename the card. Only the link text in KANBAN.md and the title frontmatter field are updated on rename. This keeps the manifest link paths stable and means renaming a card never deletes and recreates the file.


Keyboard shortcuts

ActionShortcut
Navigate cardsJ / K (down/up within a column), H / L (left/right across columns)
Open card detailE
New cardN
Delete cardD

The "Convert", "New", and "Revert" commands are palette-only (no default shortcut). You can assign shortcuts in Settings → Shortcuts.


Features

Drag and drop

Cards can be dragged between columns and reordered within a column. Dropping a card:

  • Cross-column move — Updates the card's position under the new ## Column heading in KANBAN.md.

  • Same-column reorder — Updates the list order under the same heading.

Only KANBAN.md is written; the card file itself is unchanged.

Dragging cards by touch. Each card has a card drag handle — the short grip bar at the top edge of the card. Press and hold it briefly, then drag: the card lifts and can be dropped on any list, in any direction. Dragging from the card handle is the reliable way to move a card up or down within a list, because a plain vertical swipe on the card body scrolls the list instead. A press-and-hold followed by a sideways drag also works from anywhere on the card body. A finger on the card body still scrolls the list vertically, and a finger on the board background still scrolls it sideways.

Image files have additional drop behavior in the flat List layout:

  • Drop on unused space in a list to create one card per successfully saved image. Each new card is appended to that list, uses the source filename without its extension as the heading, and stores the image as its first attachment.

  • Drop on a card or its open detail shell to attach every successful image without changing the card body.

  • Drop in the focused body editor to attach the images and insert encoded ![](../assets/<filename>) Markdown at the exact selection or caret in one edit.

The entire list body is a target, including the empty area below a sparse list. Each visible uncollapsed list reaches the board's padded bottom; long lists scroll inside their column while the board keeps independent horizontal scrolling. Collapsed columns and Add column are not image targets.

Card click and the docked/floating editor

Clicking a card opens it in the docked editor, a panel fixed to the right side of the board — the default editing surface. The docked editor provides:

  • Body editing — Textarea for the full card body (markdown supported). A leading # Heading line in the body sets the card title (see Card files above); there is no separate title field. While the body editor has focus, its frame shows an accent border — the visual signal that you're editing. Toggling Expand content removes the frame entirely (border and all), so the accent border only appears in the normal, non-expanded view.

  • Label editing — Add/remove labels with a dropdown showing all board labels and an inline label creator with an HSL color picker.

  • Timing and due-time annotations — Set a day, a specific time, or a date range, and optionally record a notify due-time annotation relative to that timing or at an absolute date and time.

Opening another card while one is already docked implicitly saves the card's unsaved changes and replaces it with the new card — there is no confirmation dialog.

Card composers — Each list's "+ Add Card" entry form (the composer) follows the same single-active-surface rule: opening a composer in another list, or another swimlane row, closes whichever composer was already open elsewhere on the board. Text already typed into the closed composer is preserved and restored the next time you reopen it.

Float and Dock — On desktop viewports, the docked editor's header has a Float editor button (hidden on narrow/mobile viewports) that detaches the editor into a floating window: a movable, resizable, non-modal window above the board. Floating windows are persistent reference windows — any number can be open at once, each new one cascade-placed a little further from the last so they don't stack exactly on top of each other. Drag a floating window by its header to move it, and drag its edges to resize it; a window may hang partially off the bottom or right edge of the viewport, but its header always stays reachable so it can be recovered. A floating window's header has a Dock editor to right button that returns it to the docked panel; if the docked slot is already occupied, that card's unsaved changes are implicitly saved and closed first, the same as opening a new card while one is docked.

Board-card indicators — separately from the body editor's own focus border above, a card whose editor is currently open shows a solid accent border when docked, or a dashed accent border with a small floating-window mark when floating.

Click Save to write the current unsaved changes at any time. Edits are also saved implicitly whenever the editor changes host — floating, docking back, or being replaced by another card — so unsaved changes are never lost across those transitions. Press Escape to close the topmost floating window if any are open, otherwise it closes the docked editor — except while the body editor has focus, where Escape does nothing at all: it does not close the panel and it does not blur the editor. Click elsewhere first to move focus out of the body editor, then Escape closes it. In vim mode, Escape while the body editor has focus behaves the same way and will not leave the editor either — click elsewhere instead.

Collapsible columns

Click the collapse button («) on a column header to minimize it. Collapsed columns show only the column name and card count. Click the expand button to restore.

Kanban image attachments are saved in the workspace assets/ directory and recorded as decoded filenames in the card's imageAttachments frontmatter. They are separate from Markdown images in the card body: attaching to a card does not insert Markdown, while an editor drop or paste intentionally does both.

The display order is stored attachments first, then body Markdown images that point to ../assets/, with canonical filename duplicates shown once. Filename spaces and other destination characters are percent-encoded in Markdown but remain decoded in frontmatter. Remote and data-URL body images can still render in Markdown, but do not become a card cover or gallery item.

In Normal, Bigger, and Biggest modes, a non-separator flat-list card with a display image shows its first item as a lazy 16:9 cropped cover. A badge counts the remaining gallery images. Click the cover — or, in Edit Card, a thumbnail in the card's image gallery — to open the same enlarged-image overlay used by the Assets filer, without opening or dragging the card and without handing the image off to another application. The overlay provides previous/next navigation (arrow keys or the arrow buttons), a position counter (e.g., "2 / 3"), and a close button; Escape also closes it. If an Edit Card panel is open at the same time (docked or floating), that panel's own Escape rules (above) take priority instead — use the overlay's close button to dismiss it in that case. Clicking an image inside a card's Markdown body does not open the overlay. Separator cards never show covers. On touch, the card drag handle sits over the top-center of the card; tapping inside that band opens card detail rather than the cover's lightbox, even on a card that has one. On a very short separator card the handle can visually overlap the card below it. If the first image is missing, the cover keeps the same 16:9 footprint and shows a Retry action; it does not promote a later image. Read errors are cached briefly so a rerender does not continually hit storage.

Supported formats are PNG, JPG/JPEG, GIF, WebP, SVG, BMP, ICO, and AVIF. TIFF/TIF are not supported. The limit is 25 MiB per image, inclusive. Browser files with a declared MIME must match their extension; clipboard images require a supported image MIME. The app preserves input order, while the storage backend sanitizes names and adds collision suffixes when necessary.

For multi-image input, accepted files are saved first and the successful subset is committed to the card/board once. Unsupported, unreadable, denied, oversize, and storage-failed items are reported by name. A partial success keeps its successful images; if every item fails, no card changes. If bytes save but the card write fails, Retry write reuses those saved filenames without uploading again. The saved bytes remain as an orphan until the write succeeds; removing a card or reference does not delete asset bytes in this release.

Pasting images

Image paste follows the active Kanban editing context:

  1. The focused Kanban body editor receives it and inserts Markdown plus attachment metadata.

  2. Otherwise, the open detail containing focus receives an attach-only update.

  3. Otherwise, the most recently activated visible detail receives it.

  4. With no applicable detail, a Paste images chooser opens.

The chooser defaults to creating one card per image. You can instead attach all images to one existing non-archived card. On mobile it fills the screen, keeps Close and the controls reachable within safe areas, and defaults to whichever column is most visible in the board's horizontally-scrolling row. Cancel, Close, and Escape change nothing and restore the focus that initiated the paste. Ordinary text paste and paste in unrelated inputs or the main document editor are not intercepted by Kanban.

Card size and content modes

List cards have three per-board modes, available under Card detail / size in Board settings and from the command palette's Kanban Card Mode menu:

  • Normal uses the Base width and the compact title/metadata presentation.

  • Bigger uses 15/11 of the Base width and renders Markdown until the first standalone root <!-- more --> marker.

  • Biggest uses 20/11 of the Base width and renders the full Markdown body.

For example, a Base width of 264 px produces 264, 360, and 480 px cards. The surrounding desktop column is always 16 px wider, producing 280, 376, and 496 px columns. The ratio is applied after the Base width is validated; the derived Bigger/Biggest width is not capped again. Display scaling is then applied to the complete geometry.

Changing the mode never changes or materializes cardWidth. The width control is therefore labelled Base width, lives in Board settings (gear icon) at every viewport width, and reports both the stored Base value and the currently rendered value. Settings and command-palette changes update the same cardMode field in KANBAN.md.

The <!-- more --> marker is structural only when it is an exact, standalone root HTML comment. Inline comments, blockquoted/list-nested comments, fenced or inline code, and comments such as <!-- more please --> remain content. The marker itself is never displayed. A leading ATX H1 (# Title) already consumed as the card title is not repeated in the body; a Setext H1 remains visible.

Large-card Markdown uses the app's read-only Preview components, including links, relative assets, lists, tables, code, and directives. Heading IDs are scoped by the card's stable identity. Editor-only interactions are disabled, and interacting with a nested link or control does not open or drag the card.

These modes affect stationary and drag-overlay cards in the List layout at every viewport width, including swimlanes; collapsed tiles retain their compact width. Now and Calendar keep their existing card rendering and width controls.

Priority indicators

Cards with a priority field display a colored left border strip:

  • Low — subtle gray

  • Medium — yellow

  • High — orange

  • Urgent — red

Due date coloring

Cards with a due date show it below the title. Overdue dates are highlighted.

Checklist progress

Cards containing - [ ] / - [x] checklist items display a progress bar showing completion status (e.g., "2/5 tasks").

Labels

Labels are defined at the board level in the labelDefs frontmatter field. Each label has a name and a color:

labelDefs: [{name: bug, color: "#e11d48"}, {name: feature, color: "#2563eb"}, {color: "#f97316"}]

Color-only labels — Labels without a name are allowed. These render as small colored dots on cards, useful as visual indicators. Cards reference them by hex color: labels: ["#f97316"].

Context menu

Right-click a card to access additional actions including editing, deleting, and changing the card's status.

Swimlanes

Cards can be grouped into horizontal swimlanes within each column. Use the swimlane toggle (≡) in the board toolbar to switch between grouping modes:

  • None — No grouping (default flat list).

  • By Priority — Groups cards by priority level (urgent → high → medium → low → none).

  • By Labels — Groups cards by their first label.

Each swimlane section is collapsible. You can also set the grouping in the manifest frontmatter with groupBy: priority or groupBy: labels.

Saved views

Define multiple board configurations in the manifest's views field to quickly switch between different perspectives of the same board.

Quick filter

Use the quick filter in the toolbar to search cards by title or label text, showing only matching cards across all columns.

Archives

The toolbar's Archives button opens a panel listing archived cards; drag one back onto the board to restore it to any column. On narrow boards, the Archives button collapses to icon-only; hover or check the accessible name for the count.


Layouts

The Kanban Board frame supports three layouts via the layout switcher in the frame toolbar:

LayoutDescription
List (default)The standard column/card board view with swimlane and drag-and-drop support. Read+write.
NowBounded horizontal time-axis view. Read-only.
CalendarMonth grid plus Week, 3 days, and Day scheduler views. Read+write in scheduler views.

List/card image drop targets and card covers are limited to the flat List layout in the first release. Swimlane and Now retain their existing card presentation and do not accept list/card image drops. Calendar's Month view keeps its existing month grid; its scheduler views use calendar interactions instead of list/card drops. A card opened from Now or Calendar still uses the shared detail editor, including detail drop and paste behavior. Switch back to the flat List layout to create cards from a list drop or use a card-front target.

Calendar

Calendar opens on the unchanged Month grid. The in-view switcher also offers Week, 3 days, and Day. Month remains the existing month surface: its pills, count badges, add-on-day behavior, and month state are not changed by the scheduler. The previous/Today/next range controls and the Time, Stack, Tray, and hour-height controls appear only in the scheduler views.

Week starts on Sunday, and the scheduler uses a fixed 24-hour local wall-clock axis. On a narrow viewport, Week and 3 days fall back to the Day surface so the same date and card contracts remain usable.

Time and Stack

The Time layout shows the scheduler grid for Week, 3 days, or Day:

  • A sticky date header shows each day, the number of cards, today/weekend treatment, and ↑n/↓n counts when timed cards continue outside the visible hour range.

  • The grid has an all-day row, an hour gutter, 24-hour labels, 15-minute drag snapping, overlap lanes, and a now-line on today. A point datetime is shown as a 30-minute visual block; that height is presentation only and does not change the stored timing.

  • A timed card can be dragged to move it, and its start/end resize handles change the range. Dragging between the all-day row and the timed grid converts the card's timing kind. A range that crosses midnight is split across its civil dates; a range ending at midnight does not occupy the ending date.

  • Dragging across an empty grid range opens the existing right-side Create Card flyout. The dragged range seeds the timing; choose the card title, List, and labels there, then press Enter or use the create action. The Stack day-add button creates an all-day card for that day.

The Stack layout answers “how many items today?” without treating Y as time. Each visible day is a kanban-style column. All-day cards come first, then timed cards ordered by start time with a small local-time label. Multi-day cards appear in every covered day. Dragging a card to another day keeps its time of day and reports a civil-day move; the + action adds an all-day card.

Tray and scheduling sources

Turn on Tray to reveal the unscheduled-card tray. It groups active cards by List, keeps the board-card rendering, and accepts cards dragged from the tray onto a timed slot, the all-day row, or a Stack day. Drag a scheduled card onto the tray to unschedule it. Press Enter on a tray card to schedule it as an all-day card on the current anchor day. Archived cards are not scheduled.

Calendar placement resolves a card's schedule in this order: timing:, then a legacy due date, then an absolute notify datetime. A due-only card is an all-day item. An absolute-notify-only card is placed at its local datetime. A malformed present timing: blocks those fallbacks, so it stays out of the grid until corrected.

Create, move, resize, and unschedule semantics

The scheduler emits a span and the kanban provider normalizes it before writing the card. Datetime endpoints snap in civil wall-clock minutes; explicit timed ranges receive the minimum logical duration; date ranges are inclusive. Mixed date/datetime endpoints become an all-day range over their covered dates. These operations never reinterpret a timing through the host timezone.

Delete or Backspace on a selected calendar card means unschedule, not delete: the provider clears timing, due, and notify together. Deleting a card still belongs to the card/archive surfaces.

Calendar keyboard shortcuts

Global calendar keys are active when the calendar has focus and the target is not an input or editor. Item keys apply to the focused card and retain focus after a mutation.

SurfaceKeyAction
Calendar / Previous/next visible range
CalendarTJump to today
CalendarWWeek view
Calendar33 days view
CalendarDDay view
CalendarSToggle Time / Stack
Time or all-day cardEnterOpen the shared card editor
Time or all-day cardDelete / BackspaceUnschedule (clear timing, due, notify)
Timed card / Move by one 15-minute slot
Timed cardShift + / Resize the end by one 15-minute slot
Timed cardAlt + Shift + / Resize the start by one 15-minute slot
Any scheduled cardAlt + / Move one civil day; Stack preserves time of day
Tray cardEnterSchedule as all-day on the current anchor day

What persists where

DataPersistence seam
Card scheduleThe card file's timing: frontmatter, using a floating local date/datetime span. Legacy due and absolute notify can supply placement until a calendar mutation writes timing.
List membership and orderKANBAN.md, as the card link under a column heading. Calendar moves do not change status or order.
Kanban Calendar viewThe frame/provider's calendarViewState, not KANBAN.md: minCellHeightPx, anchorMonth, unscheduledRevealed, view, layout, anchorDate, hourHeightPx, and trayRevealed.
Selection, active List legend filter, drag ghostEphemeral UI state; it is not persisted.

The shared three-field CalendarViewState remains the todo-board contract. The kanban scheduler extends it rather than changing the shared month-grid state. The hour-height slider commits on release, keyboard completion, blur, or its settle interval; anchor-date changes are also settle-gated so navigation does not write one state record per pointer repeat.

Approved prototype references (visual intent only):

Week · Time · darkWeek · Stack · darkCreate Card
Week Time darkWeek Stack darkCreate Card dark
Day · Stack · darkWeek · Stack · lightWeek · Time · light
Day Stack darkWeek Stack lightWeek Time light

Native macOS confirmation

Browser File and clipboard paths are covered by the CI-safe browser suite. Before a macOS release, confirm the remaining native WKWebView path with a real file drag:

  1. Open a flat List board at 1440×900 with one sparse column, one long column, and a docked card detail. Drag two supported image files from Finder to the sparse column's bottom two pixels; verify exactly two ordered cards are created once.

  2. Repeat over a card, detail shell, and focused body editor. Verify respectively attach-only, attach-only, and one Markdown insertion plus attachment metadata.

  3. Repeat over a collapsed column, Add column, Swimlane, Now, and Calendar; none may consume a list/card drop. Open a card from Now/Calendar and verify its detail/editor still consumes the drop.

  4. On a Retina display, repeat the drop near the window center and along each edge. The surface under the pointer must receive the drop exactly once, with no midpoint drift, duplicate browser/native mutation, or console error. Native positions are intentionally routed as logical client pixels; the accepted DPR=1 and DPR=2 synthetic routing evidence retains that contract.


External editing

Because each card is a plain .md file and the manifest is a plain KANBAN.md, the entire board is editable from any text editor, Claude Code, or automation script:

  • One card per file — each card is independently git-diffable, addressable, and externally editable.

  • Move or reorder — edit the link-list order in KANBAN.md; no card file changes.

  • New card — create a new .md file and add a link to it in the right column section of KANBAN.md. The app will display it on next open. Orphan files (present in the directory but not linked in KANBAN.md) are automatically placed in the first column on next load.


Note on the old single-file format

Earlier versions of zudo-text used a single .md file per board (cards as ## Heading sections with inline metadata). This format was removed before the first release. There is no migration path; boards recreatable from scratch. No backward-compatibility shim exists — old single-file boards will not open as kanban boards.

Header actions and setup guidance

Board settings, Quick filter and Saved views live in the frame header. The Layout dropdown selects Board, Now or Calendar only. At narrow frame widths the action buttons fold into a menu while the Saved views selector stays compact. Content registers frame-scoped callbacks and snapshots through kanban-instance-cache.ts; header updates do not remount the board.

Empty and missing-folder states use shared EmptyState guidance. The initial setup presentation includes an illustration and Manual link; an uninitialized directory retains the board-details form inside EmptyState. Existing folder selection, wizard and manifest-initialization controls remain available in the guidance.