Skip to content

calternal-plugin-notes

Notes Plugin HTTP surface. The server serializes Home writes through directory-relative storage and checked replace; Markdown is the source of truth, while Search, Calendar and Reminder rows are rebuildable projections (DESIGN §§2, 9, 29 and 32; batch durability and projection queue in #468). Trash reserves the Index writer before dependency reads. Its bounded retry never repeats the filesystem move, and diagnostics omit User content (#1077). Composer create receipts retain stable Log and linked Note identities after partial failure; source witnesses seal plans before later moves/deletions. Mixed retries prepare only unfinished intents and preserve response order (#844). Repeated Tag filters apply before owned and Shared Notes pagination (#1109, DESIGN §33). Request timing exposes selected operations without User data (#549). Journal day reads use the last committed source projection through WAL; reconciliation repairs imported identities under the writer guard (#549). Read-your-writes (#653) uses publication before acknowledgement: checked Daily note edits repair missing Log IDs in the durable source update, and batches commit Note, Calendar and DAV projections before returning 201. A generation wait would put scheduling delays on reads; pending read-through would duplicate the Calendar/DAV projection and ETag rules. Publishing each affected source reuses one Index transaction and keeps reads on WAL, even while a full reconcile is held. Task dependency repair still has its durable batch job (#468). API/MCP Note reads share the route and read durable bytes after the Note identity lookup (DESIGN §41). Tasks/ is declared as Plugin-owned storage; the Tasks index supplies the typed Calendar projection instead of a generic file row (#606, #662). Web/API and CalDAV Log deletes share the core re-parent rewrite; deleted line hints never restore retained children (#471; DESIGN §46 A7). Canvas Notes keep validated scene JSON separate from Markdown blocks; creation and saves use the same Home writer and index callbacks (DESIGN §60, #976). Canvas backlinks reuse the link index and caller-owned item identities; drawing references never grant access to another Home (#977, DESIGN §60). Startup queues a cursor-backed Daily Log projection rebuild for each Home after shared parser changes (#724, #998); versioned migrations reset old cursors, and the job reads Markdown only. Repeated Area Tags map to one DAV membership so legacy source cannot strand the rebuild (#1033). Durable intent recovery precedes HTTP; upgrade backfills follow it (#1011). Note and Task property DTOs publish distinct names before composition (#833). Daily GET never changes Home content; POST owns creation and navigation (#752). An exact user-scoped path query resolves one Note for links without paging the full index (#639; DESIGN §33). Voice jobs belong to a User, while their transcript cache stays in that User’s private derived store and retries remain safe (#619; DESIGN §48). The shared Markdown link Index resolves imported targets once for inline rendering, Backlinks and Files Used in; reading a Note never writes it (#856; DESIGN §§9, 33). Date and identity backfills share bounded YAML checks before source writes. Date writes preserve the verified source inode’s mtime on the staged fd. Historical recovery is removed because it lacks per-Note proof (#1148; §17).

Source: crates/plugins/notes/src/lib.rs

Module Summary
canvas_assets Home asset preparation for Canvas saves and exports (#989, DESIGN §60).
canvas_text Checked Note/Task text synchronization for linked Canvas drawings (#977, §60).
imap Home-backed Notes IMAP provider (#428, DESIGN §9).
  • pub use store::race
pub struct ApiError(StatusCode, ErrorEnvelope, bool);

Notes error plus a private transient-lock marker for complete projection retries.

Implements: Debug, std::fmt::Display, std::error::Error, IntoResponse, From<calternal_fs::Error>, From<sqlx::Error>, From<serde_json::Error>

pub fn status(&self) -> StatusCode

HTTP status for server-owned adapters that call the Notes API directly.

Source: crates/plugins/notes/src/lib.rs:2760

pub struct CalendarLogEntry

A Log entry read or written by the Calendar Plugin.

Fields

  • pub id: String
  • pub date: String
  • pub start: String
  • pub end: Option<String>
  • pub timezone: Option<String>
  • pub title: String

Implements: Clone, Debug, PartialEq, Eq

Source: crates/plugins/notes/src/calendar_links.rs:35

pub struct CalendarNote

A Note’s stable identity and title for creating a linked Event.

Fields

  • pub id: String
  • pub title: String

Implements: Clone, Debug, PartialEq, Eq

Source: crates/plugins/notes/src/calendar_links.rs:28

pub struct CanvasExportQuery

No scene is accepted here. The action reads the caller’s committed revision.

Fields

  • pub format: CanvasExportFormat
  • pub offset: usize: Byte offset for bounded CLI/MCP transfers. Omit to download the whole file.
  • pub limit: Option<usize>: A chunk is at most 512 KiB, below the shared adapter response ceiling.

Implements: Deserialize, IntoParams

Source: crates/plugins/notes/src/canvas_export.rs:31

pub struct CollabNote

Minimal persistence boundary used by live Note sessions. The Note plugin remains the only caller of calternal-fs for Note content and indexing.

Fields

  • pub canvas: bool: Detected from full source before the frontmatter prefix is split (§60, #976).
  • pub id: String: Stable Note identity, usable only after the caller authorizes this Note.
  • pub path: String
  • pub body: String
  • pub etag: String
  • pub version_name: Option<String>: Previous-content Version created by the latest committed edit.

Implements: Clone, Debug

Source: crates/plugins/notes/src/lib.rs:3782

pub struct DuplicatedJournalEntry

Result of duplicating a Journal entry. The block identity is stable and can be used by the Calendar to select or undo the new entry.

Fields

  • pub block_id: String
  • pub date: String

Implements: Clone, Debug, Serialize

Source: crates/plugins/notes/src/lib.rs:5062

pub struct DuplicatedTask

Result of duplicating a Task. The new Task is a fresh, open Task file and preserves its title, schedule, tags, recurrence, and body.

Fields

  • pub task_id: String
  • pub source: String
  • pub date: String

Implements: Clone, Debug, Serialize

Source: crates/plugins/notes/src/lib.rs:5122

pub struct ExternalEventLogInput

Input for the Calendar Plugin’s idempotent “Log this” operation.

Fields

  • pub event_key: String
  • pub date: String
  • pub start: String
  • pub end: Option<String>
  • pub timezone: String
  • pub title: String
  • pub event_url: String
  • pub provider_url: Option<String>

Implements: Clone, Debug, Deserialize, Serialize

Source: crates/plugins/notes/src/calendar_links.rs:53

pub struct LogAttachmentTrashLink

The Log link data kept in Files Trash so Undo can restore it by block ID. target is the Home path; wrapped preserves Markdown destination spelling.

Fields

  • pub date: String
  • pub day_path: String
  • pub block_id: String
  • pub title: String: Parsed Log title, reused by the file Inspector’s backlink list (#620).
  • pub target: String
  • pub raw_target: String
  • pub text: String
  • pub embed: bool
  • pub wrapped: bool: Older pending Trash rows do not have this style bit.
  • pub legacy: bool

Implements: Clone, Debug, Deserialize, Eq, PartialEq, Serialize

Source: crates/plugins/notes/src/lib.rs:3389

pub struct LoggedExternalEvent

Result of converting one external Event into a Log entry.

Fields

  • pub entry: CalendarLogEntry
  • pub created: bool

Implements: Clone, Debug, PartialEq, Eq

Source: crates/plugins/notes/src/calendar_links.rs:46

pub struct NotesJournalProvider

The DAV adapter calls this provider. Writes share the Notes user lock, preserve unrelated bytes, and use the same conditional filesystem writer and index path as Notes HTTP edits.

Implements: Clone, JournalProvider

pub fn new(root: Root, db: Db, public_url: String) -> Self

No doc comment.

Source: crates/plugins/notes/src/lib.rs:445

pub struct NotesRemindersProvider

No doc comment.

Implements: Clone, RemindersProvider

pub fn new(root: Root, db: Db, public_url: String) -> Self

No doc comment.

Source: crates/plugins/notes/src/tasks_dav.rs:48

pub struct PublicLinkedNote

A linked Note projection for public links. It exposes no owner-relative path or properties; body is omitted from the list response.

Fields

  • pub id: String
  • pub title: String
  • pub body: Option<String>

Implements: Clone, Serialize, ToSchema

Source: crates/plugins/notes/src/lib.rs:3797

pub enum CanvasExportFormat

The format is fixed; callers cannot select a program, path or browser flag.

Variants

  • Png
  • Svg

Implements: Deserialize, ToSchema

Source: crates/plugins/notes/src/canvas_export.rs:23

pub async fn adopt_change(root: &Root, db: &Db, user: &str, path: &str) -> Result<()>

Adopt a Note or folder changed through Files or an out-of-band restore. Rebuild Task membership and dependent Daily notes before live invalidation (#494, #623; DESIGN §40). The locked entry point serves Files mutations. Box the adoption future to keep batch-projection state off request stacks.

Source: crates/plugins/notes/src/lib.rs:273

pub fn adopt_change_locked<'a>(
root: &'a Root,
db: &'a Db,
user: &'a str,
path: &'a str,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>>

Complete Note, Task and Reminder adoption before Files publishes a change. The caller holds lock_home_change. Folder events adopt current and indexed descendant paths, including Task-only orphans, without scanning unrelated Notes. Prefix ranges use ‘/’ and its ASCII successor ‘0’ so SQLite can use the User/source indexes instead of scanning a Home. Return a boxed future from this factory so the large projection state never enters Files request or caller test futures (#623; DESIGN §40).

Source: crates/plugins/notes/src/lib.rs:285

pub async fn backfill_after_serving(root: &Root, db: &Db)

Run idempotent upgrade backfills after HTTP starts (#1011; DESIGN §2). Each migration retains its per-User completion marker and checked writer. Failures leave source files intact and use the existing bounded retries.

Source: crates/plugins/notes/src/lib.rs:169

pub async fn calendar_note(
root: &Root,
db: &Db,
user: &str,
id: &str,
) -> Result<CalendarNote, ApiError>

Read a normal Note by its permanent identity for an Event URL.

Source: crates/plugins/notes/src/calendar_links.rs:65

pub async fn collab_id_for_path(db: &Db, user: &str, path: &str) -> Result<Option<String>>

Resolve one current stable Note identity from its indexed path so the collaboration watcher reads only the room named by an external change (#769).

Source: crates/plugins/notes/src/lib.rs:4159

pub async fn collab_read(root: &Root, db: &Db, user: &str, id: &str) -> Result<CollabNote>

Read a Note by immutable Note identity for a data-scoped user. The body excludes the prefix of collab_split.

Source: crates/plugins/notes/src/lib.rs:3950

pub async fn collab_write(
root: &Root,
db: &Db,
user: &str,
id: &str,
expected_etag: &str,
body: &str,
timezone: Option<&str>,
) -> Result<CollabNote>

Commit one live edit with the same lock and If-Match rule as the HTTP route.

Source: crates/plugins/notes/src/lib.rs:3965

pub async fn collab_write_policy_with_intent<F, Fut>(
root: &Root,
db: &Db,
user: &str,
id: &str,
expected_etag: &str,
body: &str,
timezone: Option<&str>,
live_history: bool,
prepare: F,
) -> Result<CollabNote>
where
F: FnOnce(String) -> Fut,
Fut: std::future::Future<Output = std::result::Result<(), String>>,

Persist a save intent after computing the exact Markdown hash, before rename. The caller retains its history file lock; Notes owns source formatting and does not open a second segment handle (DESIGN §61, #975 crash review).

Source: crates/plugins/notes/src/lib.rs:3992

pub fn configure(root: Root, db: Db)

Supply the live handles and subscribe before startup reconciliation runs. The Files completion notice rebuilds all Notes after the shared scan (#470).

Source: crates/plugins/notes/src/lib.rs:2989

pub async fn detach_log_attachment_links(
root: &Root,
db: &Db,
user: &str,
links: &[LogAttachmentTrashLink],
) -> std::result::Result<(), String>

Remove the recorded links. The caller holds lock_home_change and stores the link metadata in Files before calling this function (#427).

Source: crates/plugins/notes/src/lib.rs:3473

pub async fn duplicate_journal_entry(
root: &Root,
db: &Db,
user: &str,
block_id: &str,
) -> std::result::Result<DuplicatedJournalEntry, ApiError>

Copy one Journal entry directly after its original in the same Log section. The write holds the Notes User lock and uses the lossless Daily note updater, so concurrent Journal changes cannot move or erase lines.

Source: crates/plugins/notes/src/lib.rs:5070

pub async fn duplicate_task(
root: &Root,
db: &Db,
user: &str,
task_id: &str,
date: &str,
) -> std::result::Result<DuplicatedTask, ApiError>

Copy one indexed Task into a fresh Task file for the same Calendar day. A Task file is used for inline and file Tasks alike so the duplicate has an independent identity and Undo can move exactly that new file to Trash.

Source: crates/plugins/notes/src/lib.rs:5131

pub async fn export_canvas_for_reader(
root: &Root,
db: &Db,
user: &str,
id: &str,
query: CanvasExportQuery,
recipient: Option<&str>,
) -> Result<Response>

Use the one bounded renderer after the caller verifies its current grant. Shared readers get the passive projection until #977 supplies per-item access. The app font follows the reader’s settings, as it does in the web view (§60). The caller must recheck access before returning bytes if rendering took time.

Source: crates/plugins/notes/src/canvas_export.rs:65

pub async fn file_log_attachment_links(
root: &Root,
db: &Db,
user: &str,
target: &str,
) -> std::result::Result<Vec<LogAttachmentTrashLink>, String>

Find Log links that point at one file or below a folder. The link index narrows candidate Daily notes; Markdown remains the source used for edits. The alternate Home candidate covers links indexed before Files (#856, #867).

Source: crates/plugins/notes/src/lib.rs:3409

pub async fn find_external_event_log(
root: &Root,
db: &Db,
user: &str,
event_key: &str,
) -> Result<Option<CalendarLogEntry>, ApiError>

Find the Log entry for one provider Event UID without scanning Daily notes.

Source: crates/plugins/notes/src/calendar_links.rs:296

pub async fn link_calendar_event_to_log(
root: &Root,
db: &Db,
user: &str,
id: &str,
event_url: &str,
) -> Result<CalendarLogEntry, ApiError>

Attach the stable Calendar Event link to the current Log entry text.

Source: crates/plugins/notes/src/calendar_links.rs:166

pub async fn link_note_to_calendar_event(
root: &Root,
db: &Db,
user: &str,
note_id: &str,
event_url: &str,
) -> Result<(), ApiError>

Set the reverse Event link in a Note’s scalar event: property. One Note links to one Calendar Event so the property stays directly usable.

Source: crates/plugins/notes/src/calendar_links.rs:93

pub fn local_stamp(root: &Root, user: &str, zone: Option<&str>) -> String

Local wall-clock stamp for a live save. Live saves have no HTTP request, so the collaboration room passes the zone its editor reported at connect time. A missing or unknown zone falls back to the user’s timezone setting, then UTC, like user_zone (#141).

Source: crates/plugins/notes/src/lib.rs:3675

pub async fn lock_agent_undo(user: &str) -> OwnedMutexGuard<()>

Hold the Notes and Tasks writer lock while Files performs Agent undo.

Source: crates/plugins/notes/src/lib.rs:2983

pub async fn lock_home_change(user: &str) -> OwnedMutexGuard<()>

Hold the Notes writer lock while Files moves a target or saves/restores Log links. Files pairs it with its namespace lock so Notes edits cannot race those changes.

Source: crates/plugins/notes/src/lib.rs:2978

pub async fn log_external_calendar_event(
root: &Root,
db: &Db,
user: &str,
input: ExternalEventLogInput,
) -> Result<LoggedExternalEvent, ApiError>

Create one Event-backed Log entry. The stable block ID makes retries idempotent, including concurrent requests and a response lost after write.

Source: crates/plugins/notes/src/calendar_links.rs:225

pub fn migrations() -> MigrationSet

List immutable, ordered Notes migrations. Parser rebuild versions reset cursors once; Reminder wire-only upgrades invalidate saved sync tokens once.

Source: crates/plugins/notes/src/store.rs:150

pub async fn move_from_files(
root: &Root,
db: &Db,
user: &str,
old: &str,
new: &str,
) -> Result<bool>

Apply the Note rename cascade when Files moves a Markdown Note. The calternal-id decides whether the source is a Note; plain Markdown keeps normal Files semantics. The Files caller commits its own item-ID move after this returns, while this transaction updates links and backlinks.

Source: crates/plugins/notes/src/lib.rs:3079

pub async fn move_from_files_locked(
root: &Root,
db: &Db,
user: &str,
old: &str,
new: &str,
) -> Result<bool>

Apply the Note rename cascade when Files already holds the Notes writer lock. The lock keeps the Note index, file move and attachment-link edits coherent.

Source: crates/plugins/notes/src/lib.rs:3092

pub async fn path_for_id(db: &Db, user: &str, id: &str) -> Result<String>

Resolve a live Note by immutable identity before checking a Share. Legacy path: selectors may still open the current indexed path after the identity backfill, so existing links work while new links use calternal-id (#1132, DESIGN §33).

Source: crates/plugins/notes/src/lib.rs:5007

pub async fn public_linked_note(
root: &Root,
db: &Db,
user: &str,
target: &str,
id: &str,
) -> Result<Option<PublicLinkedNote>>

Read one linked Note only while its current link index still points at the authorized item. Private Reference destinations and labels are removed.

Source: crates/plugins/notes/src/lib.rs:3851

pub async fn public_linked_notes(
db: &Db,
user: &str,
target: &str,
) -> Result<Vec<PublicLinkedNote>>

List Notes whose shared link Index references this exact Home-relative item, including Wiki embeds. The caller checked the public item identity.

Source: crates/plugins/notes/src/lib.rs:3824

pub async fn public_note_title(db: &Db, user: &str, path: &str) -> Result<Option<String>>

Return the indexed title for one item-bound public Notes link.

The Files plugin calls this only after it authorizes the link’s immutable item identity. The exact owner and path keys prevent a public lookup from crossing into another user’s Notes index.

Source: crates/plugins/notes/src/lib.rs:3812

pub async fn read_calendar_log_entry(
root: &Root,
db: &Db,
user: &str,
id: &str,
) -> Result<CalendarLogEntry, ApiError>

Read one Log entry through the same provider used by the Journal REST API.

Source: crates/plugins/notes/src/calendar_links.rs:146

pub async fn reconcile_all(root: &Root, db: &Db) -> Result<()>

Rebuild derived Note rows for every user after startup or on a job retry.

Source: crates/plugins/notes/src/lib.rs:3063

pub async fn reconcile_user(root: &Root, db: &Db, user: &str) -> Result<()>

Rebuild both Markdown projections in dependency order. Calendar Log links validate Task targets while the Note index is built, so task frontmatter must be indexed first on startup and after a Home change.

Source: crates/plugins/notes/src/lib.rs:258

pub async fn record_log_timezone(
db: &Db,
user: &str,
path: &str,
block_id: &str,
timezone: &str,
) -> Result<()>

Store a capture zone for a Log entry in the derived calendar projection. New Log entries carry their zone on the Markdown line and index projects it from there, so the line always wins. This remains for legacy lines that have no on-line zone; index_calendar_logs keeps such a zone only while the line itself has none, so the projection never contradicts the file.

Source: crates/plugins/notes/src/store.rs:1860

pub async fn recover(root: &Root, db: &Db) -> Result<()>

Recover durable writes and run upgrade backfills for non-server callers. The HTTP server uses the two phases separately so upgrades do not delay its listener (#1011; DESIGN §2). Existing callers keep both phases.

Source: crates/plugins/notes/src/lib.rs:150

pub async fn recover_before_serving(root: &Root, db: &Db) -> Result<()>

Complete interrupted renames and cross-date Journal moves before requests. These intents protect source bytes and stable block identities, so they must finish before HTTP writes; optional backfills stay separate (#1011).

Source: crates/plugins/notes/src/lib.rs:161

pub async fn refresh_target_path(db: &Db, user: &str, path: &str) -> Result<()>

Refresh references to a Home item after the Files Index changes. Notes writes call index; this path also retries either candidate for imported relative links without scanning Markdown on every query (#856). Folder moves refresh indexed descendants by prefix, after Files commits (#867).

Source: crates/plugins/notes/src/store.rs:1677

pub async fn restore_log_attachment_links(
root: &Root,
db: &Db,
user: &str,
links: &[LogAttachmentTrashLink],
) -> std::result::Result<(), String>

Restore Trash links by stable Log block ID. A missing or deleted Log row is skipped, and an edited row keeps all current children (#427).

Source: crates/plugins/notes/src/lib.rs:3522

Section titled “rewrite_indexed_markdown_links_for_moves_locked”
pub async fn rewrite_indexed_markdown_links_for_moves_locked(
root: &Root,
db: &Db,
user: &str,
moves: &[(String, String)],
) -> std::result::Result<usize, String>

Rewrite known Markdown paths after Files has reconciled a Home (#618). A repair must not enumerate 100k unrelated recordings for each move. The caller holds the namespace and Notes locks; paths are hints, bytes are read fresh through the hash-checked Note writer. Ordinary moves still scan Home.

Source: crates/plugins/notes/src/lib.rs:3285

pub async fn rewrite_log_attachment_targets_locked(
root: &Root,
db: &Db,
user: &str,
old: &str,
new: &str,
) -> std::result::Result<(), String>

Rewrite Log child links before a Files move. The caller holds lock_home_change, and Files still owns the source path (#427, DESIGN §33). Include the legacy Home candidate when Files has not resolved the link Index yet. Log attachment destinations retain their Home-root rule (#856, #867).

Source: crates/plugins/notes/src/lib.rs:3127

pub async fn rewrite_markdown_links_for_moves_locked(
root: &Root,
db: &Db,
user: &str,
moves: &[(String, String)],
) -> std::result::Result<usize, String>

Rewrite visible Markdown links for indexed path moves. Files holds both its namespace lock and lock_home_change; scanning the Home catches Notes links even when the derived link Index is stale (#618; §33).

Source: crates/plugins/notes/src/lib.rs:3223

pub fn run_voice_inference_worker(arguments: &[std::ffi::OsString]) -> Result<(), String>

Run the private inference protocol used by calternal-server’s killable child process.

Source: crates/plugins/notes/src/voice.rs:2541

pub async fn tasks_created_per_day(
db: &Db,
user: &str,
from: &str,
to: &str,
) -> Result<Vec<(String, u64)>>

Tasks created on each local day of [from, to], keyed by YYYY-MM-DD.

created is the Task’s own YYYY-MM-DD creation date from its Markdown, so no time zone applies. One indexed GROUP BY query (task_items_created) serves any window; Tasks without a creation date are not counted.

Source: crates/plugins/notes/src/tasks_store.rs:827

pub fn user_note_path_filter(
path_expression: &str,
owned_paths: &OwnedPathRegistry,
) -> (String, Vec<String>)

Build the shared Notes visibility filter for the Notes page and sidebar.

The trusted SQL expression names a stored path column or expression. Values from Plugin path declarations remain bound parameters. Applying this before pagination keeps both views complete and obeys DESIGN §31/K5: Notes does not list Plugin-owned files or app-managed Daily notes.

Source: crates/plugins/notes/src/lib.rs:4885

pub const PUBLIC_NOTE_MAX_BYTES: u64

Maximum Markdown body returned by a public linked-note endpoint.

Source: crates/plugins/notes/src/lib.rs:3805

pub const VOICE_WORKER_COMMAND: &str

Private command recognized by calternal-server for killable local inference (#619).

Source: crates/plugins/notes/src/voice.rs:73