zudo-text

検索したい単語を入力

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

Other Packages

Smaller utility packages that serve focused roles in the zudotext app.

@takazudo/file-utils

File system utilities for markdown frontmatter parsing, filename generation, and path safety. Designed for Node.js — used by the Rust backend's predecessor and by build scripts.

Exports

import {
  parseFrontmatter,
  stripFrontmatter,
  extractFrontmatterBlock,
  extractDateFromFilename,
  generateFilename,
  makeSlug,
  nowIsoLocal,
  safePath,
} from "@takazudo/file-utils";

import type { FrontmatterResult } from "@takazudo/file-utils";

// Subpath export for frontmatter only
import { parseFrontmatter } from "@takazudo/file-utils/frontmatter";

parseFrontmatter()

Parses YAML frontmatter from a markdown string. Supports quoted values, multi-line scalars (>-, >, |-, |), and CRLF normalization.

function parseFrontmatter(content: string): FrontmatterResult;

interface FrontmatterResult {
  meta: Record<string, string>;
  body: string;
}
const { meta, body } = parseFrontmatter(`---
title: Hello
date: 2026-01-15
---

Content here.`);
// meta = { title: "Hello", date: "2026-01-15" }
// body = "\nContent here."

stripFrontmatter() / extractFrontmatterBlock()

// Remove frontmatter, return body only
function stripFrontmatter(content: string): string;

// Extract the raw frontmatter block (including delimiters)
function extractFrontmatterBlock(content: string): string;

generateFilename()

Generates a date-stamped filename from a name string:

function generateFilename(name: string): string;
generateFilename("Project Update");
// "20260320-project-update.md"

extractDateFromFilename()

Extracts a date string from a timestamped filename:

function extractDateFromFilename(filename: string): string | null;
extractDateFromFilename("20260115-1430-meeting.md");
// "2026-01-15T14:30:00"

makeSlug()

Converts a string to a URL-safe slug:

function makeSlug(name: string): string;
makeSlug("Project Update");
// "project-update"

nowIsoLocal()

Returns the current timestamp in ISO local format:

function nowIsoLocal(): string;

safePath()

Prevents directory traversal attacks by ensuring the resolved path stays within the given directory:

function safePath(dir: string, filename: string): string;
// Throws "Invalid filename" if the resolved path escapes `dir`

@takazudo/code-block

React component for rendering markdown code blocks and blockquotes with a toolbar.

Exports

import { CodeBlock, markdownCodeComponents } from "@takazudo/code-block";

CodeBlock

Wraps <code> content with a toolbar that provides:

  • Copy button — copies the code text to clipboard

  • Word-wrap toggle — toggles word wrapping on/off

markdownCodeComponents

A component map for markdown renderers. Drop it into a markdown component provider to enhance <pre>, <code>, and <blockquote> elements:

import { markdownCodeComponents } from "@takazudo/code-block";

<MDXProvider components={markdownCodeComponents}>
  <MarkdownContent />
</MDXProvider>

The <pre> wrapper adds a context so <code> inside <pre> renders as a CodeBlock with toolbar. Standalone <code> (inline) renders as a plain <code> element. The <blockquote> wrapper adds a copy button.

Peer dependency: react >= 18.


@takazudo/command-palette

A VS Code-style command palette React component with fuzzy filtering, keyboard navigation, and category grouping.

Exports

import { CommandPalette } from "@takazudo/command-palette";
import type { Command } from "@takazudo/command-palette";

Command Interface

interface Command {
  id: string;
  label: string;
  description?: string;
  category?: string;
  shortcut?: string;
  keepOpen?: boolean;
  action: () => void;
}

Usage

import { CommandPalette } from "@takazudo/command-palette";

const commands = [
  {
    id: "new-draft",
    label: "New Draft",
    category: "File",
    shortcut: "Cmd+N",
    action: () => createNewDraft(),
  },
  {
    id: "toggle-vim",
    label: "Toggle Vim Mode",
    category: "Editor",
    action: () => toggleVim(),
  },
];

<CommandPalette
  open={isOpen}
  onClose={() => setIsOpen(false)}
  commands={commands}
  placeholder="Type a command..."
/>

Features:

  • Case-insensitive filtering by label and category

  • Arrow key navigation with Enter to execute

  • Escape to close

  • Automatic category grouping

  • Optional keepOpen to prevent closing after action

  • Shortcut badge display

Peer dependency: react >= 18.


@takazudo/shortcut-engine

Keyboard shortcut engine with chord support (two-step shortcuts like Mod+B → S).

Exports

import {
  createShortcutEngine,
  formatShortcutForDisplay,
  formatComboForDisplay,
} from "@takazudo/shortcut-engine";

import type {
  ShortcutEngine,
  ShortcutBinding,
  ChordState,
} from "@takazudo/shortcut-engine";

createShortcutEngine()

function createShortcutEngine(
  onChordStateChange?: (state: ChordState | null) => void,
): ShortcutEngine;

interface ShortcutEngine {
  handleKeyDown: (e: KeyboardEvent) => void;
  setBindings: (bindings: ShortcutBinding[]) => void;
  reset: () => void;
  destroy: () => void;
}

interface ShortcutBinding {
  id: string;
  shortcut: string; // "Mod+E" or "Mod+B → S"
  action: () => void;
}

Usage

import { createShortcutEngine } from "@takazudo/shortcut-engine";

const engine = createShortcutEngine((chordState) => {
  if (chordState?.pending) {
    showChordIndicator(chordState.prefix); // e.g., "Cmd+B"
  } else {
    hideChordIndicator();
  }
});

engine.setBindings([
  { id: "save", shortcut: "Mod+S", action: () => save() },
  { id: "bold", shortcut: "Mod+B → B", action: () => toggleBold() },
  { id: "strikethrough", shortcut: "Mod+B → S", action: () => toggleStrike() },
]);

document.addEventListener("keydown", engine.handleKeyDown);

// Cleanup
engine.destroy();

