Apr 8, 2026 17.5 KB markdown of v23

obsidian plugin

build an Obsidian community plugin as a marketing/distribution channel. no signup required — plugin reads file metadata directly from xattrs set by FileProvider.

consistency rule: VS Code plugin (src/vscode-sublimated/, v0.3.5, 2,951 LOC) is the reference implementation. both plugins must show the same information in the same format, adapted to each platform's UI. when adding features to either plugin, check the other for parity.

related docs (in main sublimated repo):

xattrs (existing)

FileProvider already writes these on every synced file:

  • com.sublimated.version#PS — version number (integer)

  • com.sublimated.commit#PS — last commit hash

  • com.sublimated.url#PS — web link (sublimated.com/anton/blog/readme_md)

  • com.sublimated.content-hash#PS — BLAKE3 hash

  • com.sublimated.id#PS — item ID (stable across renames)

  • com.sublimated.created-by#PS — original author name

  • com.sublimated.modified-by#PS — last author name

  • com.sublimated.modified-by-type#PS — person/ai_agent/bot

plugin reads these directly from filesystem. no API calls, no token, no signup for core features.

xattrs (new — add to FileProvider)

  • com.sublimated.sync-status#PS — synced/uploading/downloading/error (set by FileProvider during operations, more reliable than trying to read system badges)

  • com.sublimated.space#PS — space slug (so plugin knows which space without parsing paths)

  • com.sublimated.branch#PS — current branch name

  • com.sublimated.modified-at#PS — server-side timestamp of last change

note: Finder's FileProvider sync badges (green checkmark, cloud arrow etc) are NOT readable by other apps — NSFileProviderItemDecorating is private IPC between FileProvider and Finder. the sync-status xattr is our workaround: FileProvider writes it, plugin reads it.

alternative: SF_DATALESS flag via stat -f "%Xf" tells if file is cloud-only placeholder vs materialized locally. works for all FileProvider files but only gives binary downloaded/not-downloaded.

URI scheme: sublimated://

registered in apps/mac/Resources/Info.plist. existing "open in" feature uses:

sublimated://open-in?tool=zed&type=file&path={contentPath}

already supports: Zed, Cursor, Claude Code, Codex, Amp. implementation in apps/mac/Sublimated/URLHandler.swift.

open in Obsidian (web -> local)

add Obsidian as another tool in URLHandler.swift:

sublimated://open-in?tool=obsidian&type=file&path=blog/post.md

URLHandler resolves local FileProvider path (~/Library/CloudStorage/Sublimated-{space}/{subpath}) then opens:

obsidian://open?vault={vaultName}&file={relativePath}

web UI: add Obsidian to "OPEN IN" section in SharePopup.astro (next to Zed). show only when space has .obsidian/ folder (detected from items table — .obsidian is synced or known to exist).

open on web (local -> web)

plugin reads com.sublimated.url xattr -> opens https://{url} in browser. no URI scheme needed, just a browser open.

v1 scope

1. sync status bar — DONE (basic), needs update

done: basic text status bar, click to open on web, updates on file change missing (VS Code has these):

  • sync status icons: checkmark synced, up-arrow uploading, down-arrow downloading, ! error, warning conflict

  • visibility indicator: globe public, people shared, lock private

  • branch display (hide when main)

  • AI agent prefix: by AI: name when modifiedByType === "ai_agent" (vs plain by name)

  • visibility field missing from Obsidian's FileMetadata type (VS Code has 13 fields, Obsidian has 12)

  • target format: sublimated: v12 · checkmark synced · by anton · feature-x · globe (matches VS Code)

2. version history — DONE (basic), needs update

done: sidebar view, version list with number/author/timestamp, "view on web" link missing (VS Code has these):

  • group versions by day: "Today", "Yesterday", "X days ago", "Mon DD"

  • show commit message per version

  • diff viewer (click version to see diff against current)

3. view on web — DONE

done: command palette + status bar click opens sublimated.com URL from xattr

