@pedit/serve 0.8.2 → 0.8.3

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 (2) hide show
  1. package/README.md +187 -45
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -2,16 +2,29 @@
2
2
 
3
3
  Run [`pedit serve`](https://github.com/piconic-ai/pedit) for a local app. pedit holds one file's Yjs
4
4
  document and writes the file; this package starts it, checks every request, and bridges a browser's
5
- WebSocket to a stream on pedit's standard input and output. Your page edits the file live, with several pages at once, and
6
- there is no room, relay or account.
5
+ WebSocket to a stream on pedit's standard input and output. Your page edits the file live, with several
6
+ pages at once, and there is no room, relay or account until the app shares the file.
7
7
 
8
8
  Experimental (0.x): the API may change. The package is published with every pedit release and
9
- carries the same version number; the pedit it needs is listed below.
9
+ carries the same version number. The pedit it needs depends on what you use:
10
+
11
+ | To | pedit |
12
+ | --- | --- |
13
+ | Edit a file from your pages | 0.5.0 or later (`MIN_PEDIT_VERSION`) |
14
+ | Share a file with people outside, through a room | 0.6.0 or later |
15
+ | Read or change a room's keep (`status`, `keep`) | 0.8.1 or later |
16
+ | Comment as the host, and get the threads | 0.8.2 or later |
17
+
18
+ A pedit older than 0.6.0 (serve surface 1) rejects every room command with `sharing needs pedit
19
+ serve 2; this pedit is older`; one that lacks a later command rejects it with `pedit is too old for
20
+ status; update it` (or `keep`, `comment`, `reply`, `resolve`). The child goes on.
10
21
 
11
22
  ## Requirements
12
23
 
13
- - pedit 0.5.0 or later on the PATH (or pass `bin`). Older ones are refused with a message on how to update. Or let your app [download it](#installing-pedit-for-the-user) once, if you opt in.
14
- - Node 22 or later, on macOS, Linux or Windows. On Windows, pedit stops a child by closing its input and never kills it: a child that does not exit in time is reported as an error event.
24
+ - pedit on the PATH (or pass `bin`). Older ones are refused with a message on how to update. Or let
25
+ your app [download it](#installing-pedit-for-the-user) once, if you opt in.
26
+ - Node 22 or later, on macOS, Linux or Windows. On Windows, pedit stops a child by closing its input
27
+ and never kills it: a child that does not exit in time is reported as an error event.
15
28
 
16
29
  ## Install
17
30
 
@@ -23,7 +36,9 @@ npm install hono @hono/node-server @hono/node-ws # for the Hono routes
23
36
  `@pedit/serve/client` needs `yjs`, `y-protocols` and `y-websocket`; `@pedit/serve/hono` needs `hono`.
24
37
  They are peers, so your app keeps one copy of Yjs.
25
38
 
26
- ## Server
39
+ ## Quick start
40
+
41
+ The server mounts the routes under a path and says which ids exist:
27
42
 
28
43
  ```js
29
44
  import { serve } from '@hono/node-server'
@@ -46,7 +61,7 @@ injectWebSocket(server)
46
61
  process.on('SIGINT', () => routes.close().finally(() => process.exit(0)))
47
62
  ```
48
63
 
49
- ## Browser
64
+ The page connects to an id and binds the text to its editor:
50
65
 
51
66
  ```js
52
67
  import { connect } from '@pedit/serve/client'
@@ -59,41 +74,145 @@ conn.status.subscribe((status, error) => {
59
74
  const extensions = [yCollab(conn.text, conn.awareness)] // plus basicSetup, EditorState.readOnly for 'read'
60
75
  ```
61
76
 
62
- ## API
77
+ ## `@pedit/serve/hono`
78
+
79
+ ### `peditRoutes(options)`
80
+
81
+ Returns a Hono app with `POST /:id/session` and the WebSocket `GET /:id/doc`, plus the methods
82
+ below. Mount it with `app.route(path, routes)`.
83
+
84
+ | Option | |
85
+ | --- | --- |
86
+ | `upgradeWebSocket` | From the adapter, created on the parent app (`createNodeWebSocket({ app })`). |
87
+ | `root` | The directory whose files may be served; realpath'd at startup, must exist. |
88
+ | `origins` | The exact origins of your pages. Also used for the `Host` check. |
89
+ | `app` | A label another process sees if it asks pedit for a file this app holds. |
90
+ | `resolve(id, c)` | Maps an id to `{ path, role: 'edit' \| 'read' }` under `root`, or `null` for 404. Throw an `HTTPException` for anything else. |
91
+ | `bin` | Absolute path to pedit. Default: `pedit` on the PATH. |
92
+ | `idleMs` | Stop the child this long after its last page left. Default 60 s. A shared file is never stopped for idleness. |
93
+ | `graceMs` | How long a child may take to exit after its input closed, before SIGTERM. Default 5 s. On Windows it is then given up on, never killed. |
94
+ | `allowRemote` | Accept peers that are not on loopback. Default `false`. |
95
+ | `onEvent(event)` | See [events](#events). |
96
+
97
+ Types: `PeditOptions`, `PeditRoutes`, `PeditEvent`, `Role`. The helpers `isLoopback(address)` and
98
+ `inside(root, path)` are exported too.
99
+
100
+ ### Methods on the routes
101
+
102
+ Every method takes an absolute `path` to a file under `root`, checked as the routes check it: a
103
+ path outside `root`, a link out of it or a missing file rejects with `file not found` and starts no
104
+ child.
105
+
106
+ | Method | |
107
+ | --- | --- |
108
+ | `share(path, opts?)` | Open a room for the file; see [sharing](#sharing). |
109
+ | `unshare(path)` | Leave the room: a kept room stays kept, a live one ends. |
110
+ | `close(path?)` | With a path, end that file's kept room for good (a live room is only left). Without, stop every child. |
111
+ | `status(path)` | The file's room, shared now or kept without pedit; `null` when it has none. |
112
+ | `keep(path, keep)` | Change how long the relay keeps the room after pedit leaves. |
113
+ | `comment(path, range)`, `reply(path, thread, body)`, `resolve(path, thread, resolved)` | Take part in the file's comments as the host; see [comments](#comments). |
114
+
115
+ ### Events
63
116
 
64
- `@pedit/serve/hono`
117
+ `onEvent` is called with:
65
118
 
66
- | Export | |
119
+ | Event | |
67
120
  | --- | --- |
68
- | `peditRoutes(options)` | A Hono app: `POST /:id/session` and the WebSocket `GET /:id/doc`. Has `close()`. |
69
- | `options.resolve(id, c)` | Maps an id to `{ path, role: 'edit' \| 'read' }`, or `null` for 404. |
70
- | `options.root`, `origins`, `app` | The served directory, the exact page origins, and a label shown if another process holds the file. |
71
- | `options.bin`, `idleMs`, `graceMs`, `allowRemote`, `onEvent` | Absolute path to pedit; idle stop (default 60 s); how long a child may take to exit after its input closed (default 5 s; on Windows it is then given up on, never killed); accept non-loopback peers (default false); `started`/`stopped`/`error` events, and `update_available` (`{ version, url, security }`, once per version) when pedit finds a newer release. `started` carries pedit's `version` and `serve` (surface version) when it reports them. |
121
+ | `started` | `{ id, path, version?, serve? }`: a child a page opened through an id is up. `version` is pedit's version and `serve` its surface version (2 from pedit 0.6.0), when pedit reports them. A child that `share` or `status` started before any page connected is not reported here; the `ServedFiles` layer reports every child. |
122
+ | `stopped` | `{ id, path, reason: 'idle' \| 'exited' \| 'shutdown' }`, for the same children as `started`. |
123
+ | `error` | `{ id, message }`; `id` is `null` when no id is involved. |
124
+ | `update_available` | `{ version, url, security }`, once per version, when pedit finds a newer release. |
125
+ | `shared` | The result of a `share`, with `path` and `id`. |
126
+ | `peers` | `{ path, id, peers: [{ name, access }], more? }`: who is in the room; `more` counts people left out. |
127
+ | `comments` | `{ path, id, threads, more? }`: the file's threads; see [comments](#comments). |
128
+ | `room_ended` | `{ path, id, reason: 'closed' \| 'expired' \| 'maintenance' }`, also when the relay ended the room unasked. |
129
+ | `unshared` | `{ path, id }` |
130
+
131
+ For the room events, `id` is the one the routes last served that path under, or `null`.
132
+
133
+ ## `@pedit/serve/client`
72
134
 
73
- `@pedit/serve/client`
135
+ ### `connect(base, id, options?)`
74
136
 
75
- | Export | |
137
+ Opens `${base}/${id}`, where `base` is the same-origin path the routes are mounted at (`'/edit'`, not
138
+ a full URL): issues a session, connects, and resolves after the first sync with a `Connection`. It reconnects with a fresh token until the server says the
139
+ document is gone. `options` is `{ name?, color? }` for awareness.
140
+
141
+ ### `Connection`
142
+
143
+ | Field | |
76
144
  | --- | --- |
77
- | `connect(base, id, { name, color })` | Resolves after the first sync with `{ doc, text, awareness, role, status, synced, destroy }`. |
78
- | `conn.status` | `get()` and `subscribe(cb)`; states `connecting`, `connected`, `reconnecting`, `closed`. |
79
- | `PeditError.code` | `not-found`, `held`, `busy`, `forbidden`, `gone`, `unavailable`. |
145
+ | `doc`, `text` | The `Y.Doc` and `doc.getText('content')`, the file's text. |
146
+ | `awareness` | Names and cursors. Unauthenticated: anyone who can connect can claim any name. |
147
+ | `role` | `'edit'` or `'read'`. For `read`, no update is ever sent; also make the editor read-only. |
148
+ | `status` | `get()` and `subscribe(cb)`; states `connecting`, `connected`, `reconnecting`, `closed`. The callback gets a `PeditError` when a state has a cause. |
149
+ | `synced` | Resolves after the first sync. |
150
+ | `destroy()` | Closes the connection. |
80
151
 
81
- `@pedit/serve`
152
+ ### `PeditError`
82
153
 
83
- | Export | |
154
+ `error.code` (a `PeditErrorCode`) is one of:
155
+
156
+ | Code | |
84
157
  | --- | --- |
85
- | `ServedFiles` | The layer below the routes: `acquire(path)` starts or reuses a child, `close()` ends all. |
86
- | `ServedFile.share(opts)`, `unshare()`, `close()`, `status()`, `keep(keep)`, `comment(range)`, `reply(thread, body)`, `resolve(thread, resolved)` | Open a room for the file (needs pedit with `serve` 2; the child is not idle-stopped while shared). `share` resolves `{ type: 'shared', room, links, keptUntil, resumed }`, or `{ type: 'choice', room, choices }` when the file has a kept room of another history (share again with `choice`). A link is `<room>.<fragment>`: put your page URL and `#` before it. `onEvent` also gets `peers`, `room_ended` and `shared`. `status()` and `keep(keep)` are described under Sharing; the comment methods under Comments. |
87
- | `resolvePedit({ bin, install })`, `MIN_PEDIT_VERSION` | Finds pedit, checks the binary and its version. With `install: true`, downloads it when none is usable (see above) and returns `installed`. |
88
- | `HeldError`, `LimitError`, `NoPeditError` | Another process serves the file; 32 files or 8 clients; pedit missing or too old. |
158
+ | `not-found` | `resolve` returned `null` for the id. |
159
+ | `held` | Another process holds the file, and the page's role is `edit`. |
160
+ | `busy` | pedit could not serve the file just now: it is missing or not usable, the file or client limit is reached, the child failed to start, or another process holds a file the page would only read. The server reports the cause as an `error` event. |
161
+ | `forbidden` | The Host or Origin did not match, or the peer is not on loopback. |
162
+ | `gone` | pedit's saved state was discarded, so this page's copy cannot merge into the new document. The client stops. |
163
+ | `unavailable` | The session request failed, or its answer could not be read. |
164
+
165
+ Types: `Connection`, `Status`, `PeditErrorCode`, `Role`.
166
+
167
+ ## `@pedit/serve`
168
+
169
+ The layer below the routes, for an app that is not on Hono or wants its own transport.
170
+
171
+ ### `ServedFiles`
172
+
173
+ `new ServedFiles({ bin, app, idleMs?, graceMs?, onEvent? })` (`ServedFilesOptions`; `bin` must be an
174
+ absolute path). `acquire(path)` starts a child for the file or reuses the one running, and resolves a
175
+ `ServedFile`; `close()` ends every child. Its `onEvent` gets a `ServeEvent`, the same events as the
176
+ routes but by `path` only: this layer knows paths, not ids, and reports every child.
177
+
178
+ ### `ServedFile`
179
+
180
+ | Member | |
181
+ | --- | --- |
182
+ | `path`, `generation` | `generation` names the document: the same after a restart that restored it, new when it started afresh. |
183
+ | `dial(role)` | One stream to the child, as a `Bridge` (`send(frame)`, `onframe`, `onclose(reason, code?)`, `close()`); at most 8 per file (`LimitError`). |
184
+ | `share(opts?)`, `unshare()`, `close()`, `status()`, `keep(keep)` | As the routes' methods, for this file; see [sharing](#sharing). `close()` ends a kept room for good; a live one is only left. |
185
+ | `comment(range)`, `reply(thread, body)`, `resolve(thread, resolved)` | See [comments](#comments). |
186
+
187
+ ### `resolvePedit(options?)`, `MIN_PEDIT_VERSION`, `peditVersionOk(output)`
188
+
189
+ `resolvePedit({ bin?, install? })` finds pedit, checks the binary and its version, and resolves
190
+ `{ bin, version, installed? }`. With `install: true` it downloads pedit when none is usable; see
191
+ [installing pedit for the user](#installing-pedit-for-the-user). `peditVersionOk(output)` tells
192
+ whether a `pedit --version` output is at least `MIN_PEDIT_VERSION` (development builds pass and are
193
+ left to the feature checks).
194
+
195
+ Types: `ServedFilesOptions`, `ServedFile`, `ServeEvent`, `Bridge`, `Installed`, `Verified`, and the
196
+ share and comment types `ShareOptions`, `ShareResult` (`Shared`, `ShareChoice`, `ShareClosed`),
197
+ `ShareStatus`, `Peer`, `CommentRange`, `CommentThread`, `CommentMessage`, `StartedThread`, `Role`.
198
+
199
+ ### Errors
200
+
201
+ | Class | |
202
+ | --- | --- |
203
+ | `HeldError` | Another process serves the file. |
204
+ | `LimitError` | 32 files, or 8 clients on one file. |
205
+ | `NoPeditError` | pedit is missing or not trustworthy (not an absolute path, not a regular file, owned by another user, writable by group or others, or `--version` failed), too old, or could not be installed. |
89
206
 
90
207
  ## Sharing
91
208
 
92
- `peditRoutes(...)` also returns `share(path, opts)`, `unshare(path)`, `close(path)`, `status(path)`, `keep(path, keep)`, `comment(path, range)`, `reply(path, thread, body)` and `resolve(path, thread, resolved)`. `path` is an absolute path to a file under `root`, checked as the routes check it (a path outside `root`, a link out of it or a missing file rejects with `file not found`, and starts no child). Sharing needs pedit with `serve` 2; the child is not stopped for idleness while the file is shared.
209
+ `share(path, opts)` opens a room for the file on the relay, so people outside the app can edit,
210
+ comment on or read it from a page of yours ([your own review page](https://github.com/piconic-ai/pedit/blob/main/docs/review-page.md)).
211
+ While the file is shared, its child is not stopped for idleness.
93
212
 
94
213
  ```ts
95
214
  const result = await routes.share('/home/me/notes/a.md', {
96
- origins: ['https://cms.example.com'], // pages that may open the room
215
+ origins: ['https://cms.example.com'], // pages that may open the room (at most 4)
97
216
  keep: '1d', // how long the relay keeps it after you leave; '0' is a live room
98
217
  })
99
218
  if (result.type === 'shared') {
@@ -101,27 +220,36 @@ if (result.type === 'shared') {
101
220
  }
102
221
  ```
103
222
 
104
- `share` resolves one of:
223
+ `opts` also takes `relay`, the relay's URL (default: the project's, or `https://edit.piconic.ai`),
224
+ and `choice`, the user's answer to a `choice` result. `share` resolves one of:
105
225
 
106
226
  | Result | Meaning |
107
227
  | --- | --- |
108
- | `{ type: 'shared', room, links: { edit, comment?, read }, keptUntil, resumed }` | The room is open. A link is `<room>.<fragment>`: the page link is `<page URL>#<link>`. |
228
+ | `{ type: 'shared', room, links: { edit, comment?, read }, keptUntil, resumed }` | The room is open. A link is `<room>.<fragment>`: the page link is `<page URL>#<link>`. `keptUntil` is Unix ms, 0 when the relay keeps nothing. |
109
229
  | `{ type: 'choice', room, choices }` | The file has a kept room of another history. Ask the user which to keep (`room`, `app` or `later`; `text` is the characters each side holds), then call `share` again with the same options and `choice`. |
110
230
  | `{ type: 'closed' }` | `choice: 'app'` ended the kept room, so nothing is shared. Call `share` again to open a new one. |
111
231
 
112
- `unshare` leaves the room: a kept room stays until its deadline, a live one ends. `close` ends a kept room for good (called with no path it stops every child, as before). A failure from pedit rejects with its message.
113
-
114
- `status(path)` tells what room the file has, without changing anything, so an app that restarted can show a share that is still open. It resolves `{ room, links: { edit, comment?, read }, keep, host, ends, lifeEnd }`, or `null` when the file has no room (or the relay says it has ended). `host` is `'here'` while pedit shares the room now and `'away'` while it is kept without pedit; `keep` is in seconds, `ends` is when the room ends and `lifeEnd` the end of its life (both Unix ms). It starts the child if none runs, as pedit reads the file's record there, and a record of another history is still reported: `share` then gives the `choice`.
115
-
116
- `keep(path, keep)` changes how long the relay keeps the room after pedit leaves (`'3d'`, as in `share`; not `'0'`), while the file is shared with a keep. It resolves the new status. It rejects when the file is not shared, the room is live, or the relay refuses; a room kept without pedit gets its new keep from the next `share`.
232
+ `unshare` leaves the room: a kept room stays until its deadline, a live one ends. `close` ends a kept
233
+ room for good. A failure from pedit rejects with its message.
117
234
 
118
- A pedit that has no `status` or `keep` (an earlier release with `serve` 2) answers the unknown command with an error, and both reject with `pedit is too old for status; update it` (or `keep`). The child goes on.
235
+ `status(path)` tells what room the file has, without changing anything, so an app that restarted can
236
+ show a share that is still open. It resolves `{ room, links: { edit, comment?, read }, keep, host, ends, lifeEnd }`,
237
+ or `null` when the file has no room (or the relay says it has ended). `host` is `'here'` while pedit
238
+ shares the room now and `'away'` while it is kept without pedit; `keep` is in seconds, `ends` is when
239
+ the room ends and `lifeEnd` the end of its life (both Unix ms). It starts the child if none runs, as
240
+ pedit reads the file's record there, and a record of another history is still reported: `share` then
241
+ gives the `choice`.
119
242
 
120
- `onEvent` also gets `shared`, `peers` (`{ peers: [{ name, access }], more? }`, `more` counts people left out of the list), `room_ended` (`reason`: `closed`, `expired` or `maintenance`, also when the relay ended the room unasked) and `unshared`. Each carries `path` and `id`: the id the routes last served that path under, or `null`.
243
+ `keep(path, keep)` changes how long the relay keeps the room after pedit leaves (`'3d'`, as in
244
+ `share`; not `'0'`), while the file is shared with a keep. It resolves the new status. It rejects when
245
+ the file is not shared, the room is live, or the relay refuses; a room kept without pedit gets its new
246
+ keep from the next `share`.
121
247
 
122
248
  ## Comments
123
249
 
124
- While the app shares a file, it can take part in the file's comments as the host, and gets the threads live. Comments follow `docs/design/comments.md`: the same limits (a body of 1 to 4,000 characters, 500 threads, 200 comments a thread), written under the host's name as the room shows it.
250
+ While the app shares a file, it can take part in the file's comments as the host, and gets the
251
+ threads live. Comments follow `docs/design/comments.md`: the same limits (a body of 1 to 4,000
252
+ characters, 500 threads, 200 comments a thread), written under the host's name as the room shows it.
125
253
 
126
254
  ```ts
127
255
  const { thread } = await routes.comment('/home/me/notes/a.md', { from: 6, to: 10, body: 'Why wide?' })
@@ -129,16 +257,27 @@ await routes.reply('/home/me/notes/a.md', thread, 'Because it is.')
129
257
  await routes.resolve('/home/me/notes/a.md', thread, true) // false opens it again
130
258
  ```
131
259
 
132
- - `comment(path, { from, to, body })` starts a thread on `[from, to)` of the file's text, in UTF-16 code units (what `String.prototype.slice` and Yjs count). It resolves `{ thread, message }`. Only text and Markdown files take it; an empty range, one outside the text or one over 1,000 characters rejects. A file with CRLF line endings counts a line break as one unit, as your page's `Y.Text` does.
133
- - `reply(path, thread, body)` resolves `{ message }`. `resolve(path, thread, resolved)` resolves nothing; setting the state a thread has already is not an error.
134
- - They work only while the file is shared and pedit is connected to the room. Not shared, a room kept with `host: 'away'`, or a connection that is down rejects with pedit's reason: a comment written while nobody can see it would look delivered. What is saved stays readable in `.pedit/meta/`.
135
- - `onEvent` gets `{ type: 'comments', path, id, threads, more? }`: the file's threads as they are, once when sharing starts (and on a resume) and again whenever the comments, or the text they point into, change. A thread is `{ id, quote, resolved, from, to, cut?, messages: [{ id, author, time, body }] }`. `from` and `to` are where its anchor is in the text now, or `null` when it cannot be placed (its text was deleted, or it is on cells or a node). The event fits 64 KiB: open threads come first, `more` counts threads left out, and `cut` counts a thread's oldest comments left out. The event that shows a write comes before the call resolves.
136
- - A pedit without these commands makes them reject with `pedit is too old for comment; update it` (or `reply`, `resolve`).
260
+ - `comment(path, { from, to, body })` starts a thread on `[from, to)` of the file's text, in UTF-16
261
+ code units (what `String.prototype.slice` and Yjs count). It resolves `{ thread, message }`. Only
262
+ text and Markdown files take it; an empty range, one outside the text or one over 1,000 characters
263
+ rejects. A file with CRLF line endings counts a line break as one unit, as your page's `Y.Text` does.
264
+ - `reply(path, thread, body)` resolves `{ message }`. `resolve(path, thread, resolved)` resolves
265
+ nothing; setting the state a thread has already is not an error.
266
+ - They work only while the file is shared and pedit is connected to the room. Not shared, a room kept
267
+ with `host: 'away'`, or a connection that is down rejects with pedit's reason: a comment written
268
+ while nobody can see it would look delivered. What is saved stays readable in `.pedit/meta/`.
269
+ - The `comments` event carries the file's threads as they are, once when sharing starts (and on a
270
+ resume) and again whenever the comments, or the text they point into, change. A thread is
271
+ `{ id, quote, resolved, from, to, cut?, messages: [{ id, author, time, body }] }`. `from` and `to`
272
+ are where its anchor is in the text now, or `null` when it cannot be placed (its text was deleted,
273
+ or it is on cells or a node). The event fits 64 KiB: open threads come first, `more` counts threads
274
+ left out, and `cut` counts a thread's oldest comments left out. The event that shows a write comes
275
+ before the call resolves.
137
276
 
138
277
  ## Installing pedit for the user
139
278
 
140
279
  Nothing is ever downloaded unless your app asks for it. Without the opt-in, a missing or old pedit
141
- is a `NoPeditError`, as before. To let the app install pedit when needed:
280
+ is a `NoPeditError`. To let the app install pedit when needed:
142
281
 
143
282
  ```js
144
283
  const { bin, version, installed } = await resolvePedit({ install: true })
@@ -168,12 +307,14 @@ never touched.
168
307
  ## Security model
169
308
 
170
309
  - One hook, `resolve`, decides which ids exist and their role. Every other request is refused by default.
171
- - Files must be under `root` (realpath checked); ids are `[A-Za-z0-9_-]` only.
310
+ - Files must be under `root` (realpath checked); ids are `[A-Za-z0-9_-]` only, up to 128 characters.
172
311
  - Host and Origin must match `origins`, and peers must be on loopback unless `allowRemote` is set.
173
312
  - A session token is single use and expires in 60 s; it travels in the WebSocket URL, so log no `token`.
174
313
  - A `read` role is enforced by pedit itself, not just by the page: updates from a reader close the socket.
175
314
  - The browser never sees a path, a pid or a socket name.
176
- - On Windows, `resolvePedit` cannot check the owner or write access of the pedit binary it finds on the PATH, so a writable PATH directory (such as `C:\tools`) could hold a planted `pedit.exe`. Pass an absolute `bin` that you trust.
315
+ - On Windows, `resolvePedit` cannot check the owner or write access of the pedit binary it finds on the
316
+ PATH, so a writable PATH directory (such as `C:\tools`) could hold a planted `pedit.exe`. Pass an
317
+ absolute `bin` that you trust.
177
318
 
178
319
  ## Known limits
179
320
 
@@ -184,5 +325,6 @@ never touched.
184
325
  - Don't write the file yourself while a page is connected.
185
326
 
186
327
  Design: [docs/design/serve.md](https://github.com/piconic-ai/pedit/blob/main/docs/design/serve.md).
328
+ Guide: [embedding pedit](https://github.com/piconic-ai/pedit/blob/main/docs/embed.md).
187
329
  Sample: [examples/serve-app](https://github.com/piconic-ai/pedit/tree/main/examples/serve-app).
188
330
  MIT license.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pedit/serve",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Run pedit serve for a local app: the file's Yjs document over the child's stdin and stdout, with Hono routes and a browser client",
5
5
  "keywords": [
6
6
  "pedit",