Key behaviors:

  • Mod normalizationMod maps to Cmd on macOS, Ctrl on other platforms

  • Chord timeout — 1500ms to complete the second key after the prefix

  • Escape cancellation — pressing Escape during chord-pending cancels the chord

  • Modifier filtering — standalone modifier key presses (Shift, Ctrl, etc.) are ignored

Display Formatting

formatComboForDisplay("Mod+S");
// macOS: "Cmd+S", others: "Ctrl+S"

formatShortcutForDisplay("Mod+B → S");
// macOS: "Cmd+B → S", others: "Ctrl+B → S"

@takazudo/kanban-board

React component library for a drag-and-drop kanban board. Built with @dnd-kit for drag-and-drop and depends on @takazudo/kanban-parser for data types.

Exports

import {
  KanbanBoard,
  KanbanCard,
  KanbanColumn,
  KanbanCollapsedColumn,
  KanbanSwimlaneGrid,
  KanbanGridCell,
  KanbanViewSelector,
  SwimlaneToggle,
  InlineEditField,
  CardContextMenu,
  CardQuickEditMenu,
  KanbanFilterBar,
  LabelBadge,
  LabelBadgeList,
} from "@takazudo/kanban-board";

import type {
  KanbanCardMode,
  KanbanView,
  LabelDef,
  RenderKanbanCardBody,
} from "@takazudo/kanban-board";

KanbanBoard

The main container component. Renders columns with drag-and-drop card movement.

Key props:

  • board: KanbanBoard — board data (from kanban-parser)

  • board.cardMode?: KanbanCardMode"normal" | "bigger" | "biggest"; absent resolves to Normal

  • renderCardBody?: RenderKanbanCardBody — host-owned raw-Markdown renderer for Bigger and Biggest cards; receives { card, mode, hasLeadingAtxTitle }

  • labelDefs?: LabelDef[] — label color definitions

  • onCardMove, onCardReorder, onCardUpdate — mutation callbacks

  • onColumnToggleCollapse — column collapse toggle

  • groupBy?: string — enable swimlane grouping by field

  • views?: KanbanView[] / activeView? / onSelectView? — saved view support

  • onAddCard?, onCardDelete?, onEditCard? — CRUD callbacks

  • focusedCardId? / onCardHover? — keyboard navigation support

Hooks

  • useKanbanKeyboard — Vim-style keyboard navigation (J/K/H/L for movement, E to edit, N to add, D to delete). Key bindings are configurable.

Features

  • Drag-and-drop cards between columns

  • Swimlane grouping (by priority, labels, or custom field)

  • Column collapsing

  • Inline editing for card titles/bodies

  • Per-board card modes (Normal, Bigger, Biggest) with ratio-derived desktop geometry

  • Raw-Markdown card-body rendering seam owned by the host application

  • Context menu and quick-edit popover

  • Image enlargement dialog

  • View selector for saved filters/groupings

  • AI actions (label suggestion, subtask breakdown, checklist generation)

Dependencies: @takazudo/app-defaults, @takazudo/color-themes, @takazudo/kanban-parser, @takazudo/ui-components, @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities. Peer dependencies: react >= 18, react-dom >= 18.

cardWidth is the canonical Base width. The package derives List card widths with exact Normal/Bigger/Biggest ratios of 1/1, 15/11, and 20/11 at every viewport width; column, header, cell, Add Column, and drag-overlay geometry adds 16 px around the derived card width. The package does not define or accept CardDensity.

The board package deliberately does not parse Markdown. In Normal mode it uses the compact card presentation. In Bigger and Biggest it calls the memo-stable renderCardBody seam once per visible card. The application host implements that seam with read-only shared Preview components, card-scoped heading IDs, relative asset resolution, and nested-interaction isolation. The hasLeadingAtxTitle flag lets the host omit only a title already consumed by the parser; marker truncation remains a host rendering concern.


@takazudo/kanban-parser

Pure markdown parser, serializer, and directory-model adapter for kanban board data. Zero dependencies.

The package uses the directory-as-board model: a directory is a kanban board if and only if it contains a KANBAN.md manifest. The manifest carries board props in YAML frontmatter and one ## Column heading per column, each followed by an ordered markdown link-list of card files. Each card is its own .md file. See Kanban Directory Model for the full spec.

Directory-model exports

import {
  KANBAN_FILENAME,
  parseKanbanManifest,
  serializeKanbanManifest,
  parseCardFile,
  serializeCardFile,
  assembleBoard,
  planDirectoryWrites,
} from "@takazudo/kanban-parser";

import type { BoardMeta } from "@takazudo/kanban-parser";

KANBAN_FILENAME

const KANBAN_FILENAME = "KANBAN.md";

The manifest filename constant. A directory is a kanban board iff it contains this file.

parseKanbanManifest()

Parses the KANBAN.md manifest: extracts board props from frontmatter and a list of { name, cardFiles[] } column objects from the body's ## Heading / link-list structure.

function parseKanbanManifest(md: string): {
  meta: BoardMeta;
  columns: { name: string; cardFiles: string[] }[];
};

serializeKanbanManifest()

Serializes a KanbanBoard to the KANBAN.md manifest. Body link paths come from fileIndex (keyed by idStable); link text comes from each card's current title.

function serializeKanbanManifest(
  board: KanbanBoard,
  fileIndex: Map<string, string>,
): string;

parseCardFile()

Parses a single card file. Returns the card (without status/order — those are derived from the manifest) plus the idStable key. Title falls back to the first H1, then the filename slug.

function parseCardFile(
  content: string,
  filename: string,
): { card: Omit<KanbanCard, "status" | "order">; idStable: string };

serializeCardFile()

Serializes a card to its file. Preserves unknown frontmatter keys from prevContent (e.g. created_at, updated_at, custom fields) verbatim. Pass null for brand-new files.

function serializeCardFile(card: KanbanCard, prevContent: string | null): string;

assembleBoard()

Read path: assembles a full in-memory board from the manifest and all card documents. Performs card and column reconciliation (orphan documents, dangling manifest links, unknown column headings) and returns non-blocking warnings. Never mutates the workspace.

