codesynapt 0.0.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 (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +686 -0
  3. package/LICENSES.md +141 -0
  4. package/README.md +331 -0
  5. package/electron/main.cjs +2849 -0
  6. package/electron/plugin-loader.cjs +184 -0
  7. package/electron/preload.cjs +108 -0
  8. package/package.json +216 -0
  9. package/packages/core/bin/codesynapt-mcp.cjs +611 -0
  10. package/packages/core/bin/codesynapt.cjs +1933 -0
  11. package/packages/core/legacy.js +300 -0
  12. package/packages/core/lib/control-server.cjs +1539 -0
  13. package/packages/core/lib/embedding.cjs +89 -0
  14. package/packages/core/lib/logger.cjs +63 -0
  15. package/packages/core/lib/search-cache.cjs +140 -0
  16. package/packages/core/lib/search-worker.cjs +255 -0
  17. package/packages/core/lib/search.cjs +211 -0
  18. package/packages/core/lib/symbol-graph.cjs +402 -0
  19. package/packages/core/lib/symbol-parser-js.cjs +542 -0
  20. package/packages/core/lib/symbol-parser-misc.cjs +394 -0
  21. package/packages/core/lib/symbol-parser-py.cjs +215 -0
  22. package/packages/core/lib/symbol-parser-treesitter.cjs +658 -0
  23. package/packages/core/lib/symbol-parser-tsc.cjs +332 -0
  24. package/packages/core/monorepo.js +310 -0
  25. package/packages/core/parser.js +2234 -0
  26. package/packages/core/scanner.js +623 -0
  27. package/plugin-api/LICENSE +21 -0
  28. package/plugin-api/README.md +114 -0
  29. package/plugin-api/docs/01-getting-started.md +197 -0
  30. package/plugin-api/docs/02-concepts.md +269 -0
  31. package/plugin-api/docs/api-reference.md +463 -0
  32. package/plugin-api/docs/troubleshooting.md +332 -0
  33. package/plugin-api/docs/types/exporter.md +377 -0
  34. package/plugin-api/docs/types/theme.md +312 -0
  35. package/plugin-api/examples/hello-world-plugin/README.md +70 -0
  36. package/plugin-api/examples/hello-world-plugin/main.js +36 -0
  37. package/plugin-api/examples/hello-world-plugin/manifest.json +12 -0
  38. package/plugin-api/examples/mermaid-exporter/README.md +125 -0
  39. package/plugin-api/examples/mermaid-exporter/main.js +58 -0
  40. package/plugin-api/examples/mermaid-exporter/manifest.json +12 -0
  41. package/plugin-api/examples/rust-parser/README.md +71 -0
  42. package/plugin-api/examples/rust-parser/main.js +123 -0
  43. package/plugin-api/examples/rust-parser/manifest.json +12 -0
  44. package/plugin-api/examples/sunset-theme/README.md +95 -0
  45. package/plugin-api/examples/sunset-theme/manifest.json +12 -0
  46. package/plugin-api/examples/sunset-theme/theme.css +31 -0
  47. package/plugin-api/package.json +20 -0
  48. package/plugin-api/types.d.ts +395 -0
  49. package/public/app.js +6837 -0
  50. package/public/backend.js +285 -0
  51. package/public/index.html +647 -0
  52. package/public/plugin-host.js +321 -0
  53. package/public/style.css +4359 -0
  54. package/public/vendor/three.module.js +53044 -0
  55. package/scripts/competitor-watch.mjs +144 -0
  56. package/scripts/copy-vendor.js +21 -0
  57. package/scripts/download-bundled-node.cjs +53 -0
  58. package/scripts/fuses-after-pack.cjs +34 -0
  59. package/scripts/license-check.js +119 -0
  60. package/scripts/perf-test.js +200 -0
  61. package/server.js +132 -0
@@ -0,0 +1,114 @@
1
+ # filegraph3d plugin development
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
4
+ [![Plugin API: v0.1](https://img.shields.io/badge/Plugin%20API-v0.1.0-orange.svg)](./package.json)
5
+ [![Plugin types: 6](https://img.shields.io/badge/plugin%20types-6-blueviolet)](#six-plugin-types)
6
+ [![Examples: 4](https://img.shields.io/badge/examples-4-success)](./examples/)
7
+
8
+ > Build themes, exporters, parsers, layouts, panels, and context-menu
9
+ > actions for [filegraph3d](https://github.com/YOUR_USER/filegraph3d).
10
+
11
+ This package contains the public API surface for filegraph3d plugins.
12
+ It is **MIT-licensed** — you can build and distribute plugins under any
13
+ license you choose, including commercially. The main filegraph3d app
14
+ itself is AGPL-licensed; see the top-level `LICENSE` for details.
15
+
16
+ ---
17
+
18
+ ## Where to start
19
+
20
+ | If you want to… | Read this |
21
+ |---|---|
22
+ | Build your first plugin in 5 minutes | [Getting started](./docs/01-getting-started.md) |
23
+ | Understand how plugins work | [Concepts](./docs/02-concepts.md) |
24
+ | Add a new color theme | [Theme guide](./docs/types/theme.md) |
25
+ | Add an export format | [Exporter guide](./docs/types/exporter.md) |
26
+ | Look up an API method | [API reference](./docs/api-reference.md) |
27
+ | Fix something that's not working | [Troubleshooting](./docs/troubleshooting.md) |
28
+ | Copy a working example | [examples/](./examples/) |
29
+
30
+ ---
31
+
32
+ ## Six plugin types
33
+
34
+ | Type | What it does | Difficulty | Example |
35
+ |---|---|---|---|
36
+ | `theme` | A new color palette and look | ★☆☆ | [sunset-theme](./examples/sunset-theme/) |
37
+ | `exporter` | A new export format (Mermaid, GraphViz, etc.) | ★☆☆ | [mermaid-exporter](./examples/mermaid-exporter/) |
38
+ | `action` | A right-click item on graph nodes | ★☆☆ | [hello-world-plugin](./examples/hello-world-plugin/) |
39
+ | `parser` | Support for a new language | ★★☆ | [rust-parser](./examples/rust-parser/) |
40
+ | `panel` | A side panel in the app UI | ★★☆ | (in progress) |
41
+ | `layout` | A new graph layout algorithm | ★★★ | (in progress) |
42
+
43
+ A plugin has exactly one type — pick the smallest one that fits. You
44
+ can ship multiple plugins for different concerns.
45
+
46
+ ---
47
+
48
+ ## Anatomy of a plugin
49
+
50
+ Every plugin is a folder containing **at least two files**:
51
+
52
+ ```
53
+ my-plugin/
54
+ ├── manifest.json ← metadata (id, name, type, permissions)
55
+ └── main.js ← entry point (theme.css for theme plugins)
56
+ ```
57
+
58
+ That's it. No build step required (you can use plain JS), no `npm install`
59
+ needed (the API surface is just a type definition for IDE support).
60
+
61
+ ---
62
+
63
+ ## Where plugins live
64
+
65
+ filegraph3d looks for plugins in a per-user directory:
66
+
67
+ | OS | Path |
68
+ |---|---|
69
+ | **macOS** | `~/Library/Application Support/FileGraph 3D/plugins/` |
70
+ | **Windows** | `%APPDATA%\FileGraph 3D\plugins\` |
71
+ | **Linux** | `~/.config/FileGraph 3D/plugins/` |
72
+
73
+ You can open this folder from the app via **Settings → Appearance →
74
+ Open plugin folder…**.
75
+
76
+ Each plugin is its own subfolder. Restart filegraph3d after installing
77
+ a plugin to pick it up.
78
+
79
+ ---
80
+
81
+ ## TypeScript support (optional)
82
+
83
+ If you're using TypeScript, the types in `types.d.ts` give you full
84
+ IntelliSense and compile-time checks:
85
+
86
+ ```sh
87
+ npm install --save-dev @filegraph3d/plugin-api
88
+ ```
89
+
90
+ ```ts
91
+ import type { Plugin } from '@filegraph3d/plugin-api'
92
+
93
+ const plugin: Plugin = {
94
+ activate(ctx) {
95
+ ctx.log('hello from typescript')
96
+ }
97
+ }
98
+ export default plugin
99
+ ```
100
+
101
+ You can also use plain JavaScript — the API works identically.
102
+
103
+ ---
104
+
105
+ ## License
106
+
107
+ This API package: **MIT** — see [LICENSE](./LICENSE).
108
+
109
+ You can publish your plugins under any license you like (MIT,
110
+ Apache-2.0, proprietary, etc).
111
+
112
+ The filegraph3d app itself: **AGPL-3.0** — see [`../LICENSE`](../LICENSE).
113
+ Personal and internal use is free; commercial redistribution requires
114
+ a license.
@@ -0,0 +1,197 @@
1
+ # Getting started
2
+
3
+ You'll build your first filegraph3d plugin in five minutes. We'll walk
4
+ through two starter plugins — a **theme** (no JavaScript, just CSS)
5
+ and an **action** (a context-menu item that runs code).
6
+
7
+ ## Prerequisites
8
+
9
+ - filegraph3d installed and working
10
+ - A text editor of any kind
11
+ - That's it — no Node.js, no build tools, nothing to install
12
+
13
+ ## 1. Open your plugin folder
14
+
15
+ In filegraph3d, click **Settings → Appearance → Open plugin folder…**.
16
+
17
+ This opens (and creates if missing) the per-user plugin directory:
18
+
19
+ | OS | Path |
20
+ |---|---|
21
+ | macOS | `~/Library/Application Support/FileGraph 3D/plugins/` |
22
+ | Windows | `%APPDATA%\FileGraph 3D\plugins\` |
23
+ | Linux | `~/.config/FileGraph 3D/plugins/` |
24
+
25
+ ## 2A. Track A — make a theme (the easiest plugin)
26
+
27
+ Inside the plugin folder, create a new subfolder called `my-theme`:
28
+
29
+ ```
30
+ plugins/
31
+ └── my-theme/
32
+ ├── manifest.json
33
+ └── theme.css
34
+ ```
35
+
36
+ **`manifest.json`:**
37
+
38
+ ```json
39
+ {
40
+ "id": "my-theme",
41
+ "name": "My Theme",
42
+ "version": "1.0.0",
43
+ "author": "you",
44
+ "description": "My very first theme",
45
+ "type": "theme",
46
+ "main": "theme.css",
47
+ "minAppVersion": "0.10.0",
48
+ "license": "MIT"
49
+ }
50
+ ```
51
+
52
+ **`theme.css`:**
53
+
54
+ ```css
55
+ body[data-theme="my-theme"] {
56
+ --bg: #1F1B2E;
57
+ --bg-glass: rgba(30, 25, 50, 0.85);
58
+ --fg: #F0E8FF;
59
+ --fg-dim: #C5B8E0;
60
+ --fg-mute: #8B7DB0;
61
+
62
+ --border: rgba(180, 120, 255, 0.18);
63
+ --border-hot: rgba(180, 120, 255, 0.55);
64
+ --border-edge: rgba(180, 120, 255, 0.06);
65
+
66
+ --accent: #B478FF; /* purple */
67
+ --accent-warm: #FFB845; /* warm gold */
68
+ --accent-pink: #FF6B9F;
69
+ --accent-cool: #78D9FF;
70
+
71
+ --decoration: 0;
72
+ --grain: 0.02;
73
+ --radius: 4px;
74
+ }
75
+ ```
76
+
77
+ That's everything. Now:
78
+
79
+ 1. **Quit filegraph3d completely** (Cmd/Ctrl+Q, not just close window).
80
+ 2. **Reopen** the app.
81
+ 3. Go to **Settings → Appearance**. You'll see "My Theme" in the
82
+ theme grid.
83
+ 4. Click it.
84
+
85
+ The whole UI flips to your purple palette. You just made a theme.
86
+
87
+ → **For the full list of CSS variables and how to tune them, read
88
+ the [theme guide](./types/theme.md).**
89
+
90
+ ## 2B. Track B — make an action plugin (your first JS plugin)
91
+
92
+ Actions are the simplest code plugins. They add an item to the
93
+ right-click menu on graph nodes.
94
+
95
+ Create `plugins/my-action/`:
96
+
97
+ **`manifest.json`:**
98
+
99
+ ```json
100
+ {
101
+ "id": "my-action",
102
+ "name": "My Action",
103
+ "version": "1.0.0",
104
+ "author": "you",
105
+ "description": "Shows file info when you right-click a node",
106
+ "type": "action",
107
+ "main": "main.js",
108
+ "minAppVersion": "0.10.0",
109
+ "license": "MIT",
110
+ "permissions": ["context-menu", "read-graph"]
111
+ }
112
+ ```
113
+
114
+ **`main.js`:**
115
+
116
+ ```js
117
+ export default {
118
+ activate(ctx) {
119
+ ctx.log('My Action plugin loaded')
120
+
121
+ ctx.ui.registerContextMenuItem({
122
+ label: 'Show info for this file',
123
+ icon: 'ℹ',
124
+ action: (nodeId) => {
125
+ const node = ctx.graph.getNode(nodeId)
126
+ if (!node) {
127
+ ctx.toast('Node not found')
128
+ return
129
+ }
130
+ const outs = ctx.graph.outgoing(nodeId).length
131
+ const ins = ctx.graph.incoming(nodeId).length
132
+ ctx.toast(
133
+ `${nodeId}\n` +
134
+ ` ${node.loc} lines, ${node.size} bytes\n` +
135
+ ` imports ${outs}, used by ${ins}`
136
+ )
137
+ }
138
+ })
139
+ }
140
+ }
141
+ ```
142
+
143
+ Quit filegraph3d. Reopen. Open a folder. Right-click any node in the
144
+ graph. You'll see "Show info for this file" in the menu. Click it.
145
+
146
+ A toast pops up with the file's stats. You just made a code plugin.
147
+
148
+ ## What just happened
149
+
150
+ Every plugin follows the same shape:
151
+
152
+ 1. **`manifest.json`** declares who the plugin is, what it does, and
153
+ what permissions it needs.
154
+ 2. **The entry file** (either `theme.css` for themes or `main.js` for
155
+ code plugins) is the actual implementation.
156
+ 3. **For code plugins**, the default export has an `activate(ctx)`
157
+ function. `ctx` is a curated API — see the
158
+ [API reference](./api-reference.md) for the full surface.
159
+
160
+ ## Hot reload (limitations)
161
+
162
+ filegraph3d doesn't yet support hot-reload for plugins. Any change to
163
+ a plugin requires **quitting and reopening the app**.
164
+
165
+ A common workflow is:
166
+
167
+ 1. Edit the plugin file in your text editor.
168
+ 2. **Cmd/Ctrl+Q** to quit filegraph3d.
169
+ 3. Reopen the app — your plugin reloads with the changes.
170
+
171
+ (Hot reload is on the roadmap; until then, the quit-restart cycle is
172
+ fast — a few seconds.)
173
+
174
+ ## Next steps
175
+
176
+ Now that you have a working plugin:
177
+
178
+ - **[Concepts](./02-concepts.md)** — understand the plugin lifecycle,
179
+ manifest, and permission system in depth.
180
+ - **[Theme guide](./types/theme.md)** — every CSS variable you can override.
181
+ - **[Exporter guide](./types/exporter.md)** — add a new export format.
182
+ - **[API reference](./api-reference.md)** — every method on `ctx`.
183
+ - **[Troubleshooting](./troubleshooting.md)** — what to do when things break.
184
+
185
+ ## Got stuck?
186
+
187
+ If your plugin doesn't show up:
188
+
189
+ 1. Check **Settings → Appearance → Open plugin folder…** is the actual
190
+ path you put files in.
191
+ 2. Make sure each plugin is in its own **subfolder** (not loose files).
192
+ 3. Make sure `manifest.json` is **valid JSON** — paste it into
193
+ `jsonlint.com` if unsure.
194
+ 4. Open **DevTools** (View → Toggle Developer Tools) and look at the
195
+ Console — plugin load errors are logged there.
196
+
197
+ More in [troubleshooting](./troubleshooting.md).
@@ -0,0 +1,269 @@
1
+ # Concepts
2
+
3
+ Once you've built [your first plugin](./01-getting-started.md), the
4
+ pieces below explain why the system is shaped the way it is.
5
+
6
+ - [Plugin types](#plugin-types)
7
+ - [The manifest](#the-manifest)
8
+ - [Lifecycle](#lifecycle)
9
+ - [The `ctx` object](#the-ctx-object)
10
+ - [Permissions](#permissions)
11
+ - [Disposables](#disposables)
12
+ - [Plugin storage](#plugin-storage)
13
+ - [The event bus](#the-event-bus)
14
+
15
+ ## Plugin types
16
+
17
+ A plugin has exactly one `type` declared in its manifest. The type
18
+ determines what API surface the plugin gets and where its entry file
19
+ lives.
20
+
21
+ | Type | Entry file | What it adds |
22
+ |---|---|---|
23
+ | `theme` | `theme.css` | Visual theme (color variables, decorations) |
24
+ | `exporter` | `main.js` | A new "Export As…" format |
25
+ | `parser` | `main.js` | Support for a new file extension/language |
26
+ | `layout` | `main.js` | A new graph layout algorithm |
27
+ | `panel` | `main.js` | A side panel in the right rail |
28
+ | `action` | `main.js` | A right-click item on graph nodes |
29
+
30
+ Pick the smallest type that fits. If you want both a theme and a
31
+ context-menu action, ship them as **two separate plugins**.
32
+
33
+ ## The manifest
34
+
35
+ `manifest.json` is the only required file besides the entry. Every
36
+ plugin must have one.
37
+
38
+ ### Required fields
39
+
40
+ | Field | Type | Notes |
41
+ |---|---|---|
42
+ | `id` | string | Unique. Use kebab-case (`my-plugin`, not `My Plugin!`). Must match `[a-z0-9][a-z0-9-_]*`. |
43
+ | `name` | string | Display name shown to users. |
44
+ | `version` | string | Semver (`1.0.0`, `0.3.1`, etc.). |
45
+ | `type` | string | One of the six plugin types above. |
46
+ | `main` | string | Path to the entry file, relative to the plugin folder. |
47
+
48
+ ### Recommended fields
49
+
50
+ | Field | Type | Notes |
51
+ |---|---|---|
52
+ | `author` | string | Your name (no email required). Defaults to `"unknown"`. |
53
+ | `description` | string | One-line summary shown in the plugin list. |
54
+ | `minAppVersion` | string | Earliest filegraph3d version this plugin supports. |
55
+ | `license` | string | SPDX identifier (`MIT`, `Apache-2.0`, etc.). |
56
+
57
+ ### Optional fields
58
+
59
+ | Field | Type | Notes |
60
+ |---|---|---|
61
+ | `homepage` | string | URL for the plugin's documentation or repo. |
62
+ | `permissions` | string[] | What capabilities the plugin needs — see [Permissions](#permissions). |
63
+
64
+ ### Path safety
65
+
66
+ `main` must point to a file **inside** the plugin folder. Paths
67
+ containing `..` or absolute paths are rejected at load time. So
68
+ `main: "../../etc/passwd"` won't work — and that's by design.
69
+
70
+ ### Example: a full manifest
71
+
72
+ ```json
73
+ {
74
+ "id": "rust-parser",
75
+ "name": "Rust Parser",
76
+ "version": "1.0.0",
77
+ "author": "you",
78
+ "description": "Parse `use` statements from .rs files",
79
+ "type": "parser",
80
+ "main": "main.js",
81
+ "minAppVersion": "0.10.0",
82
+ "license": "MIT",
83
+ "homepage": "https://github.com/you/rust-parser",
84
+ "permissions": ["parse"]
85
+ }
86
+ ```
87
+
88
+ ## Lifecycle
89
+
90
+ Code plugins have two lifecycle hooks: `activate` and `deactivate`.
91
+
92
+ ```js
93
+ export default {
94
+ activate(ctx) {
95
+ // Called once when the plugin is loaded.
96
+ // Register everything you need here.
97
+ },
98
+
99
+ deactivate() {
100
+ // Optional. Called when the plugin is unloaded.
101
+ // Clean up timers, intervals, custom DOM elements you created
102
+ // directly, etc.
103
+ }
104
+ }
105
+ ```
106
+
107
+ Right now, "loaded" means **the app started and discovered this plugin
108
+ in the plugin folder**. There's no in-app enable/disable toggle yet
109
+ (planned). So in practice `activate()` runs once at startup and
110
+ `deactivate()` is mostly a forward-compatible hook.
111
+
112
+ ### What `activate` should do
113
+
114
+ - Register handlers (`ctx.ui.registerContextMenuItem`, etc.)
115
+ - Subscribe to events (`ctx.events.on`)
116
+ - Set up timers if needed
117
+ - Store an unsubscribe handle so `deactivate` can clean up
118
+
119
+ ### What `activate` should NOT do
120
+
121
+ - **Don't** do heavy work synchronously — it blocks app startup.
122
+ - **Don't** read large files or scan directories — defer to user
123
+ interaction.
124
+ - **Don't** throw — the app catches it, but the plugin won't load.
125
+
126
+ ### Theme plugins
127
+
128
+ Themes don't have lifecycle hooks. They're just CSS, injected once at
129
+ startup. Override the `body[data-theme="your-id"]` selector and you're
130
+ done.
131
+
132
+ ## The `ctx` object
133
+
134
+ Every `activate(ctx)` call receives a context object. It's the only
135
+ way a plugin should talk to the app — don't reach into `window`
136
+ globals or DOM internals, those aren't stable across versions.
137
+
138
+ ```js
139
+ ctx.manifest // your own manifest (handy for version checks)
140
+ ctx.appVersion // host filegraph3d version
141
+
142
+ ctx.graph // read-only access to nodes/edges/selection
143
+ ctx.ui // register UI: panels, context-menu items, commands
144
+ ctx.exporters // register export formats
145
+ ctx.parsers // register language parsers
146
+ ctx.layouts // register layout algorithms
147
+ ctx.events // subscribe to app events (selection, snapshot, etc.)
148
+
149
+ ctx.storage // persistent per-plugin key-value store
150
+ ctx.toast(msg) // show user a notification
151
+ ctx.log(...args) // log to plugin console (prefixed with plugin id)
152
+ ```
153
+
154
+ For each property's full surface, see the
155
+ [API reference](./api-reference.md).
156
+
157
+ ## Permissions
158
+
159
+ Plugins must declare what they need in `manifest.permissions`. Attempting
160
+ to call an API without the matching permission throws an error.
161
+
162
+ | Permission | Required for |
163
+ |---|---|
164
+ | `read-files` | `ctx.graph.readFile(id)` |
165
+ | `read-graph` | Always granted (nodes/edges access). Declaring it is good form. |
166
+ | `modify-graph` | Reserved for future use (adding nodes/edges from plugins). |
167
+ | `ui-panel` | `ctx.ui.registerPanel(...)` |
168
+ | `context-menu` | `ctx.ui.registerContextMenuItem(...)` |
169
+ | `export` | `ctx.exporters.register(...)` |
170
+ | `parse` | `ctx.parsers.register(...)` |
171
+
172
+ ### Why bother?
173
+
174
+ Two reasons:
175
+
176
+ 1. **Self-documentation.** Anyone reading your manifest knows what the
177
+ plugin can do without reading the code.
178
+ 2. **Future-proofing.** filegraph3d may eventually show a permission
179
+ prompt to users before enabling a plugin (Obsidian and VS Code both
180
+ have similar mechanisms). Declaring permissions now means your
181
+ plugin won't break when that lands.
182
+
183
+ ### Example
184
+
185
+ ```json
186
+ {
187
+ "id": "my-plugin",
188
+ ...
189
+ "permissions": ["read-files", "context-menu"]
190
+ }
191
+ ```
192
+
193
+ This plugin can read file contents and add right-click items. It
194
+ cannot register an exporter or a panel — if it tries, `ctx.exporters.
195
+ register(...)` throws.
196
+
197
+ ## Disposables
198
+
199
+ Several API methods return a `{ dispose() }` handle. Calling `dispose()`
200
+ removes the registration.
201
+
202
+ ```js
203
+ const panel = ctx.ui.registerPanel({
204
+ id: 'my-panel',
205
+ title: 'My Panel',
206
+ render(container) { container.textContent = 'hello' }
207
+ })
208
+
209
+ // Later, if you want to remove the panel:
210
+ panel.dispose()
211
+ ```
212
+
213
+ If you don't dispose explicitly, the app does it for you when the
214
+ plugin is unloaded. But if your plugin shows/hides UI based on state,
215
+ you'll want to call `dispose()` yourself.
216
+
217
+ ## Plugin storage
218
+
219
+ Each plugin gets a private key-value store backed by `localStorage`:
220
+
221
+ ```js
222
+ ctx.storage.set('lastSelection', { nodeId: 'src/index.js', at: Date.now() })
223
+
224
+ // Next time the plugin loads:
225
+ const last = ctx.storage.get('lastSelection')
226
+ if (last) ctx.log(`Last selection was ${last.nodeId}`)
227
+ ```
228
+
229
+ Keys are namespaced per plugin id — you can't accidentally clash with
230
+ another plugin or with the app itself.
231
+
232
+ Values must be JSON-serializable. Don't put DOM nodes, functions, or
233
+ circular structures in there.
234
+
235
+ ## The event bus
236
+
237
+ Plugins can subscribe to app-level events. These fire whenever the
238
+ relevant state changes.
239
+
240
+ | Event | When it fires | Payload |
241
+ |---|---|---|
242
+ | `snapshot:applied` | New graph data loaded (folder opened or rescanned) | `{ root: string }` |
243
+ | `selection:changed` | User clicked a node or selected something | `string \| null` (node id) |
244
+ | `filter:changed` | Search text or filter state changed | (no payload) |
245
+ | `focus:changed` | Hovered/focused node changed | `string \| null` |
246
+ | `graph:cleared` | Folder was closed | (no payload) |
247
+ | `activeset:changed` | User starred a file or toggled a pipeline | (no payload) |
248
+
249
+ ```js
250
+ const off = ctx.events.on('selection:changed', (nodeId) => {
251
+ if (nodeId) ctx.log('user picked', nodeId)
252
+ })
253
+
254
+ // Unsubscribe:
255
+ off()
256
+ ```
257
+
258
+ `ctx.events.on()` returns an unsubscribe function. Save it and call it
259
+ from `deactivate()`, or it'll keep firing after your plugin is gone.
260
+
261
+ ## What's next
262
+
263
+ You now know enough to build any plugin type. The next docs are
264
+ type-specific:
265
+
266
+ - [Theme guide](./types/theme.md)
267
+ - [Exporter guide](./types/exporter.md)
268
+ - [API reference](./api-reference.md) — the precise surface
269
+ - [Troubleshooting](./troubleshooting.md) — when things go wrong