@lapis-notes/api 0.1.4 → 0.2.1

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 (188) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/README.md +21 -2
  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 +21 -9
  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 +21 -3
  31. package/dist/context.svelte.js +63 -20
  32. package/dist/diagnostics.d.ts +8 -0
  33. package/dist/diagnostics.js +6 -0
  34. package/dist/editor-save-context.d.ts +12 -1
  35. package/dist/editor-save-context.js +32 -1
  36. package/dist/editor-view-registry.d.ts +10 -0
  37. package/dist/editor-view-registry.js +44 -4
  38. package/dist/editor.svelte.js +128 -37
  39. package/dist/embed-registry.d.ts +10 -0
  40. package/dist/embed-registry.js +15 -0
  41. package/dist/file-editing-failures.d.ts +2 -0
  42. package/dist/file-editing-failures.js +29 -0
  43. package/dist/file-editing-native-issues.d.ts +19 -0
  44. package/dist/file-editing-native-issues.js +65 -0
  45. package/dist/file-editing-sessions.d.ts +56 -8
  46. package/dist/file-editing-sessions.js +185 -15
  47. package/dist/file-manager.d.ts +8 -1
  48. package/dist/file-manager.js +37 -3
  49. package/dist/file-state-store.d.ts +5 -0
  50. package/dist/file-state-store.js +34 -0
  51. package/dist/file-text-conflicts.d.ts +25 -0
  52. package/dist/file-text-conflicts.js +35 -0
  53. package/dist/file-text-mutation.d.ts +13 -0
  54. package/dist/file-text-mutation.js +100 -0
  55. package/dist/file-text-persistence.d.ts +36 -0
  56. package/dist/file-text-persistence.js +234 -0
  57. package/dist/file-text-state-writer.d.ts +34 -0
  58. package/dist/file-text-state-writer.js +183 -0
  59. package/dist/files-configuration.d.ts +20 -0
  60. package/dist/files-configuration.js +47 -0
  61. package/dist/generated/plugin-host-modules.schema.generated.d.ts +87 -37
  62. package/dist/generated/plugin-host-modules.schema.generated.js +102 -37
  63. package/dist/glob.d.ts +3 -0
  64. package/dist/glob.js +2 -1
  65. package/dist/icons/changes.d.ts +3 -0
  66. package/dist/icons/changes.js +16 -0
  67. package/dist/icons/index.d.ts +5 -1
  68. package/dist/icons/index.js +44 -3
  69. package/dist/icons/registry.d.ts +35 -0
  70. package/dist/icons/registry.js +154 -0
  71. package/dist/icons/safe-svg.d.ts +2 -0
  72. package/dist/icons/safe-svg.js +44 -0
  73. package/dist/index.d.ts +5 -1
  74. package/dist/index.js +3 -1
  75. package/dist/lapis-extension.d.ts +6 -2
  76. package/dist/lapis-extension.js +2 -0
  77. package/dist/metadata.svelte.js +45 -2
  78. package/dist/native-background-indexing.d.ts +48 -0
  79. package/dist/native-background-indexing.js +208 -0
  80. package/dist/native-database-protocol.d.ts +29 -0
  81. package/dist/native-database-protocol.js +1 -0
  82. package/dist/native-editing-intent-lease.d.ts +17 -0
  83. package/dist/native-editing-intent-lease.js +188 -0
  84. package/dist/native-editing-intents-protocol.d.ts +70 -0
  85. package/dist/native-editing-intents-protocol.js +1 -0
  86. package/dist/native-editing-intents.d.ts +8 -0
  87. package/dist/native-editing-intents.js +1 -0
  88. package/dist/native-editing-problems.d.ts +3 -0
  89. package/dist/native-editing-problems.js +180 -0
  90. package/dist/native-editing-recovery.d.ts +24 -0
  91. package/dist/native-editing-recovery.js +198 -0
  92. package/dist/native-editing-session.d.ts +51 -0
  93. package/dist/native-editing-session.js +251 -0
  94. package/dist/native-history-index-protocol.d.ts +80 -0
  95. package/dist/native-history-index-protocol.js +1 -0
  96. package/dist/native-history-index.d.ts +6 -0
  97. package/dist/native-history-index.js +1 -0
  98. package/dist/native-indexing-protocol.d.ts +63 -0
  99. package/dist/native-indexing-protocol.js +1 -0
  100. package/dist/native-participant-protocol.d.ts +67 -0
  101. package/dist/native-participant-protocol.js +3 -0
  102. package/dist/native-queue-protocol.d.ts +73 -0
  103. package/dist/native-queue-protocol.js +3 -0
  104. package/dist/native-tasks-protocol.d.ts +264 -0
  105. package/dist/native-tasks-protocol.js +1 -0
  106. package/dist/native-tasks.d.ts +35 -0
  107. package/dist/native-tasks.js +79 -0
  108. package/dist/native-turn-protocol.d.ts +81 -0
  109. package/dist/native-turn-protocol.js +1 -0
  110. package/dist/native-turns.d.ts +29 -0
  111. package/dist/native-turns.js +2 -0
  112. package/dist/plugin-asset-server.js +3 -2
  113. package/dist/plugin-dependency-resolver.d.ts +1 -1
  114. package/dist/plugin-distribution/compatibility.d.ts +1 -1
  115. package/dist/plugin-distribution/compatibility.js +10 -5
  116. package/dist/plugin-distribution/installed-plugin-state.js +2 -1
  117. package/dist/plugin-distribution/installer.d.ts +6 -0
  118. package/dist/plugin-distribution/installer.js +90 -37
  119. package/dist/plugin-distribution/manager.d.ts +4 -1
  120. package/dist/plugin-distribution/manager.js +12 -5
  121. package/dist/plugin-distribution/registry-cache.js +2 -1
  122. package/dist/plugin-distribution/registry-source-state.js +2 -1
  123. package/dist/plugin-distribution/schemas.d.ts +26 -8
  124. package/dist/plugin-distribution/schemas.js +2 -1
  125. package/dist/plugin-distribution/types.d.ts +4 -0
  126. package/dist/plugin-manager.d.ts +20 -4
  127. package/dist/plugin-manager.js +228 -64
  128. package/dist/plugin-runtime-entry.d.ts +1 -1
  129. package/dist/plugin-runtime-entry.js +16 -0
  130. package/dist/plugin.d.ts +18 -2
  131. package/dist/plugin.js +93 -39
  132. package/dist/registry-schema/catalog-v1.d.ts +60 -9
  133. package/dist/registry-schema/catalog-v1.js +2 -1
  134. package/dist/registry-schema/catalog-v2.d.ts +60 -9
  135. package/dist/registry-schema/catalog-v2.js +1 -1
  136. package/dist/search-result-provider.d.ts +95 -0
  137. package/dist/search-result-provider.js +42 -0
  138. package/dist/status-bar.svelte.d.ts +26 -20
  139. package/dist/status-bar.svelte.js +1 -0
  140. package/dist/storage/app-database.d.ts +6 -2
  141. package/dist/storage/app-database.js +41 -34
  142. package/dist/storage/desktop-native.d.ts +5 -3
  143. package/dist/storage/desktop-native.js +59 -6
  144. package/dist/storage/directory-watcher.d.ts +27 -0
  145. package/dist/storage/directory-watcher.js +201 -0
  146. package/dist/storage/fs.d.ts +14 -27
  147. package/dist/storage/fs.js +2 -168
  148. package/dist/storage/index-projection.js +33 -15
  149. package/dist/storage/memory-vault-adapter.d.ts +2 -0
  150. package/dist/storage/memory-vault-adapter.js +14 -1
  151. package/dist/storage/path.d.ts +6 -2
  152. package/dist/storage/path.js +8 -2
  153. package/dist/storage/projection-query-candidates.d.ts +3 -0
  154. package/dist/storage/projection-query-candidates.js +69 -0
  155. package/dist/storage/remote-app-database.d.ts +2 -2
  156. package/dist/storage/remote-app-database.js +1 -1
  157. package/dist/storage/runtime.d.ts +3 -0
  158. package/dist/storage/runtime.js +3 -0
  159. package/dist/storage/search-embedding-record.d.ts +3 -0
  160. package/dist/storage/search-embedding-record.js +18 -0
  161. package/dist/storage/turso-app-database.d.ts +2 -0
  162. package/dist/storage/turso-app-database.js +67 -30
  163. package/dist/storage/vault-listing.d.ts +3 -0
  164. package/dist/storage/vault-listing.js +36 -0
  165. package/dist/storage/vault-reconciliation.d.ts +19 -0
  166. package/dist/storage/vault-reconciliation.js +137 -0
  167. package/dist/storage/vault-session.d.ts +8 -0
  168. package/dist/storage/vault-session.js +10 -2
  169. package/dist/storage/vault.svelte.d.ts +53 -4
  170. package/dist/storage/vault.svelte.js +223 -87
  171. package/dist/sync-host.d.ts +12 -3
  172. package/dist/sync-host.js +5 -1
  173. package/dist/vault-change-views.d.ts +4 -0
  174. package/dist/vault-change-views.js +43 -0
  175. package/dist/vault-changes.d.ts +37 -0
  176. package/dist/vault-changes.js +228 -0
  177. package/dist/view.svelte.d.ts +182 -149
  178. package/dist/view.svelte.js +11 -2
  179. package/dist/workspace-close-guard.d.ts +3 -0
  180. package/dist/workspace-close-guard.js +35 -0
  181. package/dist/workspace-host-internal.js +10 -2
  182. package/dist/workspace-layout-normalizer.d.ts +4 -2
  183. package/dist/workspace-layout-normalizer.js +5 -5
  184. package/dist/workspace-view-document.d.ts +3 -0
  185. package/dist/workspace-view-document.js +12 -0
  186. package/dist/workspace.svelte.d.ts +13 -2
  187. package/dist/workspace.svelte.js +371 -82
  188. package/package.json +45 -8