function assembleBoard(
  manifestMd: string,
  cardFiles: { filename: string; content: string }[],
): { board: KanbanBoard; fileIndex: Map<string, string>; warnings: FormatWarning[] };

planDirectoryWrites()

Write path: plans the workspace writes for a board edit. Allocates stable filenames for new cards, identifies documents to delete, and serializes the updated manifest. A move/reorder-only change produces no cardWrites — only the manifest changes.

function planDirectoryWrites(
  prev: KanbanBoard | null,
  next: KanbanBoard,
  fileIndex: Map<string, string>,
  prevContents: Map<string, string>,
): {
  board: KanbanBoard;
  manifest: string;
  cardWrites: { filename: string; content: string }[];
  cardDeletes: string[];
  fileIndex: Map<string, string>;
};

Board Operations (unchanged)

All operations are immutable — they return a new KanbanBoard:

import {
  moveCard,
  moveCardToPosition,
  reorderCard,
  updateCard,
  addCard,
  removeCard,
  toggleColumnCollapse,
  getColumnCards,
} from "@takazudo/kanban-parser";
FunctionDescription
moveCard(board, cardId, newStatus)Move card to a new column
moveCardToPosition(board, cardId, newStatus, newOrder)Move card to specific position
reorderCard(board, cardId, newOrder)Reorder within same column
updateCard(board, cardId, updates, fileIndex?)Update card properties; directory callers pass the stable filename index for titleless fallbacks
addCard(board, title, status)Create a new card
removeCard(board, cardId)Delete a card
toggleColumnCollapse(board, columnId)Toggle column collapse
getColumnCards(board, columnId)Get cards in a column (sorted by order)

Utilities

  • countChecklist(body) — count - [x] and - [ ] items, returns { total, checked }

  • slugify(text) — convert to kebab-case slug (preserves non-ASCII)

  • uniqueSlug(title, existingIds) — generate unique slug with -2, -3 suffix

  • extractImages(markdown) — extract image URLs from ![alt](path) patterns


@takazudo/todo-board

React component library for a TODO checklist board. Displays items split into TODO / DONE sections with checkbox toggling, inline title editing, and an add-item input. Built on @takazudo/todo-parser for data types and @takazudo/ui-components for the inline edit field.

Exports

import {
  TodoBoard,
  TodoItemRow,
  TodoAddInput,
  InlineEditField,
} from "@takazudo/todo-board";

import type {
  TodoBoardProps,
  TodoItemRowProps,
  TodoAddInputProps,
  InlineEditFieldProps,
  TodoItem,
} from "@takazudo/todo-board";

TodoBoard

The main container component. Splits items into TODO and DONE sections, renders each as a TodoItemRow, and provides an add button.

Key props:

  • items: TodoItem[] — the full list (both todo and done)

  • onToggle: (itemId: string) => void — checkbox toggle

  • onAdd: (title: string) => void — new item creation

  • onRemove: (itemId: string) => void — item deletion

  • onUpdate: (itemId: string, updates) => void — inline title/body edit

TodoItemRow

A single checklist row with checkbox, title, and remove button. Supports inline editing via onUpdate.

TodoAddInput

Text input for adding new items. Shows on "+ Add" click, submits on Enter, cancels on Escape.

Peer dependencies: react >= 18, react-dom >= 18.


@takazudo/todo-parser

Pure markdown parser and serializer for TODO checklist data. Parses markdown files with type: todo frontmatter into structured TodoList objects. All operations are immutable. Zero dependencies.

Exports

import {
  parseTodoMarkdown,
  serializeTodoMarkdown,
  hasTodoFrontmatter,
  toggleItem,
  addItem,
  removeItem,
  updateItem,
  reorderItem,
  slugify,
  uniqueSlug,
} from "@takazudo/todo-parser";

import type { TodoItem, TodoList } from "@takazudo/todo-parser";

parseTodoMarkdown()

Parses a markdown string into a TodoList. The format uses YAML frontmatter for metadata and ## TODO / ## DONE sections with checkbox items:

const list = parseTodoMarkdown(`---
type: todo
title: Sprint Tasks
---

## TODO

- [ ] Write tests
  Some notes about testing
- [ ] Update docs

## DONE

- [x] Set up CI
`);
// list.items = [
//   { id: "write-tests", title: "Write tests", done: false, body: "Some notes about testing" },
//   { id: "update-docs", title: "Update docs", done: false, body: "" },
//   { id: "set-up-ci", title: "Set up CI", done: true, body: "" },
// ]

serializeTodoMarkdown()

Converts a TodoList back to markdown. Preserves the original section order and any extra sections.

hasTodoFrontmatter()

Returns true if the markdown content has type: todo in its frontmatter. Used to detect TODO files.

Operations

All operations are immutable — they return a new TodoList:

FunctionDescription
toggleItem(list, itemId)Toggle an item's done state
addItem(list, title)Add a new TODO item
removeItem(list, itemId)Remove an item
updateItem(list, itemId, updates)Update title and/or body (re-generates ID on title change)
reorderItem(list, itemId, newIndex)Move an item to a new position

Utilities

  • slugify(text) — convert to kebab-case slug (preserves non-ASCII characters)

  • uniqueSlug(title, existingIds) — generate unique slug with -2, -3 suffix


@takazudo/cloud-crypto

End-to-end encryption primitives for cloud sync. Uses the Web Crypto API (PBKDF2, AES-256-GCM, HMAC-SHA256) — no external cryptography dependencies.

Exports

import {
  deriveKeys,
  deriveVerificationHash,
  encryptContent,
  decryptContent,
  computeHmac,
  computeHmacRaw,
  encryptPath,
  decryptPath,
  serializeBlob,
  deserializeBlob,
  BLOB_VERSION,
  toBase64Url,
  fromBase64Url,
  concatBuffers,
  hexEncode,
  hexDecode,
} from "@takazudo/cloud-crypto";

import type {
  DerivedKeys,
  KeyDerivationParams,
  EncryptedPayload,
} from "@takazudo/cloud-crypto";

