zudo-text

検索したい単語を入力

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

Pins API

The Pins API manages content-directory pins addressed by array index (pinIndex) — distinct from headerLeftPins (the toolbar layout-template pins, see Pins). The commands below (pins_list, pins_read, …) are unchanged across backends, but path resolution differs by mode (epic #4204, cloud-primary pivot):

  • Cloud workspace mode (the default, desktop and web) — every pin is addressed at the fixed workspace-relative path pins/<pinIndex> (D4's pins namespace). AppSettings.pins (the path/title config below) is not consulted — there is no way to point a pin at an arbitrary directory, since cloud-primary has no concept of "arbitrary local directory" outside the workspace (only External File Editor addresses local disk, D5).

  • Local engine (tauri-app/core/src/pins.rs, http_server.rs's /api/pins/* routes) — used only by pnpm dev:rest's non-workspace fallback. This is the pre-pivot implementation described below: it reads AppSettings.pins from disk and resolves path against the local project root. It is slated for removal in S19 (#4223) once REST's non-workspace fallback goes away.

Note

If you are integrating against the cloud-workspace behavior, skip straight to the Commands section — the request/response shapes are identical; only the "Pin Configuration" section below is local-engine-only.

Data Structures

PinEntry

Returned by pins_list. Represents a file or directory in the pin tree.

interface PinEntry {
  name: string;              // Filename or directory name
  type: 'file' | 'directory';
  path: string;              // Relative path from pin root (e.g. "subdir/file.md")
  title: string;             // From frontmatter `name` or `title`, or filename stem
  description: string;       // From frontmatter `description`, or empty string
  modifiedAt: string;        // ISO 8601 timestamp (files only, empty for directories)
  children?: PinEntry[];     // Populated only in deep (list_pin_tree) mode; absent in lazy mode
  hasChildren?: boolean;     // Set for directories in lazy mode — indicates whether a directory
                             // has at least one visible child; absent for file entries
}

The title field prefers the frontmatter name key over title. Directories use their directory name as the title.

Pin Configuration (local engine only)

This section describes the tauri-app/core/src/pins.rs local engine's reading of .zudotext.settings.json's pins array — the pnpm dev:rest non-workspace fallback only (see the note at the top of this page). Cloud workspace mode ignores this array entirely and always addresses pins/<pinIndex> inside the workspace.

AppSettings.pins is a typed PinConfigEntry[] (@takazudo/app-defaults, epic #4204 D4):

{
  "pins": [
    { "path": ".zudotext/skills", "title": "Skills" },
    { "path": "notes", "title": "Notes", "type": "directory" }
  ]
}

If no pins are configured, the default pin { "path": "pins", "title": "Pins", "type": "directory" } is used (defined in @takazudo/app-defaults).

Path resolution (local engine):

  • path must be workspace-relative POSIX — no leading /, no .. segment (validateSettings drops any entry that fails this check, falling back to the default pin if none remain). This was relaxed pre-pivot to also accept local absolute paths (/absolute/path/to/docs, for pointing a pin at an arbitrary directory outside the project root via the native file picker); that capability has no equivalent under cloud-primary (D4/D5 — External File Editor is the only local-file provider) and is no longer accepted for workspace pins.

  • The local engine resolves path relative to the project root, with traversal checking (safe_path) — paths that escape the project root are rejected at read/write time regardless of what validateSettings already filtered.

Commands

pins_list

List files and directories in a pin as a tree structure.

const entries = await invoke<PinEntry[]>('pins_list', {
  pinIndex: 0,
});
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array

Returns: Result<PinEntry[], string> — the top-level entries only (one level deep).

Behavior:

  • Lists only the immediate children of the pin root — does not recurse

  • For directory entries, sets hasChildren: true/false to indicate whether the directory has visible children; does not populate children

  • Only includes .md files and directories; skips dotfiles and non-markdown files

  • Sorts: directories first, then alphabetically by name

  • For file-type pins, returns a single-item list for the file itself

  • Use pins_list_children to lazily expand individual directories

pins_read

Read the content of a file within a pin directory.

const content = await invoke<string | null>('pins_read', {
  pinIndex: 0,
  entryPath: 'subdir/file.md',
});
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array
entryPathstringRelative path within the pin directory

Returns: Result<string | null, string> — file content, or null if the file doesn't exist.

Security: The entryPath is validated through safe_path() to prevent directory traversal.

pins_write

Write content to a file within a pin directory.

const success = await invoke<boolean>('pins_write', {
  pinIndex: 0,
  entryPath: 'file.md',
  content: '---\ntitle: Updated\n---\nNew content',
});
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array
entryPathstringRelative path within the pin directory
contentstringFull file content

Returns: Result<boolean, string>true on success.

Behavior: Returns an error if the parent directory doesn't exist. Does not create intermediate directories.

pins_delete

Delete a file within a pin directory.

const success = await invoke<boolean>('pins_delete', {
  pinIndex: 0,
  entryPath: 'old-file.md',
});
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array
entryPathstringRelative path within the pin directory

Returns: Result<boolean, string>true on success (including if the file was already missing).

pins_list_children

Fetch the immediate children of a directory within a pin (lazy expansion).

const entries = await invoke<PinEntry[]>('pins_list_children', {
  pinIndex: 0,
  dirPath: 'subdir',
});
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array
dirPathstringRelative path from the pin root to the directory to expand

Returns: Result<PinEntry[], string> — one level of entries with hasChildren flags, same shape as pins_list.

Behavior: Called by the frontend when the user expands a directory node. The dirPath is validated against safe_path() so it cannot escape the pin root. Returns an error for file-type pins.

pins_create_file

Create a new empty file in a directory-type pin, using a timestamp-based filename.

const filename = await invoke<string>('pins_create_file', {
  pinIndex: 0,
});
// filename: "20250308143045-new.md"
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array

Returns: Result<string, string> — the generated filename on success.

Behavior: Creates the pin directory if it doesn't exist. Returns an error for file-type pins.

pins_write_new_file

Write a new file into a directory-type pin. Fails if the file already exists ("write-once / no-overwrite" semantics).

const filename = await invoke<string>('pins_write_new_file', {
  pinIndex: 0,
  filename: 'my-note.md',
  content: '---\ntitle: My Note\n---\nContent',
});
ParameterTypeDescription
pinIndexnumberZero-based index into the pins settings array
filenamestringThe filename to create
contentstringFull file content

Returns: Result<string, string> — the filename on success.

Typed errors (stable string prefixes):

Error prefixMeaning
"collision"File already exists
"file-pin-not-supported"Pin is a file-type pin
"InvalidFilename: ..."Empty, contains path separators, or .. in name
"io: ..."Disk or permission error

Watcher Commands

Pin files live in the workspace in cloud mode, so there is no OS file to watch — bridge.pins.watchFile / unwatchFile / onFileChanged are no-ops there (and have no renderer consumer; they predate the frameset pin model). A caller that needs to observe pin-file changes under cloud workspace mode should subscribe to bridge.workspaceFiles.subscribe with a pins/ prefix instead, which reports local and remote changes alike. The commands below describe the local-engine (Tauri desktop invoke) behavior.

pins_watch_file

Start watching a single pin file for external modifications.

const success = await invoke<boolean>('pins_watch_file', {
  pinIndex: 0,
  entryPath: 'notes/daily.md',
});
ParameterTypeDescription
pinIndexnumberZero-based index (reserved for future use)
entryPathstringRelative path from project root to watch

Returns: Result<boolean, string>true on success.

Behavior:

  • Stops any existing pin watcher before starting a new one (only one pin file can be watched at a time)

  • Returns an error if the file doesn't exist

  • See the File Watchers page for debounce and mtime details

pins_unwatch_file

Stop watching the current pin file.

const success = await invoke<boolean>('pins_unwatch_file');

Returns: Result<boolean, string>true on success.

Event

pins:fileChanged

Emitted when the watched pin file is modified externally.

await listen<{ entryPath: string }>('pins:fileChanged', (event) => {
  console.log('Pin file changed:', event.payload.entryPath);
});
Payload fieldTypeDescription
entryPathstringThe relative path that was passed to pins_watch_file