@gmickel/gno 0.28.2 → 0.29.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 (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 +38 -0
  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 +532 -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
@@ -2,14 +2,26 @@ import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { createRoot } from "react-dom/client";
3
3
 
4
4
  import { HelpButton } from "./components/HelpButton";
5
- import { QuickSwitcher, saveRecentDocument } from "./components/QuickSwitcher";
5
+ import { QuickSwitcher } from "./components/QuickSwitcher";
6
6
  import { ShortcutHelpModal } from "./components/ShortcutHelpModal";
7
+ import { WorkspaceTabs } from "./components/WorkspaceTabs";
7
8
  import { CaptureModalProvider, useCaptureModal } from "./hooks/useCaptureModal";
8
9
  import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
9
10
  import { parseDocumentDeepLink } from "./lib/deep-links";
11
+ import { saveRecentDocument } from "./lib/navigation-state";
12
+ import {
13
+ activateWorkspaceTab,
14
+ closeWorkspaceTab,
15
+ createWorkspaceTab,
16
+ loadWorkspaceState,
17
+ saveWorkspaceState,
18
+ updateActiveTabLocation,
19
+ type WorkspaceState,
20
+ } from "./lib/workspace-tabs";
10
21
  import Ask from "./pages/Ask";
11
22
  import Browse from "./pages/Browse";
12
23
  import Collections from "./pages/Collections";
24
+ import Connectors from "./pages/Connectors";
13
25
  import Dashboard from "./pages/Dashboard";
14
26
  import DocumentEditor from "./pages/DocumentEditor";
15
27
  import DocView from "./pages/DocView";
@@ -24,7 +36,8 @@ type Route =
24
36
  | "/ask"
25
37
  | "/edit"
26
38
  | "/collections"
27
- | "/graph";
39
+ | "/graph"
40
+ | "/connectors";
28
41
  type Navigate = (to: string | number) => void;
29
42
 
30
43
  const routes: Record<Route, React.ComponentType<{ navigate: Navigate }>> = {
@@ -34,6 +47,7 @@ const routes: Record<Route, React.ComponentType<{ navigate: Navigate }>> = {
34
47
  "/doc": DocView,
35
48
  "/edit": DocumentEditor,
36
49
  "/collections": Collections,
50
+ "/connectors": Connectors,
37
51
  "/ask": Ask,
38
52
  "/graph": GraphView,
39
53
  };
@@ -43,13 +57,21 @@ interface AppContentProps {
43
57
  navigate: Navigate;
44
58
  shortcutHelpOpen: boolean;
45
59
  setShortcutHelpOpen: (open: boolean) => void;
60
+ workspace: WorkspaceState;
61
+ onActivateTab: (tabId: string) => void;
62
+ onCloseTab: (tabId: string) => void;
63
+ onNewTab: () => void;
46
64
  }
47
65
 
48
66
  function AppContent({
49
67
  location,
50
68
  navigate,
69
+ onActivateTab,
70
+ onCloseTab,
71
+ onNewTab,
51
72
  shortcutHelpOpen,
52
73
  setShortcutHelpOpen,
74
+ workspace,
53
75
  }: AppContentProps) {
54
76
  const { openCapture } = useCaptureModal();
55
77
  const [quickSwitcherOpen, setQuickSwitcherOpen] = useState(false);
@@ -112,6 +134,13 @@ function AppContent({
112
134
  return (
113
135
  <>
114
136
  <div className="flex min-h-screen flex-col">
137
+ <WorkspaceTabs
138
+ activeTabId={workspace.activeTabId}
139
+ onActivate={onActivateTab}
140
+ onClose={onCloseTab}
141
+ onNewTab={onNewTab}
142
+ tabs={workspace.tabs}
143
+ />
115
144
  <div className="flex-1">
116
145
  <Page key={location} navigate={navigate} />
117
146
  </div>
@@ -179,25 +208,86 @@ function AppContent({
179
208
  }
180
209
 
181
210
  function App() {
182
- const [location, setLocation] = useState<string>(
183
- window.location.pathname + window.location.search
211
+ const initialLocation = window.location.pathname + window.location.search;
212
+ const [workspace, setWorkspace] = useState<WorkspaceState>(() =>
213
+ loadWorkspaceState(initialLocation)
184
214
  );
185
215
  const [shortcutHelpOpen, setShortcutHelpOpen] = useState(false);
216
+ const activeTab =
217
+ workspace.tabs.find((tab) => tab.id === workspace.activeTabId) ??
218
+ workspace.tabs[0];
219
+ const location = activeTab?.location ?? "/";
186
220
 
187
221
  useEffect(() => {
188
- const handlePopState = () =>
189
- setLocation(window.location.pathname + window.location.search);
222
+ const currentLocation = window.location.pathname + window.location.search;
223
+ if (location !== currentLocation) {
224
+ window.history.replaceState({}, "", location);
225
+ }
226
+ }, [location]);
227
+
228
+ useEffect(() => {
229
+ const handlePopState = () => {
230
+ setWorkspace((current) =>
231
+ updateActiveTabLocation(
232
+ current,
233
+ window.location.pathname + window.location.search
234
+ )
235
+ );
236
+ };
190
237
  window.addEventListener("popstate", handlePopState);
191
238
  return () => window.removeEventListener("popstate", handlePopState);
192
239
  }, []);
193
240
 
241
+ useEffect(() => {
242
+ saveWorkspaceState(workspace);
243
+ }, [workspace]);
244
+
194
245
  const navigate = useCallback((to: string | number) => {
195
246
  if (typeof to === "number") {
196
247
  window.history.go(to);
197
248
  return;
198
249
  }
199
250
  window.history.pushState({}, "", to);
200
- setLocation(to);
251
+ setWorkspace((current) => updateActiveTabLocation(current, to));
252
+ }, []);
253
+
254
+ const activateTab = useCallback((tabId: string) => {
255
+ setWorkspace((current) => {
256
+ const next = activateWorkspaceTab(current, tabId);
257
+ const tab =
258
+ next.tabs.find((entry) => entry.id === next.activeTabId) ??
259
+ next.tabs[0];
260
+ if (tab) {
261
+ window.history.pushState({}, "", tab.location);
262
+ }
263
+ return next;
264
+ });
265
+ }, []);
266
+
267
+ const closeTab = useCallback((tabId: string) => {
268
+ setWorkspace((current) => {
269
+ const next = closeWorkspaceTab(current, tabId);
270
+ const tab =
271
+ next.tabs.find((entry) => entry.id === next.activeTabId) ??
272
+ next.tabs[0];
273
+ if (tab) {
274
+ window.history.replaceState({}, "", tab.location);
275
+ }
276
+ return next;
277
+ });
278
+ }, []);
279
+
280
+ const openNewTab = useCallback(() => {
281
+ setWorkspace((current) => {
282
+ const next = createWorkspaceTab(current, "/search");
283
+ const tab =
284
+ next.tabs.find((entry) => entry.id === next.activeTabId) ??
285
+ next.tabs[0];
286
+ if (tab) {
287
+ window.history.pushState({}, "", tab.location);
288
+ }
289
+ return next;
290
+ });
201
291
  }, []);
202
292
 
203
293
  return (
@@ -205,8 +295,12 @@ function App() {
205
295
  <AppContent
206
296
  location={location}
207
297
  navigate={navigate}
298
+ onActivateTab={activateTab}
299
+ onCloseTab={closeTab}
300
+ onNewTab={openNewTab}
208
301
  setShortcutHelpOpen={setShortcutHelpOpen}
209
302
  shortcutHelpOpen={shortcutHelpOpen}
303
+ workspace={workspace}
210
304
  />
211
305
  </CaptureModalProvider>
212
306
  );