4. open in Obsidian (from sublimated.com) — DONE (plugin side)

done: URI handler for obsidian://sublimated?action=open&file=path + OAuth token action=auth not done: web UI changes (add Obsidian to SharePopup.astro OPEN IN section), macOS app URLHandler.swift

5. welcome modal — DONE

done: first-run modal with setup steps, links, hasSeenWelcome flag

6. settings page — DONE (basic)

done: API token input, test connection, workspace detection (.sbl/ folder), links missing: OAuth sign-in button (see oauth-signin section below)

7. file explorer decorations — NEW (VS Code has this)

VS Code shows sync status badges on files in the explorer tree:

  • checkmark synced (no color), up-arrow uploading (blue), down-arrow downloading (blue), ! error (red), warning conflict (red)

  • Obsidian equivalent: FileExplorerPlugin API or CSS-based decorations on file tree items

  • read sync-status xattr per file, show badge/icon next to filename

8. cache layer — NEW (VS Code has this)

VS Code has cache.ts (85 lines) with in-memory cache for xattr reads and API responses. Obsidian plugin currently re-reads all xattrs on every file switch with no caching.

  • add TTL-based cache for xattr reads (avoid shelling out xattr 13 times on every tab switch)

  • cache API responses (folder context, file context)

  • invalidate on file save events

9. workspace detection — needs update

done: checks for .sbl/ folder in vault root missing (VS Code has these):

  • CloudStorage mount path detection: ~/Library/CloudStorage/Sublimated-{space}/ (prod) and ~/Library/CloudStorage/sublimated-dev-{space}/ (dev)

  • .sbl/config or .sbl/config.yml parsing for space slug (VS Code reads plain text space=... or YAML space: ...)

  • detection result should include: space slug, folder path, root path, detection mode (cloud-storage vs sbl-config vs sbl-folder)

10. comments and threads (VS Code has full implementation)

VS Code: comments/controller.ts (513 lines) — native CommentController with gutter "+", threading, resolve/reopen, reactions, anchor resolution (text search -> line number -> top of file).

Obsidian equivalent:

  • sidebar view: list all comments on the active file (anchored to specific text)

  • each comment shows: author, timestamp, quoted text, resolved/unresolved status

  • threaded replies shown inline under parent comment

  • clicking a comment scrolls to the anchored text in the editor

  • anchor resolution: same cascade as LSP — Loro cursors -> PM JSON blockId -> text search (quote+prefix+suffix) -> blockIndex -> orphaned

  • unresolved comments shown as notices/callouts or gutter markers

  • "resolve" button on each comment (API call, just a toggle)

  • "reply on sublimated.com" button opens browser at the thread

  • new comment creation happens on sublimated.com (no input forms in plugin)

  • API endpoint: GET /v1/spaces/{slug}/items/{id}/context (returns comments with FileAnchor)

11. reactions

  • show reactions on file or section level in sidebar or status bar

  • hover or click to see who reacted

  • adding reactions happens on sublimated.com — plugin links to web UI

  • reactions are anchored to text via FileAnchor (same as comments)

12. section-level history (VS Code has CodeLens equivalent)

VS Code: codelens.ts (157 lines) — CodeLens above headings showing "last: {author}, {timeAgo}" and version/comment counts. Obsidian has no CodeLens, so use different UI.

  • command palette: "sublimated: show section history"

  • for each heading in the current file, show: last author, edit count, actor count

  • sections derived from AST (heading boundaries)

  • useful for long docs — see which sections changed recently and by whom

  • API provides this via file context endpoint (sections array with lastAuthor, editCount, actorCount)

13. hover and diagnostics (VS Code has these, Obsidian needs equivalents)

VS Code: hover.ts (163 lines) — wikilink hovers showing file info (versions, comments, backlinks). diagnostics.ts (104 lines) — broken link warnings, unresolved comment indicators.

