@lapis-notes/api 0.1.3 → 0.2.0

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.
Files changed (199) hide show
  1. package/CHANGELOG.md +304 -13
  2. package/README.md +19 -0
  3. package/dist/__tests__/fixtures/indexed-mutations.json +6862 -0
  4. package/dist/__tests__/fixtures/memory-storage.json +309 -0
  5. package/dist/__tests__/fixtures/native-facade-contract.json +3224 -0
  6. package/dist/__tests__/fixtures/projection-query.json +195 -0
  7. package/dist/__tests__/fixtures/projection-values.json +60 -0
  8. package/dist/__tests__/fixtures/search-preparation.json +1513 -0
  9. package/dist/__tests__/fixtures/search-retrieval.json +15409 -0
  10. package/dist/__tests__/fixtures/search-semantic-model.json +3691 -0
  11. package/dist/__tests__/fixtures/search-semantic-retrieval.json +6603 -0
  12. package/dist/__tests__/fixtures/search-storage.json +409 -0
  13. package/dist/__tests__/fixtures/task-projection.json +428 -0
  14. package/dist/__tests__/indexed-mutation-support.js +262 -0
  15. package/dist/__tests__/projection-query-support.js +40 -0
  16. package/dist/__tests__/search-retrieval-support.js +47 -0
  17. package/dist/app-database-host.d.ts +1 -0
  18. package/dist/app-shell-plugin-persistence.d.ts +3 -2
  19. package/dist/app-shell-plugin-persistence.js +37 -3
  20. package/dist/cache.svelte.d.ts +8 -1
  21. package/dist/cache.svelte.js +103 -5
  22. package/dist/command.svelte.js +29 -2
  23. package/dist/components/editor/editor.css +4 -0
  24. package/dist/components/editor/editor.svelte +24 -11
  25. package/dist/components/editor/embedded-editor-surface.js +3 -1
  26. package/dist/components/editor/inline-title-alignment.d.ts +3 -0
  27. package/dist/components/editor/inline-title-alignment.js +57 -0
  28. package/dist/configuration.svelte.d.ts +270 -206
  29. package/dist/configuration.svelte.js +161 -86
  30. package/dist/context.svelte.d.ts +23 -3
  31. package/dist/context.svelte.js +68 -30
  32. package/dist/diagnostics.d.ts +8 -0
  33. package/dist/diagnostics.js +6 -0
  34. package/dist/editor-save-context.d.ts +35 -0
  35. package/dist/editor-save-context.js +132 -0
  36. package/dist/editor-view-registry.d.ts +10 -0
  37. package/dist/editor-view-registry.js +44 -4
  38. package/dist/editor.svelte.d.ts +19 -1
  39. package/dist/editor.svelte.js +528 -55
  40. package/dist/embed-registry.d.ts +10 -0
  41. package/dist/embed-registry.js +15 -0
  42. package/dist/file-editing-failures.d.ts +2 -0
  43. package/dist/file-editing-failures.js +29 -0
  44. package/dist/file-editing-native-issues.d.ts +19 -0
  45. package/dist/file-editing-native-issues.js +65 -0
  46. package/dist/file-editing-sessions.d.ts +76 -0
  47. package/dist/file-editing-sessions.js +367 -0
  48. package/dist/file-manager.d.ts +9 -1
  49. package/dist/file-manager.js +45 -3
  50. package/dist/file-state-store.d.ts +5 -0
  51. package/dist/file-state-store.js +34 -0
  52. package/dist/file-text-conflicts.d.ts +25 -0
  53. package/dist/file-text-conflicts.js +35 -0
  54. package/dist/file-text-mutation.d.ts +13 -0
  55. package/dist/file-text-mutation.js +100 -0
  56. package/dist/file-text-persistence.d.ts +36 -0
  57. package/dist/file-text-persistence.js +234 -0
  58. package/dist/file-text-state-writer.d.ts +34 -0
  59. package/dist/file-text-state-writer.js +183 -0
  60. package/dist/files-configuration.d.ts +20 -0
  61. package/dist/files-configuration.js +47 -0
  62. package/dist/generated/plugin-host-modules.schema.generated.d.ts +114 -34
  63. package/dist/generated/plugin-host-modules.schema.generated.js +132 -34
  64. package/dist/glob.d.ts +3 -0
  65. package/dist/glob.js +2 -1
  66. package/dist/icons/changes.d.ts +3 -0
  67. package/dist/icons/changes.js +16 -0
  68. package/dist/icons/index.d.ts +5 -1
  69. package/dist/icons/index.js +44 -3
  70. package/dist/icons/registry.d.ts +35 -0
  71. package/dist/icons/registry.js +154 -0
  72. package/dist/icons/safe-svg.d.ts +2 -0
  73. package/dist/icons/safe-svg.js +44 -0
  74. package/dist/index.d.ts +6 -1
  75. package/dist/index.js +4 -1
  76. package/dist/lapis-extension.d.ts +6 -2
  77. package/dist/lapis-extension.js +2 -0
  78. package/dist/metadata-store.d.ts +23 -0
  79. package/dist/metadata-store.js +1 -0
  80. package/dist/metadata.svelte.js +45 -2
  81. package/dist/native-background-indexing.d.ts +48 -0
  82. package/dist/native-background-indexing.js +208 -0
  83. package/dist/native-database-protocol.d.ts +29 -0
  84. package/dist/native-database-protocol.js +1 -0
  85. package/dist/native-editing-intent-lease.d.ts +17 -0
  86. package/dist/native-editing-intent-lease.js +188 -0
  87. package/dist/native-editing-intents-protocol.d.ts +70 -0
  88. package/dist/native-editing-intents-protocol.js +1 -0
  89. package/dist/native-editing-intents.d.ts +8 -0
  90. package/dist/native-editing-intents.js +1 -0
  91. package/dist/native-editing-problems.d.ts +3 -0
  92. package/dist/native-editing-problems.js +180 -0
  93. package/dist/native-editing-recovery.d.ts +24 -0
  94. package/dist/native-editing-recovery.js +198 -0
  95. package/dist/native-editing-session.d.ts +51 -0
  96. package/dist/native-editing-session.js +251 -0
  97. package/dist/native-history-index-protocol.d.ts +80 -0
  98. package/dist/native-history-index-protocol.js +1 -0
  99. package/dist/native-history-index.d.ts +6 -0
  100. package/dist/native-history-index.js +1 -0
  101. package/dist/native-indexing-protocol.d.ts +63 -0
  102. package/dist/native-indexing-protocol.js +1 -0
  103. package/dist/native-participant-protocol.d.ts +67 -0
  104. package/dist/native-participant-protocol.js +3 -0
  105. package/dist/native-queue-protocol.d.ts +73 -0
  106. package/dist/native-queue-protocol.js +3 -0
  107. package/dist/native-tasks-protocol.d.ts +264 -0
  108. package/dist/native-tasks-protocol.js +1 -0
  109. package/dist/native-tasks.d.ts +35 -0
  110. package/dist/native-tasks.js +79 -0
  111. package/dist/native-turn-protocol.d.ts +81 -0
  112. package/dist/native-turn-protocol.js +1 -0
  113. package/dist/native-turns.d.ts +29 -0
  114. package/dist/native-turns.js +2 -0
  115. package/dist/plugin-asset-server.js +3 -2
  116. package/dist/plugin-dependency-resolver.d.ts +1 -1
  117. package/dist/plugin-distribution/compatibility.d.ts +1 -1
  118. package/dist/plugin-distribution/compatibility.js +10 -5
  119. package/dist/plugin-distribution/installed-plugin-state.js +2 -1
  120. package/dist/plugin-distribution/installer.d.ts +6 -0
  121. package/dist/plugin-distribution/installer.js +90 -37
  122. package/dist/plugin-distribution/manager.d.ts +4 -1
  123. package/dist/plugin-distribution/manager.js +12 -5
  124. package/dist/plugin-distribution/registry-cache.js +2 -1
  125. package/dist/plugin-distribution/registry-source-state.js +2 -1
  126. package/dist/plugin-distribution/schemas.d.ts +26 -8
  127. package/dist/plugin-distribution/schemas.js +2 -1
  128. package/dist/plugin-distribution/types.d.ts +4 -0
  129. package/dist/plugin-manager.d.ts +21 -5
  130. package/dist/plugin-manager.js +228 -64
  131. package/dist/plugin-runtime-entry.d.ts +1 -1
  132. package/dist/plugin-runtime-entry.js +16 -0
  133. package/dist/plugin.d.ts +19 -3
  134. package/dist/plugin.js +110 -44
  135. package/dist/registry-schema/catalog-v1.d.ts +60 -9
  136. package/dist/registry-schema/catalog-v1.js +2 -1
  137. package/dist/registry-schema/catalog-v2.d.ts +60 -9
  138. package/dist/registry-schema/catalog-v2.js +1 -1
  139. package/dist/search-result-provider.d.ts +95 -0
  140. package/dist/search-result-provider.js +42 -0
  141. package/dist/status-bar.svelte.d.ts +26 -20
  142. package/dist/status-bar.svelte.js +1 -0
  143. package/dist/storage/app-database.d.ts +9 -23
  144. package/dist/storage/app-database.js +41 -34
  145. package/dist/storage/desktop-native.d.ts +5 -3
  146. package/dist/storage/desktop-native.js +59 -6
  147. package/dist/storage/directory-watcher.d.ts +27 -0
  148. package/dist/storage/directory-watcher.js +201 -0
  149. package/dist/storage/fs.d.ts +14 -27
  150. package/dist/storage/fs.js +2 -168
  151. package/dist/storage/index-projection.js +33 -15
  152. package/dist/storage/memory-vault-adapter.d.ts +2 -0
  153. package/dist/storage/memory-vault-adapter.js +14 -1
  154. package/dist/storage/path.d.ts +6 -2
  155. package/dist/storage/path.js +8 -2
  156. package/dist/storage/projection-query-candidates.d.ts +3 -0
  157. package/dist/storage/projection-query-candidates.js +69 -0
  158. package/dist/storage/remote-app-database.d.ts +2 -2
  159. package/dist/storage/remote-app-database.js +1 -1
  160. package/dist/storage/runtime.d.ts +3 -0
  161. package/dist/storage/runtime.js +3 -0
  162. package/dist/storage/search-embedding-record.d.ts +3 -0
  163. package/dist/storage/search-embedding-record.js +18 -0
  164. package/dist/storage/turso-app-database.d.ts +2 -0
  165. package/dist/storage/turso-app-database.js +67 -30
  166. package/dist/storage/vault-listing.d.ts +3 -0
  167. package/dist/storage/vault-listing.js +36 -0
  168. package/dist/storage/vault-reconciliation.d.ts +19 -0
  169. package/dist/storage/vault-reconciliation.js +137 -0
  170. package/dist/storage/vault-session.d.ts +8 -0
  171. package/dist/storage/vault-session.js +10 -2
  172. package/dist/storage/vault.svelte.d.ts +53 -4
  173. package/dist/storage/vault.svelte.js +223 -87
  174. package/dist/sync-editor-flush.d.ts +6 -0
  175. package/dist/sync-editor-flush.js +22 -0
  176. package/dist/sync-host-local.d.ts +15 -0
  177. package/dist/sync-host-local.js +228 -0
  178. package/dist/sync-host-security.d.ts +24 -9
  179. package/dist/sync-host-security.js +47 -14
  180. package/dist/sync-host.d.ts +33 -4
  181. package/dist/sync-host.js +160 -10
  182. package/dist/sync-repository.d.ts +106 -0
  183. package/dist/sync-repository.js +18 -0
  184. package/dist/vault-change-views.d.ts +4 -0
  185. package/dist/vault-change-views.js +43 -0
  186. package/dist/vault-changes.d.ts +37 -0
  187. package/dist/vault-changes.js +228 -0
  188. package/dist/view.svelte.d.ts +182 -149
  189. package/dist/view.svelte.js +39 -21
  190. package/dist/workspace-close-guard.d.ts +3 -0
  191. package/dist/workspace-close-guard.js +35 -0
  192. package/dist/workspace-host-internal.js +10 -2
  193. package/dist/workspace-layout-normalizer.d.ts +4 -2
  194. package/dist/workspace-layout-normalizer.js +5 -5
  195. package/dist/workspace-view-document.d.ts +3 -0
  196. package/dist/workspace-view-document.js +12 -0
  197. package/dist/workspace.svelte.d.ts +13 -2
  198. package/dist/workspace.svelte.js +371 -82
  199. package/package.json +61 -8
