verstak-desktop/docs/NOTES_FILES_PLUGIN_PLAN.md

15 KiB

Notes/Files Plugin Architecture Plan

This document locks the Notes/Files/Open architecture for the next milestones. Files Core Service was implemented in Milestone 6a; this document still does not start Notes UI, Notes plugin, Files UI plugin, editor plugin, watcher, sync, or binary streaming.

Current Readiness

The platform is ready for bundled plugin UI experiments. Files Core is available as a safe vault-scoped text file API. Notes, Files UI, and editor/viewer UI still need plugin-level implementations and host surfaces before real product use.

Already available:

  • Plugin discovery, lifecycle, settings, capabilities, bundled commands, and bundled frontend events.
  • Workspace lifecycle APIs for top-level physical folders under the vault root.
  • Plugin-owned internal storage directories: .verstak/plugin-data/<pluginId>, .verstak/plugin-settings/<pluginId>, and .verstak/plugin-cache/<pluginId>.
  • Contribution registry entries for fileActions, noteActions, contextMenuEntries, searchProviders, activityProviders, and statusBarItems.

Not available yet:

  • Notes plugin/API as a semantic view over Markdown files.
  • Files UI plugin.
  • Editor/viewer plugin.
  • Open/edit resource routing and provider selection.
  • UI hosts for file actions, note actions, context menus, search providers, activity providers, or status bar items.
  • Watcher/indexer for external filesystem changes.
  • Real plugin isolation. Current permission checks are contract/policy checks, not a security boundary for bundled frontend JavaScript.

Canonical Notes Model

Notes are ordinary human-readable Markdown files inside the vault. They must be visible and editable outside Verstak.

Canonical rules:

  • Notes are .md files, not opaque records.
  • Canonical folder name is exactly Notes.
  • Do not create lowercase notes.
  • Do not store user notes in .verstak/notes/.
  • Do not store user notes in .verstak/plugin-data/verstak.notes/.
  • Do not use UUID-only filenames for notes.
  • The note title is the source of truth.
  • The filename is a normalized projection from the title.
  • RenameNote must update both the title and the .md filename.

Canonical scoped paths:

  • Workspace overview notes live under <workspace>/Notes/.
  • The default overview note is <workspace>/Notes/Overview.md.
  • <workspace> is the top-level physical folder name under the vault root.
  • Files plugin workspace views are scoped with workspaceRootPath, which is the selected top-level workspace folder name.

Visibility requirements:

  • Notes UI must show notes as semantic notes.
  • Files UI must show the same .md files as ordinary files.
  • External file managers must show the same .md files.
  • Outside Verstak, the files must remain useful as normal Markdown.

There is no canonical metadata workspace tree. Adding note as a workspace node type is not part of the next milestone. The Notes service can index and manage Markdown files inside canonical Notes/ folders under each top-level workspace.

Title To Filename Contract

The title is the source of truth. The filename is derived from the title when a note is created or renamed.

Normalization rules:

  • Replace spaces with _.
  • Replace typographic dashes with -.
  • Allow only letters, digits, ., _, and -.
  • Append .md if the normalized name does not already end with .md.
  • Reject empty normalized names.
  • Preserve canonical Notes folder casing.

Examples:

Title Filename
Overview Overview.md
Meeting Notes Meeting_Notes.md
Plan — Phase 1 Plan_-_Phase_1.md
A/B Test: Result AB_Test_Result.md

Collision Policy

Same-folder collisions must not be solved silently with _2, _3, or timestamp suffixes.

CreateNote and RenameNote must return a conflict error if the normalized target filename already exists in the target Notes/ folder. The UI should show a clear dialog or notification and ask the user to change the title.

Required conflict metadata:

  • requested title;
  • normalized filename;
  • target vault-relative path;
  • existing vault-relative path.

Files Service Model

Files service is the raw vault file layer. It works with vault-relative paths and does not understand note semantics.

Rules:

  • All public Files API paths are canonical vault-relative slash paths.
  • Backslashes are rejected instead of normalized.
  • Absolute POSIX paths, Windows drive paths, and UNC/network paths are rejected.
  • .. traversal is rejected.
  • Null bytes are rejected.
  • .verstak/ is reserved case-insensitively and hidden/forbidden by default.
  • Access to .verstak/ is allowed only through internal APIs, not through the normal plugin Files API.
  • Symlink read/write/move/trash operations are forbidden in Milestone 6a. Metadata may report a final symlink as type: "symlink".
  • Writes must be atomic: write a temp file in the same directory, close it, then rename.
  • Delete must follow the trash policy until permanent delete is explicitly designed.
  • Trash metadata records originalPath, deletedAt, originalType, trashId, and basename for future restore work. Restore is deferred.
  • Binary files are deferred for write/streaming APIs. Milestone 6a lists binary metadata but read/write is UTF-8 text only with a 2 MB read limit.