Obsidian equivalent:

  • reading view: render broken [[links]] with visual indicator (strikethrough, red text)

  • sidebar or notice: list unresolved comments with context

  • hover on wikilinks (if Obsidian API supports it): show version count, last author, comment count

14. block attribution (PM JSON)

  • sidebar or hover: per-paragraph attribution

  • each block (PM JSON blockId) shows: original author, creation date, edit count, last editor

  • requires PM JSON representation (block IDs stable across edits)

15. character-level attribution (Loro)

  • deeper attribution: percentage breakdown of who wrote what in a paragraph

  • distinguishes human vs AI edits

  • Loro CRDT tracks character-level authorship

  • could render as colored highlights in reading view (different color per author)

16. @mention rendering

  • render @username as styled inline elements (colored, linked)

  • clicking @mention opens that user's profile on sublimated.com

  • in editing mode: autocomplete dropdown when typing @ (users with access to the space)

  • API provides user list via folder context endpoint

17. stale content detection

  • embedded files (![[data.csv]], ![[report.md]]) checked for staleness

  • if referenced file changed since current file was last edited, show notice

  • uses content-hash xattr on referenced file vs stored hash at embed time

18. activity tracking

  • plugin sends editor events to API as they happen (same as LSP):

    • file opened, content changed (with position info), file saved, file closed

  • API endpoint: POST /v1/spaces/{slug}/items/{id}/events

  • server computes section-level focus data from change positions

  • powers writing analytics: time spent editing vs reading per section

  • sidebar view: list all files that link to the current file via [[wikilinks]]

  • each backlink shows: file path, link context (surrounding text), version count, last author

  • clicking a backlink opens that file

  • data from item_dependencies table (dep_type = 'links_to')

  • Obsidian has native backlinks but sublimated adds: version count, author info, comment count per linking file

  • [[links]] pointing to nonexistent files flagged in reading view or sidebar

  • offer to create the missing file

  • also detects broken content embeds (![[missing.png]])

  • uses folder context items list to check existence

implementation work

plugin — exists, needs updates

existing: TypeScript + esbuild, main.ts + manifest.json + styles.css, plugin ID sublimated existing: xattr.ts (reads 12 attrs), api.ts (versions + test connection), settings.ts, version-view.ts, welcome-modal.ts

immediate work (VS Code parity for status bar):

  • add visibility to FileMetadata in xattr.ts (13th field, matching VS Code)

  • update status bar format to match VS Code: icons, visibility, branch, AI prefix

  • add cache layer (new cache.ts) for xattr reads and API responses

  • update workspace detection: CloudStorage mount path, .sbl/config parsing

  • add file explorer decorations (sync badges)