Key Derivation

deriveKeys() derives three independent sub-keys from a single password using PBKDF2 (600,000 iterations by default):

const keys = await deriveKeys({ password: "user-password" });
// keys.encryptionKey — AES-256-GCM for file content
// keys.hmacKey       — HMAC-SHA256 for integrity
// keys.pathKey       — AES-256-GCM for path encryption
// keys.salt          — 32-byte salt (generated or provided)

deriveVerificationHash() produces a hex hash for password verification without exposing encryption keys. It uses a distinct salt prefix ("verify:") so compromising this hash does not leak the encryption keys.

const hash = await deriveVerificationHash("user-password", keys.salt);
// hex string for server-side password verification

Encrypt / Decrypt

// Encrypt file content
const payload = await encryptContent(keys.encryptionKey, "plaintext");
// payload.ciphertext: ArrayBuffer, payload.iv: Uint8Array (12 bytes)

// Decrypt
const plainBuffer = await decryptContent(keys.encryptionKey, payload);
const text = new TextDecoder().decode(plainBuffer);

Path Encryption

File paths are encrypted separately so the server cannot see filenames:

const encrypted = await encryptPath(keys.pathKey, "notes/hello.md");
const original = await decryptPath(keys.pathKey, encrypted);

Blob Serialization

serializeBlob() / deserializeBlob() pack an encrypted payload (IV + ciphertext) into a versioned binary format for storage and transport.

HMAC

computeHmac() returns a hex-encoded HMAC-SHA256 digest. computeHmacRaw() returns the raw ArrayBuffer.


@takazudo/cloud-sync

Client-side cloud sync engine with WebSocket real-time updates, local change tracking, offline queuing, and conflict resolution.

Exports

import {
  CloudSyncClient,
  CloudSyncError,
  ChangeTracker,
  OfflineQueue,
  WsManager,
  detectConflictType,
  resolveCloudConflict,
  autoResolveConflicts,
} from "@takazudo/cloud-sync";

import type {
  CloudSyncOptions,
  ChangeNotification,
  TrackedChange,
  QueuedChange,
  CloudConflictType,
  CloudConflictInfo,
  CloudConflictResolution,
  WsConnectionState,
  WsManagerOptions,
} from "@takazudo/cloud-sync";

CloudSyncClient

HTTP client for the cloud sync REST API. Handles workspace operations, push/pull sync, file versioning, and device management.

const client = new CloudSyncClient({
  serverUrl: "https://sync.example.com",
  token: "auth-token",
  workspaceId: "workspace-id",
  deviceId: "device-id",
});

ChangeTracker

Tracks local file changes (upsert/delete) in memory. Changes are accumulated until a sync push is triggered.

const tracker = new ChangeTracker();
tracker.track("notes/hello.md", "upsert", "sha256-hash");
const pending = tracker.getPending(); // TrackedChange[]
tracker.acknowledge(["notes/hello.md"]); // clear after successful push

OfflineQueue

Persists queued changes to localStorage so they survive app restarts. Provides compaction (deduplicating by path) and batch flush.

const queue = new OfflineQueue("workspace-id", localStorage);
queue.enqueue({ path: "notes/hello.md", action: "upsert", contentHash: "..." });
const result = await queue.flush(pushFn);
// result.accepted: string[], result.failed: QueuedChange[]

WsManager

WebSocket connection manager with automatic reconnection (exponential backoff), ping/pong keep-alive, and cursor-based change streaming.

const ws = new WsManager({
  url: "wss://sync.example.com/ws",
  token: "auth-token",
  deviceId: "device-id",
  cursor: 0,
  onChanges: (changes, cursor) => { /* real-time updates */ },
  onConnected: () => { /* ... */ },
  onDisconnected: () => { /* ... */ },
  onError: (err) => { /* ... */ },
});

Conflict Resolution

  • detectConflictType(info) — Classifies a conflict as "concurrent-edit", "edit-delete", or "delete-edit"

  • resolveCloudConflict(info, strategy) — Resolves a single conflict with the given strategy

  • autoResolveConflicts(conflicts, strategy) — Batch-resolves conflicts using a default strategy


@takazudo/sync-logger

Structured logging utility for sync operations. Provides a module-scoped logger with configurable log levels and pluggable handlers. Used by the sync subsystem (cloud-sync, sync-client, backend-bridge) for consistent, filterable logging.

Exports

import {
  createSyncLogger,
  setSyncLogHandler,
  setSyncLogLevel,
} from "@takazudo/sync-logger";

import {
  consoleLogHandler,
  jsonLogHandler,
  createCallbackHandler,
} from "@takazudo/sync-logger";

import type {
  LogHandler,
  LogLevel,
  SyncLogEntry,
  SyncLogger,
} from "@takazudo/sync-logger";

Types

type LogLevel = "debug" | "info" | "warn" | "error";

interface SyncLogEntry {
  timestamp: string;
  level: LogLevel;
  module: string;
  message: string;
  data?: Record<string, unknown>;
}

type LogHandler = (entry: SyncLogEntry) => void;

interface SyncLogger {
  debug(message: string, data?: Record<string, unknown>): void;
  info(message: string, data?: Record<string, unknown>): void;
  warn(message: string, data?: Record<string, unknown>): void;
  error(message: string, data?: Record<string, unknown>): void;
}

Usage

import { createSyncLogger, setSyncLogHandler, setSyncLogLevel } from "@takazudo/sync-logger";
import { consoleLogHandler } from "@takazudo/sync-logger";

// Configure global handler and minimum level
setSyncLogHandler(consoleLogHandler);
setSyncLogLevel("debug");

// Create a module-scoped logger
const log = createSyncLogger("cloud-sync");
log.info("Sync started", { workspaceId: "abc123" });
log.error("Push failed", { path: "notes/hello.md", status: 500 });

Built-in Handlers

HandlerDescription
consoleLogHandlerFormatted output to console.log / console.warn / console.error with timestamp, level, and module prefix
jsonLogHandlerOutputs each entry as a single-line JSON string via console.log
createCallbackHandler(fn)Wraps any (entry: SyncLogEntry) => void callback as a LogHandler