Minimum Files methods:

  • ListVaultFiles(relativeDir).
  • GetVaultFileMetadata(relativePath).
  • ReadVaultTextFile(relativePath).
  • WriteVaultTextFile(relativePath, content, options).
  • CreateVaultFolder(relativePath).
  • MoveVaultPath(fromRelativePath, toRelativePath).
  • TrashVaultPath(relativePath).

Milestone 6a status: implemented in internal/core/files and exposed to bundled plugins as api.files.list, api.files.metadata, api.files.readText, api.files.writeText, api.files.createFolder, api.files.move, and api.files.trash. It is still text-only for reads/writes and has no watcher, binary streaming, external editor integration, or Files UI plugin.

Later Files methods:

  • WatchVaultFiles(scope) once watcher/event delivery is ready.
  • ReadVaultFileBytes / WriteVaultFileBytes for binary files.
  • OpenExternal(relativePath) with explicit permission and UX confirmation.
  • RevealInFileManager(relativePath).

Notes Service Model

Notes API is a semantic layer over Markdown files managed by the Files/path policy.

Rules:

  • A note physically is a .md file.
  • Notes API and Files API must not create two sources of truth.
  • Notes API reads/writes the same files that Files API lists.
  • The note title is the semantic source of truth and is projected to the filename. If frontmatter or a first-heading convention is introduced later, RenameNote must keep that visible title metadata and the filename synchronized.
  • Other note metadata should be derived from the file path and filesystem metadata, or from Markdown frontmatter if a future milestone introduces it.
  • If a note is changed through Files API or an external editor, the future watcher/indexer must observe it.
  • Until watcher/indexer exists, external changes require reload/rescan.

Minimum Notes methods:

  • ListNotes(scope) where scope resolves to a canonical Notes/ folder.
  • GetNote(notePath).
  • CreateNote(scope, title, initialBody).
  • RenameNote(notePath, newTitle).
  • UpdateNoteBody(notePath, body).
  • TrashNote(notePath).

Later Notes methods:

  • SearchNotes(query, filters).
  • ListBacklinks(notePath).
  • ResolveNoteLinks(notePath).
  • ExportNote(notePath, format).

Notes Vs Files Relationship

Files owns safe raw vault file access. Notes owns note semantics.

The same physical note must be visible through both APIs:

  • Files sees Project/Notes/Overview.md as a file.
  • Notes sees Project/Notes/Overview.md as a note with title Overview.

There must be no duplicate note content stored in plugin settings, plugin data, or a separate .verstak note database. Indexes and caches may exist later, but they must be rebuildable from the canonical Markdown files.

Capabilities And Permissions

Existing permissions that remain useful:

  • vault.read for existing vault-level read policy.
  • vault.write for existing vault-level write policy.
  • vault.watch for future watcher support.
  • ui.register for sidebar/views/settings contributions.
  • commands.register for bundled command handlers.
  • events.publish and events.subscribe for frontend/backend event flows.

New permissions required before real Notes/Files plugins ship:

  • files.read
  • files.write
  • files.delete
  • workbench.open

Future Notes semantic permissions are deferred until a real Notes plugin/API ships:

  • notes.read
  • notes.write
  • notes.delete

Those permissions are still policy checks until sidecar/sandbox work provides a real isolation boundary.

Recommended capabilities:

  • verstak/core/files/v1
  • verstak/core/notes/v1
  • verstak/files/v1 provided by the official Files plugin.
  • verstak/notes/v1 provided by the official Notes plugin.

Frontend Components And Extension Points

Contribution points already registered but not fully hosted:

  • fileActions
  • noteActions
  • contextMenuEntries
  • searchProviders
  • activityProviders
  • statusBarItems

UI work needed:

  • Files view host with tree/list modes and selection state.
  • Notes view host with note list, open/edit entry points, and preview/details region.
  • Context menu host that merges core actions with plugin contributions.
  • Command palette host for contributed commands.
  • Search provider host with cancellation/debounce and result ownership.
  • Status bar host for lightweight plugin state.
  • Selection/event model for active file, active note, and active workspace node.

The first implementation should host only the contribution points needed by the official Notes and Files plugins.

Open/Edit Resource Model

Files and Notes must not embed a concrete editor or viewer. They request that the Workbench open or edit a resource. The Workbench/provider registry selects the plugin component.

Required model:

  • Files plugin lists files and calls open/edit for a vault file.
  • Notes plugin presents Markdown files under canonical Notes/ folders and calls open/edit for the same vault file with notes context.
  • .md or .markdown inside a canonical Notes/ folder opens in markdown mode with notes context.
  • .md or .markdown outside Notes/ opens in generic markdown mode.
  • Plain text opens in generic-text mode.
  • The same editor provider may support text, generic markdown, and notes-context markdown.
  • User preferences can select another provider for text, markdown, and notes-context markdown.
  • Community editor plugins can replace the default editor through the same provider registry.
  • Core desktop owns registry, routing, Workbench host slot/tab, and preferences.
  • Core desktop does not own concrete Files UI, Notes UI, Markdown editor, or file preview UI.