@@ -1,8 +1,10 @@
1
- import { z } from "zod";
1
+ import { z } from "zod/v3";
2
2
  import { SettingTab } from "./settings.svelte";
3
3
  import { cloneDeep, isEqual } from "lodash-es";
4
4
  import { EventDispatcher } from "./events";
5
- import { dirname } from "./storage";
5
+ import { dirname, normalizePath } from "./storage";
6
+ import { TFile } from "./storage/fs";
7
+ import { FileTextStateWriter } from "./file-text-state-writer";
6
8
  function isRecoverableConfigurationReadError(error) {
7
9
  let current = error;
8
10
  while (current) {
@@ -626,6 +628,9 @@ export class Configuration extends EventDispatcher {
626
628
  basePath;
627
629
  values = $state({});
628
630
  loaded = false;
631
+ stateBaseline = null;
632
+ stateWriter;
633
+ updateTail = Promise.resolve();
629
634
  schema = new ConfigurationSchema();
630
635
  constructor(app, basePath) {
631
636
  super();
@@ -647,49 +652,60 @@ export class Configuration extends EventDispatcher {
647
652
  return cloneDeep(pluginData[pluginId]);
648
653
  }
649
654
  async updatePluginData(pluginId, value, options = {}) {
650
- const prev = this.getPluginData(pluginId);
651
- const pluginData = this.getPluginDataStore(this.values);
652
- const nextPluginData = {
653
- ...pluginData,
654
- [pluginId]: cloneDeep(value),
655
- };
656
- const next = { ...this.values, pluginData: nextPluginData };
657
- await this.persist(next);
658
- this.emit("plugin-data-updated", {
659
- pluginId,
660
- value: this.getPluginData(pluginId),
661
- prev,
662
- origin: options.origin ?? "configuration-update",
655
+ const captured = cloneDeep(value);
656
+ return this.change(() => {
657
+ const prev = this.getPluginData(pluginId);
658
+ const pluginData = {
659
+ ...this.getPluginDataStore(this.values),
660
+ [pluginId]: captured,
661
+ };
662
+ return {
663
+ data: { ...this.values, pluginData },
664
+ notify: () => this.emit("plugin-data-updated", {
665
+ pluginId,
666
+ value: this.getPluginData(pluginId),
667
+ prev,
668
+ origin: options.origin ?? "configuration-update",
669
+ }),
670
+ };
663
671
  });
664
672
  }
665
673
  async removePluginData(pluginId, options = {}) {
666
- const pluginData = this.getPluginDataStore(this.values);
667
- if (!Object.hasOwn(pluginData, pluginId)) {
668
- return;
669
- }
670
- const prev = cloneDeep(pluginData[pluginId]);
671
- const nextPluginData = { ...pluginData };
672
- delete nextPluginData[pluginId];
673
- const next = { ...this.values };
674
- if (Object.keys(nextPluginData).length > 0) {
675
- next.pluginData = nextPluginData;
676
- }
677
- else {
678
- delete next.pluginData;
679
- }
680
- await this.persist(next);
681
- this.emit("plugin-data-updated", {
682
- pluginId,
683
- value: undefined,
684
- prev,
685
- origin: options.origin ?? "configuration-update",
674
+ return this.change(() => {
675
+ const pluginData = this.getPluginDataStore(this.values);
676
+ if (!Object.hasOwn(pluginData, pluginId))
677
+ return;
678
+ const prev = cloneDeep(pluginData[pluginId]);
679
+ const nextPluginData = { ...pluginData };
680
+ delete nextPluginData[pluginId];
681
+ const next = { ...this.values };
682
+ if (Object.keys(nextPluginData).length)
683
+ next.pluginData = nextPluginData;
684
+ else
685
+ delete next.pluginData;
686
+ return {
687
+ data: next,
688
+ notify: () => this.emit("plugin-data-updated", {
689
+ pluginId,
690
+ value: undefined,
691
+ prev,
692
+ origin: options.origin ?? "configuration-update",
693
+ }),
694
+ };
686
695
  });
687
696
  }
688
697
  async reloadFromDisk(options = {}) {
698
+ if (this.stateWriter?.dirty)
699
+ return;
689
700
  const previousValues = cloneDeep(this.values);
690
- this.values = await this.readValuesFromDisk();
701
+ const loaded = await this.readValuesFromDisk();
702
+ if (this.stateWriter &&
703
+ !this.stateWriter.acceptBaseline(this.stateBaseline))
704
+ return;
705
+ this.values = loaded;
691
706
  this.loaded = true;
692
- await this.materializeSchemaDefaults();
707
+ if (options.materializeDefaults !== false)
708
+ await this.materializeSchemaDefaults();
693
709
  if (options.emitPluginDataEvents !== false) {
694
710
  this.emitPluginDataDiffs(previousValues, this.values, options.origin ?? "external-reload");
695
711
  }
@@ -747,38 +763,98 @@ export class Configuration extends EventDispatcher {
747
763
  async updateConfigurationOptions(changes) {
748
764
  const validated = Object.entries(changes).map(([key, value]) => ({
749
765
  key,
750
- value: this.schema.validateConfigurationOption(key, value),
766
+ value: cloneDeep(this.schema.validateConfigurationOption(key, value)),
751
767
  }));
752
- const changed = validated
753
- .map(({ key, value }) => ({
754
- key,
755
- value,
756
- prev: this.values[key],
757
- }))
758
- .filter(({ value, prev }) => !isEqual(value, prev));
759
- if (changed.length === 0)
760
- return;
761
- const next = { ...this.values };
762
- for (const { key, value } of changed) {
763
- next[key] = cloneDeep(value);
764
- }
765
- await this.persist(next);
766
- for (const { key, value, prev } of changed) {
767
- this.emit("updated", {
768
+ return this.change(() => {
769
+ const changed = validated
770
+ .map(({ key, value }) => ({
768
771
  key,
769
- value: cloneDeep(value),
770
- prev: cloneDeep(prev),
771
- });
772
- }
772
+ value,
773
+ prev: this.values[key],
774
+ }))
775
+ .filter(({ value, prev }) => !isEqual(value, prev));
776
+ if (changed.length === 0)
777
+ return;
778
+ const next = { ...this.values };
779
+ for (const { key, value } of changed) {
780
+ next[key] = cloneDeep(value);
781
+ }
782
+ return {
783
+ data: next,
784
+ notify: () => {
785
+ for (const { key, value, prev } of changed)
786
+ this.emit("updated", {
787
+ key,
788
+ value: cloneDeep(value),
789
+ prev: cloneDeep(prev),
790
+ });
791
+ },
792
+ };
793
+ });
773
794
  }
774
795
  async removeConfigurationOption(key) {
775
- const value = this.values[key];
776
- if (value !== undefined) {
796
+ return this.change(() => {
797
+ const value = this.values[key];
798
+ if (value === undefined)
799
+ return;
777
800
  const next = { ...this.values };
778
801
  delete next[key];
779
- await this.persist(next);
780
- this.emit("updated", { key, prev: value, value: undefined });
802
+ return {
803
+ data: next,
804
+ notify: () => this.emit("updated", { key, prev: value, value: undefined }),
805
+ };
806
+ });
807
+ }
808
+ getStateWriter() {
809
+ if (this.stateWriter)
810
+ return this.stateWriter;
811
+ const file = this.app.vault.getFileByPath(this.basePath) ??
812
+ new TFile(normalizePath(this.basePath), { ctime: 0, mtime: 0, size: 0 }, null);
813
+ this.stateWriter = new FileTextStateWriter(this.app, file, this.stateBaseline, (text) => {
814
+ const next = JSON.parse(text || "{}");
815
+ if (!next || typeof next !== "object" || Array.isArray(next))
816
+ throw new Error("Invalid app configuration record");
817
+ const previous = this.values;
818
+ this.values = next;
819
+ this.emitPluginDataDiffs(previous, next, "external-reload");
820
+ for (const key of new Set([
821
+ ...Object.keys(previous),
822
+ ...Object.keys(next),
823
+ ])) {
824
+ if (!isEqual(previous[key], next[key]))
825
+ this.emit("updated", {
826
+ key,
827
+ prev: previous[key],
828
+ value: next[key],
829
+ });
830
+ }
831
+ });
832
+ return this.stateWriter;
833
+ }
834
+ change(calculate) {
835
+ if (this.app.vault.supportsConditionalTextWrites) {
836
+ return this.getStateWriter().update(() => {
837
+ const change = calculate();
838
+ if (!change)
839
+ return;
840
+ return {
841
+ contents: JSON.stringify(change.data, null, 2),
842
+ onSaved: () => {
843
+ this.values = change.data;
844
+ change.notify?.();
845
+ },
846
+ };
847
+ });
781
848
  }
849
+ const run = this.updateTail.then(async () => {
850
+ const change = calculate();
851
+ if (!change)
852
+ return;
853
+ await this.persist(change.data);
854
+ change.notify?.();
855
+ });
856
+ this.updateTail = run.catch(() => { });
857
+ return run;
782
858
  }
783
859
  async persist(data) {
784
860
  const file = this.app.vault.getFileByPath(this.basePath);
@@ -803,10 +879,12 @@ export class Configuration extends EventDispatcher {
803
879
  async readValuesFromDisk() {
804
880
  const file = this.app.vault.getFileByPath(this.basePath);
805
881
  if (!file) {
882
+ this.stateBaseline = null;
806
883
  return {};
807
884
  }
808
885
  try {
809
886
  const contents = await this.app.vault.read(file);
887
+ this.stateBaseline = contents;
810
888
  const data = JSON.parse(contents);
811
889
  if (typeof data === "object" && data !== null && !Array.isArray(data)) {
812
890
  return data;
@@ -856,32 +934,29 @@ export class Configuration extends EventDispatcher {
856
934
  if (!this.loaded) {
857
935
  return;
858
936
  }
859
- const next = { ...this.values };
860
- let changed = false;
861
- for (const [key, defaultValue] of this.schema.getDefaultEntries()) {
862
- if (!(key in next) || next[key] === undefined) {
863
- next[key] = cloneDeep(defaultValue);
864
- changed = true;
865
- continue;
866
- }
867
- try {
868
- const validated = this.schema.validateConfigurationOption(key, next[key]);
869
- if (!isEqual(validated, next[key])) {
870
- next[key] = validated;
937
+ return this.change(() => {
938
+ const next = { ...this.values };
939
+ let changed = false;
940
+ for (const [key, defaultValue] of this.schema.getDefaultEntries()) {
941
+ if (!(key in next) || next[key] === undefined) {
942
+ next[key] = cloneDeep(defaultValue);
943
+ changed = true;
944
+ continue;
945
+ }
946
+ try {
947
+ const validated = this.schema.validateConfigurationOption(key, next[key]);
948
+ if (!isEqual(validated, next[key])) {
949
+ next[key] = validated;
950
+ changed = true;
951
+ }
952
+ }
953
+ catch (error) {
954
+ console.warn(`Invalid configuration value for ${key}; restoring default.`, error);
955
+ next[key] = cloneDeep(defaultValue);
871
956
  changed = true;
872
957
  }
873
958
  }
874
- catch (error) {
875
- console.warn(`Invalid configuration value for ${key}; restoring default.`, error);
876
- next[key] = cloneDeep(defaultValue);
877
- changed = true;
878
- }
879
- }
880
- if (changed) {
881
- await this.persist(next);
882
- }
883
- else {
884
- this.values = next;
885
- }
959
+ return changed ? { data: next } : undefined;
960
+ });
886
961
  }
887
962
  }
@@ -1,3 +1,5 @@
1
+ import type { IconRegistry } from "./icons/registry";
2
+ import type { NativeTasks, NativeTasksSession } from "./native-tasks";
1
3
  import { Workspace } from "./workspace.svelte";
2
4
  import { TFile, Vault, type AppDatabase, type DataAdapter, type VaultSession } from "./storage";
3
5
  import { AppSettings } from "./settings.svelte";
@@ -6,12 +8,16 @@ import { PluginManager } from "./plugin-manager";
6
8
  import { Configuration } from "./configuration.svelte";
7
9
  import { CommandManager, Keymap, Scope, type UserEvent } from "./command.svelte";
8
10
  import { MetadataCache } from "./cache.svelte";
11
+ import type { NativeBackgroundIndexingObserver } from "./native-background-indexing";
12
+ import type { NativeHistoryIndex } from "./native-history-index";
13
+ import type { NativeEditingIntents } from "./native-editing-intents";
9
14
  import type { MarkdownDirectiveRenderer, MarkdownPostProcessor, MarkdownViewMenuItemProvider } from "./markdown";
10
15
  import { type Logger } from "./logging";
11
16
  import type { Component } from "./view.svelte";
12
17
  import { MetadataTypeManager } from "./metadata.svelte";
13
18
  import type { EditorSuggest } from "./suggest";
14
19
  import { FileManager } from "./file-manager";
20
+ import { VaultChangeService } from "./vault-changes";
15
21
  import { EmbedRegistry } from "./embed-registry";
16
22
  import type { OpenViewState } from "./workspace.svelte";
17
23
  import { LocalizationManager } from "./localization-manager.svelte";
@@ -26,6 +32,7 @@ import { DefaultPluginDistributionManager, type DefaultPluginDistributionManager
26
32
  import type { PluginDependencyResolverFactory } from "./plugin-dependency-resolver";
27
33
  import type { PluginAssetServer } from "./plugin-asset-server";
28
34
  import { SearchDocumentProviderRegistry } from "./search-document-provider";
35
+ import { SearchResultProviderRegistry } from "./search-result-provider";
29
36
  import { DailyDocumentProviderRegistry } from "./daily-document-provider";
30
37
  import { AppToolRegistry } from "./agent-tools";
31
38
  import { MarkdownExtensionRegistry } from "./markdown-extension-registry";
@@ -55,14 +62,19 @@ export interface AppWorkspaceShellProperties {
55
62
  */
56
63
  export type AppStateProperties = {
57
64
  version: string;
58
- configPath: string;
65
+ configPath?: string;
59
66
  adapter?: DataAdapter;
60
67
  session?: VaultSession;
68
+ nativeHistoryIndex?: NativeHistoryIndex;
69
+ nativeEditingIntents?: NativeEditingIntents;
70
+ nativeTasks?: NativeTasks;
61
71
  appDatabase?: AppDatabase;
62
72
  safeMode?: AppSafeModeState;
63
73
  createCommunityPluginDependencyResolver?: PluginDependencyResolverFactory;
64
74
  pluginAssetServer?: PluginAssetServer;
65
75
  pluginDistributionOptions?: AppPluginDistributionOptions;
76
+ /** Host restrictions apply to discovery, installation and external loading. */
77
+ unavailablePlugins?: DefaultPluginDistributionManagerOptions["unavailablePlugins"];
66
78
  /** Host-owned Nostr account, keychain, and remote-signer implementation. */
67
79
  nostrSignerBroker?: NostrSignerBroker;
68
80
  syncHost?: SyncHostServiceOptions;
@@ -70,8 +82,8 @@ export type AppStateProperties = {
70
82
  markdownRenderer: (markdown: string, el: HTMLElement, sourcePath: string, component: Component) => Promise<void>;
71
83
  };
72
84
  /** Renderer-safe application properties exposed to plugins and views. */
73
- export type AppPublicProperties = Pick<AppStateProperties, "version" | "configPath" | "workspaceShell" | "markdownRenderer">;
74
- export type AppPluginDistributionOptions = Omit<DefaultPluginDistributionManagerOptions, "adapter" | "appVersion" | "platform" | "workspaceTrusted" | "pluginManager">;
85
+ export type AppPublicProperties = Pick<AppStateProperties, "version" | "configPath" | "workspaceShell" | "markdownRenderer"> & { configPath: string };
86
+ export type AppPluginDistributionOptions = Omit<DefaultPluginDistributionManagerOptions, "adapter" | "appVersion" | "platform" | "workspaceTrusted" | "pluginManager" | "unavailablePlugins">;
75
87
  export type ExtType = Extension | ((context: Record<string, any>) => Extension);
76
88
  /**
77
89
  * Details about the last startup failure that caused safe mode to activate.
@@ -156,10 +168,13 @@ export declare class SecretStorage {
156
168
  export declare class App {
157
169
  readonly props: AppPublicProperties;
158
170
  readonly i18n: LocalizationManager;
171
+ readonly icons: IconRegistry;
159
172
  readonly workspace: Workspace;
160
173
  readonly vault: Vault;
161
174
  readonly appDatabase: AppDatabase;
162
175
  readonly session?: VaultSession;
176
+ readonly nativeHistoryIndex?: NativeHistoryIndex;
177
+ readonly nativeTasks?: NativeTasksSession;
163
178
  readonly safeMode: AppSafeModeState;
164
179
  plugins: PluginManager;
165
180
  scope: Scope;
@@ -169,6 +184,7 @@ export declare class App {
169
184
  readonly editorSuggest: Map<string, Set<EditorSuggest<any>>>;
170
185
  readonly configuration: Configuration;
171
186
  readonly fileManager: FileManager;
187
+ readonly vaultChanges: VaultChangeService;
172
188
  readonly contextKeys: ContextKeyService;
173
189
  readonly statusBar: StatusBarManager;
174
190
  readonly urls: AppUrlService;
@@ -184,8 +200,10 @@ export declare class App {
184
200
  settingsOpen: boolean;
185
201
  settingsTabId: string | null;
186
202
  metadataCache: MetadataCache;
203
+ get backgroundIndexing(): NativeBackgroundIndexingObserver | undefined;
187
204
  embedRegistry: EmbedRegistry;
188
205
  readonly searchDocumentProviders: SearchDocumentProviderRegistry;
206
+ readonly searchResultProviders: SearchResultProviderRegistry;
189
207
  readonly dailyDocumentProviders: DailyDocumentProviderRegistry;
190
208
  readonly indexProjections: import("./storage/index-projection").IndexProjectionRegistry;
191
209
  readonly agentTools: AppToolRegistry;
@@ -1,15 +1,20 @@
1
+ import { IconRegistry } from "./icons/registry";
2
+ import { FILES_CONFIGURATION_SCHEMA } from "./files-configuration";
3
+ import { DEFAULT_VAULT_EXCLUDES } from "./storage/vault-listing";
1
4
  import { Workspace } from "./workspace.svelte";
2
- import { getAdapterVaultId, TFile, Vault, } from "./storage";
5
+ import { getAdapterVaultId, isNativeDesktopRuntime, appStatePath, TFile, Vault, } from "./storage";
3
6
  import { IndexProjectionRegistry } from "./storage/index-projection";
4
7
  import { AppSettings } from "./settings.svelte";
5
8
  import { NativeDesktopCommunityPluginExecutionHost, PluginManager, } from "./plugin-manager";
6
9
  import { hasNativeDesktopCapability } from "./storage/desktop-native";
7
10
  import { Configuration } from "./configuration.svelte";
8
11
  import { CommandManager, Keymap, Scope, } from "./command.svelte";
12
+ import { NativeTasksSession } from "./native-tasks";
9
13
  import { MetadataCache } from "./cache.svelte";
10
14
  import { logging } from "./logging";
11
15
  import { MetadataTypeManager } from "./metadata.svelte";
12
16
  import { FileManager } from "./file-manager";
17
+ import { VaultChangeService } from "./vault-changes";
13
18
  import { EmbedRegistry } from "./embed-registry";
14
19
  import { findOpenFileLeaf, leafFilePath, applyOpenViewStateToLeaf, } from "./open-file";
15
20
  import { LocalizationManager, localeManager, } from "./localization-manager.svelte";
@@ -29,6 +34,7 @@ import { resolveMetadataFieldValuesAsync } from "./configuration-option-source-p
29
34
  import { flushWorkspaceEditors } from "./sync-editor-flush";
30
35
  import { FileEditingSessionRegistry } from "./file-editing-sessions";
31
36
  import { SearchDocumentProviderRegistry } from "./search-document-provider";
37
+ import { SearchResultProviderRegistry } from "./search-result-provider";
32
38
  import { DailyDocumentProviderRegistry } from "./daily-document-provider";
33
39
  import { AppToolRegistry } from "./agent-tools";
34
40
  import { AppSkillRegistry, AppSlashCommandRegistry } from "./agent-skills";
@@ -129,10 +135,13 @@ export class SecretStorage {
129
135
  export class App {
130
136
  props;
131
137
  i18n = localeManager;
138
+ icons = new IconRegistry();
132
139
  workspace;
133
140
  vault = $state();
134
141
  appDatabase;
135
142
  session;
143
+ nativeHistoryIndex;
144
+ nativeTasks;
136
145
  safeMode;
137
146
  plugins = $state();
138
147
  scope = $state(new Scope(undefined, this));
@@ -142,6 +151,7 @@ export class App {
142
151
  editorSuggest = new Map();
143
152
  configuration;
144
153
  fileManager = new FileManager(this);
154
+ vaultChanges = new VaultChangeService(this);
145
155
  contextKeys = new ContextKeyService();
146
156
  statusBar = new StatusBarManager();
147
157
  urls = new AppUrlService(this);
@@ -158,9 +168,13 @@ export class App {
158
168
  settingsOpen = $state(false);
159
169
  settingsTabId = $state(null);
160
170
  metadataCache = $state();
171
+ get backgroundIndexing() {
172
+ return this.metadataCache.backgroundIndexing;
173
+ }
161
174
  embedRegistry = new EmbedRegistry();
162
175
  configurationOptionSources = new ConfigurationOptionSourceRegistry();
163
176
  searchDocumentProviders = new SearchDocumentProviderRegistry();
177
+ searchResultProviders = new SearchResultProviderRegistry();
164
178
  dailyDocumentProviders = new DailyDocumentProviderRegistry();
165
179
  indexProjections = new IndexProjectionRegistry();
166
180
  agentTools = new AppToolRegistry();
@@ -169,7 +183,7 @@ export class App {
169
183
  agentResultViews = new AppResultViewRegistry();
170
184
  markdownExtensions = new MarkdownExtensionRegistry();
171
185
  markdownFileSurfaces = new MarkdownFileSurfaceRegistry();
172
- fileEditingSessions = new FileEditingSessionRegistry();
186
+ fileEditingSessions;
173
187
  lastEvent = $state(null);
174
188
  renderContext = new RenderContext();
175
189
  secretStorage = new SecretStorage();
@@ -195,7 +209,7 @@ export class App {
195
209
  constructor(props) {
196
210
  this.props = Object.freeze({
197
211
  version: props.version,
198
- configPath: props.configPath,
212
+ configPath: props.configPath ?? appStatePath("app.json"),
199
213
  workspaceShell: props.workspaceShell,
200
214
  markdownRenderer: props.markdownRenderer,
201
215
  });
@@ -205,6 +219,9 @@ export class App {
205
219
  throw new Error("App requires a vault adapter or vault session");
206
220
  }
207
221
  this.session = props.session;
222
+ this.fileEditingSessions = new FileEditingSessionRegistry(props.nativeEditingIntents ?? props.session?.nativeEditingIntents);
223
+ this.nativeHistoryIndex =
224
+ props.nativeHistoryIndex ?? props.session?.nativeHistoryIndex;
208
225
  this.safeMode = props.safeMode ?? createDefaultAppSafeModeState();
209
226
  const appDatabase = props.session?.appDatabase ?? props.appDatabase;
210
227
  if (!appDatabase) {
@@ -229,9 +246,18 @@ export class App {
229
246
  this.settings.addGroup((options) => options.setId("options").setTitle("Options"));
230
247
  this.settings.addGroup((options) => options.setId("core-plugins").setTitle("Core plugins"));
231
248
  this.settings.addGroup((options) => options.setId("community-plugins").setTitle("Community plugins"));
232
- this.configuration = new Configuration(this, props.configPath);
249
+ this.configuration = new Configuration(this, this.props.configPath);
250
+ this.configuration.schema.register(FILES_CONFIGURATION_SCHEMA);
233
251
  this.workspace.bindConfiguration();
234
- this.vault = new Vault(adapter);
252
+ this.vault = new Vault(adapter, this.fileEditingSessions, {
253
+ listingExclusions: () => this.configuration
254
+ .getConfiguration()
255
+ .get("files.exclude", DEFAULT_VAULT_EXCLUDES),
256
+ });
257
+ this.configuration.on("updated", ({ key }) => {
258
+ if (key === "files.exclude")
259
+ this.vault.trigger("load");
260
+ });
235
261
  this.#syncHost = new SyncHostService(this.appDatabase, this.vault, Date.now, {
236
262
  ...props.syncHost,
237
263
  flushOpenEditor: (path) => flushWorkspaceEditors(this.workspace, path),
@@ -239,13 +265,21 @@ export class App {
239
265
  hostSyncServices.set(this, this.#syncHost);
240
266
  this.workspaceTrust = new WorkspaceTrustService(adapter);
241
267
  this.metadataCache = new MetadataCache(this);
268
+ const nativeTasks = props.session?.nativeTasks ?? props.nativeTasks;
269
+ this.nativeTasks = nativeTasks
270
+ ? new NativeTasksSession(nativeTasks, () => {
271
+ const state = this.plugins?.plugins.get("tasks")?.state;
272
+ return state === "enabled" || state === "enabling";
273
+ })
274
+ : undefined;
242
275
  installVaultMaintenanceCommands(this);
243
- this.plugins = new PluginManager(this, "/.obsidian/plugins", adapter, {
276
+ this.plugins = new PluginManager(this, "/" + appStatePath("plugins"), adapter, {
244
277
  communityPluginHost: hasNativeDesktopCapability("plugin-sidecar")
245
278
  ? new NativeDesktopCommunityPluginExecutionHost(adapter, undefined, props.createCommunityPluginDependencyResolver)
246
279
  : undefined,
247
280
  createCommunityPluginDependencyResolver: props.createCommunityPluginDependencyResolver,
248
281
  pluginAssetServer: props.pluginAssetServer,
282
+ unavailablePlugins: props.unavailablePlugins,
249
283
  createSyncHostCapabilities: (pluginId, grants) => this.#syncHost.forPlugin(pluginId, grants),
250
284
  revokeSyncHostCapabilities: (pluginId) => this.#syncHost.revokePlugin(pluginId),
251
285
  });
@@ -263,7 +297,10 @@ export class App {
263
297
  ...props.pluginDistributionOptions,
264
298
  adapter,
265
299
  appVersion: this.version,
266
- platform: props.session?.runtime === "deno-desktop" ? "desktop" : "web",
300
+ unavailablePlugins: props.unavailablePlugins,
301
+ platform: isNativeDesktopRuntime(props.session?.runtime)
302
+ ? "desktop"
303
+ : "web",
267
304
  workspaceTrusted: async () => (await this.workspaceTrust.ready()).trusted,
268
305
  pluginManager: this.plugins,
269
306
  });
@@ -319,17 +356,21 @@ export class App {
319
356
  ? view.editor
320
357
  : undefined;
321
358
  });
322
- openEditor?.view.dispatch({
323
- changes: [...edits]
324
- .sort((left, right) => left.from - right.from)
325
- .map((edit) => ({
326
- from: edit.from,
327
- to: edit.to,
328
- insert: edit.insert,
329
- })),
330
- userEvent: "input.complete",
331
- });
332
- await this.vault.modify(file, value);
359
+ if (openEditor) {
360
+ openEditor.view.dispatch({
361
+ changes: [...edits]
362
+ .sort((left, right) => left.from - right.from)
363
+ .map((edit) => ({
364
+ from: edit.from,
365
+ to: edit.to,
366
+ insert: edit.insert,
367
+ })),
368
+ userEvent: "input.complete",
369
+ });
370
+ await openEditor.flushChanges();
371
+ }
372
+ else
373
+ await this.vault.modify(file, value);
333
374
  }
334
375
  }
335
376
  }
@@ -403,16 +444,18 @@ export class App {
403
444
  const existingLeaf = findOpenFileLeaf(this.workspace, file);
404
445
  if (existingLeaf) {
405
446
  span.setAttribute("workspace.file_already_open", true);
406
- await applyOpenViewStateToLeaf(existingLeaf, openState);
407
447
  this.workspace.activateLeaf(existingLeaf, {
408
448
  operation: "open-existing-file",
409
449
  });
450
+ await this.workspace.revealLeaf(existingLeaf);
451
+ await applyOpenViewStateToLeaf(existingLeaf, openState);
410
452
  return;
411
453
  }
412
454
  const reusedRootLeaf = Boolean(this.workspace.activeRootLeaf);
413
455
  const leaf = this.workspace.getLeaf();
414
456
  span.setAttribute("workspace.reused_active_leaf", reusedRootLeaf);
415
- this.workspace.activeLeaf = leaf;
457
+ this.workspace.activateLeaf(leaf, { operation: "open-file" });
458
+ await this.workspace.revealLeaf(leaf);
416
459
  if (leafFilePath(leaf) === file.path) {
417
460
  span.setAttribute("workspace.file_already_open", true);
418
461
  if (openState?.state) {
@@ -41,6 +41,13 @@ export interface WorkspaceDiagnosticEntry {
41
41
  resource: DiagnosticResource | null;
42
42
  diagnostic: WorkspaceDiagnostic;
43
43
  }
44
+ /** Application-owned navigation target for a serializable diagnostic resource. */
45
+ export interface DiagnosticLocation {
46
+ resource: DiagnosticResource;
47
+ range?: DiagnosticRange;
48
+ diagnostic: WorkspaceDiagnostic;
49
+ related?: DiagnosticRelatedInformation;
50
+ }
44
51
  export interface DiagnosticCollectionOptions {
45
52
  label?: string;
46
53
  buildItemMenu?: (menu: Menu, entry: WorkspaceDiagnosticEntry) => void;
@@ -81,6 +88,7 @@ export declare class DiagnosticsManager {
81
88
  }
82
89
  export declare function bindRuntimePluginDiagnostics(plugins: {
83
90
  on(name: "plugin-error", listener: (id: string, message: string) => void): unknown;
91
+ on(name: "plugin-unloaded", listener: (id: string) => void): unknown;
84
92
  on(name: "plugin-enabled", listener: (plugin: {
85
93
  manifest: {
86
94
  id: string;
@@ -58,9 +58,15 @@ export function bindRuntimePluginDiagnostics(plugins, diagnostics) {
58
58
  return;
59
59
  publish();
60
60
  });
61
+ const unloadedRef = plugins.on("plugin-unloaded", (id) => {
62
+ if (!pluginFailures.delete(id))
63
+ return;
64
+ publish();
65
+ });
61
66
  return () => {
62
67
  plugins.offref(errorRef);
63
68
  plugins.offref(enabledRef);
69
+ plugins.offref(unloadedRef);
64
70
  };
65
71
  }
66
72
  export function diagnosticResourceForPath(path) {