overmux 0.0.4 → 0.0.5

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 (141) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +2 -2
  3. package/dist/bin.js +140 -127
  4. package/dist/bin.js.map +1 -1
  5. package/dist/docs/000-index.md +2 -4
  6. package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
  7. package/dist/docs/100-introduction/{300-why-overmux.md → 300-why-i-built-overmux.md} +1 -1
  8. package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  9. package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  10. package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  11. package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  12. package/dist/docs/400-reference/100-project-structure.md +83 -0
  13. package/dist/docs/400-reference/200-configuration.md +257 -0
  14. package/dist/docs/400-reference/300-storage-locations.md +46 -0
  15. package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
  16. package/dist/docs/400-reference/500-server/000-index.md +13 -0
  17. package/dist/docs/400-reference/500-server/100-resources.md +191 -0
  18. package/dist/docs/400-reference/500-server/200-operations.md +111 -0
  19. package/dist/docs/400-reference/500-server/300-streams.md +140 -0
  20. package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
  21. package/dist/docs/400-reference/500-server/500-api.md +118 -0
  22. package/dist/docs/400-reference/600-client/000-index.md +9 -0
  23. package/dist/docs/400-reference/600-client/100-api.md +370 -0
  24. package/dist/docs/400-reference/600-client/200-theming.md +94 -0
  25. package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  26. package/dist/docs/400-reference/{400-cli → 700-cli}/050-init.md +2 -4
  27. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/100-serve.md +9 -4
  28. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
  29. package/dist/docs/400-reference/{400-cli → 700-cli}/300-call.md +1 -4
  30. package/dist/docs/400-reference/{400-cli → 700-cli}/350-instance.md +1 -4
  31. package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
  32. package/dist/docs/400-reference/{400-cli → 700-cli}/700-desktop.md +1 -3
  33. package/dist/docs/500-hosted-pages.md +0 -1
  34. package/dist/exports/client.d.ts +5 -14
  35. package/dist/exports/client.d.ts.map +1 -1
  36. package/dist/exports/client.js +141 -46
  37. package/dist/exports/client.js.map +1 -1
  38. package/dist/exports/{index-DS70rKzo.d.ts → index-Cz3xkCa4.d.ts} +50 -36
  39. package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
  40. package/dist/exports/index.d.ts +1 -1
  41. package/dist/exports/index.js +19 -5
  42. package/dist/exports/index.js.map +1 -1
  43. package/dist/exports/{notifications-av0FK0yZ.js → notifications-BFAD3QQl.js} +29 -4
  44. package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
  45. package/dist/exports/server.d.ts +1 -44
  46. package/dist/exports/server.d.ts.map +1 -1
  47. package/dist/exports/server.js +9 -1061
  48. package/dist/exports/server.js.map +1 -1
  49. package/dist/internal/server/coordinator/server-child.js +47 -43
  50. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  51. package/docs/000-index.md +2 -4
  52. package/docs/100-introduction/200-how-overmux-works.md +126 -2
  53. package/docs/100-introduction/{300-why-overmux.md → 300-why-i-built-overmux.md} +1 -1
  54. package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  55. package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  56. package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  57. package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  58. package/docs/400-reference/100-project-structure.md +83 -0
  59. package/docs/400-reference/200-configuration.md +257 -0
  60. package/docs/400-reference/300-storage-locations.md +46 -0
  61. package/docs/400-reference/400-authentication-and-security.md +37 -0
  62. package/docs/400-reference/500-server/000-index.md +13 -0
  63. package/docs/400-reference/500-server/100-resources.md +191 -0
  64. package/docs/400-reference/500-server/200-operations.md +111 -0
  65. package/docs/400-reference/500-server/300-streams.md +140 -0
  66. package/docs/400-reference/500-server/400-notifications.md +32 -0
  67. package/docs/400-reference/500-server/500-api.md +118 -0
  68. package/docs/400-reference/600-client/000-index.md +9 -0
  69. package/docs/400-reference/600-client/100-api.md +370 -0
  70. package/docs/400-reference/600-client/200-theming.md +94 -0
  71. package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  72. package/docs/400-reference/{400-cli → 700-cli}/050-init.md +2 -4
  73. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/100-serve.md +9 -4
  74. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
  75. package/docs/400-reference/{400-cli → 700-cli}/300-call.md +1 -4
  76. package/docs/400-reference/{400-cli → 700-cli}/350-instance.md +1 -4
  77. package/docs/400-reference/700-cli/600-docs.md +128 -0
  78. package/docs/400-reference/{400-cli → 700-cli}/700-desktop.md +1 -3
  79. package/docs/500-hosted-pages.md +0 -1
  80. package/package.json +3 -2
  81. package/src/internal/cli/app.ts +3 -5
  82. package/src/internal/cli/commands/docs-ai-context.ts +33 -0
  83. package/src/internal/cli/commands/docs.ts +19 -2
  84. package/src/internal/cli/commands/init-template.ts +1 -1
  85. package/src/internal/cli/commands/serve.ts +3 -0
  86. package/src/internal/cli/login.ts +9 -9
  87. package/src/internal/client/client-definition.ts +6 -8
  88. package/src/internal/client/host/deep-link-navigation.ts +75 -0
  89. package/src/internal/client/host/overmux-host.tsx +9 -0
  90. package/src/internal/client/index.ts +0 -6
  91. package/src/internal/client/overmux-react.ts +71 -40
  92. package/src/internal/server/auth/auth-service.ts +2 -2
  93. package/src/internal/server/auth/instance-control.ts +65 -14
  94. package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
  95. package/src/internal/server/runtime/create-runtime.ts +5 -3
  96. package/src/internal/server/runtime/runtime-instance.ts +5 -4
  97. package/src/internal/server/runtime/runtime-operations.ts +9 -3
  98. package/src/internal/server/runtime/runtime-resources.ts +12 -32
  99. package/src/internal/server/runtime/runtime-streams.ts +5 -6
  100. package/src/internal/server/server-logger.ts +5 -10
  101. package/src/internal/server/server-startup-options.ts +5 -10
  102. package/src/internal/server/start-application-server.ts +4 -7
  103. package/src/public/ai-context.ts +7 -29
  104. package/src/public/client.ts +0 -6
  105. package/src/public/config.ts +156 -31
  106. package/src/public/server.ts +1 -16
  107. package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
  108. package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
  109. package/dist/docs/300-fundamentals/200-configuration.md +0 -3
  110. package/dist/docs/300-fundamentals/300-theming.md +0 -54
  111. package/dist/docs/300-fundamentals/400-server.md +0 -3
  112. package/dist/docs/300-fundamentals/500-client.md +0 -3
  113. package/dist/docs/300-fundamentals/600-operations.md +0 -3
  114. package/dist/docs/300-fundamentals/700-resources.md +0 -3
  115. package/dist/docs/300-fundamentals/800-streams.md +0 -3
  116. package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  117. package/dist/docs/400-reference/100-configuration.md +0 -23
  118. package/dist/docs/400-reference/200-server-api.md +0 -21
  119. package/dist/docs/400-reference/300-client-api.md +0 -39
  120. package/dist/docs/400-reference/400-cli/400-check.md +0 -20
  121. package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
  122. package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
  123. package/dist/exports/index-DS70rKzo.d.ts.map +0 -1
  124. package/dist/exports/notifications-av0FK0yZ.js.map +0 -1
  125. package/docs/100-introduction/100-what-is-overmux.md +0 -7
  126. package/docs/300-fundamentals/100-project-structure.md +0 -23
  127. package/docs/300-fundamentals/200-configuration.md +0 -3
  128. package/docs/300-fundamentals/300-theming.md +0 -54
  129. package/docs/300-fundamentals/400-server.md +0 -3
  130. package/docs/300-fundamentals/500-client.md +0 -3
  131. package/docs/300-fundamentals/600-operations.md +0 -3
  132. package/docs/300-fundamentals/700-resources.md +0 -3
  133. package/docs/300-fundamentals/800-streams.md +0 -3
  134. package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  135. package/docs/400-reference/100-configuration.md +0 -23
  136. package/docs/400-reference/200-server-api.md +0 -21
  137. package/docs/400-reference/300-client-api.md +0 -39
  138. package/docs/400-reference/400-cli/400-check.md +0 -20
  139. package/docs/400-reference/400-cli/500-ai-context.md +0 -102
  140. package/docs/400-reference/400-cli/600-docs.md +0 -23
  141. package/src/internal/cli/commands/ai.ts +0 -44
