@lapis-notes/api 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,122 @@
1
1
  # @lapis-notes/api
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
8
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
9
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Keep editor saves and
10
+ failures bound to their original document and App across file switches and
11
+ close. Register retained writers with the App-owned session registry, expose
12
+ protection and awaitable disposal, and support a captured asynchronous
13
+ external persistence writer. Block typing/rebinding during guarded admission
14
+ while allowing controlled clean refresh.
15
+
16
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
17
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
18
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Expose the headless
19
+ opaque Sync repository contract and add purpose-separated local-state
20
+ encryption for durable encrypted checkpoints and publication queues. Existing
21
+ encrypted object tags and wire formats remain compatible.
22
+
23
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
24
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
25
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Expose an App-owned file
26
+ editing-session registry for independent file writers. Serialize guarded
27
+ mutation, protect and flush every matching session before admission, retain
28
+ detached dirty writers for retry, and drain cleanup before another mutation or
29
+ shutdown. Production Sync guard selection still requires complete writer
30
+ integration.
31
+
32
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
33
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
34
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Register and protect
35
+ asynchronous editor document loads before reading, drain original drafts and
36
+ publish the target file and contents together. Superseded, unloaded and failed
37
+ loads preserve the previous document and release their provisional session.
38
+ Managed file and folder renames now protect and flush all registered writers
39
+ before moving files or rewriting links.
40
+
41
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
42
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
43
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Expose protocol-neutral
44
+ guarded file generations, immutable mutation identifiers, durable receipts and
45
+ an opaque local-state crypto boundary for host-owned native Sync vault
46
+ transactions. Existing protocol and encryption formats stay unchanged.
47
+
48
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
49
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
50
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Add explicit
51
+ host-delivered pairing without relay addresses while preserving default relay
52
+ invitations and the signed/encrypted Nostr message format. Expose copied
53
+ installed content-key epoch numbers only while unlocked so history policy can
54
+ use the actual keyring instead of assuming an epoch.
55
+
56
+ Expose the same implementation through the headless `sync-security-host`
57
+ export for trusted Node/Deno hosts, with no plugin runtime platforms
58
+ advertised.
59
+
60
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
61
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
62
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Allow trusted hosts to
63
+ retain wrapped Sync keys and network grants in a dedicated security store
64
+ independent of disposable application indexes. Keep legacy storage compatible
65
+ and reject invalid or unavailable dedicated stores without fallback.
66
+
67
+ Expose the shared metadata-store types through a headless host-only
68
+ entrypoint, retaining the existing AppDatabase metadata exports.
69
+
70
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
71
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
72
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Add an optional opaque
73
+ immutable record repository extension for independently encrypted local
74
+ receipts. Native mutation recovery can compact completed work without growing
75
+ its active journal or exposing receipt contents in an index.
76
+
77
+ - [#28](https://github.com/lapismd/lapis-notes/pull/28)
78
+ [`967a92f`](https://github.com/lapismd/lapis-notes/commit/967a92fb123c9cb66fca976c689fafdd00aa65b6)
79
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Allow trusted hosts to
80
+ delegate scoped Sync security to native custody while preserving legacy plugin
81
+ credentials. Check permissions before and after asynchronous operations,
82
+ reject local enrollment for delegated plugins, and await security revocation
83
+ when a plugin is disabled, including unload failures. Add host disposal that
84
+ invalidates runtimes and drains delegated cleanup.
85
+
86
+ ## 0.1.3
87
+
88
+ ### Patch Changes
89
+
90
+ - [#19](https://github.com/lapismd/lapis-notes/pull/19)
91
+ [`a85055b`](https://github.com/lapismd/lapis-notes/commit/a85055b6570244d5dde2ea1145be3609260d91ca)
92
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Restore the omitted API
93
+ 0.1.2 release notes and keep changelog generation independent of optional code
94
+ formatter plugins. Verify current-version notes before and after preparing a
95
+ release so incomplete changelogs stop publication.
96
+
97
+ - [#17](https://github.com/lapismd/lapis-notes/pull/17)
98
+ [`0c8cbd0`](https://github.com/lapismd/lapis-notes/commit/0c8cbd0811663a9c63590d4d730445c80df91e6c)
99
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Download selected plugin
100
+ releases through the catalog's signed delivery endpoint so browser hosts can
101
+ install archives from the registry. Retain verified archive integrity and
102
+ signatures, allow origin fallback for transport failures, and stop fallback
103
+ after cancellation or revocation.
104
+
105
+ ## 0.1.2
106
+
107
+ ### Patch Changes
108
+
109
+ - Use published Bits UI 2.19.5 so destroyed text-selection layers stop document
110
+ pointer listeners before reading component-owned reactive state. Retain the
111
+ existing approved Link Preview selection-probe and listener cleanup when
112
+ refreshing the runtime.
113
+ - Require published Design Core 0.3.2 for native window-controls spacing, bold
114
+ settings headings, selected-tab indicators and mobile shell teardown. Consume
115
+ the npm release through portable semver ranges and lockfile integrity.
116
+ - Expose the existing optional App argument in the Notice declaration so
117
+ app-owned callers can use the public constructor without falling back to
118
+ compatibility state.
119
+
3
120
  ## 0.1.1
4
121
 
5
122
  ### Patch Changes
@@ -7,14 +124,16 @@
7
124
  - Own the transport-neutral registry schema and portable Turso build helper,
8
125
  removing the Community dependency from static registry producers and hosts.
9
126
 
10
- - Expose host-owned secure sync capabilities and controlled declarative plugin settings.
127
+ - Expose host-owned secure sync capabilities and controlled declarative plugin
128
+ settings.
11
129
 
12
130
  - Allow indexed consumers to select metadata fingerprint inputs. Preserve native
13
131
  filesystem error codes and reconcile files removed during startup as deletions
14
132
  while retaining unexpected read failures.
15
133
 
16
- - Support verified static V2 catalogs alongside Nostr sources, retain installation
17
- source provenance, and render themed registry images and structured release notes.
134
+ - Support verified static V2 catalogs alongside Nostr sources, retain
135
+ installation source provenance, and render themed registry images and
136
+ structured release notes.
18
137
  - Updated dependencies []:
19
138
  - @lapis-notes/ui@0.1.1
20
139
 
@@ -86,6 +86,7 @@
86
86
  });
87
87
 
88
88
  function renameFile(name: string | null) {
89
+ if (editor.editingProtected) return;
89
90
  if (name && editor.file && name !== editor.file.name) {
90
91
  const path = `${dirname(editor.file.path)}/${name}.${editor.file.extension}`;
91
92
  app.fileManager.renameFile(editor.file, path);
@@ -125,7 +126,7 @@
125
126
  onfocusout={(evt) =>
126
127
  renameFile((evt.target as HTMLDivElement).getText())}
127
128
  class="inline-title cm-editor-inline-title"
128
- contenteditable="true"
129
+ contenteditable={!editor.editingProtected}
129
130
  {spellcheck}
130
131
  autocapitalize="on"
131
132
  tabindex="-1"
@@ -144,7 +145,7 @@
144
145
  data-mobile-inline-title="true"
145
146
  data-mobile-inline-title-input="true"
146
147
  aria-label="Active note title"
147
- contenteditable="true"
148
+ contenteditable={!editor.editingProtected}
148
149
  {spellcheck}
149
150
  autocapitalize="on"
150
151
  tabindex="-1"
@@ -30,6 +30,7 @@ import { DailyDocumentProviderRegistry } from "./daily-document-provider";
30
30
  import { AppToolRegistry } from "./agent-tools";
31
31
  import { MarkdownExtensionRegistry } from "./markdown-extension-registry";
32
32
  import { MarkdownFileSurfaceRegistry } from "./markdown-file-surface-registry";
33
+ import { FileEditingSessionRegistry } from "./file-editing-sessions";
33
34
  import { NostrSignerHost, type NostrSignerBroker } from "./nostr-signer";
34
35
  import { SyncHostService, type SyncHostServiceOptions } from "./sync-host";
35
36
  export interface AppWorkspaceShellApplicationProperties {
@@ -190,6 +191,7 @@ export declare class App {
190
191
  readonly agentTools: AppToolRegistry;
191
192
  readonly markdownExtensions: MarkdownExtensionRegistry;
192
193
  readonly markdownFileSurfaces: MarkdownFileSurfaceRegistry;
194
+ readonly fileEditingSessions: FileEditingSessionRegistry;
193
195
  readonly agentSkills: import("./agent-skills").AppSkillRegistry;
194
196
  readonly agentSlashCommands: import("./agent-skills").AppSlashCommandRegistry;
195
197
  readonly agentResultViews: import("./agent-result-views").AppResultViewRegistry;
@@ -26,14 +26,16 @@ import { WorkspaceTrustService } from "./workspace-trust";
26
26
  import { DefaultPluginDistributionManager, } from "./plugin-distribution";
27
27
  import { ConfigurationOptionSourceRegistry } from "./configuration-option-source-registry";
28
28
  import { resolveMetadataFieldValuesAsync } from "./configuration-option-source-providers";
29
+ import { flushWorkspaceEditors } from "./sync-editor-flush";
30
+ import { FileEditingSessionRegistry } from "./file-editing-sessions";
29
31
  import { SearchDocumentProviderRegistry } from "./search-document-provider";
30
32
  import { DailyDocumentProviderRegistry } from "./daily-document-provider";
31
33
  import { AppToolRegistry } from "./agent-tools";
32
- import { AppSkillRegistry, AppSlashCommandRegistry, } from "./agent-skills";
34
+ import { AppSkillRegistry, AppSlashCommandRegistry } from "./agent-skills";
33
35
  import { AppResultViewRegistry } from "./agent-result-views";
34
36
  import { MarkdownExtensionRegistry } from "./markdown-extension-registry";
35
37
  import { MarkdownFileSurfaceRegistry } from "./markdown-file-surface-registry";
36
- import { SyncHostService, } from "./sync-host";
38
+ import { SyncHostService } from "./sync-host";
37
39
  import { installApplicationCompatibility, resolveApplication, } from "./application-compatibility";
38
40
  import { provideApplicationState, useApplicationState, } from "./application-state.svelte";
39
41
  const hostSyncServices = new WeakMap();
@@ -167,6 +169,7 @@ export class App {
167
169
  agentResultViews = new AppResultViewRegistry();
168
170
  markdownExtensions = new MarkdownExtensionRegistry();
169
171
  markdownFileSurfaces = new MarkdownFileSurfaceRegistry();
172
+ fileEditingSessions = new FileEditingSessionRegistry();
170
173
  lastEvent = $state(null);
171
174
  renderContext = new RenderContext();
172
175
  secretStorage = new SecretStorage();
@@ -231,15 +234,7 @@ export class App {
231
234
  this.vault = new Vault(adapter);
232
235
  this.#syncHost = new SyncHostService(this.appDatabase, this.vault, Date.now, {
233
236
  ...props.syncHost,
234
- flushOpenEditor: async (path) => {
235
- const active = this.workspace.activeEditor;
236
- if (!active?.editor || !active.file)
237
- return { flushed: false, path };
238
- if (path && active.file.path !== path)
239
- return { flushed: false, path };
240
- await active.editor.flushChanges();
241
- return { flushed: true, path: active.file.path };
242
- },
237
+ flushOpenEditor: (path) => flushWorkspaceEditors(this.workspace, path),
243
238
  });
244
239
  hostSyncServices.set(this, this.#syncHost);
245
240
  this.workspaceTrust = new WorkspaceTrustService(adapter);
@@ -0,0 +1,24 @@
1
+ export interface EditorSaveSnapshot {
2
+ content: string;
3
+ sequence: number;
4
+ documentGeneration: number;
5
+ }
6
+ /** One document's queued saves and failures; the editor supplies shared serialization. */
7
+ export declare class EditorSaveContext {
8
+ #private;
9
+ private readonly options;
10
+ constructor(options: {
11
+ serialize(run: () => Promise<void>): Promise<void>;
12
+ persist(snapshot: EditorSaveSnapshot): Promise<void>;
13
+ saved(snapshot: EditorSaveSnapshot): void;
14
+ unownedExternal: boolean;
15
+ released(): void;
16
+ });
17
+ get dirty(): boolean;
18
+ queue(snapshot: EditorSaveSnapshot, explicit?: boolean): Promise<void> | undefined;
19
+ flushSave(): Promise<void> | undefined;
20
+ cancelSave(): void;
21
+ cancelChange(): void;
22
+ flush(retryFailed?: boolean): Promise<void>;
23
+ retire(): void;
24
+ }
@@ -0,0 +1,101 @@
1
+ import debounce from "lodash-es/debounce";
2
+ /** One document's queued saves and failures; the editor supplies shared serialization. */
3
+ export class EditorSaveContext {
4
+ options;
5
+ #latest;
6
+ #completed = 0;
7
+ #failure;
8
+ #waitingChange;
9
+ #waitingSave;
10
+ #pending = new Set();
11
+ #retired = false;
12
+ #unverified = false;
13
+ #change = debounce((snapshot) => {
14
+ this.#waitingChange = undefined;
15
+ return this.#enqueue(snapshot);
16
+ }, 500);
17
+ #save = debounce((snapshot) => {
18
+ this.#waitingSave = undefined;
19
+ return this.#enqueue(snapshot);
20
+ }, 500);
21
+ constructor(options) {
22
+ this.options = options;
23
+ }
24
+ get dirty() {
25
+ return (!!this.#waitingChange ||
26
+ !!this.#waitingSave ||
27
+ this.#pending.size > 0 ||
28
+ !!this.#failure ||
29
+ this.#unverified);
30
+ }
31
+ queue(snapshot, explicit = false) {
32
+ this.#latest = snapshot;
33
+ if (this.options.unownedExternal)
34
+ this.#unverified = true;
35
+ if (explicit) {
36
+ this.#waitingSave = snapshot;
37
+ return this.#save(snapshot);
38
+ }
39
+ this.#waitingChange = snapshot;
40
+ return this.#change(snapshot);
41
+ }
42
+ flushSave() {
43
+ return this.#save.flush();
44
+ }
45
+ cancelSave() {
46
+ this.#save.cancel();
47
+ this.#waitingSave = undefined;
48
+ }
49
+ cancelChange() {
50
+ this.#change.cancel();
51
+ this.#waitingChange = undefined;
52
+ }
53
+ #flushPending() {
54
+ this.#change.flush();
55
+ this.#save.flush();
56
+ }
57
+ #enqueue(snapshot) {
58
+ const flight = this.options.serialize(async () => {
59
+ if (snapshot.sequence <= this.#completed)
60
+ return;
61
+ await this.options.persist(snapshot);
62
+ this.options.saved(snapshot);
63
+ this.#completed = snapshot.sequence;
64
+ if (this.#failure && this.#failure.snapshot.sequence <= snapshot.sequence)
65
+ this.#failure = undefined;
66
+ });
67
+ this.#pending.add(flight);
68
+ // Observe failures even when a debounce caller does not await its result.
69
+ void flight.then(() => {
70
+ this.#pending.delete(flight);
71
+ this.#releaseIfClean();
72
+ }, (error) => {
73
+ this.#failure = { snapshot, error };
74
+ this.#pending.delete(flight);
75
+ });
76
+ return flight;
77
+ }
78
+ async flush(retryFailed = true) {
79
+ const retry = this.#failure;
80
+ this.#flushPending();
81
+ if (retryFailed && retry && this.#latest === retry.snapshot)
82
+ this.#enqueue(retry.snapshot);
83
+ do {
84
+ await Promise.allSettled([...this.#pending]);
85
+ this.#flushPending();
86
+ } while (this.#pending.size > 0);
87
+ if (this.#failure)
88
+ throw this.#failure.error;
89
+ this.#releaseIfClean();
90
+ }
91
+ retire() {
92
+ this.#retired = true;
93
+ // Retiring a context never replaces another context's debounce payload.
94
+ this.#flushPending();
95
+ this.#releaseIfClean();
96
+ }
97
+ #releaseIfClean() {
98
+ if (this.#retired && !this.dirty)
99
+ this.options.released();
100
+ }
101
+ }
@@ -43,6 +43,12 @@ export interface EditorTransaction {
43
43
  }
44
44
  /** Controls whether an editor persists its own debounced document changes. */
45
45
  export type EditorPersistence = "vault" | "external";
46
+ export interface EditorPersistenceRequest {
47
+ readonly content: string;
48
+ readonly file: TFile | null;
49
+ readonly application: App;
50
+ }
51
+ export type EditorPersistenceWriter = (request: EditorPersistenceRequest) => Promise<void>;
46
52
  export interface MarkdownFileInfo {
47
53
  /** @public */
48
54
  app: App;
@@ -70,10 +76,20 @@ export declare class Editor extends EventDispatcher<{
70
76
  readonly id: `${string}-${string}-${string}-${string}-${string}`;
71
77
  private destroyed;
72
78
  private application;
73
- private readonly pendingChange;
74
79
  constructor(data?: string, extensions?: Extension[], application?: App);
75
80
  /** Bind this editor to the application that owns its view. */
76
81
  bindApplication(application: App): this;
82
+ /** Capture an explicit asynchronous owner for external-mode persistence. */
83
+ setExternalPersistenceWriter(writer?: EditorPersistenceWriter): this;
84
+ get hasUnsavedChanges(): boolean;
85
+ get editingProtected(): boolean;
86
+ get refreshError(): unknown;
87
+ /** Protect the current document and drain its captured persistence. */
88
+ protect(): Promise<() => void>;
89
+ /** Register and protect a target, then publish its file and contents atomically. */
90
+ loadDocument(file: TFile, read: () => Promise<string>, publish?: (contents: string) => void): Promise<boolean>;
91
+ /** Cancel a pending publication while observing any late read result. */
92
+ cancelDocumentLoad(): void;
77
93
  /** The owning application, with the global alias retained as fallback. */
78
94
  get app(): App;
79
95
  readonly save: import("lodash-es").DebouncedFunc<() => Promise<void>>;
@@ -88,6 +104,8 @@ export declare class Editor extends EventDispatcher<{
88
104
  getValue(): string;
89
105
  refresh(): void;
90
106
  destroy(): void;
107
+ /** Stop editing immediately and await all retained document persistence. */
108
+ dispose(): Promise<void>;
91
109
  trackChanges(callback?: (data: string, editor: Editor) => void): () => void;
92
110
  updateExtensions(extensions: Extension[], context?: Record<string, any>): this;
93
111
  setValue(content: string): void;