Design

  • Global singleton — one handler and one minimum level for the entire app. Call setSyncLogHandler() once at app startup.

  • Module-scoped loggerscreateSyncLogger(module) returns a logger that tags every entry with the module name.

  • Level filtering — entries below setSyncLogLevel() threshold are silently dropped.

  • Error-safe — if the handler throws, the error is swallowed so logging never crashes the caller.


@takazudo/css-playground

Interactive CSS development tool for designing and testing UI patterns. Built with React 19, React Router, Tailwind CSS 4, and Vite. Runs on port 5380.

This is a development-only tool and is not shipped with the app.


@takazudo/directive-registry

Registry of MDX directive definitions and their attribute schemas. Centralizes directive metadata so authoring help dialogs, validation, and the renderer all share a single source of truth.

Exports

import {
  directives,
  imageAttrs,
  validateDirectiveAttrs,
  validateImageAttrs,
  validateAttrsAgainstSchema,
  suggestDirectiveName,
} from "@takazudo/directive-registry";

import type { AttrSchema, DirectiveDef, ValidationResult } from "@takazudo/directive-registry";

directives

Array of all registered DirectiveDef objects. Each entry carries a name, kind ("container" or "leaf"), an attrs schema, a description, an exampleSrc, and a category.

Built-in categories: "admonition" (note, tip, info, warning, danger) and "layout" (steps, card, card-grid, image, video, …).

validateDirectiveAttrs()

Validates an attribute map against a named directive's schema:

function validateDirectiveAttrs(
  name: string,
  attrs: Record<string, unknown>,
): ValidationResult;

Returns { ok: true } on success or { ok: false, errors: string[] } on failure.

suggestDirectiveName()

Returns the closest registered directive name to the given input (Levenshtein distance), or null if nothing is close enough. Used to surface "did you mean :::note?" messages.


@takazudo/frameset

Split-panel frame layout system for the writing app. Manages a tree of horizontal/vertical splits, frame chrome (header, collapse/zoom/close controls), and the active-frame context shared across panes.

Primary exports

import {
  Frameset,
  FrameChrome,
  LeafRenderer,
  ActiveFrameProvider,
  useActiveFrame,
  useFramesetCommands,
  createFrameStateMachine,
  framesetsEquivalent,
  findMatchingSavedFrameset,
} from "@takazudo/frameset";

import type {
  FramesetProps,
  FramesetHandle,
  FrameChromeProps,
  FrameId,
  ProviderMeta,
  SavedFrameset,
} from "@takazudo/frameset";

Frameset

The root split-panel container. Takes a FramesetTree from @takazudo/view-provider and renders the recursive split/leaf layout with resize dividers. Exposes a FramesetHandle ref for imperative focus control.

FrameChrome

Per-leaf chrome wrapper (header bar, split/close/zoom/collapse buttons, active-frame highlight overlay). Consumers wrap each leaf's content in FrameChrome.

useFramesetCommands()

Hook that wires window-level events (FRAME_SPLIT_RIGHT_EVENT, FRAME_CLOSE_EVENT, etc.) to dispatch tree mutations on the active leaf. Called once at the app level.

createFrameStateMachine()

Returns a mutable frameset state machine that owns the FramesetTree and exposes typed mutation methods (split, close, zoom, collapse, replaceProvider, …). Used by the write-page to drive the persisted frameset.

Persistence helpers

framesetsEquivalent(a, b) — pure comparison of two saved framesets by their canonical content signature. findMatchingSavedFrameset(tree, saved[]) — finds the best-matching saved frameset from a list, used by the restore hook.

Dependencies: @takazudo/view-provider, @takazudo/ui-components, @takazudo/app-defaults, @takazudo/color-themes. Peer dependencies: react >= 19, react-dom >= 19.


@takazudo/view-provider

Core type system and tree utilities for the frameset architecture. Defines the ViewProvider interface, FramesetTree shape, and pure tree mutation functions consumed by @takazudo/frameset and the writing app.

Exports

import {
  createProviderRegistry,
  makeMarkdownFileProvider,
  createConsumesBus,
  splitLeaf,
  removeLeaf,
  replaceLeafProvider,
  findLeaf,
  validateFramesetTree,
  FRAME_STATES,
  SCROLL_SYNC_CHANNEL,
} from "@takazudo/view-provider";

import type {
  ViewProvider,
  FramesetTree,
  LeafNode,
  SplitNode,
  FrameState,
  FrameContext,
  ProviderRegistry,
  LayoutDef,
  JsonValue,
} from "@takazudo/view-provider";

ViewProvider

The interface every pane implements. Key methods: serialize(props) / deserialize(blob) for persistence, renderLeaf(props, context) to render content, optional renderSettings / canClose / acceptsFileDrop. Each provider declares a stable id string.

createProviderRegistry()

Creates the runtime registry that maps provider id strings to ViewProvider instances. The frameset shell calls registry.renderLeaf for each leaf node.

makeMarkdownFileProvider()

Factory that creates a full ViewProvider from a config object — handles file open/save, debounced writes, layout switching, and file-drop. Used by the kanban, mindmap, and todo providers to avoid duplicating the same ~100-line hook pattern.

createConsumesBus()

Pub/sub bus for cross-frame data sharing (scroll sync, active-file broadcast). Producers call bus.publish(key, value); consumers call bus.subscribe(key, handler).

Tree utilities

splitLeaf, removeLeaf, replaceLeafProvider, findLeaf, zoomLeaf, collapseLeaf, restoreLeaf, setRatio, validateFramesetTree — pure functions that return a new FramesetTree without mutating the input.


@takazudo/settings-sections

Shared AppSettings editor sections used by both the writing app's settings dialog and the ROOT's generate-child wizard step. Each section takes { value: AppSettings, onChange } so the same component works in both contexts.

Exports

