overmux 0.0.8 → 0.0.9

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 (29) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/bin.js +58 -20
  3. package/dist/bin.js.map +1 -1
  4. package/dist/docs/100-introduction/300-why-i-built-overmux.md +1 -1
  5. package/dist/docs/200-getting-started/200-install-overmux-desktop.md +13 -0
  6. package/dist/docs/400-reference/500-server/100-resources.md +25 -0
  7. package/dist/docs/400-reference/500-server/500-api.md +4 -2
  8. package/dist/docs/400-reference/600-client/010-commands.md +0 -29
  9. package/dist/exports/client.d.ts +1 -1
  10. package/dist/exports/client.d.ts.map +1 -1
  11. package/dist/exports/client.js +89 -25
  12. package/dist/exports/client.js.map +1 -1
  13. package/dist/exports/{index-Cz3xkCa4.d.ts → index-Dk11nKIv.d.ts} +2 -2
  14. package/dist/exports/index-Dk11nKIv.d.ts.map +1 -0
  15. package/dist/exports/index.d.ts +1 -1
  16. package/dist/exports/index.js.map +1 -1
  17. package/dist/internal/server/coordinator/server-child.js +75 -22
  18. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  19. package/docs/100-introduction/300-why-i-built-overmux.md +1 -1
  20. package/docs/200-getting-started/200-install-overmux-desktop.md +13 -0
  21. package/docs/400-reference/500-server/100-resources.md +25 -0
  22. package/docs/400-reference/500-server/500-api.md +4 -2
  23. package/docs/400-reference/600-client/010-commands.md +0 -29
  24. package/package.json +1 -1
  25. package/src/internal/client/overmux-react.ts +121 -27
  26. package/src/internal/server/runtime/runtime-resources.ts +99 -20
  27. package/src/internal/server/websocket-connection.ts +27 -2
  28. package/src/public/config.ts +1 -1
  29. package/dist/exports/index-Cz3xkCa4.d.ts.map +0 -1
@@ -19,4 +19,4 @@ I lived in a customized tmux setup. But kept noticing that that the terminal, fo
19
19
 
