zudo-text

検索したい単語を入力

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

Auto-managed frontmatter fields

The frontmatter schema (defined per-workspace in .zudotext/frontmatter-schema.json, or the built-in default when that file is absent) declares which fields a note is expected to have and which ones the app fills in automatically. This page covers the two automatic modes, how and where they are applied, the design decision that makes the renderer the single writer, and the back-compat rules for the legacy date field.

For the schema file format and validation behavior, see Frontmatter schema.

Auto-managed fields

A field can carry an "auto" property that tells the writing app when to populate it. Two modes exist:

ModeBehavior
"on-create"Written once when the file is first created. Never overwritten on subsequent saves — the original creation timestamp is preserved even if you rename or reorganise the file.
"on-save"Updated on every save. Also written on create, so a brand-new note's updated_at and created_at start out identical.

Only "datetime" and "date" fields honor the "auto" property. Any other type with "auto" set is silently skipped — no error is raised, and the field is left untouched.

Default schema

The default schema (returned by getDefaultSchema() in @takazudo/frontmatter-schema) declares both temporal fields, but not as auto:

  • created_attype: "datetime" (no auto)

  • updated_attype: "datetime" (no auto)

If your workspace does not have a frontmatter-schema.json, these two fields are not stamped automatically — filesystem birthtime/mtime are the authoritative timestamps for notes archived via the Note Tray pipeline (epic #3426, no zudo-text-proprietary sidecar file). Declare your own frontmatter-schema.json with auto: "on-create" / auto: "on-save" on created_at/updated_at (see the custom schema example below) if you want the app to keep stamping frontmatter timestamps.

Custom schema example

{
  "fields": [
    { "name": "title", "type": "string", "required": true },
    { "name": "created_at", "type": "datetime", "auto": "on-create" },
    { "name": "updated_at", "type": "datetime", "auto": "on-save" }
  ]
}

How auto fields are applied

The renderer is the single writer of auto-managed fields. The flow on every save:

  1. The renderer parses the file's frontmatter.

  2. It calls applyAutoFields(fm, schema, "save", new Date()) from @takazudo/frontmatter-schema.

  3. The resulting frontmatter block is re-serialized and the full file content is sent to the backend.

  4. The Rust backend writes the content verbatim — it does not inject, inspect, or rewrite any timestamps.

On file creation the call is applyAutoFields(fm, schema, "create", new Date()) — the same function, different event argument, which causes "on-create" fields to be written and "on-save" fields to be written as well (a fresh note gets both timestamps on its first write).

Writer-policy decision

The schema lives in the TypeScript package @takazudo/frontmatter-schema. The renderer is the only side that loads and evaluates it.

Duplicating the schema parsing and application logic in Rust would mean maintaining two implementations of the same spec, with all the drift and coordination risk that implies. Instead, the Rust backend is kept deliberately simple: it writes raw content and has no knowledge of which fields are auto-managed. The renderer owns that responsibility end-to-end.

Consequences for future contributors:

  • Any new Rust write path (a new Tauri command, a migration script, a sync operation) must not inject timestamps into frontmatter. Write the content as given and let the renderer handle the auto-field pass.

  • Any new auto-managed field is added to the schema and handled automatically by applyAutoFields. No Rust change is needed.

Legacy date field — back-compat rules

Older notes may have a top-level date: in their frontmatter instead of created_at:. The schema module recognises date as a legacy alias under the following rules:

(a) date present, created_at absent — the read path surfaces the legacy value as created_at. The file on disk is not rewritten — the original date: key is preserved in the YAML.

(b) Both date and created_at present with different valuescreated_at wins. A non-blocking warning is logged; no silent data change occurs.

(c) Save path does not migrate date to created_at — if you want to migrate a legacy file, edit it manually. The app will not silently rewrite your frontmatter keys.

(d) Validator treats date as a known alias — when the schema has auto-managed temporal fields, the presence of a date: key in frontmatter does not produce an "unknown field" warning.

The read adapter is exported from @takazudo/frontmatter-schema as readLegacyDate(fm).

Authoring custom auto fields

A schema author can declare any temporal field (type: "datetime" or type: "date") with auto: "on-create" or auto: "on-save". The renderer will populate it without any code change required.

{
  "fields": [
    { "name": "title", "type": "string", "required": true },
    { "name": "created_at", "type": "datetime", "auto": "on-create" },
    { "name": "updated_at", "type": "datetime", "auto": "on-save" },
    { "name": "last_published_at", "type": "datetime", "auto": "on-save" }
  ]
}

In this example, last_published_at is bumped on every save alongside updated_at. You might use this pattern in a workspace where notes are published externally and you want a separate timestamp that tracks when the file was last touched in the app.

Non-temporal types ("string", "number", "boolean", "enum", "string-array") with "auto" set are skipped — they are left untouched on save and on create.

See also