import {
  GeneralSettings,
  EditorSettings,
  StylesSettings,
  SettingInputRow,
  applyDisplayScale,
  getDisplayScaleLabel,
  VALID_DISPLAY_SCALES,
  loadSystemFonts,
  inputCls,
  selectCls,
  labelCls,
} from "@takazudo/settings-sections";

import type {
  SettingInputRowProps,
  DisplayScale,
} from "@takazudo/settings-sections";

Section components

  • GeneralSettings — app name, display scale, Spotlight search toggle, and other general preferences

  • EditorSettings — font family, font size, line height, tab size, vim mode, word wrap

  • StylesSettings — color ramp editing plus semantic-token ramp-reference mapping

These sections are consumed by both the writing app's Settings dialog and the generate-child flow in ROOT. Writing-app-only sections (AI, sync, shortcuts, iOS) are not in this package.

applyDisplayScale()

Applies a DisplayScale multiplier to the document root font size. Called at app startup and on settings change. Valid scales: VALID_DISPLAY_SCALES = [0.8, 0.9, 1.0, 1.1, 1.25, 1.5].


@takazudo/inline-command-skills

Skill loader, parser, resolver, and CodeMirror autocomplete provider for the @@ inline AI command. Skills are markdown files with YAML frontmatter discovered from two directories at runtime.

Exports

import {
  parseSkillFile,
  resolveSkill,
  createSkillLoader,
  skillAutocomplete,
  panelSkillAutocomplete,
  WORKSPACE_SKILLS_DIR_REL,
} from "@takazudo/inline-command-skills";

import type {
  Skill,
  SkillRegistry,
  SkillResolution,
  SkillLoader,
  LoaderConfig,
  LoaderEvent,
  SkillMode,
  SkillSourceLayer,
} from "@takazudo/inline-command-skills";

createSkillLoader()

Creates a hot-reloading skill loader. Scans a workspace-scoped dir (<workspace>/.zudotext/skills/) and a user-scoped dir (~/.config/zudotext/<appname>/skills/). On name collision the workspace file wins; the user copy is reported as shadowed.

const loader = createSkillLoader({ workspaceDir, userDir, fs });
const registry = await loader.load();
const unsub = loader.watch((event: LoaderEvent) => { /* added/updated/removed/shadowed */ });

parseSkillFile()

Parses a single skill file. Returns ParseResult — either { ok: true, skill: Skill } or { ok: false, reason }. Validation failures are soft — callers log and skip, never throw.

resolveSkill()

Extracts the /skill-name token and trailing args from an inline command string:

resolveSkill("/translate-en hello world", registry);
// { skill: Skill, args: "hello world" }

skillAutocomplete / panelSkillAutocomplete

Standard @codemirror/autocomplete CompletionSource functions. Pass into autocompletion({ override: [...] }) to show skill name completions as the user types @@ /....


@takazudo/cm-inline-command

CodeMirror extension that drives the @@ inline AI command capture surface. Detects the @@ trigger on the current line, enters capture mode, renders a status pill, and dispatches keymap events to consumer-provided handlers.

Exports

import {
  inlineCommandExtension,
  inlineCommandStateField,
  enterCaptureEffect,
  exitCaptureEffect,
  setCommandTextEffect,
  setComposingEffect,
  composingField,
  captureStateChanged,
  detectTriggerOnLine,
  isInFencedCode,
} from "@takazudo/cm-inline-command";

import type {
  InlineCommandOptions,
  InlineCommandState,
  SubmitPayload,
  CancelPayload,
  TriggerMatch,
} from "@takazudo/cm-inline-command";

inlineCommandExtension()

Main entry point. Returns a Extension[] array to add to a CodeMirror editor:

const ext = inlineCommandExtension({
  insertShortcut: "Mod-j",         // shortcut to insert "@@ " at cursor
  submitInline: (payload) => { /* Tab gesture: bake inline */ },
  submitPanel: (payload) => { /* Mod-Enter gesture: open panel */ },
  cancel: (payload) => { /* Escape: cancel capture */ },
});

When @@ is present at the cursor line, the extension enters capture mode: a cm-inline-command-line CSS class marks the line and an end-of-line status pill ("Tab to insert · ⌘↵ panel · Esc to cancel") appears. Tab, Mod-Enter, and Escape are intercepted only while capture is active.

inlineCommandStateField

StateField<InlineCommandState> — holds { active, lineFrom, command, requestId }. Read via view.state.field(inlineCommandStateField). The requestId monotonically increments on each fresh @@ capture so downstream stream handlers can discard stale responses.

Peer dependencies: @codemirror/state >= 6, @codemirror/view >= 6.


@takazudo/cm-ghost-text

CodeMirror extension for inline ghost-text (AI suggestion) display with Tab-to-accept. Renders the suggestion as a non-editable widget at the cursor position and intercepts Tab to apply it.

Exports

import {
  ghostTextExtension,
  ghostTextField,
  setGhostEffect,
  setComposingEffect,
  composingField,
  ghostAcceptAnnotation,
  dispatchSetSuggestion,
  dispatchClearSuggestion,
  trimDuplicatePrefix,
} from "@takazudo/cm-ghost-text";

ghostTextExtension()

Returns a Extension array. Accepts one option: acceptKey (CodeMirror key string, e.g. "Tab").

const ext = ghostTextExtension({ acceptKey: "Tab" });

dispatchSetSuggestion() / dispatchClearSuggestion()

Convenience dispatch helpers for the consuming driver:

dispatchSetSuggestion(view, "suggested completion text", requestId);
dispatchClearSuggestion(view);

ghostTextField

StateField<{ text: string | null; requestId: number }>. The requestId lets the stream driver discard suggestions for stale requests. Read via view.state.field(ghostTextField).

trimDuplicatePrefix()

Utility that removes the longest common prefix between the current document line and an AI suggestion string, so the ghost text only shows the novel suffix.

Peer dependencies: @codemirror/state >= 6, @codemirror/view >= 6.


@takazudo/cm-search-highlight

CodeMirror extension for the two-tier search highlight layer (normal matches + one active match). Receives pre-computed ranges from the cross-pane search orchestrator and renders them as find-match / find-match-active decorations.

