Files
zettelclean/README.md

135 lines
6.1 KiB
Markdown
Raw Normal View History

<!--
***************************************************************************
* (C)opyright 2026 by Ruben Carlo Benante *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see http://www.gnu.org/licenses/. *
* *
* Contact author at: *
* Ruben Carlo Benante *
* rcb@beco.cc *
***************************************************************************
-->
2026-07-28 16:52:19 -03:00
# Zettelclean
2026-07-28 16:52:19 -03:00
Keep a Zettelkasten filename's slug in sync with the note's H1 heading.
2026-07-28 16:52:19 -03:00
Zettelclean resolves the "three sources of truth for a title" problem (filename vs H1 vs
YAML `title:`) by separating concerns:
2026-07-28 16:52:19 -03:00
- The **H1 heading** in the note body is the single source of truth for the human title.
- The **filename** is a derived machine identity: `TIMEID-slug(H1).md`.
- No YAML is written. Files stay pure Markdown.
2026-07-28 16:52:19 -03:00
It is a small, one-way, prefix-gated renamer: edit the H1, and a moment later the filename's
slug tail follows. Nothing else is touched.
2026-07-28 16:52:19 -03:00
## How it works
2026-07-28 16:52:19 -03:00
- A note is a zettel when its filename starts with a **TIMEID**: a run of 12 to 18 digits
(a timestamp), for example `20260728145404-my-note.md`. Files without that prefix are
ignored, so plain notes, daily notes, Excalidraw and Kanban files are never renamed.
- The TIMEID is minted **once** and never changes. Only the slug tail is regenerated.
- When you edit the H1, zettelclean waits about 5 seconds of idle, then renames the file to
`TIMEID-slug(H1).md` using Obsidian's own rename (so backlinks are updated). A 10 second
cooldown prevents a just-renamed file from being renamed again.
- Sync is strictly one-way, H1 to filename. Renaming a file by hand is never fought.
- Delete the H1 and the filename collapses back to the bare `TIMEID.md`.
2026-07-28 16:52:19 -03:00
The slug is lowercase, ASCII, dash-separated: accents are folded (`Ciências` becomes
`ciencias`), runs of punctuation and whitespace collapse to a single dash, and leading and
trailing dashes are trimmed.
2026-07-28 16:52:19 -03:00
## Creating notes
2026-07-28 16:52:19 -03:00
Use Obsidian's built-in **Unique note creator** core plugin to mint the TIMEID:
2026-07-28 16:52:19 -03:00
1. Settings -> Core plugins -> enable "Unique note creator".
2. Set "Unique note format" to `YYYYMMDDHHmmss` (14 digits, second precision - avoids
same-minute collisions).
3. Click the "Create new unique note" ribbon icon. You get `20260728145404.md`; type your
`# Title`, and zettelclean takes over from there.
2026-07-28 16:52:19 -03:00
Zettelclean also accepts an existing 12-digit (`YYYYMMDDHHmm`) vault if you prefer not to
change the setting.
2026-07-28 16:52:19 -03:00
### Retrofitting existing notes
2026-07-28 16:52:19 -03:00
Notes created before you adopted this workflow have no TIMEID. Right-click such a note in the
file explorer and choose **"Prefix timestamp to filename"**. It mints a fresh timestamp,
2026-07-28 16:52:19 -03:00
prepends it, and the note is now a zettel that syncs on future H1 edits.
2026-07-28 16:52:19 -03:00
## Pretty sidebar (optional)
2026-07-28 16:52:19 -03:00
By default the file explorer shows the slugged filename (with dashes). If you want the sidebar,
tabs and graph to show the pretty H1 instead - with no YAML in your files - install the
**Front Matter Title** community plugin and set its feature templates to:
2026-07-28 16:52:19 -03:00
```
#heading | _basename
```
2026-07-28 16:52:19 -03:00
That reads the first H1 directly and falls back to the filename when there is none.
2026-07-28 16:52:19 -03:00
## Settings
2026-07-28 16:52:19 -03:00
None. Zettelclean is zero-config in this version. The 5 second settle and 10 second cooldown
are fixed.
2026-07-28 16:52:19 -03:00
## Development
Dependencies are managed with **pnpm** (see "Why pnpm, not npm" below). Node 22+ ships
`corepack`, so you do not need to install pnpm globally, and the `packageManager` field in
`package.json` pins the exact version.
```bash
corepack pnpm install # first time, or after a dependency change
corepack pnpm run dev # compile in watch mode
corepack pnpm run build # typecheck and produce main.js
corepack pnpm test # run the slug unit tests (Node's built-in runner)
corepack pnpm run lint # eslint with the Obsidian ruleset
```
2026-07-28 16:52:19 -03:00
The pure string logic lives in `src/slug.ts` and is unit-tested in isolation; the Obsidian
wiring lives in `src/main.ts`. Tests run on Node's built-in test runner (`node --test`) - there
is no vitest or jest dependency.
### Why pnpm, not npm
**Do not run `npm install` or `npm ci` in this repo.** This project is developed on a **ZFS**
filesystem, and npm's installer renames many directories in parallel (to hoist and dedupe
packages). That races ZFS's directory-metadata handling and aborts with:
```
npm error code ENOTEMPTY
npm error syscall rename
```
pnpm avoids the problem entirely: it hard-links packages from a global content-addressable
store instead of renaming directories into place, so it installs cleanly on the same disk.
Notes for contributors:
- Only `npm install` / `npm ci` are affected. Running *scripts* through npm (`npm run build`)
still works, because that just spawns tsc/esbuild and installs nothing. Still, prefer `pnpm`
everywhere for consistency.
- The lockfile is `pnpm-lock.yaml` (committed). There is no `package-lock.json`; do not create
one.
2026-07-28 16:52:19 -03:00
## License
GNU General Public License v3.0 or later (`GPL-3.0-or-later`).
Copyright (C) 2026 Ruben Carlo Benante (Dr. Béco) <rcb@beco.cc>.
See [LICENSE](LICENSE) for the full text.