@gmickel/gno 0.28.2 → 0.29.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 (52) hide show
  1. package/README.md +10 -2
  2. package/package.json +1 -1
  3. package/src/app/constants.ts +4 -2
  4. package/src/cli/commands/mcp/install.ts +4 -4
  5. package/src/cli/commands/mcp/status.ts +7 -7
  6. package/src/cli/commands/skill/install.ts +5 -5
  7. package/src/cli/program.ts +2 -2
  8. package/src/collection/add.ts +10 -0
  9. package/src/collection/types.ts +1 -0
  10. package/src/config/types.ts +12 -2
  11. package/src/core/depth-policy.ts +1 -1
  12. package/src/core/file-ops.ts +203 -1
  13. package/src/llm/registry.ts +20 -4
  14. package/src/serve/AGENTS.md +16 -16
  15. package/src/serve/CLAUDE.md +16 -16
  16. package/src/serve/config-sync.ts +32 -1
  17. package/src/serve/connectors.ts +243 -0
  18. package/src/serve/context.ts +9 -0
  19. package/src/serve/doc-events.ts +31 -1
  20. package/src/serve/embed-scheduler.ts +12 -0
  21. package/src/serve/import-preview.ts +173 -0
  22. package/src/serve/public/app.tsx +101 -7
  23. package/src/serve/public/components/AIModelSelector.tsx +383 -145
  24. package/src/serve/public/components/AddCollectionDialog.tsx +123 -7
  25. package/src/serve/public/components/BootstrapStatus.tsx +133 -0
  26. package/src/serve/public/components/CaptureModal.tsx +5 -2
  27. package/src/serve/public/components/CollectionsEmptyState.tsx +63 -0
  28. package/src/serve/public/components/FirstRunWizard.tsx +622 -0
  29. package/src/serve/public/components/HealthCenter.tsx +128 -0
  30. package/src/serve/public/components/IndexingProgress.tsx +21 -2
  31. package/src/serve/public/components/QuickSwitcher.tsx +62 -36
  32. package/src/serve/public/components/TagInput.tsx +5 -1
  33. package/src/serve/public/components/WikiLinkAutocomplete.tsx +15 -6
  34. package/src/serve/public/components/WorkspaceTabs.tsx +60 -0
  35. package/src/serve/public/hooks/use-doc-events.ts +48 -4
  36. package/src/serve/public/lib/local-history.ts +40 -7
  37. package/src/serve/public/lib/navigation-state.ts +156 -0
  38. package/src/serve/public/lib/workspace-tabs.ts +235 -0
  39. package/src/serve/public/pages/Ask.tsx +11 -1
  40. package/src/serve/public/pages/Browse.tsx +73 -0
  41. package/src/serve/public/pages/Collections.tsx +29 -13
  42. package/src/serve/public/pages/Connectors.tsx +178 -0
  43. package/src/serve/public/pages/Dashboard.tsx +493 -67
  44. package/src/serve/public/pages/DocView.tsx +192 -34
  45. package/src/serve/public/pages/DocumentEditor.tsx +127 -5
  46. package/src/serve/public/pages/Search.tsx +12 -1
  47. package/src/serve/routes/api.ts +541 -62
  48. package/src/serve/server.ts +79 -2
  49. package/src/serve/status-model.ts +149 -0
  50. package/src/serve/status.ts +706 -0
  51. package/src/serve/watch-service.ts +73 -8
  52. package/src/types/electrobun-shell.d.ts +43 -0
@@ -8,35 +8,76 @@ import type { EmbedScheduler } from "./embed-scheduler";
8
8
 
9
9
  import { defaultSyncService } from "../ingestion";
10
10
 
11
+ export interface CollectionWatchState {
12
+ expectedCollections: string[];
13
+ activeCollections: string[];
14
+ failedCollections: Array<{ collection: string; reason: string }>;
15
+ queuedCollections: string[];
16
+ syncingCollections: string[];
17
+ lastEventAt: string | null;
18
+ lastSyncAt: string | null;
19
+ }
20
+
11
21
  interface CollectionWatchServiceOptions {
12
22
  collections: Collection[];
13
23
  store: SqliteAdapter;
14
24
  scheduler: EmbedScheduler | null;
15
25
  eventBus: DocumentEventBus;
26
+ watchFactory?: typeof watch;
16
27
  }
17
28
 