Exports

import {
  searchHighlightExtension,
  searchHighlightField,
  setSearchHighlights,
  setSearchHighlightsEffect,
  searchScrollbarMarkersExtension,
} from "@takazudo/cm-search-highlight";

import type { SearchHighlightRange } from "@takazudo/cm-search-highlight";

searchHighlightExtension()

Returns a Extension[] array. Adds searchHighlightField and the decoration provider. Does not register any key bindings — the orchestrator owns the Cmd+F shortcut at the React level.

setSearchHighlights()

Factory for setSearchHighlightsEffect. Pass the result to view.dispatch:

view.dispatch({ effects: setSearchHighlights(ranges) });

ranges is SearchHighlightRange[] — each item has from, to (document offsets), and active: boolean. Pass an empty array to clear all highlights.

CSS styles for find-match / find-match-active come from @takazudo/find-in-page/styles.css.

Peer dependencies: @codemirror/state >= 6, @codemirror/view >= 6.


Orchestrator for the bilateral search highlight feature. Given an editor EditorView and a preview HTMLElement that share the same markdown source, runs a query against both panes, pairs hits by source line, and keeps next/prev navigation in lockstep.

Exports

import {
  createCrossPaneSearch,
  findEditorHits,
  findInnermostAnchors,
  wrapPreviewMatchesInAnchor,
  clearPreviewMarks,
  pairHitsByLine,
} from "@takazudo/cross-pane-search";

import type {
  CrossPaneSearch,
  CrossPaneSearchOptions,
  SearchState,
  EditorHit,
  PreviewMark,
  CanonicalHit,
} from "@takazudo/cross-pane-search";

createCrossPaneSearch()

Main entry point. Returns a CrossPaneSearch controller:

const search = createCrossPaneSearch({ view, previewContainer });
search.apply(query);       // run search, highlight both panes
search.next();             // advance active hit
search.prev();             // reverse active hit
search.clear();            // remove all highlights
search.destroy();          // teardown subscriptions

The orchestrator uses the innermost-anchor selector ([data-source-line="N"]:not(:has([data-source-line="N"]))) from @takazudo/remark-source-line to locate preview anchors, then walks text nodes to place <mark> wrappers. Editor decorations are dispatched via @takazudo/cm-search-highlight.

Dependencies: @takazudo/cm-search-highlight. Peer dependencies: @codemirror/state >= 6, @codemirror/view >= 6.


@takazudo/timeline-board

React component library for a virtualized timeline view of drafts. Displays drafts as vertically or horizontally scrolling cards with a minimap, keyboard navigation, and a detail panel.

Exports

import {
  TimelineBoard,
  TimelineCard,
  TimelineMinimap,
  TimelineDetailPanel,
  TimelineLayoutShell,
  useTimelineKeyboard,
} from "@takazudo/timeline-board";

import type {
  TimelineBoardProps,
  TimelineBoardToolbar,
  TimelineCardProps,
  TimelineDetailPanelProps,
  TimelineLayoutShellProps,
  UseTimelineKeyboardOptions,
} from "@takazudo/timeline-board";

TimelineBoard

Main container. Renders a virtualized list of draft cards (powered by @tanstack/react-virtual) in horizontal or vertical layout. Supports:

  • activeDraft / draftCount — draft index management

  • loadItemContent(n) — async loader for inactive card content

  • onFilesChangedSubscribe / onExternalActivateSubscribe — external event hooks

  • toolbar: TimelineBoardToolbar — controls for layout direction, card width, sort, and full-content toggle

TimelineLayoutShell

Wraps TimelineBoard with a collapsible TimelineDetailPanel. Manages the panel open/close state.

useTimelineKeyboard()

Hook for keyboard navigation. Handles arrow keys for next/prev draft, configurable key bindings.

Dependencies: @takazudo/ui-components, @takazudo/app-defaults, @takazudo/file-utils, @tanstack/react-virtual. Peer dependencies: react >= 18, react-dom >= 18.


@takazudo/frontmatter-schema

Frontmatter field schema definitions, parser, validator, and auto-field logic for markdown documents. Zero runtime dependencies.

Exports

import {
  parseSchema,
  validateFrontmatter,
  getDefaultSchema,
  applyAutoFields,
  readLegacyDate,
  LEGACY_FIELD_ALIASES,
  toIsoLocal,
  toDateOnly,
} from "@takazudo/frontmatter-schema";

import type {
  Schema,
  FieldDef,
  FieldType,
  StringFieldDef,
  NumberFieldDef,
  BooleanFieldDef,
  DateFieldDef,
  DatetimeFieldDef,
  EnumFieldDef,
  StringArrayFieldDef,
  ValidationError,
  ValidationResult,
  FrontmatterValue,
  FrontmatterObject,
  AutoFieldEvent,
} from "@takazudo/frontmatter-schema";

parseSchema()

Parses a YAML/JSON schema definition into a typed Schema object with fields: FieldDef[].

validateFrontmatter()

Validates a frontmatter object against a schema:

const result = validateFrontmatter(frontmatter, schema);
// result: { ok: true } | { ok: false, errors: ValidationError[] }

applyAutoFields()

Applies auto: "on-create" / auto: "on-save" fields (e.g. created_at, updated_at). Takes an AutoFieldEvent ("create" | "save") and the existing frontmatter object; returns an updated copy.

getDefaultSchema()

Returns the built-in default schema (date, title, tags fields) used when no per-directory .schema.yaml is found.


@takazudo/frontmatter-ui

React field renderer components for editing frontmatter values. Each component receives a typed FieldDef and a current value, and fires onChange on edit.

Exports

import {
  StringField,
  NumberField,
  BooleanField,
  DateField,
  DateTimeField,
  EnumField,
  TagsField,
  StringArrayField,
  FrontmatterPanel,
  formatRelativeTime,
} from "@takazudo/frontmatter-ui";