Minimal contribution extension:

{
  "contributes": {
    "openProviders": [
      {
        "id": "verstak.platform-test.markdown-diagnostic",
        "title": "Platform Test Markdown Diagnostic",
        "priority": 100,
        "component": "MarkdownDiagnosticProvider",
        "supports": [
          {
            "kind": "vault-file",
            "extensions": [".md", ".markdown"],
            "contexts": ["generic-markdown", "notes-markdown"]
          },
          {
            "kind": "vault-file",
            "mime": ["text/plain"],
            "extensions": [".txt", ".log", ".json", ".yaml", ".yml", ".toml", ".ini", ".conf"],
            "contexts": ["generic-text"]
          }
        ]
      }
    ]
  }
}

Open request shape:

type OpenResourceRequest = {
  kind: "vault-file";
  path: string;
  mode?: "view" | "edit";
  mime?: string;
  extension?: string;
  context?: {
    sourcePluginId?: string;
    sourceView?: "files" | "notes" | string;
    isInsideNotesFolder?: boolean;
    notesScopePath?: string;
    notesMode?: boolean;
  };
};

Provider selection rules:

  1. Match resource kind.
  2. Match extension and/or mime.
  3. Prefer providers that explicitly support the request context.
  4. Apply user preference for text, markdown, or notes-context markdown when the preferred provider is enabled and still supports the resource.
  5. Otherwise choose highest priority.
  6. Break ties deterministically by plugin id, then provider id.
  7. If no provider matches, return a Workbench no-provider state rather than hardcoding a core editor.
  8. If a preferred provider plugin is disabled or unavailable, fall back to the deterministic default and surface a non-blocking preference warning later.

Initial preferences:

  • defaultTextEditorProvider;
  • defaultMarkdownEditorProvider;
  • defaultNotesMarkdownEditorProvider.

Per-extension overrides are deferred.

Migration Risks

  • Adding note as a workspace node type is a workspace schema migration and is explicitly out of scope for the next milestone.
  • The canonical path rules must be locked before writing real files into user vaults.
  • Rename behavior can break external links if link rewriting is not designed.
  • Case/folder path ownership must be clear before scoped Notes/ folders are created.
  • Raw Files API can expose .verstak internals unless reserved paths are blocked.
  • File writes need atomic behavior and conflict handling before sync.
  • Large/binary files require streaming or byte APIs; text APIs are not enough.
  • External editor changes require reload/rescan until watcher/indexer exists.
  • Bundled frontend plugins are trusted/cooperative and not isolated from the shared JS context.

Test Plan

Backend Go tests:

  • Vault-relative path normalization and traversal rejection.
  • Reserved .verstak path behavior.
  • Files list/read/write/mkdir/move/trash with vault closed/open states.
  • Atomic text writes and temp-file cleanup on failure.
  • Notes Notes/ folder casing and no lowercase notes.
  • Title to filename normalization.
  • CreateNote and RenameNote conflict errors without silent suffixes.
  • Notes and Files read the same physical .md file.
  • Permission checks for files.*, notes.*, vault.read, and vault.write.

Frontend/unit tests:

  • SDK and plugin API shape for Files and Notes draft methods.
  • Readable errors for missing permissions, closed vault, missing file, missing note, reserved path, and collision.
  • Contribution host rendering for note/file actions and context menus.

Playwright e2e tests:

  • Create a text file, reload, and verify it is visible in Files.
  • Create a note, reload, and verify it is visible in both Notes and Files.
  • Rename a note and verify title plus filename change together.
  • Attempt same-folder collision and verify user-facing conflict handling.
  • External file change requires reload/rescan until watcher exists.

Implementation Order

  1. Define canonical vault-relative path rules and reserved path policy.
  2. Implement Files core service with safe list/read/write/mkdir/move/trash.
  3. Define open/edit resource request, provider contribution shape, and provider selection rules.
  4. Extend contribution registry/types with openProviders.
  5. Add Workbench open/edit routing and a provider-hosted tab/slot.
  6. Add preferences for text, markdown, and notes-context markdown provider ids.
  7. Use platform-test diagnostic provider to verify routing; real default editor plugin is deferred to Milestone 6c.
  8. Build official Files plugin that calls open/edit resource.
  9. Build official Notes plugin as a contextual view over Markdown files in canonical Notes/ folders.
  10. Implement future Notes semantic helpers only as a facade over Markdown files, never as a second source of truth.