later work:

  • comments sidebar (equivalent of VS Code's CommentController)

  • section-level info (equivalent of VS Code's CodeLens)

  • hover/diagnostics (equivalent of VS Code's hover + diagnostics providers)

  • API client: expand to match VS Code's api.ts (folder context, file context, editor events, comments)

FileProvider changes (sdk/swift/)

  • add 4 new xattrs: sync-status, space, branch, modified-at

  • add visibility xattr (already in VS Code constants.ts but needs FileProvider to set it)

  • set sync-status during upload/download/error operations in SublimatedFileProviderExtension.swift

web UI changes (sublimated.com worker)

  • add Obsidian to OPEN IN tools in SharePopup.astro (with Obsidian icon)

  • detect .obsidian/ folder presence to show/hide the button

macOS app changes

  • add obsidian case to URLHandler.swift tool router

  • resolve path -> obsidian://open?vault={vault}&file={path}

API (api.sublimated.com)

  • same endpoints as language server:

    • GET /v1/spaces/:slug/items/:folderId/context — folder context (items, links, users)

    • GET /v1/spaces/:slug/items/:id/context — file context (comments, versions, sections, blocks, attribution, reactions, session)

    • POST /v1/spaces/:slug/items/:id/events — editor events (open, change, save, close)

    • POST /v1/spaces/:slug/messages — comment resolve

  • plugin uses folder context on vault open, file context on note open

  • editor events sent as user interacts (same as LSP)

OAuth sign-in

add OAuth redirect flow so users can sign in with one click instead of pasting API tokens.

how it works:

  1. user clicks "Sign in with sublimated" in plugin settings

  2. plugin opens browser to id.sublimated.com/auth/obsidian

  3. user signs in (GitHub, Apple, email — whatever id.sublimated.com supports)

  4. after sign-in, server redirects to obsidian://sublimated?action=auth&token={sessionToken}

  5. plugin URI handler catches the redirect, stores the token

  6. API calls use Authorization: Bearer {token} — same as the macOS app

server-side: add GET /auth/obsidian route to id.sublimated.com. bearer plugin already set up. plugin-side: main.ts already handles obsidian://sublimated?action=auth&token=... in handleUri(). settings.ts needs a "Sign in" button that opens the browser URL.

notes:

  • keep manual API token input as fallback

  • token should be long-lived (months/never) since there's no session refresh in Obsidian

  • consider scoped access token (read-only, specific space) vs full session

submission

  • source of truth: https://sublimated.com/sublimated/code/obsidian (dogfooded on our own platform)

  • GitHub mirror for submission: https://github.com/sublimates/obsidian — Obsidian community plugins require a GitHub repo URL in community-plugins.json. keep sublimated.com as primary, push to GitHub for releases

  • PR to obsidianmd/obsidian-releases adding entry to community-plugins.json

  • must work on Windows, macOS, Linux (xattr features macOS only, degrade gracefully)

  • automated validation + manual review

  • README with screenshots, description, setup instructions

  • stale PR policy: 30 days warning, 45 days auto-close

cross-platform notes

  • xattrs are macOS only — on Windows/Linux plugin works in API-only mode (token required for everything)

  • FileProvider is macOS only — but version history and "view on web" work everywhere with API token

  • status bar degrades: on non-mac, show "sublimated" with link to web, no local sync/version info

open questions

git-only users (no FileProvider)

what if someone clones via git clone https://sublimated.com/slug.git and opens the clone as an Obsidian vault? no FileProvider means no xattrs, no sync status, no .sbl/ detection. options:

  • plugin detects .git/ with sublimated remote URL instead of .sbl/ folder

  • shell out to git log, git status, git diff to show version/sync info

  • show unsynced changes by running git status — dirty files = unsynced

  • push/pull via git commands from within the plugin

  • this is a separate mode from FileProvider mode — need to detect which one we're in

auto-push

should the plugin offer auto-push in settings? when user saves a file:

  • run git add . && git commit -m "auto" && git push in background

  • or debounce: push every N minutes if there are uncommitted changes

  • risk: merge conflicts if someone else pushes. how to handle?

  • could be opt-in toggle in settings: "auto-sync" on/off, with interval selector

  • only makes sense for git-clone mode — FileProvider already syncs automatically

sessions, checkpoints, and focus mode

the macOS app tracks editing sessions with checkpoints, focus mode, and attribution (human vs AI edits). how does this surface in Obsidian?

  • session sidebar: show active session with elapsed time, word count delta, checkpoint list

  • checkpoints: auto-snapshot at intervals or on save. "restore checkpoint" command to revert to a point-in-time

  • focus mode: start/stop focus session from command palette. track uninterrupted writing time, words written

  • attribution: show which parts were edited by human vs AI agent (inline highlights or gutter marks)

  • this is a differentiator vs plain Obsidian — writing analytics and session tracking built into the sync layer

sharing and access visibility

shared/public folders and files should be visible in Obsidian:

  • status bar: show sharing status alongside sync info

  • file explorer decorations: small icon (people, globe) next to shared/public files and folders

  • command: "sublimated: show access" — opens panel showing who has access, roles, visibility

  • needs: com.sublimated.visibility#PS xattr (private/shared/public) and optionally com.sublimated.shared-with#PS

  • API endpoint: GET /v1/items/{id}/access for detailed access panel

promotion