18
29
  export class CollectionWatchService {
19
- readonly #collections: Collection[];
30
+ #collections: Collection[];
20
31
  readonly #store: SqliteAdapter;
21
32
  readonly #scheduler: EmbedScheduler | null;
22
33
  readonly #eventBus: DocumentEventBus;
23
- readonly #watchers: FSWatcher[] = [];
34
+ readonly #watchers = new Map<string, FSWatcher>();
24
35
  readonly #pendingByCollection = new Map<string, Set<string>>();
25
36
  readonly #timers = new Map<string, ReturnType<typeof setTimeout>>();
26
37
  readonly #syncing = new Set<string>();
27
38
  readonly #suppressedPaths = new Map<string, number>();
39
+ readonly #watchFactory: typeof watch;
40
+ readonly #failedCollections = new Map<string, string>();
41
+ #lastEventAt: string | null = null;
42
+ #lastSyncAt: string | null = null;
28
43
 
29
44
  constructor(options: CollectionWatchServiceOptions) {
30
45
  this.#collections = options.collections;
31
46
  this.#store = options.store;
32
47
  this.#scheduler = options.scheduler;
33
48
  this.#eventBus = options.eventBus;
49
+ this.#watchFactory = options.watchFactory ?? watch;
34
50
  }
35
51
 
36
52
  start(): void {
53
+ this.updateCollections(this.#collections);
54
+ }
55
+
56
+ updateCollections(collections: Collection[]): void {
57
+ this.#collections = collections;
58
+ const nextNames = new Set(collections.map((collection) => collection.name));
59
+
60
+ for (const [collectionName, watcher] of this.#watchers) {
61
+ if (!nextNames.has(collectionName)) {
62
+ watcher.close();
63
+ this.#watchers.delete(collectionName);
64
+ this.#failedCollections.delete(collectionName);
65
+ this.#pendingByCollection.delete(collectionName);
66
+ const timer = this.#timers.get(collectionName);
67
+ if (timer) {
68
+ clearTimeout(timer);
69
+ this.#timers.delete(collectionName);
70
+ }
71
+ this.#syncing.delete(collectionName);
72
+ }
73
+ }
74
+
37
75
  for (const collection of this.#collections) {
76
+ if (this.#watchers.has(collection.name)) {
77
+ continue;
78
+ }
38
79
  try {
39
- const watcher = watch(
80
+ const watcher = this.#watchFactory(
40
81
  collection.path,
41
82
  { recursive: true },
42
83
  (_eventType, filename) => {
@@ -47,12 +88,17 @@ export class CollectionWatchService {
47
88
  if (suppressedUntil && suppressedUntil > Date.now()) {
48
89
  return;
49
90
  }
91
+ this.#lastEventAt = new Date().toISOString();
50
92
  this.#queueChange(collection.name, relPath);
51
93
  }
52
94
  );
53
- this.#watchers.push(watcher);
54
- } catch {
55
- // Best-effort watch support; unsupported platforms can still rely on manual sync.
95
+ this.#watchers.set(collection.name, watcher);
96
+ this.#failedCollections.delete(collection.name);
97
+ } catch (error) {
98
+ this.#failedCollections.set(
99
+ collection.name,
100
+ error instanceof Error ? error.message : "watch unavailable"
101
+ );
56
102
  }
57
103
  }
58
104
  }
@@ -65,15 +111,33 @@ export class CollectionWatchService {
65
111
  for (const timer of this.#timers.values()) {
66
112
  clearTimeout(timer);
67
113
  }
68
- for (const watcher of this.#watchers) {
114
+ for (const watcher of this.#watchers.values()) {
69
115
  watcher.close();
70
116
  }
71
117
  this.#timers.clear();
72
- this.#watchers.length = 0;
118
+ this.#watchers.clear();
73
119
  this.#pendingByCollection.clear();
74
120
  this.#syncing.clear();
75
121
  }
76
122
 
123
+ getState(): CollectionWatchState {
124
+ return {
125
+ expectedCollections: this.#collections.map(
126
+ (collection) => collection.name
127
+ ),
128
+ activeCollections: [...this.#watchers.keys()],
129
+ failedCollections: [...this.#failedCollections.entries()].map(
130
+ ([collection, reason]) => ({ collection, reason })
131
+ ),
132
+ queuedCollections: [...this.#pendingByCollection.entries()]
133
+ .filter(([, relPaths]) => relPaths.size > 0)
134
+ .map(([collectionName]) => collectionName),
135
+ syncingCollections: [...this.#syncing],
136
+ lastEventAt: this.#lastEventAt,
137
+ lastSyncAt: this.#lastSyncAt,
138
+ };
139
+ }
140
+
77
141
  #queueChange(collectionName: string, relPath: string): void {
78
142
  const pending =
79
143
  this.#pendingByCollection.get(collectionName) ?? new Set<string>();
@@ -132,6 +196,7 @@ export class CollectionWatchService {
132
196
  return;
133
197
  }
134
198
 
199
+ this.#lastSyncAt = new Date().toISOString();
135
200
  this.#scheduler?.notifySyncComplete(relPaths);
136
201
 
137
202
  for (const relPath of relPaths) {
@@ -0,0 +1,43 @@
1
+ declare module "electrobun" {
2
+ export interface ElectrobunConfig {
3
+ app: {
4
+ name: string;
5
+ identifier: string;
6
+ version: string;
7
+ urlSchemes?: string[];
8
+ };
9
+ runtime?: Record<string, unknown>;
10
+ build?: Record<string, unknown>;
11
+ }
12
+ }
13
+
14
+ declare module "electrobun/bun" {
15
+ export const ApplicationMenu: {
16
+ setApplicationMenu(menu: unknown): void;
17
+ };
18
+
19
+ export const BuildConfig: {
20
+ get(): Promise<{ runtime?: Record<string, unknown> }>;
21
+ };
22
+
23
+ export class BrowserWindow {
24
+ constructor(options: unknown);
25
+ show(): void;
26
+ focus(): void;
27
+ webview: {
28
+ loadURL(url: string): void;
29
+ };
30
+ }
31
+
32
+ export const GlobalShortcut: {
33
+ register(shortcut: string, handler: () => void): boolean;
34
+ };
35
+
36
+ const Electrobun: {
37
+ events: {
38
+ on(name: string, handler: (event: unknown) => void): void;
39
+ };
40
+ };
41
+
42
+ export default Electrobun;
43
+ }