@@ -0,0 +1,370 @@
1
+ ---
2
+ title: Client API
3
+ ---
4
+
5
+ ## defineOvermuxClient
6
+
7
+ `defineOvermuxClient` defines your browser application: its root React component, commands, appearance, and navigation behavior. It returns the definition for `OvermuxHost` to mount; it does not render the application itself.
8
+
9
+ ```tsx
10
+ // src/ui/app.tsx
11
+ import { defineOvermuxClient } from "overmux/client";
12
+
13
+ const App = () => <main>Overmux is running.</main>;
14
+
15
+ export default defineOvermuxClient({
16
+ component: App,
17
+ commands: {},
18
+ });
19
+ ```
20
+
21
+ | Option | Purpose |
22
+ | --- | --- |
23
+ | `component` | Required root React component. |
24
+ | `commands` | Required command registry. Use `{}` when your application has no commands, or pass a registry created with `defineCommandRegistry`. |
25
+ | `appearance` | Optional theme preferences: `scheme` is `"dark"`, `"light"`, or `"system"` (default); `contrast` is `"auto"` (default), `"high"`, or `"normal"`. |
26
+ | `navigate` | Optional `(route: string) => unknown` callback for validated deep links targeting the current instance. The route includes its query and fragment. Supply your router's navigation function to avoid a full page reload; by default, Overmux uses document navigation on the current origin. |
27
+ | `shortcutOverrides` | Optional map from command IDs to replacement keyboard bindings. An empty array removes a command's default bindings. |
28
+ | `chordPrefixes` | Optional prefixes for multi-step keyboard shortcuts. Each entry has a `binding` and `unmatched: "replay-to-focused-input"`, which replays buffered keystrokes to a focused input registered with `useShortcutInputTarget` when the sequence does not match or times out. |
29
+
30
+ The function validates shortcut overrides and chord prefixes, then returns the supplied definition while preserving its command IDs for type checking.
31
+
32
+ ## OvermuxHost
33
+
34
+ `OvermuxHost` mounts your client definition inside the Overmux browser runtime. Your application owns the browser entry point and calls React's `createRoot`; the host provides the server connection, query cache, command and shortcut handling, and theme around your root component.
35
+
36
+ ```tsx
37
+ // src/ui/main.tsx
38
+ import { OvermuxHost } from "overmux/client";
39
+ import { createRoot } from "react-dom/client";
40
+
41
+ import definition from "./app";
42
+ import "./styles.css";
43
+
44
+ const root = document.querySelector("#root");
45
+ if (!root) {
46
+ throw new Error("Missing root element");
47
+ }
48
+
49
+ createRoot(root).render(<OvermuxHost definition={definition} />);
50
+ ```
51
+
52
+ Here, `./app` exports the definition from the previous section. Your HTML entry point must contain an element with `id="root"` and load this module.
53
+
54
+ The required `definition` prop is the object returned by `defineOvermuxClient`. Supply your root component through `definition.component`, not as children of `OvermuxHost`. Components using Overmux runtime hooks must render inside that hosted application.
55
+
56
+ The host loads the server's available API definitions before rendering your component. It displays a recovery screen during startup or when startup fails, redirects to login when authentication is required, and catches rendering errors in your application. Unmounting it disposes the server connection and runtime subscriptions.
57
+
58
+ ## createOvermuxHooks
59
+
60
+ `createOvermuxHooks` creates hooks typed from the default export of your server module. Create them once in a shared UI module, then import individual hooks from that module. The server type determines the permitted resource, operation, and stream IDs and their input, output, and message shapes.
61
+
62
+ ```tsx
63
+ // src/ui/overmux.ts
64
+ import { createOvermuxHooks } from "overmux/client";
65
+ import type server from "../server/index";
66
+
67
+ export const { useInstance, useOperation, useResource, useStream } =
68
+ createOvermuxHooks<typeof server>();
69
+ ```
70
+
71
+ This assumes `src/server/index.ts` default-exports the `defineOvermuxServer(...)` result. The following hook examples import from `./overmux`; that is the application-owned module above. The `import type` keeps server implementation code out of the browser bundle.
72
+
73
+ ## useResource
74
+
75
+ `useResource` reads a server resource and keeps it current when a subscription resource invalidates it. It accepts the resource's application-defined ID and exactly the input shape declared by that resource's contract.
76
+
77
+ ```tsx
78
+ // src/ui/workspace-name.tsx
79
+ import { useResource } from "./overmux";
80
+
81
+ export const WorkspaceName = ({ workspaceId }: { workspaceId: string }) => {
82
+ const workspace = useResource({ id: "workspace", input: { workspaceId } });
83
+
84
+ if (workspace.status === "pending") return <p>Loading…</p>;
85
+ if (workspace.status === "error") return <p>{workspace.error.message}</p>;
86
+ return <h1>{workspace.data.name}</h1>;
87
+ };
88
+ ```
89
+
90
+ This application defines a `workspace` resource whose input is `{ workspaceId: string }` and output is `{ name: string }`. The result has `status: "pending" | "error" | "success"`, `refetch()`, and data or error appropriate to that status. Inputs are serialized into the query key, so use JSON-serializable values. A changed ID or input reads a separate cached resource.
91
+
92
+ ## useOperation
93
+
94
+ `useOperation` returns a TanStack Query mutation for a server operation. Call `mutate` for fire-and-forget UI events or await `mutateAsync` when subsequent work needs its typed result.
95
+
96
+ ```tsx
97
+ // src/ui/rename-workspace-button.tsx
98
+ import { useOperation } from "./overmux";
99
+
100
+ export const RenameWorkspaceButton = ({ workspaceId }: { workspaceId: string }) => {
101
+ const renameWorkspace = useOperation({ id: "renameWorkspace" });
102
+
103
+ return (
104
+ <button
105
+ disabled={renameWorkspace.isPending}
106
+ onClick={() => renameWorkspace.mutate({ name: "Planning", workspaceId })}
107
+ >
108
+ Rename workspace
109
+ </button>
110
+ );
111
+ };
112
+ ```
113
+
114
+ Here `renameWorkspace` is an application-defined operation accepting `{ workspaceId: string; name: string }`; its return type, error state, reset behavior, and mutation status come from the mutation result. Pass `signal` to the hook when the operation should observe an `AbortSignal`. Operations do not automatically refresh resources - the server handler should invalidate affected resources, or the UI can refetch them.
115
+
116
+ ## useStream
117
+
118
+ `useStream` opens a bidirectional server stream for the component lifetime. The stream's ID, open input, outgoing message, and incoming message shapes all come from the server contract.
119
+
120
+ ```tsx
121
+ // src/ui/terminal.tsx
122
+ import { useEffect, useState } from "react";
123
+
124
+ import { useStream } from "./overmux";
125
+
126
+ export const Terminal = ({ paneId }: { paneId: string }) => {
127
+ const terminal = useStream({ id: "terminal", input: { paneId } });
128
+ const [output, setOutput] = useState("");
129
+
130
+ const { subscribe } = terminal;
131
+ useEffect(
132
+ () => subscribe(({ data }) => setOutput((value) => value + data)),
133
+ [subscribe],
134
+ );
135
+
136
+ return (
137
+ <section>
138
+ <pre>{output}</pre>
139
+ <button
140
+ disabled={terminal.status !== "open"}
141
+ onClick={() => terminal.send({ data: "ls\r", type: "input" })}
142
+ >
143
+ List files
144
+ </button>
145
+ </section>
146
+ );
147
+ };
148
+ ```
149
+
150
+ This application defines `terminal` with open input `{ paneId: string }`, client messages `{ type: "input"; data: string }`, and server messages `{ data: string }`. `status` is `"opening"`, `"open"`, or `"closed"`; `connectionId` changes for each server-confirmed lifetime, including reconnects. `send` returns `false` when no stream is currently available. `subscribe` returns an unsubscribe function. The hook closes its stream on unmount or when its ID/input changes; `close()` closes it earlier.
151
+
152
+ ## useInstance
153
+
154
+ `useInstance` returns the discovered server identity, or `undefined` until it is available.
155
+
156
+ ```tsx
157
+ // src/ui/open-in-overmux.tsx
158
+ import { useInstance } from "./overmux";
159
+
160
+ export const OpenInOvermux = () => {
161
+ const instance = useInstance();
162
+ return <code>{instance?.deepLinkPrefix ?? "Connecting…"}</code>;
163
+ };
164
+ ```
165
+
166
+ The identity includes `instanceId` and `deepLinkPrefix`, the prefix used to construct links to this running instance. It updates when discovery changes.
167
+
168
+ ## defineCommandRegistry
169
+
170
+ `defineCommandRegistry` defines named commands for `defineOvermuxClient`. Each command has a title, optional default keyboard bindings, optional Zod `params`, and optionally a default `run` implementation. It returns the same registry with each object key attached as its typed command ID.
171
+
172
+ ```tsx
173
+ // src/ui/commands.ts
174
+ import { defineCommandRegistry } from "overmux/client";
175
+ import { z } from "zod";
176
+ import type server from "../server/index";
177
+
178
+ export const commands = defineCommandRegistry<typeof server>()({
179
+ closePane: {
180
+ defaultBindings: [["§", "X"]],
181
+ params: z.object({ paneId: z.string() }),
182
+ run: ({ overmuxServerApi, params }) =>
183
+ overmuxServerApi.executeOperation("closePane", { paneId: params.paneId }),
184
+ title: "Close pane",
185
+ },
186
+ openSettings: { title: "Open settings" },
187
+ });
188
+ ```
189
+
190
+ `closePane` is an application command with a `paneId` parameter, and the server defines a `closePane` operation with matching `{ paneId: string }` input. `OvermuxServerApi` is the typed, client-safe server bridge supplied to default command handlers. It exposes `executeOperation(id, input?, signal?)`, plus `getInstanceId()` and `getDeepLinkPrefix()`; it cannot read resources or open streams. A command without `run`, such as `openSettings`, requires each registration to provide a local handler.
191
+
192
+ Bindings are key-binding strings or arrays of two or more strings for a chord. A binding may also be conditional with `{ binding, when: { media } }`; its media query determines whether it is active. Invalid bindings fail when the registry is defined.
193
+
194
+ ## useCommand
195
+
196
+ `useCommand` registers a command implementation for the mounted component. When multiple registrations exist, the one containing the focused element wins; otherwise the most recently registered one wins.
197
+
198
+ ```tsx
199
+ // src/ui/pane.tsx
200
+ import { useRef } from "react";
201
+
202
+ import { useCommand } from "overmux/client";
203
+
204
+ import { commands } from "./commands";
205
+
206
+ export const Pane = ({ paneId }: { paneId: string }) => {
207
+ const element = useRef<HTMLDivElement>(null);
208
+ useCommand(commands.closePane, { element, params: { paneId } });
209
+
210
+ return <div ref={element}>Pane {paneId}</div>;
211
+ };
212
+ ```
213
+
214
+ The default `closePane.run` from the registry runs here with these parameters. Pass `run` to replace that implementation locally, or `enabled: false` to expose but disable it. Commands with `params` require params. Commands with no default `run` require a local `run`. Registration is removed on unmount.
215
+
216
+ ## useCommands
217
+
218
+ `useCommands` returns the commands registered in the current hosted application, ready for a command palette or menu.
219
+
220
+ ```tsx
221
+ // src/ui/command-list.tsx
222
+ import { useCommands } from "overmux/client";
223
+
224
+ export const CommandList = () => (
225
+ <ul>
226
+ {useCommands().map((command) => (
227
+ <li key={command.id}>
228
+ <button disabled={!command.enabled} onClick={() => void command.execute()}>
229
+ {command.title}
230
+ </button>
231
+ </li>
232
+ ))}
233
+ </ul>
234
+ );
235
+ ```
236
+
237
+ Each entry has `id`, `title`, active `bindings`, `enabled`, optional `disabledReason`, and `execute()`. Only registered commands appear. The list reflects focus-sensitive command selection and active conditional bindings.
238
+
239
+ ## skipToken
240
+
241
+ `skipToken` conditionally removes a parameterized command registration or skips a resource read while preserving hook call order.
242
+
243
+ ```tsx
244
+ // src/ui/selected-pane.tsx
245
+ import { skipToken, useCommand } from "overmux/client";
246
+
247
+ import { commands } from "./commands";
248
+ import { useResource } from "./overmux";
249
+
250
+ export const SelectedPane = ({ paneId }: { paneId?: string }) => {
251
+ const pane = useResource({ id: "pane", input: paneId ? { paneId } : skipToken });
252
+ useCommand(commands.closePane, paneId ? { params: { paneId } } : skipToken);
253
+
254
+ return pane?.status === "success" ? <p>{pane.data.title}</p> : null;
255
+ };
256
+ ```
257
+
258
+ This application defines the `pane` resource with `{ paneId: string }` input and `{ title: string }` output. A skipped resource returns `undefined` and does not fetch or subscribe. `skipToken` is valid only for commands that declare params; parameterless commands must always register.
259
+
260
+ ## useShortcutInputTarget
261
+
262
+ `useShortcutInputTarget` identifies an input that can receive replayed keystrokes from an unmatched configured chord prefix. Register it only when `definition.chordPrefixes` includes that prefix with `unmatched: "replay-to-focused-input"`.
263
+
264
+ ```tsx
265
+ // src/ui/search.tsx
266
+ import { useRef } from "react";
267
+ import { useShortcutInputTarget } from "overmux/client";
268
+
269
+ export const Search = () => {
270
+ const container = useRef<HTMLDivElement>(null);
271
+ const input = useRef<HTMLInputElement>(null);
272
+ useShortcutInputTarget({ container, input });
273
+
274
+ return <div ref={container}><input ref={input} aria-label="Search" /></div>;
275
+ };
276
+ ```
277
+
278
+ When focus is inside `container`, an unmatched or timed-out prefix is replayed to `input`; the deepest matching container wins. The host waits one second for the rest of a chord. Escape cancels a pending chord without replaying it. The hook unregisters on unmount.
279
+
280
+ ## formatShortcutBinding
281
+
282
+ `formatShortcutBinding` formats a shortcut binding for display using the current platform's key labels.
283
+
284
+ ```tsx
285
+ // src/ui/shortcut-hint.tsx
286
+ import { formatShortcutBinding } from "overmux/client";
287
+
288
+ const closePaneBinding = ["§", "X"] as const;
289
+
290
+ export const ShortcutHint = () => <kbd>{formatShortcutBinding(closePaneBinding)}</kbd>;
291
+ ```
292
+
293
+ The binding above is a two-step chord, displayed as its formatted keys separated by spaces. Use the same binding shape accepted by command `defaultBindings` or shortcut overrides.
294
+
295
+ ## OvermuxThemeScope
296
+
297
+ `OvermuxThemeScope` applies Overmux theme variables and creates a local overlay root for its descendants. `OvermuxHost` already provides one around the application; use another scope to isolate an embedded area or override its appearance.
298
+
299
+ ```tsx
300
+ // src/ui/embedded-preview.tsx
301
+ import { OvermuxThemeScope } from "overmux/client";
302
+
303
+ export const EmbeddedPreview = () => (
304
+ <OvermuxThemeScope scheme="dark" contrast="high" style={{ "--om-color-accent": "#ff8a00" }}>
305
+ <section>Preview</section>
306
+ </OvermuxThemeScope>
307
+ );
308
+ ```
309
+
310
+ `scheme` defaults to `"system"` and accepts `"dark"` or `"light"`; `contrast` defaults to `"auto"` and accepts `"high"` or `"normal"`. `style` accepts normal React CSS properties plus documented `--om-*` theme tokens. Nested scopes inherit normally through CSS but own their portal roots.
311
+
312
+ ## OvermuxPortal
313
+
314
+ `OvermuxPortal` renders children into the nearest `OvermuxThemeScope` overlay root, keeping overlays within that scope's theme and stacking context.
315
+
316
+ ```tsx
317
+ // src/ui/dialog.tsx
318
+ import { OvermuxPortal } from "overmux/client";
319
+
320
+ export const Dialog = () => (
321
+ <OvermuxPortal>
322
+ <div role="dialog">Saved</div>
323
+ </OvermuxPortal>
324
+ );
325
+ ```
326
+
327
+ Outside a theme scope it uses `document.body`. Pass `external={{ container, scopeAttributesAndVariables: "caller-owned" }}` to render into another element. That explicit marker means the caller, not Overmux, must ensure the external container has the needed theme attributes and CSS variables.
328
+
329
+ ## Clipboard
330
+
331
+ `readClipboardText` and `writeClipboardText` read and write plain text through the browser Clipboard API. On the Overmux desktop host, writes dispatch through its clipboard bridge; reads still use the browser API.
332
+
333
+ ```tsx
334
+ // src/ui/copy-link.tsx
335
+ import { writeClipboardText } from "overmux/client";
336
+
337
+ export const CopyLink = () => (
338
+ <button onClick={() => void writeClipboardText(window.location.href)}>Copy link</button>
339
+ );
340
+ ```
341
+
342
+ To read text, import and await `readClipboardText`:
343
+
344
+ ```ts
345
+ import { readClipboardText } from "overmux/client";
346
+
347
+ export const pasteInto = async (input: HTMLTextAreaElement) => {
348
+ input.value = await readClipboardText();
349
+ };
350
+ ```
351
+
352
+ Both functions return promises and reject when their browser capability is unavailable or permission is denied. Desktop write resolution confirms dispatch, not completion. Call them from a user gesture where browsers require it.
353
+
354
+ ## Background notifications
355
+
356
+ `enableBackgroundNotifications` requests permission, creates or reconciles a browser push subscription, and stores it with the server. `disableBackgroundNotifications` removes the stored subscription when possible. Configure background delivery on the server before exposing these controls.
357
+
358
+ ```tsx
359
+ // src/ui/notification-settings.tsx
360
+ import { disableBackgroundNotifications, enableBackgroundNotifications } from "overmux/client";
361
+
362
+ export const NotificationSettings = () => (
363
+ <p>
364
+ <button onClick={() => void enableBackgroundNotifications()}>Enable notifications</button>
365
+ <button onClick={() => void disableBackgroundNotifications()}>Disable notifications</button>
366
+ </p>
367
+ );
368
+ ```
369
+
370
+ `enableBackgroundNotifications()` resolves `true` only after permission and subscription setup succeed; it resolves `false` for unavailable support or declined permission, and rejects for setup or server failures. It requires a secure context, service worker, Push API, and Notification API. It is unsupported in the desktop host; on iPhone and iPad it requires an installed Home Screen web app. Enabling and disabling are user actions because browsers may prompt for permission.
@@ -0,0 +1,94 @@
1
+ ---
2
+ title: Theming and CSS
3
+ ---
4
+
5
+ Your app owns its UI and CSS. Overmux provides scoped component styles and optional CSS variables for customization.
6
+
7
+ ## Appearance
8
+
9
+ Set `appearance` in `defineOvermuxClient({ appearance, commands, component })`. Types are exported from `overmux/client`.
10
+
11
+ | Property | Values | Default | Behavior |
12
+ | --- | --- | --- | --- |
13
+ | `scheme` | `"light"`, `"dark"`, `"system"` | `"system"` | Selects the fallback palette and CSS `color-scheme`. System follows `prefers-color-scheme`. |
14
+ | `contrast` | `"normal"`, `"high"`, `"auto"` | `"auto"` | High selects system-color fallbacks; auto responds to `prefers-contrast: more`. |
15
+
16
+ Browser forced colors apply regardless of `contrast`, with `forced-color-adjust: auto`. Explicit color tokens override palette fallbacks; test custom colors in each mode. Appearance does not persist preferences or configure third-party themes.
17
+
18
+ ## Tokens
19
+
20
+ Set public tokens on `:root` for the whole document, or on a theme scope for one subtree. `OvermuxStyle` provides typed inline styles for these tokens alongside React CSS properties.
21
+
22
+ | Token | CSS value | Purpose |
23
+ | --- | --- | --- |
24
+ | `--om-color-canvas` | Color | Page background |
25
+ | `--om-color-surface` | Color | Main surface |
26
+ | `--om-color-panel` | Color | Secondary background |
27
+ | `--om-color-fg` | Color | Primary text |
28
+ | `--om-color-muted` | Color | Secondary text |
29
+ | `--om-color-border` | Color | Borders and separators |
30
+ | `--om-color-accent` | Color | Interactive accents |
31
+ | `--om-color-accent-fg` | Color | Foreground on accent backgrounds, where consumed |
32
+ | `--om-color-danger` | Color | Errors and destructive states |
33
+ | `--om-color-success` | Color | Success and added-content states |
34
+ | `--om-font-sans` | Font-family list | Interface text |
35
+ | `--om-font-mono` | Font-family list | Code and diagnostics |
36
+ | `--om-spacing` | Length | Spacing unit; components may scale it |
37
+ | `--om-radius` | Length | Corner radius; components may scale it |
38
+ | `--om-elevation` | Box-shadow | Panel and popover shadows |
39
+ | `--om-focus-ring` | Box-shadow | Focus-ring shadows |
40
+ | `--om-motion-duration` | Time | Transition duration |
41
+
42
+ Tokens apply only where components consume them. Fallbacks vary by component; runtime palette variables beginning with `--_om-` are private. **Public tokens are not populated with default palette values:** app CSS using `var(--om-color-canvas)` must define it or provide a fallback. Explicit tokens inherit normally and do not change automatically with the scheme.
43
+
44
+ ## `OvermuxThemeScope`
45
+
46
+ Import from `overmux/client`. The host already supplies an outer scope; add scopes for local themes or portal placement.
47
+
48
+ ```tsx
49
+ <OvermuxThemeScope scheme="light" style={{ "--om-color-accent": "purple" }}>
50
+ <Preview />
51
+ </OvermuxThemeScope>
52
+ ```
53
+
54
+ | Prop | Type | Default |
55
+ | --- | --- | --- |
56
+ | `children` | `ReactNode` | Required |
57
+ | `scheme` | `OvermuxScheme` | `"system"` |
58
+ | `contrast` | `OvermuxContrast` | `"auto"` |
59
+ | `className` | `string` | Unset |
60
+ | `style` | `OvermuxStyle` | Unset |
61
+
62
+ Renders a `div` with `data-om-scope`, `data-om-scheme`, `data-om-contrast`, and an internal overlay container. Sets foreground, font family, and `color-scheme`, but does not paint a background.
63
+
64
+ Nested scopes resolve their own defaults, not their parent's appearance props. Public token overrides still inherit and take precedence over the nested palette. Tokens placed inside the app cannot affect ancestor host UI.
65
+
66
+ ## `OvermuxPortal`
67
+
68
+ Import from `overmux/client`. Use `<OvermuxPortal><Popup /></OvermuxPortal>` to render overlays within the nearest theme scope.
69
+
70
+ | Context | Destination |
71
+ | --- | --- |
72
+ | Inside a scope | Nearest scope's internal overlay container |
73
+ | Outside a scope, in a browser | `document.body` |
74
+ | Scope container not mounted, or no document | Nothing rendered |
75
+ | `external={{ container, scopeAttributesAndVariables: "caller-owned" }}` | Supplied element; caller owns its scope attributes and variables |
76
+
77
+ The overlay container is a sibling of the scope's children. Put shared overlay tokens on the scope, not a descendant around the portal call site. External containers receive no automatic copying of scope attributes or variables. Portals supply placement, not positioning, focus trapping, or dismissal.
78
+
79
+ ## CSS and integrations
80
+
81
+ Import app CSS from browser code with `import "./styles.css"`. Theme scopes, the UI package, and Git, Pi, xterm, and Zellij React components import their own styles. Layout, resets, and font loading remain app-owned.
82
+
83
+ Built-in styles use `@scope` and the `om.components` cascade layer, with top-level ordering `theme, base, om`. Normal unlayered app CSS takes precedence over layered styles. Theme rules stop at nested scopes, but global app selectors and inherited properties can still affect components. Browser support for `@scope` and cascade layers is required.
84
+
85
+ | Component family | Shared styling | Separate configuration |
86
+ | --- | --- | --- |
87
+ | Runtime host UI | Theme tokens where consumed | Client `appearance` |
88
+ | UI split views | Border and accent tokens | App owns pane contents |
89
+ | Git React UI | Colors, fonts, spacing, radius | Diff options and themes; `registerGitDiffTheme` registers custom themes |
90
+ | Pi React UI | Colors, fonts, spacing, radius, elevation | Code highlighting uses Tokyo Night |
91
+ | xterm | Wrapper background: `--om-xterm-terminal-background` | `options.theme` for terminal colors; options such as `fontFamily` for fonts |
92
+ | tmux / Zellij terminal wrappers | xterm wrapper styles | Forwarded xterm props |
93
+
94
+ Scope appearance does not automatically update terminal palettes or syntax-highlighting themes.
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: Tech stack recommendations
3
+ ---
4
+
5
+ We recommend:
6
+
7
+ - [pnpm](https://pnpm.io/) for package management.
8
+ - [TanStack Router](https://tanstack.com/router) for routing.
9
+ - [shadcn/ui](https://ui.shadcn.com/) for UI components.
10
+ - [Zod](https://zod.dev/) for schemas and runtime validation.
11
+
12
+ If your app already uses a different stack, follow its existing conventions.
@@ -1,16 +1,14 @@
1
1
  ---
2
- title: "overmux init"
2
+ title: "`overmux init`"
3
3
  ---
4
4
 
5
- # `overmux init`
6
-
7
5
  Create a minimal, runnable Overmux application without prompting.
8
6
 
9
7
  ```sh
10
8
  overmux init
11
9
  ```
12
10
 
13
- The application is created in `$XDG_CONFIG_HOME/overmux`.
11
+ The application is created in `$XDG_CONFIG_HOME/overmux` (default `~/.config/overmux`). See [storage locations](../300-storage-locations.md) for directory defaults and overrides.
14
12
 
15
13
  The command validates the toolchain before writing files. When Mise is installed, it must be [activated in the shell](https://mise.jdx.dev/getting-started.html#activate-mise); the generated `mise.toml` pins Node 22, pnpm 10, and the latest published Overmux version. Without Mise, pnpm 10 or newer must already be available and `mise.toml` is omitted.
16
14
 
@@ -1,10 +1,7 @@
1
1
  ---
2
- title: "overmux serve"
3
- description: "Serve the Overmux web UI"
2
+ title: "`overmux serve`"
4
3
  ---
5
4
 
6
- # `overmux serve`
7
-
8
5
  Serve the authenticated Overmux web UI. Development starts private Vite behind the public Overmux gateway. Production builds with Vite and serves static assets without running Vite.
9
6
 
10
7
  ## Usage
@@ -14,6 +11,8 @@ overmux serve [--config path] [--host host] [--port port] [--login | --no-login]
14
11
  overmux serve --production [--no-build] [--config path] [--host host] [--port port] [--login | --no-login]
15
12
  ```
16
13
 
14
+ See [storage locations](../300-storage-locations.md) for default configuration paths, logs, and persistent data.
15
+
17
16
  ## Options
18
17
 
19
18
  | Flag | Description | Default |
@@ -31,3 +30,9 @@ overmux serve --production [--no-build] [--config path] [--host host] [--port po
31
30
  A grant prints one server-issued URL for every authenticated browser origin. Redeeming its code or any URL consumes the grant and authenticates only that origin; create another grant for another origin. The output identifies the browser login page, expiry, and command for creating another grant.
32
31
 
33
32
  `--no-build` without `--production` is invalid. Vite's build output cleanup follows its `build.emptyOutDir` setting.
33
+
34
+ ## Local instance discovery
35
+
36
+ The server stores its private control socket and registration in `$XDG_RUNTIME_DIR/overmux` when `XDG_RUNTIME_DIR` is absolute. If it is unset, empty, or relative, the directory is `/tmp/overmux-<uid>`, where `<uid>` is your numeric Unix user ID. If the preferred runtime directory exceeds 80 UTF-8 bytes, Overmux instead uses `/tmp/overmux-<uid>-<hash>`, with the first 12 hexadecimal SHA-256 characters of the preferred directory path. These fallbacks use literal `/tmp`, independently of `TMPDIR` and XDG state storage.
37
+
38
+ The directory must belong to the current user, must not itself be a symlink, and is secured with owner-only permissions (`0700`). The CLI uses the same location for discovery. After upgrading from a version that used state storage or another temporary directory, restart existing servers so the CLI can discover them; old locations are not searched.
@@ -1,10 +1,7 @@
1
1
  ---
2
- title: "overmux auth"
3
- description: "Manage CLI-issued browser authentication"
2
+ title: "`overmux auth`"
4
3
  ---
5
4
 
6
- # `overmux auth`
7
-
8
5
  Manage browser login grants and sessions for a running local Overmux server.
9
6
 
10
7
  Authentication administration uses the private same-user control socket. When multiple local servers are running, use `--port` to select one.
@@ -1,10 +1,7 @@
1
1
  ---
2
- title: "overmux call"
3
- description: "Invoke an operation on a running Overmux server"
2
+ title: "`overmux call`"
4
3
  ---
5
4
 
6
- # `overmux call`
7
-
8
5
  Invoke an operation on a running local Overmux server.
9
6
 
10
7
  The CLI discovers the local instance and obtains a short-lived bearer credential through its private same-user control socket. Use `--port` to select an instance when multiple local servers are running.
@@ -1,10 +1,7 @@
1
1
  ---
2
- title: "overmux instance"
3
- description: "Read the identity of a running Overmux server"
2
+ title: "`overmux instance`"
4
3
  ---
5
4
 
6
- # `overmux instance`
7
-
8
5
  Print the live server's `instanceId` and `deepLinkPrefix`. Like `overmux call`, this discovers a local running server through its private same-user control channel. It queries the identity resolved by that running server, never reloads local configuration, and does not verify HTTP or WebSocket reachability.
9
6
 
10
7
  ```sh