20
20
  [Herdr](https://github.com/herdrdev/herdr) (a TUI) and [Orca](https://github.com/stablyai/orca) (web based) offer a decent default experience. But they lack the customizability I love with the terminal and neovim. They use their own muxer implementations and dictate workflows.
21
21
 
22
- This future filled me with existential dread, so I built Overmux to embed my terminal in a Desktop webapp and build my own experience with a mix of tmux terminals + custom web uis.
22
+ This future didn't _spark joy_ for me, so I built Overmux to embed my terminal in a Desktop webapp and build my own experience with a mix of tmux terminals + custom web uis.
@@ -22,6 +22,19 @@ Download the ZIP for your Mac from the [Overmux releases](https://github.com/ric
22
22
 
23
23
  Overmux is not currently signed or notarized. Follow [Apple's instructions for opening an app from an unidentified developer](https://support.apple.com/en-us/102445) to allow it to run.
24
24
 
25
+ <details>
26
+ <summary>macOS says Overmux is damaged or the developer cannot be verified</summary>
27
+
28
+ For quarantine-related blocking, run this **only for a trusted download from [official Overmux releases](https://github.com/richardgill/overmux/releases)**:
29
+
30
+ ```bash
31
+ xattr -dr com.apple.quarantine "$HOME/Applications/Overmux.app"
32
+ ```
33
+
34
+ Use `/Applications/Overmux.app` instead if you installed it there. Then reopen Overmux in Finder.
35
+
36
+ </details>
37
+
25
38
  ## Linux
26
39
 
27
40
  Download the AppImage or Debian package from the [Overmux releases](https://github.com/richardgill/overmux/releases).
@@ -73,6 +73,31 @@ When the subscription ends, Overmux calls the cleanup function returned by `subs
73
73
 
74
74
  The React code stays the same: `useResource({ id: "ls" })`.
75
75
 
76
+ ### Asynchronous setup
77
+
78
+ `subscribe` can be synchronous or asynchronous. Both forms return a cleanup function. For example, replace the `ls` resource's `subscribe` handler to resolve the directory's canonical path before watching it:
79
+
80
+ ```ts
81
+ import { watch } from "node:fs";
82
+ import { realpath } from "node:fs/promises";
83
+
84
+ // Inside the resource definition:
85
+ subscribe: async (_input, invalidate, context) => {
86
+ const directory = await realpath("/");
87
+ // The subscription may have ended while resolving the path.
88
+ context.signal.throwIfAborted();
89
+
90
+ const watcher = watch(directory, { signal: context.signal }, () => {
91
+ invalidate();
92
+ });
93
+ return () => watcher.close();
94
+ },
95
+ ```
96
+
97
+ `context.signal` is an `AbortSignal` that Overmux aborts when the client unsubscribes, disconnects, or the server shuts down. Cancellation does not automatically stop an async function: pass the signal to APIs that support it and check it after awaiting setup. Here, `realpath` does not accept a signal, so `throwIfAborted()` prevents starting a watcher if cancellation happened during that work.
98
+
99
+ Overmux calls the returned cleanup function once, even if setup finishes after cancellation. Cleanup may also be asynchronous. In this example, the signal can close the watcher before Overmux calls cleanup; `watcher.close()` is safe to call again. If setup throws before returning cleanup, your handler must release any resources it already acquired.
100
+
76
101
  ## Derived
77
102
 
78
103
  Derived resources compute a value from other resources.
@@ -47,7 +47,7 @@ A resource definition has a `contract` created with `defineResourceContract({ in
47
47
  | Kind | Required handler API |
48
48
  | --- | --- |
49
49
  | `query` | `read(input, context) => output \| Promise<output>` |
50
- | `subscription` | `read(input, context)` and `subscribe(input, invalidate, context) => disposer` |
50
+ | `subscription` | `read(input, context)` and `subscribe(input, invalidate, context) => (() => void \| Promise<void>) \| Promise<() => void \| Promise<void>>` |
51
51
  | `derived` | `dependencies` and `combine(dependencies) => output` |
52
52
 
53
53
  ```ts
@@ -69,7 +69,9 @@ const resources = {
69
69
  };
70
70
  ```
71
71
 
72
- `subscribe` must synchronously return a cleanup function. Overmux calls it when that client subscription, or the server runtime, closes. A derived resource reads the resources named by `dependencies`; invalidating a dependency also invalidates its dependants. See [Resources](./100-resources.md) for contracts, derived dependencies, and invalidation.
72
+ `subscribe` returns a cleanup function, directly or asynchronously. Overmux aborts `context.signal` when the subscription ends, including during setup, on client disconnect, or on server shutdown. Overmux calls the returned cleanup function once, even if setup returns it after cancellation. The cleanup function itself may also be asynchronous. If setup throws before returning cleanup, the handler must release any resources it already acquired. See [Asynchronous setup](./100-resources.md#asynchronous-setup) for an example.
73
+
74
+ A derived resource reads the resources named by `dependencies`; invalidating a dependency also invalidates its dependants. See [Resources](./100-resources.md) for contracts, derived dependencies, and invalidation.
73
75
 
74
76
  ## Stream handlers
75
77
 
@@ -158,32 +158,3 @@ const CommandMenu = () => {
158
158
  Each entry exposes `id`, `title`, `bindings`, `enabled`, and `execute()`. The list includes disabled commands but excludes commands without a registration.
159
159
 
160
160
  Each command appears once, even if multiple components register it. `execute()` selects the handler using the current focus and runs it only if enabled.
161
-
162
- ## Using commands from packages
163
-
164
- Packages can export commands to include alongside your own:
165
-
166
- ```tsx
167
- import { sourceControlCommands } from "@overmux/git/react";
168
-
169
- export const client = defineOvermuxClient({
170
- commands: {
171
- ...commands,
172
- ...sourceControlCommands,
173
- },
174
- component: App,
175
- });
176
- ```
177
-
178
- Adding commands to the registry does not register their handlers. For this package, pass the commands to `SourceControlView`:
179
-
180
- ```tsx
181
- <SourceControlView
182
- {...sourceControlProps}
183
- commandHandles={sourceControlCommands}
184
- />
185
- ```
186
-
187
- The view registers handlers for navigating changed files and scrolling diffs. They then appear in `useCommands()` alongside your own registered commands.
188
-
189
- Keep command IDs unique when combining registries, and pass the same command objects to the client and the component.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overmux",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/richardgill/overmux",
6
6
  "repository": {
@@ -127,13 +127,131 @@ const useCanonicalInput = (input: unknown) => {
127
127
  return useMemo(() => (key ? JSON.parse(key) : undefined), [key]);
128
128
  };
129
129
 
130
+ // Query identity shares pending work across observers without retaining unused cache entries.
131
+ const pendingResourceInvalidations = new WeakSet<object>();
132
+
133
+ type ResourceQuery = {
134
+ queryClient: ReturnType<typeof useQueryClient>;
135
+ queryKey: readonly unknown[];
136
+ };
137
+
138
+ const invalidateResource = ({ queryClient, queryKey }: ResourceQuery) => {
139
+ const query = queryClient.getQueryCache().find({ exact: true, queryKey });
140
+
141
+ if (query && query.state.fetchStatus !== "idle") {
142
+ // TanStack coalesces initial-read invalidations into that same read. Queue a
143
+ // follow-up instead, without cancelling reads or overlooking same-turn changes.
144
+ pendingResourceInvalidations.add(query);
145
+ return;
146
+ }
147
+
148
+ if (query) {
149
+ pendingResourceInvalidations.delete(query);
150
+ }
151
+
152
+ void queryClient.invalidateQueries({ exact: true, queryKey });
153
+ };
154
+
155
+ const flushResourceInvalidation = ({
156
+ queryClient,
157
+ queryKey,
158
+ }: ResourceQuery) => {
159
+ const query = queryClient.getQueryCache().find({ exact: true, queryKey });
160
+
161
+ if (!query || query.state.fetchStatus !== "idle") {
162
+ return;
163
+ }
164
+
165
+ // All observers see settlement. Consuming the shared flag before starting a read
166
+ // ensures their callbacks cannot keep triggering each other's follow-up reads.
167
+ if (pendingResourceInvalidations.delete(query)) {
168
+ void queryClient.invalidateQueries({ exact: true, queryKey });
169
+ }
170
+ };
171
+
172
+ const useResourceInvalidation = ({
173
+ queryKey,
174
+ skipped,
175
+ }: {
176
+ queryKey: readonly [id: string, input: unknown];
177
+ skipped: boolean;
178
+ }) => {
179
+ const [id, canonicalInput] = queryKey;
180
+ const { manifest, overmuxServerApi } = useRuntime();
181
+ const queryClient = useQueryClient();
182
+ const invalidate = useCallback(
183
+ () => invalidateResource({ queryClient, queryKey }),
184
+ [queryClient, queryKey],
185
+ );
186
+
187
+ useEffect(() => {
188
+ if (skipped) {
189
+ return;
190
+ }
191
+
192
+ let active = true;
193
+ const cache = queryClient.getQueryCache();
194
+ const observedQuery = cache.find({ exact: true, queryKey });
195
+
196
+ // Fast reads can enter and leave fetching between React renders. Observe cache
197
+ // settlement directly, then defer the follow-up outside TanStack's notification.
198
+ const unsubscribe = cache.subscribe((event) => {
199
+ if (
200
+ event.type === "updated" &&
201
+ event.query === observedQuery &&
202
+ event.query.state.fetchStatus === "idle" &&
203
+ pendingResourceInvalidations.has(event.query)
204
+ ) {
205
+ queueMicrotask(() => {
206
+ if (active) {
207
+ flushResourceInvalidation({ queryClient, queryKey });
208
+ }
209
+ });
210
+ }
211
+ });
212
+
213
+ flushResourceInvalidation({ queryClient, queryKey });
214
+ return () => {
215
+ active = false;
216
+ unsubscribe();
217
+ };
218
+ }, [queryClient, queryKey, skipped]);
219
+
220
+ useEffect(() => {
221
+ if (skipped || !manifest.resources.includes(id)) {
222
+ return;
223
+ }
224
+
225
+ let active = true;
226
+ const unsubscribe = overmuxServerApi.subscribeResource({
227
+ id,
228
+ input: canonicalInput,
229
+ onError: () => active && invalidate(),
230
+ onInvalidate: () => active && invalidate(),
231
+ });
232
+
233
+ return () => {
234
+ active = false;
235
+ unsubscribe();
236
+ };
237
+ }, [
238
+ manifest.resources,
239
+ canonicalInput,
240
+ id,
241
+ invalidate,
242
+ overmuxServerApi,
243
+ skipped,
244
+ ]);
245
+
246
+ return invalidate;
247
+ };
248
+
130
249
  const useResourceById = <TOutput>(
131
250
  id: string,
132
251
  input: unknown,
133
252
  skipped: boolean,
134
253
  ): ResourceResult<TOutput> | undefined => {
135
- const { manifest, overmuxServerApi } = useRuntime();
136
- const queryClient = useQueryClient();
254
+ const { overmuxServerApi } = useRuntime();
137
255
  const canonicalInput = useCanonicalInput(input);
138
256
  const queryKey = useMemo(
139
257
  () => [id, canonicalInput] as const,
@@ -148,31 +266,7 @@ const useResourceById = <TOutput>(
148
266
  }) as Promise<TOutput>,
149
267
  queryKey,
150
268
  });
151
- useEffect(
152
- () =>
153
- !skipped && manifest.resources.includes(id)
154
- ? overmuxServerApi.subscribeResource({
155
- id,
156
- input: canonicalInput,
157
- onError: () =>
158
- void queryClient.invalidateQueries({ exact: true, queryKey }),
159
- onInvalidate: () =>
160
- void queryClient.invalidateQueries({ exact: true, queryKey }),
161
- })
162
- : undefined,
163
- [
164
- manifest.resources,
165
- canonicalInput,
166
- id,
167
- overmuxServerApi,
168
- queryClient,
169
- queryKey,
170
- skipped,
171
- ],
172
- );
173
- const refetch = useCallback(() => {
174
- void queryClient.invalidateQueries({ exact: true, queryKey });
175
- }, [queryClient, queryKey]);
269
+ const refetch = useResourceInvalidation({ queryKey, skipped });
176
270
  if (skipped) {
177
271
  return undefined;
178
272
  }
@@ -19,7 +19,7 @@ export type RuntimeResource = {
19
19
  input: unknown,
20
20
  listener: () => void,
21
21
  signal?: AbortSignal,
22
- ) => RuntimeDisposer;
22
+ ) => Promise<RuntimeDisposer>;
23
23
  };
24
24
 
25
25
  export type RuntimeResourceDefinitions = Readonly<
@@ -47,6 +47,51 @@ type CreateRuntimeResourcesOptions = {
47
47
  lifecycle: RuntimeLifecycle;
48
48
  };
49
49
 
50
+ const createSubscriptionLifetime = ({
51
+ entry,
52
+ invalidationListeners,
53
+ lifecycle,
54
+ signal,
55
+ }: {
56
+ entry: InvalidationListener;
57
+ invalidationListeners: Set<InvalidationListener>;
58
+ lifecycle: RuntimeLifecycle;
59
+ signal?: AbortSignal;
60
+ }) => {
61
+ const controller = new AbortController();
62
+ const subscriptionSignal = AbortSignal.any([
63
+ lifecycle.requestSignal(signal),
64
+ controller.signal,
65
+ ]);
66
+ subscriptionSignal.throwIfAborted();
67
+ const registeredEntry = {
68
+ ...entry,
69
+ listener: () => {
70
+ if (!subscriptionSignal.aborted) {
71
+ entry.listener();
72
+ }
73
+ },
74
+ };
75
+ const disposers: RuntimeDisposer[] = [];
76
+ // Own the lifetime before invoking userland, which can invalidate and
77
+ // trigger unsubscribe synchronously, or await setup past cancellation.
78
+ const release = lifecycle.track(async () => {
79
+ invalidationListeners.delete(registeredEntry);
80
+ await lifecycle.disposeAll(disposers);
81
+ }, subscriptionSignal);
82
+ const dispose = () => {
83
+ controller.abort(new Error("Resource subscription closed"));
84
+ return release();
85
+ };
86
+ invalidationListeners.add(registeredEntry);
87
+ return {
88
+ dispose,
89
+ disposers,
90
+ refresh: registeredEntry.listener,
91
+ signal: subscriptionSignal,
92
+ };
93
+ };
94
+
50
95
  export const createRuntimeResources = ({
51
96
  definitions,
52
97
  instance,
@@ -119,13 +164,14 @@ export const createRuntimeResources = ({
119
164
  return definition.contract.output.parse(rawOutput);
120
165
  };
121
166
 
122
- const activateWatchers = (
167
+ const activateSubscriptions = async (
123
168
  id: string,
124
169
  rawInput: unknown,
125
- signal: AbortSignal | undefined,
170
+ signal: AbortSignal,
126
171
  activated: Set<string>,
127
172
  disposers: RuntimeDisposer[],
128
- ) => {
173
+ ): Promise<void> => {
174
+ signal.throwIfAborted();
129
175
  if (activated.has(id)) {
130
176
  return;
131
177
  }
@@ -133,44 +179,77 @@ export const createRuntimeResources = ({
133
179
  const definition = definitions[id]!;
134
180
  const input = definition.contract.input.parse(rawInput);
135
181
  if (definition.kind === "subscription") {
136
- const dispose = definition.subscribe(
182
+ const dispose = await definition.subscribe(
137
183
  input,
138
- () => invalidateResource(id, input),
184
+ () => {
185
+ if (!signal.aborted) {
186
+ invalidateResource(id, input);
187
+ }
188
+ },
139
189
  context(signal),
140
190
  );
141
191
  if (typeof dispose !== "function") {
142
192
  throw new Error(`Resource ${id} subscribe must return a disposer`);
143
193
  }
194
+ // Cancellation releases acquired resources without waiting for pending
195
+ // userland setup. A cleanup returned after cancellation is released here.
196
+ if (signal.aborted) {
197
+ await dispose();
198
+ signal.throwIfAborted();
199
+ }
144
200
  disposers.push(dispose);
145
201
  return;
146
202
  }
147
203
  if (definition.kind === "derived") {
148
- graph.dependencies.get(id)?.forEach((dependencyId) => {
149
- activateWatchers(dependencyId, rawInput, signal, activated, disposers);
150
- });
204
+ for (const dependencyId of graph.dependencies.get(id) ?? []) {
205
+ await activateSubscriptions(
206
+ dependencyId,
207
+ rawInput,
208
+ signal,
209
+ activated,
210
+ disposers,
211
+ );
212
+ }
151
213
  }
152
214
  };
153
215
 
154
216
  Object.keys(definitions).forEach((name) => {
155
217
  resources.set(name, {
156
218
  read: (input, signal) => read(name, input, signal),
157
- subscribe: (rawInput, listener, signal) => {
219
+ subscribe: async (rawInput, listener, signal) => {
158
220
  lifecycle.requestSignal(signal).throwIfAborted();
159
221
  const input = definitions[name]!.contract.input.parse(rawInput);
160
- const entry = { input, listener, resourceId: name };
161
- const watcherDisposers: RuntimeDisposer[] = [];
162
- invalidationListeners.add(entry);
222
+ const subscription = createSubscriptionLifetime({
223
+ entry: { input, listener, resourceId: name },
224
+ invalidationListeners,
225
+ lifecycle,
226
+ signal,
227
+ });
163
228
  try {
164
- activateWatchers(name, rawInput, signal, new Set(), watcherDisposers);
229
+ await activateSubscriptions(
230
+ name,
231
+ rawInput,
232
+ subscription.signal,
233
+ new Set(),
234
+ subscription.disposers,
235
+ );
236
+ subscription.signal.throwIfAborted();
237
+ // Refresh only this subscriber to cover changes during setup. Returning
238
+ // cleanup does not imply an external event source is already ready.
239
+ subscription.refresh();
240
+ subscription.signal.throwIfAborted();
241
+ return subscription.dispose;
165
242
  } catch (cause) {
166
- invalidationListeners.delete(entry);
167
- void lifecycle.disposeAll(watcherDisposers).catch(() => undefined);
243
+ try {
244
+ await subscription.dispose();
245
+ } catch (cleanupError) {
246
+ throw new AggregateError(
247
+ [cause, cleanupError],
248
+ "Resource subscription setup and cleanup failed",
249
+ );
250
+ }
168
251
  throw cause;
169
252
  }
170
- return lifecycle.track(async () => {
171
- invalidationListeners.delete(entry);
172
- await lifecycle.disposeAll(watcherDisposers);
173
- }, signal);
174
253
  },
175
254
  });
176
255
  });
@@ -301,7 +301,7 @@ const subscribeResource = (
301
301
  state.subscriptions.set(message.subscriptionId, entry);
302
302
  const signal = AbortSignal.any([operationSignal, controller.signal]);
303
303
  try {
304
- entry.dispose = resource.subscribe(
304
+ const dispose = await resource.subscribe(
305
305
  message.input,
306
306
  () =>
307
307
  void send(state, {
@@ -310,8 +310,33 @@ const subscribeResource = (
310
310
  }),
311
311
  signal,
312
312
  );
313
+ // While setup awaits, the client can unsubscribe and reuse this ID.
314
+ // Compare entry identity so late setup cannot attach to its replacement.
315
+ // Core makes cleanup safe to call again if cancellation already released it.
316
+ if (state.subscriptions.get(message.subscriptionId) === entry) {
317
+ entry.dispose = dispose;
318
+ } else {
319
+ await dispose();
320
+ }
313
321
  } catch (cause) {
314
- await disposeSubscription(state, message.subscriptionId);
322
+ // A cancelled setup may settle after its ID has been reused. Do not
323
+ // remove the replacement or deliver the old failure to its client.
324
+ if (signal.aborted) {
325
+ // Ignore expected cancellation, but log other late failures,
326
+ // including cleanup errors, without sending them to the client.
327
+ if (cause !== signal.reason) {
328
+ state.serverLogger?.log({
329
+ correlationId: identity.operationId,
330
+ details: { ...identity, ...diagnosticErrorDetails(cause) },
331
+ event: "resource-subscription-cancelled-error",
332
+ level: "error",
333
+ });
334
+ }
335
+ return;
336
+ }
337
+ if (state.subscriptions.get(message.subscriptionId) === entry) {
338
+ await disposeSubscription(state, message.subscriptionId);
339
+ }
315
340
  throw cause;
316
341
  }
317
342
  });
@@ -86,7 +86,7 @@ export type SubscriptionResourceDefinition<
86
86
  input: z.output<TInput>,
87
87
  invalidate: () => void,
88
88
  context: TContext,
89
- ) => RuntimeDisposer;
89
+ ) => RuntimeDisposer | Promise<RuntimeDisposer>;
90
90
  };
91
91
 
92
92
  export type DerivedResourceDefinition<
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-Cz3xkCa4.d.ts","names":[],"sources":["../../../shared/dist/index.d.ts","../../../ai-context/dist/index.d.ts","../../src/public/ai-context.ts","../../src/public/notifications.ts","../../src/public/contracts.ts","../../src/public/config.ts"],"mappings":"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"}