Files
backnotprop__plannotator/packages/ui/utils/octarine.ts
Michael Ramos d5e0ff6fe6 feat: Octarine notes integration + auto-save for all integrations (#297)
* feat: Octarine notes integration + auto-save for all integrations

Add Octarine as a third notes app integration alongside Obsidian and Bear.
Uses the octarine:// URI scheme to create notes via deep links, following
the same x-callback-url pattern as Bear.

- New file: packages/ui/utils/octarine.ts (cookie-backed settings)
- Server: saveToOctarine() in integrations.ts, wired into /api/approve
  and /api/save-notes endpoints
- UI: Octarine tab in Settings, card in Export > Notes, dropdown button,
  Cmd+S shortcut support
- Parallelize all integration saves with Promise.allSettled (was sequential)
- Add auto-save on plan arrival toggle to Bear and Octarine (Obsidian
  already had this), consolidate into a single effect + single API call

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add YAML frontmatter properties to Octarine notes

Prepend Octarine-compatible YAML frontmatter with tags, Status, Author,
and Last Edited properties. Uses the same extractTags() as Obsidian for
auto-generated tags (project name, title words, code fence languages).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use fresh=true for Octarine saves to prevent content duplication

Octarine's create action appends by default. If auto-save fires on plan
arrival and the user then approves within the same minute, the same path
gets hit twice — doubling the content. Using fresh=true replaces instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: trim workspace and folder in saveToOctarine before building URI

The UI checks workspace.trim().length > 0 but the server used raw values.
Accidental whitespace in settings would cause Octarine saves to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use isOctarineConfigured() for client-side Octarine guards

Auto-save and approve checks used raw workspace truthiness, so
whitespace-only workspace would trigger a save attempt that the server
rejects. Now uses isOctarineConfigured() which trims before checking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent Bear duplicate notes and align default-save dropdown gates

Bear creates a new note on every save, so skip it on approve when
arrival auto-save already succeeded. Gate the default-save dropdown
by actual configuration (vault path for Obsidian, workspace for
Octarine) instead of just the enabled toggle, matching the shortcut
behavior. Self-heal stale defaults back to "ask".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 21:07:01 -07:00

54 lines
1.6 KiB
TypeScript

/**
* Octarine Notes Integration Utility
*
* Manages settings for auto-saving plans to Octarine.
* Uses octarine:// URI scheme — no vault detection needed.
*/
import { storage } from './storage';
const STORAGE_KEY_ENABLED = 'plannotator-octarine-enabled';
const STORAGE_KEY_WORKSPACE = 'plannotator-octarine-workspace';
const STORAGE_KEY_FOLDER = 'plannotator-octarine-folder';
const STORAGE_KEY_AUTOSAVE = 'plannotator-octarine-autosave';
/**
* Octarine integration settings
*/
export interface OctarineSettings {
enabled: boolean;
workspace: string;
folder: string;
autoSave: boolean;
}
/**
* Get current Octarine settings from storage
*/
export function getOctarineSettings(): OctarineSettings {
return {
enabled: storage.getItem(STORAGE_KEY_ENABLED) === 'true',
workspace: storage.getItem(STORAGE_KEY_WORKSPACE) ?? '',
folder: storage.getItem(STORAGE_KEY_FOLDER) || 'plannotator',
autoSave: storage.getItem(STORAGE_KEY_AUTOSAVE) === 'true',
};
}
/**
* Save Octarine settings to storage
*/
export function saveOctarineSettings(settings: OctarineSettings): void {
storage.setItem(STORAGE_KEY_ENABLED, String(settings.enabled));
storage.setItem(STORAGE_KEY_WORKSPACE, settings.workspace);
storage.setItem(STORAGE_KEY_FOLDER, settings.folder);
storage.setItem(STORAGE_KEY_AUTOSAVE, String(settings.autoSave));
}
/**
* Check if Octarine integration is properly configured
*/
export function isOctarineConfigured(): boolean {
const settings = getOctarineSettings();
return settings.enabled && settings.workspace.trim().length > 0;
}