import type {
  StringFieldProps,
  NumberFieldProps,
  BooleanFieldProps,
  DateFieldProps,
  DateTimeFieldProps,
  EnumFieldProps,
  TagsFieldProps,
  StringArrayFieldProps,
  FrontmatterPanelProps,
} from "@takazudo/frontmatter-ui";

Field components

ComponentInput typeNotes
StringFieldText inputSingle-line string value
NumberFieldNumber inputNumeric value
BooleanFieldCheckboxBoolean toggle
DateFieldDate pickerISO date (YYYY-MM-DD)
DateTimeFieldDatetime inputISO datetime; formatRelativeTime() formats for display
EnumFieldSelect dropdownValues from EnumFieldDef.values
TagsFieldTag chip inputComma/Enter delimited tags (string array)
StringArrayFieldMulti-value inputGeneric string array

FrontmatterPanel

Container that renders all fields for a document's frontmatter, given a Schema and a current FrontmatterObject. Wires each field component to the appropriate FieldDef and calls onChange(updatedFrontmatter) on any edit.

Peer dependencies: react >= 18, react-dom >= 18.


@takazudo/gfm-table

Strict GFM (GitHub Flavored Markdown) table parser and emitter. Refuses malformed input with a typed error code rather than silently producing corrupt output.

Exports

import { parse, emit, makeEmpty, addRowAt, addColAt, removeRowAt, removeColAt, setHeader, setCell, setAlignment } from "@takazudo/gfm-table";

import type { TableData, Alignment, ParseResult, ParseError, ParseOk, ContentWarning } from "@takazudo/gfm-table";

parse()

function parse(source: string): ParseResult;

Returns { ok: true, data: TableData, warnings: ContentWarning[] } on success. On failure returns a typed error with one of four reason values: "empty", "line-not-row", "missing-separator-row", "column-count-mismatch".

ContentWarning covers content that parses but renders unexpectedly: "list-in-cell", "code-fence-in-cell".

emit()

Serializes a TableData back to GFM markdown. Escapes embedded pipe characters.

Table mutation helpers

All operations are immutable — they return a new TableData:

FunctionDescription
makeEmpty(rows?, cols?)Create an empty table
addRowAt(data, index)Insert a blank row at the given index
addColAt(data, index)Insert a blank column at the given index
removeRowAt(data, index)Delete a row
removeColAt(data, index)Delete a column (no-op if only one column)
setHeader(data, col, value)Update a header cell
setCell(data, row, col, value)Update a body cell
setAlignment(data, col, align)Set column alignment ("left", "center", "right", or null)

Zero dependencies.


@takazudo/design-token-lint

Linter that checks Tailwind CSS class names against the project design system tokens. Flags raw spacing/color values that should use semantic tokens instead.

Exports

import {
  checkClass,
  checkClassWithConfig,
  setConfig,
  getConfig,
  extractClasses,
  lintFile,
  lintContent,
  loadConfig,
  compileConfig,
  compilePattern,
  lintCssZIndex,
  lintTsxZIndex,
  DEFAULT_CONFIG,
} from "@takazudo/design-token-lint";

import type {
  Violation,
  ExtractedClass,
  ExtractorOptions,
  LintResult,
  LintConfig,
  CompiledConfig,
  CompiledRule,
  ZIndexViolation,
} from "@takazudo/design-token-lint";

checkClass()

Checks a single Tailwind class name against the active config. Returns a Violation (with className and reason) or null if the class is permitted.

lintContent() / lintFile()

Scan a JSX/TSX string or file path for class violations:

const result: LintResult = lintContent(source, config);
// result.violations: Violation[]
// result.warnings: string[]

lintFile(filePath, config) reads the file and delegates to lintContent.

loadConfig() / compileConfig()

loadConfig(dir) searches for a design-token-lint.config.json in the given directory and returns a LintConfig. compileConfig(config) converts pattern strings (with {n}, {color}, {shade} placeholders) into regex-based CompiledRule objects.

z-index lint

lintCssZIndex(cssSource) / lintTsxZIndex(tsxSource) — specialized linters that flag raw z-index integer values not coming from the design token scale.


@takazudo/remark-source-line

remark plugin that stamps data-source-line="<n>" on every block-level mdast node. The attribute is used by the cross-pane search orchestrator (@takazudo/cross-pane-search) to locate the preview DOM element corresponding to a given editor source line.

Usage

import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import remarkSourceLine from "@takazudo/remark-source-line";

const file = await unified()
  .use(remarkParse)
  .use(remarkSourceLine)
  .use(remarkRehype)
  .process(markdownSource);

The default export is the remark plugin. No named exports.

Stamped node types

Stamps every block-level mdast node: paragraph, heading, code, blockquote, list, listItem, table, tableRow, tableCell, thematicBreak, footnoteDefinition, containerDirective, leafDirective. Inline nodes are not stamped — they inherit the block ancestor's line via DOM ancestry.

Multi-line blocks use the start line (position.start.line, 1-based).

Query-time contract (innermost-anchor selector)

Because nested blocks both carry a stamp, the cross-pane search orchestrator must query with:

[data-source-line="N"]:not(:has([data-source-line="N"]))

to select the innermost element for line N and avoid false multi-matches. GFM table cells are an exception — sibling cells on the same row share the same line number and both match; the orchestrator handles this by walking all candidates in document order.

Zero dependencies (only unist-util-visit as a dev/peer). Works in any unified pipeline.

Shared discovery primitives

@takazudo/ui-components exports EmptyState for illustrated guidance, primary/secondary actions and optional Manual access. Its callback receives manualHref verbatim. Renderer callers use the central openManual helper with a relative manual path, rather than resolving the URL twice.

@takazudo/frameset exports FrameHeaderActionGroup and FrameHeaderActionButton; @takazudo/view-provider exposes the optional HeaderActions slot and registry capability metadata. See Frame Component Contract.

The Help catalog and Dashboard widget registry are renderer data, not separate workspace packages. Add feature definitions in tauri-app/renderer/data/help-catalog/; the Help dialog, empty-frame picker and discovery widgets reuse those definitions and illustrations.