@@ -0,0 +1,198 @@
1
+ const matches = (left, right) => left.editor_id === right.editor_id &&
2
+ left.path === right.path &&
3
+ left.generation === right.generation &&
4
+ left.revision === right.revision &&
5
+ left.active === right.active &&
6
+ left.dirty === right.dirty &&
7
+ left.detached === right.detached;
8
+ /** Passive recovery metadata. Native intents contain no document or draft bytes. */
9
+ export class NativeEditingRecovery {
10
+ port;
11
+ track;
12
+ #listeners = new Set();
13
+ #adopted = new Set();
14
+ #acting = new Set();
15
+ #snapshot = Object.freeze({
16
+ claims: Object.freeze([]),
17
+ });
18
+ #observers = 0;
19
+ #epoch = 0;
20
+ #mutation = 0;
21
+ #timer;
22
+ #pending;
23
+ constructor(port, track) {
24
+ this.port = port;
25
+ this.track = track;
26
+ }
27
+ snapshot() {
28
+ return this.#snapshot;
29
+ }
30
+ subscribe(listener) {
31
+ this.#listeners.add(listener);
32
+ listener(this.#snapshot);
33
+ return () => {
34
+ this.#listeners.delete(listener);
35
+ };
36
+ }
37
+ /** Shell-owned read-only observation; multiple shells share one poll. */
38
+ observe() {
39
+ if (this.#observers++ === 0)
40
+ void this.#poll(++this.#epoch);
41
+ let held = true;
42
+ return () => {
43
+ if (!held)
44
+ return;
45
+ held = false;
46
+ if (--this.#observers === 0) {
47
+ this.#epoch++;
48
+ clearTimeout(this.#timer);
49
+ this.#timer = undefined;
50
+ }
51
+ };
52
+ }
53
+ async #poll(epoch) {
54
+ try {
55
+ await this.#load(epoch);
56
+ }
57
+ catch {
58
+ /* Failure remains in the snapshot. */
59
+ }
60
+ if (this.#observers && epoch === this.#epoch)
61
+ this.#timer = setTimeout(() => void this.#poll(epoch), 1000);
62
+ }
63
+ refresh() {
64
+ return this.#load(this.#epoch);
65
+ }
66
+ #load(epoch) {
67
+ if (this.#pending?.epoch === epoch)
68
+ return this.#pending.promise;
69
+ const mutation = this.#mutation;
70
+ const promise = (async () => {
71
+ try {
72
+ const value = await this.port.call({ kind: "list" });
73
+ if (value.kind !== "states")
74
+ throw new Error("Native editing claims are unavailable");
75
+ if (epoch !== this.#epoch || mutation !== this.#mutation)
76
+ return;
77
+ const retained = new Set(value.states.map((state) => state.editor_id));
78
+ for (const id of this.#adopted)
79
+ if (!retained.has(id))
80
+ this.#adopted.delete(id);
81
+ this.#publish(value.states.filter((state) => state.detached || this.#adopted.has(state.editor_id)));
82
+ }
83
+ catch (error) {
84
+ if (epoch === this.#epoch && mutation === this.#mutation)
85
+ this.#publish(this.#snapshot.claims, error);
86
+ throw error;
87
+ }
88
+ })();
89
+ const pending = { epoch, promise };
90
+ this.#pending = pending;
91
+ void promise
92
+ .finally(() => {
93
+ if (this.#pending === pending)
94
+ this.#pending = undefined;
95
+ })
96
+ .catch(() => { });
97
+ return promise;
98
+ }
99
+ /** Hosts call this before destructive App/registry teardown. */
100
+ async assertClear() {
101
+ await this.refresh();
102
+ if (this.#snapshot.claims.length)
103
+ throw new Error("Unresolved editing claims require review in Problems");
104
+ }
105
+ recover(captured) {
106
+ return this.#act("recover", captured);
107
+ }
108
+ discard(captured) {
109
+ return this.#act("discard", captured);
110
+ }
111
+ #act(kind, captured) {
112
+ return this.track(async () => {
113
+ const current = this.#snapshot.claims.find((state) => state.editor_id === captured.editor_id);
114
+ if (!current || !matches(current, captured))
115
+ throw new Error("Editing claim changed; refresh Problems");
116
+ if (this.#acting.has(current.editor_id))
117
+ throw new Error("Editing claim recovery is pending");
118
+ if (kind === "recover" && !current.detached)
119
+ throw new Error("Editing claim is already attached");
120
+ this.#acting.add(current.editor_id);
121
+ this.#mutation++;
122
+ const accepts = (value) => kind === "discard"
123
+ ? value.kind === "retired"
124
+ : value.kind === "state" &&
125
+ matches(value.state, {
126
+ ...current,
127
+ generation: current.generation + 1,
128
+ detached: false,
129
+ });
130
+ try {
131
+ let value;
132
+ try {
133
+ value = await this.port.call({
134
+ kind,
135
+ editor_id: current.editor_id,
136
+ generation: current.generation,
137
+ revision: current.revision,
138
+ });
139
+ if (!accepts(value))
140
+ throw new Error("Editing claim recovery response changed");
141
+ }
142
+ catch (error) {
143
+ try {
144
+ const inspected = await this.port.call({
145
+ kind: "inspect",
146
+ editor_id: current.editor_id,
147
+ });
148
+ if (!accepts(inspected))
149
+ throw error;
150
+ value = inspected;
151
+ }
152
+ catch {
153
+ throw error;
154
+ }
155
+ }
156
+ const remaining = this.#snapshot.claims.filter((state) => state.editor_id !== current.editor_id);
157
+ if (value.kind === "state") {
158
+ this.#adopted.add(current.editor_id);
159
+ this.#publish([...remaining, value.state]);
160
+ }
161
+ else {
162
+ this.#adopted.delete(current.editor_id);
163
+ this.#publish(remaining);
164
+ }
165
+ }
166
+ catch (error) {
167
+ this.#publish(this.#snapshot.claims, error);
168
+ throw error;
169
+ }
170
+ finally {
171
+ this.#acting.delete(current.editor_id);
172
+ }
173
+ });
174
+ }
175
+ #publish(claims, error) {
176
+ const message = error
177
+ ? error instanceof Error
178
+ ? error.message
179
+ : String(error)
180
+ : undefined;
181
+ if (this.#snapshot.error === message &&
182
+ claims.length === this.#snapshot.claims.length &&
183
+ claims.every((state, index) => matches(state, this.#snapshot.claims[index])))
184
+ return;
185
+ this.#snapshot = Object.freeze({
186
+ claims: Object.freeze(claims.map((state) => Object.freeze({ ...state }))),
187
+ ...(message ? { error: message } : {}),
188
+ });
189
+ for (const listener of this.#listeners) {
190
+ try {
191
+ listener(this.#snapshot);
192
+ }
193
+ catch (error) {
194
+ console.error("Editing recovery subscriber failed", error);
195
+ }
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,51 @@
1
+ import type { FileEditingSession } from "./file-editing-sessions";
2
+ import type { NativeEditingIntents } from "./native-editing-intents";
3
+ /** Producer-owned local protection and last acknowledged destination bytes. */
4
+ export interface NativeEditingSession {
5
+ /** Block input synchronously, before native admission's first await. */
6
+ hold(): () => void | Promise<void>;
7
+ savedText(): string | null | undefined;
8
+ /**
9
+ * Observe native-owned saved bytes while the existing producer stays
10
+ * protected.
11
+ */
12
+ refreshSavedText?(text: string | null): void | Promise<void>;
13
+ /** Logical state has no editable buffer while idle. Documents remain default. */
14
+ readonly lifetime?: "interactive" | "operation";
15
+ }
16
+ export type NativeEditingPhase = "admission" | "handoff" | "rearm" | "retirement";
17
+ export interface NativeEditingIssue {
18
+ readonly id: string;
19
+ readonly path: string;
20
+ readonly phase: NativeEditingPhase;
21
+ readonly status: "pending" | "failed";
22
+ readonly message: string;
23
+ }
24
+ export type FileEditingRegistration = (() => void) & {
25
+ readonly native: boolean;
26
+ /** An explicit retry may reconcile native admission without changing identity. */
27
+ ready(): Promise<void>;
28
+ /** Capture a newer dirty generation; the active intent already fences writers. */
29
+ changed(): void;
30
+ /** Quiesce an acknowledged noninteractive operation; documents stay active. */
31
+ idle(): Promise<void>;
32
+ };
33
+ /** Adds a native fence to an existing writer without replacing its persistence. */
34
+ export declare class NativeRegisteredEditingSession implements FileEditingSession {
35
+ #private;
36
+ private readonly session;
37
+ private readonly port;
38
+ private readonly path;
39
+ private readonly retiring;
40
+ constructor(session: FileEditingSession, port: NativeEditingIntents, path: string, retiring: () => boolean);
41
+ get dirty(): boolean;
42
+ get retired(): boolean;
43
+ get issue(): NativeEditingIssue | undefined;
44
+ subscribe(listener: () => void): () => void;
45
+ settle(): Promise<void>;
46
+ ready(): Promise<void>;
47
+ changed(): void;
48
+ idle(): Promise<void>;
49
+ flush(): Promise<void>;
50
+ protect(): Promise<() => Promise<void>>;
51
+ }
@@ -0,0 +1,251 @@
1
+ import { NativeEditingIntentLease } from "./native-editing-intent-lease";
2
+ /** Adds a native fence to an existing writer without replacing its persistence. */
3
+ export class NativeRegisteredEditingSession {
4
+ session;
5
+ port;
6
+ path;
7
+ retiring;
8
+ #lease;
9
+ #initialRelease;
10
+ #initiallyDirty;
11
+ #initialDirtyReported = false;
12
+ #protected = 0;
13
+ #scoped;
14
+ #admitted = false;
15
+ #idle = false;
16
+ #refreshPending = false;
17
+ #handoffFailure;
18
+ #issue;
19
+ #epoch = 0;
20
+ #pending = new Set();
21
+ #listeners = new Set();
22
+ constructor(session, port, path, retiring) {
23
+ this.session = session;
24
+ this.port = port;
25
+ this.path = path;
26
+ this.retiring = retiring;
27
+ if (!session.native)
28
+ throw new Error(`Native editing lifecycle is required: ${path}`);
29
+ this.#scoped = session.native.lifetime === "operation";
30
+ this.#initiallyDirty = session.dirty;
31
+ if (!this.#scoped || this.#initiallyDirty)
32
+ void this.ready().catch(() => { });
33
+ }
34
+ get dirty() {
35
+ return this.session.dirty;
36
+ }
37
+ get retired() {
38
+ return !this.#lease || this.#lease.retired;
39
+ }
40
+ get issue() {
41
+ return this.#issue;
42
+ }
43
+ subscribe(listener) {
44
+ this.#listeners.add(listener);
45
+ return () => this.#listeners.delete(listener);
46
+ }
47
+ #publish(issue) {
48
+ this.#issue = issue;
49
+ for (const listener of [...this.#listeners]) {
50
+ try {
51
+ listener();
52
+ }
53
+ catch (error) {
54
+ console.error("Native editor issue listener failed", error);
55
+ }
56
+ }
57
+ }
58
+ async settle() {
59
+ while (this.#pending.size)
60
+ await Promise.allSettled([...this.#pending]);
61
+ }
62
+ #transition(phase, run) {
63
+ this.#epoch++;
64
+ const pending = Object.freeze({
65
+ id: crypto.randomUUID(),
66
+ path: this.path,
67
+ phase,
68
+ status: "pending",
69
+ message: `Native editor ${phase} is pending`,
70
+ });
71
+ this.#publish(pending);
72
+ const transition = (async () => {
73
+ try {
74
+ await run();
75
+ if (this.#issue === pending)
76
+ this.#publish(undefined);
77
+ }
78
+ catch (error) {
79
+ if (this.#issue === pending)
80
+ this.#publish(Object.freeze({
81
+ ...pending,
82
+ status: "failed",
83
+ message: `Native editor ${phase} requires retry`,
84
+ }));
85
+ throw error;
86
+ }
87
+ })();
88
+ this.#pending.add(transition);
89
+ void transition
90
+ .finally(() => this.#pending.delete(transition))
91
+ .catch(() => { });
92
+ return transition;
93
+ }
94
+ #hold() {
95
+ this.#initialRelease ??= this.session.native.hold();
96
+ if (typeof this.#initialRelease !== "function")
97
+ throw new Error("Invalid native editing protection release");
98
+ }
99
+ #intent() {
100
+ return (this.#lease ??= new NativeEditingIntentLease(this.port, this.path, () => this.#scoped && !this.session.dirty
101
+ ? undefined
102
+ : this.session.native.savedText()));
103
+ }
104
+ async #releaseInitial() {
105
+ if (!this.#initialRelease)
106
+ return;
107
+ await this.#initialRelease();
108
+ this.#initialRelease = undefined;
109
+ }
110
+ async #refreshSaved() {
111
+ if (this.session.dirty)
112
+ throw new Error(`Protected saved-text refresh requires clean state: ${this.path}`);
113
+ const native = this.session.native;
114
+ const before = native.savedText();
115
+ const current = await this.port.read(this.path);
116
+ const text = current?.text ?? null;
117
+ if (before === undefined || before === text)
118
+ return;
119
+ if (!native.refreshSavedText)
120
+ throw new Error(`Protected saved-text refresh is unavailable: ${this.path}`);
121
+ await native.refreshSavedText(text);
122
+ if (this.session.dirty || native.savedText() !== text)
123
+ throw new Error(`Protected saved-text refresh was not acknowledged: ${this.path}`);
124
+ }
125
+ async ready() {
126
+ this.#hold();
127
+ const lease = this.#intent();
128
+ if (!this.#admitted || (this.#scoped && (this.#idle || this.#issue))) {
129
+ const phase = this.#admitted ? "rearm" : "admission";
130
+ await this.#transition(phase, () => this.#scoped ? lease.resume() : lease.ready());
131
+ this.#admitted = true;
132
+ this.#idle = false;
133
+ }
134
+ else
135
+ await lease.ready();
136
+ if (this.#initiallyDirty && !this.#initialDirtyReported) {
137
+ this.#initialDirtyReported = true;
138
+ this.changed();
139
+ }
140
+ await this.#releaseInitial();
141
+ }
142
+ changed() {
143
+ if (!this.#lease)
144
+ throw new Error("Native state admission is required");
145
+ const epoch = this.#epoch;
146
+ void this.#lease.changed().catch(() => {
147
+ if (this.#epoch !== epoch)
148
+ return;
149
+ this.#publish(Object.freeze({
150
+ id: crypto.randomUUID(),
151
+ path: this.path,
152
+ phase: "handoff",
153
+ status: "failed",
154
+ message: "Native editor change reporting requires retry",
155
+ }));
156
+ });
157
+ }
158
+ async idle() {
159
+ if (!this.#scoped ||
160
+ !this.#lease ||
161
+ this.session.dirty ||
162
+ (this.#idle && !this.#issue))
163
+ return;
164
+ this.#hold();
165
+ await this.#transition("handoff", () => this.#lease.yield());
166
+ this.#idle = true;
167
+ await this.#releaseInitial();
168
+ }
169
+ async flush() {
170
+ if (this.#lease?.retired && !this.session.dirty)
171
+ return;
172
+ if (this.#scoped && !this.session.dirty) {
173
+ await this.idle();
174
+ return;
175
+ }
176
+ await this.ready();
177
+ await this.session.flush();
178
+ if (this.retiring() && !this.session.dirty)
179
+ await this.#transition("retirement", () => this.#lease.retire());
180
+ else if (this.#scoped)
181
+ await this.idle();
182
+ else if (this.#protected && !this.session.dirty)
183
+ await this.#yieldProtected();
184
+ }
185
+ async #yieldProtected() {
186
+ try {
187
+ await this.#reconcileBeforeYield();
188
+ await this.#transition("handoff", () => this.#lease.yield());
189
+ this.#handoffFailure = undefined;
190
+ }
191
+ catch (error) {
192
+ this.#handoffFailure = { error };
193
+ throw error;
194
+ }
195
+ }
196
+ /**
197
+ * A clean interactive producer acknowledges authoritative saved bytes under
198
+ * the guard's held protection (LN-CORE-028), so external drift reconciles
199
+ * exactly like the producer's own file-change projection before the yield
200
+ * comparison. A failed observation defers to the fenced yield check, which
201
+ * retains protection and the observable handoff retry.
202
+ */
203
+ async #reconcileBeforeYield() {
204
+ if (this.session.dirty || !this.session.native?.refreshSavedText)
205
+ return;
206
+ try {
207
+ await this.#refreshSaved();
208
+ }
209
+ catch {
210
+ /* The yield comparison below remains the authoritative fenced failure. */
211
+ }
212
+ }
213
+ async protect() {
214
+ // The producer acquires its local lock synchronously before yielding.
215
+ const protection = this.session.protect();
216
+ const release = await protection;
217
+ this.#protected++;
218
+ let held = true, cleanupStarted = false;
219
+ return async () => {
220
+ if (!held)
221
+ return;
222
+ const retry = cleanupStarted;
223
+ cleanupStarted = true;
224
+ if (this.retiring() && !this.session.dirty && this.#lease) {
225
+ await this.#transition("retirement", () => this.#lease.retire());
226
+ this.#handoffFailure = undefined;
227
+ }
228
+ else {
229
+ if (this.#handoffFailure) {
230
+ // Initial cleanup cannot turn the failed Yield into a no-op Resume.
231
+ if (!retry)
232
+ throw this.#handoffFailure.error;
233
+ await this.#yieldProtected();
234
+ }
235
+ if (!this.#scoped || this.session.dirty)
236
+ await this.#transition("rearm", async () => {
237
+ if (this.#lease?.yielded && !this.session.dirty)
238
+ this.#refreshPending = true;
239
+ await this.#intent().resume();
240
+ if (this.#refreshPending) {
241
+ await this.#refreshSaved();
242
+ this.#refreshPending = false;
243
+ }
244
+ });
245
+ }
246
+ await release();
247
+ held = false;
248
+ this.#protected--;
249
+ };
250
+ }
251
+ }
@@ -0,0 +1,80 @@
1
+ export type HistoryExpression = {
2
+ type: "term";
3
+ field: string | null;
4
+ value: string;
5
+ } | {
6
+ type: "and";
7
+ clauses: Array<HistoryExpression>;
8
+ } | {
9
+ type: "or";
10
+ clauses: Array<HistoryExpression>;
11
+ } | {
12
+ type: "not";
13
+ operand: HistoryExpression;
14
+ };
15
+ export type HistoryIndexQuery = {
16
+ expression: HistoryExpression;
17
+ case_sensitive: boolean;
18
+ cursor: string | null;
19
+ limit: number;
20
+ };
21
+ export type HistoryIndexCommand = {
22
+ kind: "start";
23
+ } | {
24
+ kind: "status";
25
+ } | {
26
+ kind: "query";
27
+ search: HistoryIndexQuery;
28
+ } | {
29
+ kind: "context";
30
+ commit: string;
31
+ path: string;
32
+ start: number;
33
+ end: number;
34
+ };
35
+ export type HistoryIndexProgress = {
36
+ generation: number;
37
+ indexed: number;
38
+ omitted: number;
39
+ complete: boolean;
40
+ };
41
+ export type HistoryCheckpoint = {
42
+ operation: string;
43
+ commit: string;
44
+ change_id: string;
45
+ description: string;
46
+ timestamp_millis: number;
47
+ };
48
+ export type HistorySavedRevision = {
49
+ checkpoint: HistoryCheckpoint;
50
+ author: string;
51
+ email: string;
52
+ };
53
+ export type HistoryIndexRecord = {
54
+ revision: HistorySavedRevision;
55
+ parents: Array<string>;
56
+ path: string | null;
57
+ content_id: string | null;
58
+ deleted: boolean;
59
+ previous: string | null;
60
+ unavailable: boolean;
61
+ };
62
+ export type HistoryIndexHit = {
63
+ record: HistoryIndexRecord;
64
+ excerpt: string;
65
+ line: number | null;
66
+ };
67
+ export type HistoryIndexPage = {
68
+ status: HistoryIndexProgress;
69
+ hits: Array<HistoryIndexHit>;
70
+ next: string | null;
71
+ reset: boolean;
72
+ };
73
+ export type HistoryIndexContext = {
74
+ text: string;
75
+ start: number;
76
+ end: number;
77
+ before: boolean;
78
+ after: boolean;
79
+ };
80
+ export type HistoryIndexValue = HistoryIndexProgress | HistoryIndexPage | HistoryIndexContext;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { HistoryIndexCommand, HistoryIndexValue } from "./native-history-index-protocol";
2
+ export type * from "./native-history-index-protocol";
3
+ /** Already admitted to one vault; observers never own indexing or capture. */
4
+ export interface NativeHistoryIndex {
5
+ call(command: HistoryIndexCommand): Promise<HistoryIndexValue>;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ import type { SearchChunkingSettings } from "./native-database-protocol";
2
+ export type ProjectionContribution = {
3
+ projection_id: string;
4
+ owner_plugin_id: string;
5
+ schema_version: number;
6
+ configuration_hash: string;
7
+ path_prefix: string | null;
8
+ };
9
+ export type SearchProviderContribution = {
10
+ provider_id: string;
11
+ version: string;
12
+ priority: number;
13
+ };
14
+ export type SearchIndexingSelection = {
15
+ providers: Array<SearchProviderContribution>;
16
+ chunking: SearchChunkingSettings;
17
+ };
18
+ export type IndexingCommand = {
19
+ kind: "select_metadata";
20
+ } | {
21
+ kind: "select_search";
22
+ selection: SearchIndexingSelection;
23
+ } | {
24
+ kind: "disable_search";
25
+ } | {
26
+ kind: "register_processor";
27
+ owner_plugin_id: string;
28
+ extension: string;
29
+ } | {
30
+ kind: "register_projection";
31
+ contribution: ProjectionContribution;
32
+ } | {
33
+ kind: "disable_projection";
34
+ projection_id: string;
35
+ } | {
36
+ kind: "refresh";
37
+ rebuild: boolean;
38
+ } | {
39
+ kind: "status";
40
+ };
41
+ export type IndexingPhase = "idle" | "discovering" | "metadata" | "tasks" | "docs" | "search";
42
+ export type IndexingStatus = {
43
+ passes: number;
44
+ indexed: number;
45
+ deleted: number;
46
+ pending: number;
47
+ error: string | null;
48
+ active: boolean;
49
+ processed: number;
50
+ total: number;
51
+ phase: IndexingPhase;
52
+ completed_at?: number;
53
+ };
54
+ export type ContributionPlacement = "native" | "client_dependent";
55
+ export type ContributionStatus = {
56
+ projection_id: string;
57
+ placement: ContributionPlacement;
58
+ message: string | null;
59
+ };
60
+ export type IndexingValue = {
61
+ status: IndexingStatus | null;
62
+ contribution: ContributionStatus | null;
63
+ };
@@ -0,0 +1 @@
1
+ export {};