Ctrl K
ComunidadGuías…

OBSIDIAN-COMPAT.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Round B/C spec — Obsidian vault compatibility, view/admin modes, scale

> **Historical.** This is the specification the compatibility work was built from, kept as a record
> of the decisions. What a vault kept in Obsidian gets today is documented in the manual where each
> feature lives: [the editor](docs/editor.md), [drawings](docs/drawing.md#in-obsidian),
> [trackers](docs/trackers.md#in-obsidian), [sigils](docs/sigils.md#in-obsidian) and
> [orbits](docs/orbits.md#where-the-schedule-lives).

Target vault reality: real Obsidian vaults with thousands of notes and heavy media —
nested folders, attachments (png/jpg/svg/pdf/…) alongside the markdown, and a `.obsidian/`
config dir (must be ignored everywhere: tree, index, graph, watcher).

## B1. Attachments (server)

- `GET /api/file?path=` serves any non-md vault file (png/jpg/jpeg/gif/webp/svg/pdf/mp4/mp3…)
  with correct Content-Type, ETag from mtime+size, and the same path-safety as notes. 404 others.
- Tree/index still list only .md, but `resolveEmbed(name)` resolves attachment basenames
  (case-insensitive, shortest-path) like wikilinks — expose `GET /api/resolve?name=` returning
  `{ path } | 404` for both notes and attachments.
- **Uploads honour Obsidian's own setting.** `settings.attachments.mode` is named after
  Obsidian's "Default location for new attachments" and offers the same four choices (vault
  root / same folder as the note / subfolder of the note's folder / one specified folder), so a
  migrating vault keeps writing attachments where its owner already expects. `POST /api/upload`
  accepts every kind an Obsidian vault holds — images, PDF, audio, video — sniffed by magic
  number. Existing attachments are never moved by a change of the setting. See CONTRACTS.md,
  "Attachments".

## B2. Editor rendering (client, live preview widgets)

- `![[image.png]]` and `![[image.png|300]]` → inline <img> widget (max-width 100%, optional px
  width from alias), loaded from /api/file; broken → dashed placeholder with filename.
- Standard `![alt](relative or URL)` images likewise (vault-relative resolved against note dir) —
  **and on the PUBLISHED site too**: the indexer records those destinations as `NoteRecord.assets`,
  resolved against the note's folder, so `allowedAttachments()` sees them. It only saw
  `[[wikilink]]` embeds before, which meant every markdown-embedded image in a published note was
  a 404 to every visitor while the admin saw it and nothing said why.
- `![[Note]]` note embed → rendered transclusion card: note title header + rendered content
  (read-only, depth 1, cycle-safe), gold left border.
- `![[file.pdf]]` → card link that opens /api/file in new tab.
- Callouts: `> [!note] Title` (+ tip/info/warning/danger/quote/example, case-insensitive, foldable
  with `-`) → styled callout block in live preview: tinted bg, icon, bold title; body renders as
  usual. Colors per type from tokens (add --callout-* tokens).
- `==highlight==` → gold-soft background mark. `%%comment%%` → hidden in preview, faint when
  cursor inside. Footnotes `[^1]` → superscript link.
- Math: `$inline$` and `$$block$$` via KaTeX (add dependency; render in widget when cursor
  outside). Code fences: CM6 syntax highlighting for common langs (@codemirror/language-data).
- Frontmatter: when cursor outside the `---` block, collapse it to a neat properties card
  (key: value chips; tags clickable).
- All of this in live preview; a separate READING VIEW (Ctrl/Cmd+E toggles editor⇄reading) renders
  the full note to HTML (same features, plus TOC in the right panel above backlinks) — implement
  as a client-side renderer sharing the resolve logic. Reading view is the default for
  non-admin visitors (see C1).

- **Trackers are an Astrolabe extension, and they degrade honestly.** A ` ```tracker ` fence renders
  as a progress card here (see [docs/trackers.md](docs/trackers.md)); opened in Obsidian, the same
  note shows a labelled code block whose every line is readable — `title: Elden Ring`,
  `progress: 62/130`. Nothing is converted and nothing is hidden, which is the same bargain
  templates make (`client/templates.ts`): a feature the other app does not have must leave the
  file saying exactly what it said. The same is true of ` ```tracker-board `, which degrades to
  its own (usually empty) fence.

## B3. Obsidian niceties

- Daily note command (palette + Ctrl/Cmd+D): opens/creates `daily/YYYY-MM-DD.md`.
- `[[Note#Heading]]` navigates to the heading; heading autocomplete after `#` inside `[[ ]]`.
- Unresolved wikilinks styled dashed; clicking one creates the note (admin only).
- Outline/TOC panel section (right panel): headings of open note, click scrolls.
- Excalidraw drawings: the plugin's `.excalidraw.md` (plain or compressed scene) and Excalidraw's
  own `.excalidraw` both open in a full canvas, save back in the same spelling (frontmatter,
  compression and the `## Text Elements` list kept), and `![[sketch.excalidraw]]` renders the svg
  exported beside the file — the plugin's auto-export name. A vault with `.obsidian/` gets new
  drawings in the plugin's spelling. See [docs/drawing.md](docs/drawing.md).

## C1. Auth: public view / admin edit

- `.env`: `ADMIN_PASSWORD_HASH` (argon2id — add `argon2` dep), `SESSION_SECRET`, optional
  `PUBLIC=false` to require login even for viewing. No hash set → open admin (local mode) with a
  startup warning.
- `POST /api/login {password}` → argon2 verify → httpOnly signed session cookie (HMAC of
  SESSION_SECRET, 30d); `POST /api/logout`; `GET /api/me` → `{ admin: boolean, public: boolean }`.
- When a hash IS set: all mutating endpoints (PUT/POST/DELETE note/folder/rename) require admin
  session → 401 otherwise. GET endpoints stay public unless `PUBLIC=false`.
- Client: on boot fetch /api/me. Non-admin → reading view only, no editor, no create/rename/delete
  UI (hide, don't just disable), status bar shows a quiet "sign in" link → minimal login modal.
  Admin → full editor. Login state changes rerender without reload. Rate-limit login (in-memory,
  10/min/IP).

## C2. Scale (1.4k notes, 738MB)

- Indexer: build once at boot (stream files, skip >2MB md), incremental on watch events; boot
  under 3s on the real vault. Watcher: ignore `.obsidian`, `.git`, `.trash`, dotfiles.
- Tree: render collapsed at depth>1 by default for huge vaults; folder toggle stays O(subtree).
- Graph: cap physics with Barnes-Hut or spatial grid; 1.4k nodes at interactive fps; labels only
  when zoomed/hovered; initial layout seeded deterministically (hash of path) so it's stable.
- Search debounce + cancel stale; snippet generation lazy.
- `/api/file` streams (no full-buffer reads) for large media.

## Config file

- Support `.env` (loaded via `node --env-file=.env` in npm scripts, document in README) with:
  `PORT`, `ASTROLABE_VAULT`, `ADMIN_PASSWORD_HASH`, `SESSION_SECRET`, `PUBLIC`, `HOME_NOTE`
  (note opened for visitors on first load — the vault's homepage).
- `npm run hash-password` script → prompts, prints argon2 hash (scripts/hash-password.ts).

## Non-negotiables

- Never write outside the vault; never serve dotfiles; `.obsidian` untouched.
- Real-vault smoke: boot against a READ-ONLY COPY of a real Obsidian vault (copy a subset ~200
  notes + their images to a scratch dir for tests; never point tests at a live vault).
- Keep DESIGN.md aesthetics for every new surface (callouts, login modal, reading view, TOC).