Frame Components Reference
Every provider you can load into a Space — what each one shows, when to use it, and what the layout switcher does.
A frame component (also called a view provider) is the self-contained UI you load into any leaf of a Space. Every Space is divided into one or more leaves; each leaf holds exactly one frame component with its own toolbar, content area, and chrome. You can split a Space to run two frame components side by side, replace the one in a leaf through the frame menu's Empty frame action and the picker, and — for providers that support it — pop a leaf out into its own floating window.
The canonical entry point for choosing a frame component is the empty-leaf picker, which appears whenever a leaf is set to the Empty provider. Type to filter by name or description, then hover or use the arrow keys to explore a view before loading it. At wide and medium frame widths, a side pane explains what the selected view does, when to use it, its layouts, and its manual page. In narrow frames each compact row has a separate About [type] help button. Help opens only on that button’s activation, inside the same frame body; Back or Escape returns to the search and source row without loading a view. Click a row or press Enter to load it; views requiring configuration start with Set up. Wide-pane Tab cycles through search, the current row, and the Manual button when available. Compact rows use sibling native primary and help buttons.
Kanban Board, Mind Map, and TODO Board setup starts with a compact first step: the New and Existing choices appear before the catalog illustration and explanation. The question-mark help button opens an About [type] detail view; Back or Escape returns to the source setup with its values, instructions, and errors preserved. Other ProviderFirstStep callers keep the full explanatory layout unless they opt in.
If you are building a new provider rather than choosing one, see the Frame Component Contract for the implementer-facing contract.
Note Tray (id: core.inbox)
Note Tray toolbar plus a swappable editor / preview / timeline view. Each split pane is its own Note Tray.
The Note Tray is the primary writing and browsing surface — it is the same provider for both the Inbox and the Archives, pointed at different directories via its directory prop ("inbox" by default; the Archives pin sets it to "archives"). It combines the Note Tray toolbar (the numbered-note carousel at the top) with a text editor and an optional rendered-markdown preview. Every Note Tray leaf on the same directory works off the same shared content pool — you can split the Space and have one leaf in edit mode while the other shows the rendered preview, and they stay in sync. A sweep button lets you quickly clear out empty numbered slots.
The frame header exposes Pile View, Find similar and History, with General settings controlling their visibility. These actions use the owning tray and move into the header overflow at narrow widths. An empty editable Markdown editor shows three-line guidance: start writing, right-click a table for Edit sheet…, and type @@ for inline AI. Read-only editors and code-mode editors omit this placeholder.
The Note Tray toolbar shows a numbered pill per note plus a pill for each "named" (non-numeric) .md document in the directory — named documents render after all numbered pills and open in the same editor, outside the numbered slot pool. Archived notes are stored as numbered N.md documents (1.md, 2.md, … — no upper bound): archiving moves the active note into a fresh N.md slot in archives/ (collision-safe max+1). Workspace metadata supplies creation and modification times unless your workspace schema declares its own auto fields (see Auto-managed frontmatter fields).
Missing numbered slots (epic #6745). A numbered slot with no document — typically a middle note that was archived — stays a visible, selectable pill (dashed outline, accessible name Empty slot N). Pointer, Previous/Next, the numbered shortcuts and palette navigation all visit it instead of refusing or skipping it. In the text-editor layout the document area then shows "Slot N is empty" with an explicit Create note action and no editor; the hidden owner editor is read-only and unregistered, and document actions (archive, move, format, publish, header note actions) are withheld, so visiting, leaving, restoring or rendering a gap never writes. Create note (also on the region's context menu and the Create Note in Empty Slot palette command) runs createNoteAtSlot (tauri-), which calls the non-overwriting notes.createAt and then opens the slot in the frame that asked, focusing its editor. A present-but-empty note is distinct: it is listed, keeps the plain empty pill (Note N (empty)) and opens as an ordinary editable document. Pile View cards mirror the split — missing slots offer Create note, blank notes offer Open. Timeline and grid layouts have no missing-slot state and keep refusing gap activation. New note keeps its normal append policy and never fills a gap.
Note tabs and card state. In the text-editor layout collapsed notes are connected tabs: a 56px row with a 40px cap and a 24px identity box; the selected cap runs through the document-side clearance over a 2px baseline layer and fills with the document background, in all four bar placements. Timeline, Grid and Pile mark the selected/current card with the shared inset Solid Frame (SelectionFrame) and paint focus or the Pile browsing cursor as separate inner rings (SelectionInnerRing), so no state changes the card box. Sticker paper is always accent. See the l-design-system skill for the full contract.
Pile View opening target. The Pile View event carries the invoking frame; resolvePileOpeningTarget (tauri-) reads that Note Tray frame's own selection (directory plus number, missing slot or named file) and the dialog snapshots it at open. The same identity is the pinned current frame, the initial browsing cursor and the one initial scroll target. A layout-phase pass centers it with container-relative rect math and an instant scroll before the first card frame paints; later cursor moves, filter changes, archive/undo or backend pointer changes never recenter. A target that is not in the list marks nothing current, and the cursor falls back to the first visible card for browsing only. In All scope, keys are directory-qualified so the same number in another tray never matches.
When to use:
Any time you are writing, editing, or reviewing a note from scratch (Inbox) or browsing what you have already archived (Archives).
When you want a side-by-side edit-and-preview layout for a long document.
When you want to see your notes laid out as a scrollable timeline to review recent work.
When you want to search — open Pile View from any Note Tray leaf for a local (this tray) or global (every tray) search.
Layouts:
| Layout id | What it shows |
|---|---|
text-editor | The standard CodeMirror editor (or rendered-markdown preview). Default. |
timeline-vertical | Notes laid out as vertical cards — good for tall monitors. |
timeline-horizontal | Notes laid out as horizontal cards — good for wide monitors. |
Local files are separate: Note Tray directories belong to the cloud workspace. The External File Editor operates on local filesystem paths, so switching to it does not carry the Note Tray's workspace directory across.
Pop-out: Cannot be popped out into a separate window (per-window store; cross-window sync would require a separate layer).
Persistence: The directory prop, layout (text-editor / timeline-vertical / timeline-horizontal), view mode (edit / preview), and timeline sort/card-width preferences are each persisted per leaf. Two Note Tray leaves can independently be in edit mode and preview mode at the same time.
Singleton scope: Multiple OK — you can open as many Note Tray leaves as you like, each pointed at the same or a different directory; leaves on the same directory share content via a live-mirror store.
External File Editor (id: core.external-file-editor)
Open and edit files from anywhere on the filesystem. Supports multiple tabs.
The External File Editor is the only provider in the app that hosts a tab strip and embedded file tree. Each tab and the tree root hold local filesystem paths, independent of the cloud workspace. Edits are written back to disk with a short debounce. If a tab has unsaved changes the provider will veto closing the leaf until the changes are flushed.
When to use:
Editing a config file, script, or reference document that lives outside the workspace.
Keeping multiple files open in tabs within a single leaf, preserving the rest of the Space for other providers.
Browsing the active file's directory in the embedded tree and opening text files as tabs.
Pop-out: Cannot be popped out into a separate window.
Persistence: Open paths, active path, tree root, and collapsed state are persisted in the leaf's props blob, so the tab set and embedded tree survive reload. The tree width is the global clamped directoryView.lastSidebarWidthPx setting.
Singleton scope: Multiple OK — several ExternalFileEditor leaves can exist in the same Space, each with its own tab set.
Embedded file tree: A named edge tab on the seam expands or collapses the EFE file tree; there is no panel button in the toolbar or tab strip. The button reports aria-expanded, responds to pointer and native keyboard activation, has a visible focus outline, and changes its label between Collapse directory tree and Expand directory tree. The pane remains mounted while collapsed so its root, expansion, scroll, and session state survive. When a file is active, expanding an unrooted tree roots it at that file's directory. Clicking any text file calls the containing EFE session directly and adds the file as a new tab — this includes code and data files (.json, .tsx, .ts, .yaml, …) in addition to Markdown. The Edit|Preview toggle appears only for Markdown tabs (.md, .mdx, .markdown); code file tabs open in editor-only mode.
The former standalone Directory View provider has been retired. There is no separate file-tree frame, cross-frame open-file event, or Directory View picker entry. Pre-release pins/settings that still name the retired provider are not migrated and may require a settings reset; local files themselves are never deleted. treeRoot is part of EFE pin identity, while treeCollapsed is not.
External-change detection
The ExternalFileEditor watches the active tab's file for modifications by external programs (another editor, a script, a version-control operation). When such a change is detected, a yellow conflict banner appears below the tab strip:
File modified externally — Show Diff · Keep Mine · Load Incoming
Three resolution options are available:
| Action | Result |
|---|---|
| Show Diff | Opens a side-by-side diff dialog comparing your current buffer ("Mine") against the incoming disk content ("Incoming"). The banner stays visible; you still need to choose Keep Mine or Load Incoming to resolve. |
| Keep Mine | Dismisses the banner and keeps your buffer unchanged. Your next save will overwrite the disk, resolving the divergence. |
| Load Incoming | Replaces your buffer with the incoming disk content, marks the tab as saved, and dismisses the banner. |
The watcher runs only while the tab is active. Switching to another tab stops the watcher for the previous file and starts one for the newly active file. When you switch back, the editor immediately compares the file on disk against your buffer to catch any changes that happened while the tab was inactive — even if no watcher event fired. If the contents differ a conflict banner appears straight away.
Self-writes (your own Cmd+S saves) do not trigger the banner: the backend records the write mtime and suppresses the watcher event, and the frontend also guards with a content-equality check (disk === buffer → bail).
Doc Cloud (id: core.doc-cloud)
Browse and author zudo-doc-cloud projects in one unified frame.
Doc Cloud contains its Projects list, project overview and activity, service-style outline, page tabs, editor, labelled Content preview, checkpoints, and publishing inside one provider. The outline is a collapsible pane, not another frame. Opening a page creates or activates a tab in the same frame; the frame id and frameset leaf count do not change.
When to use:
Editing a zudo-doc-cloud page through the guarded Personal authoring API.
Reviewing hosted status, history, activity, or publication progress.
Keeping several remote pages in tabs without allocating another frameset leaf.
Layouts: Edit, Preview, and Split live in Doc Cloud's own toolbar. Preview is explicitly labelled Content preview: it follows the service Markdown, admonition, asset, and sanitization pipeline, but does not promise published-site theme parity.
Outline seam: The named Collapse/Expand Doc Cloud outline edge tab reports aria-expanded, supports pointer and keyboard activation, shows visible focus, and keeps the outline DOM node mounted while hidden.
Persistence: The leaf stores only projectSlug, initial surface, editor layout, and outline collapse as safe seeds. Loaded projects, tabs, source text, save guards, queries, activity, and hosted state stay in the sole frameId-keyed Doc Cloud session cache. The calling token is never settings or provider state.
Pop-out: Cannot be popped out.
Singleton scope: One per frameset. A pin for Doc Cloud is a one-leaf template, never a split outline/editor template.
See Doc Cloud Unified Frame for transport, credential, state, preview, vendoring, and open-question contracts.
Kanban Board (id: core.kanban-board)
Visualise a directory as a kanban board with swimlanes.
The Kanban Board provider renders a workspace directory as a kanban board. A directory is a board if and only if it contains a KANBAN.md manifest file. The manifest carries board properties (columns, labels, groupBy) in YAML frontmatter and lists each card as a markdown link under its column heading. Each card is its own .md file in the directory. Drag cards between columns; edits write back to KANBAN.md automatically. Card files are only written when card content (title, body, labels, priority, due, notify) changes — move and reorder operations write only the manifest. The board data is parsed by the @takazudo/kanban-parser package.
For the full directory model specification, see Kanban Directory Model.
When to use:
Tracking the status of a batch of messages or tasks in a visual board.
Managing a project checklist in kanban format inside the app.
Keeping a kanban board visible alongside an editor or preview leaf while you work through tasks.
Layouts:
| Layout id | What it shows |
|---|---|
list | The standard column/card board with drag-and-drop and swimlanes. Default. Read+write. |
now | Bounded horizontal time-axis view. Read-only. |
calendar | Month grid view. Read-only. |
Pop-out: Can be popped out into a separate window.
Persistence: The directory path and active layout id are persisted in the leaf's props blob.
Singleton scope: Multiple OK.
Mind Map (id: core.mindmap-board)
Visualise a markdown mind map file as an interactive diagram.
The Mind Map provider reads a Markdown file and renders it as an interactive mind-map diagram. Nodes can be collapsed, expanded, and navigated. Edits to the diagram write back to the underlying Markdown file with a debounce, and you can drop a .md file onto the leaf to switch files. The outline view and the diagram view stay in sync — they represent the same Markdown tree.
When to use:
Planning the structure of a long document or email campaign as a branching outline.
Reviewing a hierarchical note visually instead of reading it line by line.
Keeping a mind map visible while composing a related note in a split Inbox leaf.
Layouts:
| Layout id | What it shows |
|---|---|
mindmap | The interactive node-link diagram. Default. Read+write. |
outline | A structured outline view over the same Markdown tree. Read+write. |
Pop-out: Can be popped out into a separate window.
Persistence: The path of the loaded Markdown file and active layout id are persisted in the leaf's props blob.
Singleton scope: Multiple OK.
TODO Board (id: core.todo-board)
Manage a markdown TODO file with interactive checkboxes.
The TODO Board provider reads a Markdown file containing a task list and renders it with interactive checkboxes and inline editing. Checking or unchecking a task writes back to the Markdown file immediately. You can reorder tasks, add new ones, and edit titles inline. Drop a .md file onto the leaf to load a different TODO file. The file stays plain Markdown throughout — no proprietary format.
When to use:
Tracking to-do items for a project alongside a kanban or editor leaf.
Reviewing and ticking off a checklist before archiving a note.
Maintaining a shared task list file that also stays readable in a plain text editor.
Pop-out: Can be popped out into a separate window.
Persistence: The path of the loaded Markdown file is persisted in the leaf's props blob.
Singleton scope: Multiple OK.
Empty (id: core.empty)
An empty frame — pick a view to load here.
The Empty provider is the default state of a newly created leaf. Its content area shows the empty-leaf picker — a searchable list of every registered provider with name, description, and icon. Typing filters the list. Clicking an entry replaces the Empty provider with the selected one. Dropping a local file onto the empty leaf opens it directly in the first provider that accepts file drops (typically ExternalFileEditor); to browse for a file instead of dropping one, load External File Editor and use its own Open file / Add file pickers.
Native file drops. The Empty provider registers its root as a position-routed drop zone (registerFileDropZone, no targetKind). It never shows the File Drop Intent dialog:
A drop containing a directory replaces the leaf with the External File Editor rooted at the first directory found (
treeRoot, tree expanded, no open tabs).Otherwise every text path (per
classifyFile()) opens as a tab viaopenExternalFilesBeside(), which replaces the empty leaf in place; the first path is the active tab.A drop with only images or binaries leaves the frame empty and shows the info toast "Drop images and files onto a note to attach them."
Browser drops (mock/web builds) are not handled here — File objects carry no filesystem path. See Media uploader for the Note Tray side of text-file drops.
The explanation pane follows the frame width, not the window width: wide and half-width panes show a separate detail area; narrow panes keep rows compact and open an explicit help body. Descriptions remain searchable at every size.
When to use:
Starting a new leaf from scratch and browsing what views are available.
Replacing the current view in a leaf with a different one.
Opening an arbitrary file from the filesystem into a compatible provider.
Pop-out: Cannot be popped out.
Persistence: No persistent props — the picker itself has no saved state.
Singleton scope: Multiple OK — any number of empty leaves can coexist in a Space.
Shared compact help API
EmptyFrameHelpButton renders a separate 44 × 44 CSS px minimum target with an accessible About [title] name. Pass title, onClick, and optional controls, expanded, and ref. Keep it outside the primary action.
EmptyFrameHelpDetail accepts id, provider, onBack, optional backRef, and onOpenManual. It renders Back, About, catalog explanation, use cases, informational layouts, illustration, and Manual in a vertical scroll body. It never calls provider activation callbacks. The host retains its source state, handles Escape and focus restoration, and supplies registry metadata through EmptyFrameNavProvider.explain. The chooser retains its mounted source list during help so query, identity, and list scroll survive the round trip.
Dashboard (id: core.dashboard)
Dashboard is the core.dashboard provider: a frameset singleton that presents six configurable widget types — Recent notes, Calendar, Upcoming events, Quick actions, Tip of the day, and What's new. Its built-in pin precedes Inbox and Archives; / also opens it when the pin is hidden. A fresh workspace still boots into Inbox. The provider has no layouts, no per-leaf instance cache, and canPopOut: false.
Ordered placement and responsive spans
AppSettings.dashboard.widgets is an ordered list of DashboardWidgetInstance values. Each value owns a stable id, a widget type, a persisted w/h footprint, an optional title, and type-specific JSON settings. packDashboard() scans the list row by row and places each item in the first unoccupied span. The order is the source of truth; there are no persisted x/y coordinates. Reordering inserts an item before or after a deterministic target, then repacks every item densely.
The board derives 4, 3, 2, or 1 columns from the Dashboard frame width. Math.min(widget.w, columns) clamps only the rendered span, so a saved wide footprint survives a narrow frame and expands again when the frame widens. Supported footprints come from packages/; a settings panel disables choices wider than the current column count while retaining the selected stored value. The edit overlay consumes the same packed placements and renders guides only for vacant cells, so a widget's body is never crossed by a guide.
Edit and drag lifecycle
DashboardBoard keeps the local order separate from the persisted widgets prop. Pointer pickup captures the source dimensions and creates one inert, fixed-size DOM snapshot for the drag overlay. The live widgets stay mounted; peers receive translation-only placement animation. Hit regions are frozen at activation, and before/after intent includes a small hysteresis band to avoid target oscillation. A valid pointer release inside a widget's hit region or a keyboard Space/Enter completion commits the new order; a gap, empty cell, or other undefined target cancels the release. Escape, pointer cancel, window blur/resize, Tab, a column change, an external widget update, edit-mode exit, unmount, or a sealed workspace boundary cancels the session and leaves persistence untouched. Focus returns to the surviving drag handle after preview or rollback.
Pointer pickup can begin on the non-control portion of the title row or on its grip. Buttons, inputs, selects, textareas, links, and contenteditable descendants are excluded. The grip is the keyboard pickup target: Space or Enter starts the session, arrows move the preview by list position, and Alt+arrow retains the direct list-step shortcut. The live region announces pickup, the current position, placement, or cancellation. During edit mode the body subtree is inert so a drag cannot trigger a widget action, while the settings and removal controls in the shell remain available. Widget content also inherits DashboardWidgetEditingContext through React portals: open details dialogs close when editing starts or their source trigger disappears. Board-level settings remain available, and focus falls back to the widget gear if the original trigger is gone or inert.
The gear is available in both ordinary and edit mode. WidgetSettingsPopover anchors its desktop panel to the selected widget's top-right and clamps it to the visible frame; its body scrolls between a fixed header and footer. The existing mobile viewport predicate switches to the fullscreen settings dialog. The panel owns the common title and supported-size controls, then mounts the type-specific settings and the matching features/dashboard#<type> manual link.
Widget registry and source contracts
tauri- is the single renderer registry for the six types. It supplies the title, description, icon, supported sizes, default footprint, default settings, body component, settings component, and the widget manual path features/dashboard#<type>. The six paths resolve to the direct manual headings recent-notes, calendar, upcoming-events, quick-actions, tip-of-the-day, and whats-new. The Help catalog keeps the Dashboard feature at manualPath: "features/dashboard"; its live feature entry is also the source for What's new and Tip of the day detail links.
The data widgets read the workspace model through the backend bridge. Recent notes loads selected note directories and opens a note by dispatching dashboard:open-note with the owning Dashboard frameId; it does not infer ownership from the currently focused frame. Calendar and Upcoming events consume non-archived Kanban card timings. boards: null means all boards and boards: [] means none. Quick Actions stores only selected saved-action ids and their order, then resolves definitions and commands at render/run time. Tip of the day stores the current tip date/id and successful Try it now ids per widget instance. What's new reads curated release items and stores read/dismissed version state per widget instance; rendering, settings, and Manual links do not mark an item read.
Workspace persistence and teardown
DashboardBoard debounces widget edits before calling the provider's workspace-safe save callback. The callback re-reads current settings, writes only dashboard.widgets, and refreshes the settings context after a successful save. A workspace-boundary flush writes the latest snapshot synchronously through saveBlocking when possible. The callback is keyed by the captured workspace id, so a delayed write cannot enter the successor workspace. A failed save retains its pending payload for the next boundary retry, and the boundary flush waits for in-flight operations before reporting the failure.
The Dashboard provider itself serializes {} because its content belongs to workspace settings rather than provider props. WorkspaceDashboard is keyed by workspace id; unmounting the old instance unregisters its boundary writer and flushes its own pending edits. A late asynchronous action completion after the originating widget or workspace has gone away is discarded. Synchronous feature actions are dispatched before normal React teardown so a successful Try can still record its discovery state while the originating instance exists. Recent note opening uses a separate dashboard:open-note event with the explicit source frame id and does not mutate discovery preferences; the host revalidates that frame before opening the note.