@plurnk/plurnk-web 0.0.0-stage → 1.1.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +337 -2
  3. package/SPEC.md +869 -0
  4. package/ai.plurnk/.env.defaults +38 -0
  5. package/ai.plurnk/assets.json +193 -0
  6. package/ai.plurnk/creations.js +65 -0
  7. package/ai.plurnk/diagram-compiler.js +15 -0
  8. package/ai.plurnk/diagrams.js +130 -0
  9. package/ai.plurnk/directory-addresses.js +35 -0
  10. package/ai.plurnk/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  11. package/ai.plurnk/dist/assets/index-B4w6iDKw.css +1 -0
  12. package/ai.plurnk/dist/assets/index-B4w6iDKw.css.br +0 -0
  13. package/ai.plurnk/dist/assets/index-B4w6iDKw.css.gz +0 -0
  14. package/ai.plurnk/dist/assets/index-CGRGblLU.js +2 -0
  15. package/ai.plurnk/dist/assets/index-CGRGblLU.js.br +0 -0
  16. package/ai.plurnk/dist/assets/index-CGRGblLU.js.gz +0 -0
  17. package/ai.plurnk/dist/assets/math-renderer-CZ_XtOMq.css +1 -0
  18. package/ai.plurnk/dist/assets/math-renderer-CZ_XtOMq.css.br +0 -0
  19. package/ai.plurnk/dist/assets/math-renderer-CZ_XtOMq.css.gz +0 -0
  20. package/ai.plurnk/dist/assets/math-renderer-CnlgS6FH.js +96 -0
  21. package/ai.plurnk/dist/assets/math-renderer-CnlgS6FH.js.br +0 -0
  22. package/ai.plurnk/dist/assets/math-renderer-CnlgS6FH.js.gz +0 -0
  23. package/ai.plurnk/dist/assets/overview-DEhW9HUG.js +1 -0
  24. package/ai.plurnk/dist/assets/overview-DEhW9HUG.js.br +0 -0
  25. package/ai.plurnk/dist/assets/overview-DEhW9HUG.js.gz +0 -0
  26. package/ai.plurnk/dist/assets/preload-helper-BjS5LQBu.js +1 -0
  27. package/ai.plurnk/dist/assets/preload-helper-BjS5LQBu.js.br +0 -0
  28. package/ai.plurnk/dist/assets/preload-helper-BjS5LQBu.js.gz +0 -0
  29. package/ai.plurnk/dist/assets/session-BcdGbgAa.js +4 -0
  30. package/ai.plurnk/dist/assets/session-BcdGbgAa.js.br +0 -0
  31. package/ai.plurnk/dist/assets/session-BcdGbgAa.js.gz +0 -0
  32. package/ai.plurnk/dist/assets/session-agent-DwY8S586.js +111 -0
  33. package/ai.plurnk/dist/assets/session-agent-DwY8S586.js.br +0 -0
  34. package/ai.plurnk/dist/assets/session-agent-DwY8S586.js.gz +0 -0
  35. package/ai.plurnk/dist/assets/src-Du01yeMu.js +1 -0
  36. package/ai.plurnk/dist/assets/src-Du01yeMu.js.br +0 -0
  37. package/ai.plurnk/dist/assets/src-J2qT9xpv.js +6 -0
  38. package/ai.plurnk/dist/assets/src-J2qT9xpv.js.br +0 -0
  39. package/ai.plurnk/dist/assets/src-J2qT9xpv.js.gz +0 -0
  40. package/ai.plurnk/dist/icons/APACHE-2.0.txt +202 -0
  41. package/ai.plurnk/dist/icons/APACHE-2.0.txt.br +0 -0
  42. package/ai.plurnk/dist/icons/APACHE-2.0.txt.gz +0 -0
  43. package/ai.plurnk/dist/icons/NOTO-NOTICE.txt +10 -0
  44. package/ai.plurnk/dist/icons/NOTO-NOTICE.txt.br +0 -0
  45. package/ai.plurnk/dist/icons/NOTO-NOTICE.txt.gz +0 -0
  46. package/ai.plurnk/dist/icons/plurnk-maskable.svg +64 -0
  47. package/ai.plurnk/dist/icons/plurnk-maskable.svg.br +0 -0
  48. package/ai.plurnk/dist/icons/plurnk-maskable.svg.gz +0 -0
  49. package/ai.plurnk/dist/icons/plurnk.svg +63 -0
  50. package/ai.plurnk/dist/icons/plurnk.svg.br +0 -0
  51. package/ai.plurnk/dist/icons/plurnk.svg.gz +0 -0
  52. package/ai.plurnk/dist/index.html +421 -0
  53. package/ai.plurnk/dist/manifest.webmanifest +27 -0
  54. package/ai.plurnk/dist/manifest.webmanifest.br +0 -0
  55. package/ai.plurnk/dist/manifest.webmanifest.gz +0 -0
  56. package/ai.plurnk/dist/vendor/CFWORKER-JSON-SCHEMA-LICENSE.txt +25 -0
  57. package/ai.plurnk/dist/vendor/CFWORKER-JSON-SCHEMA-LICENSE.txt.br +0 -0
  58. package/ai.plurnk/dist/vendor/CFWORKER-JSON-SCHEMA-LICENSE.txt.gz +0 -0
  59. package/ai.plurnk/dist/vendor/DEPENDENCIES-LICENSE.txt +571 -0
  60. package/ai.plurnk/dist/vendor/DEPENDENCIES-LICENSE.txt.br +0 -0
  61. package/ai.plurnk/dist/vendor/DEPENDENCIES-LICENSE.txt.gz +0 -0
  62. package/ai.plurnk/dist/vendor/MARKED-FOOTNOTE-LICENSE.txt +25 -0
  63. package/ai.plurnk/dist/vendor/MARKED-FOOTNOTE-LICENSE.txt.br +0 -0
  64. package/ai.plurnk/dist/vendor/MARKED-FOOTNOTE-LICENSE.txt.gz +0 -0
  65. package/ai.plurnk/dist/vendor/OTM-LICENSE.txt +21 -0
  66. package/ai.plurnk/dist/vendor/OTM-LICENSE.txt.br +0 -0
  67. package/ai.plurnk/dist/vendor/OTM-LICENSE.txt.gz +0 -0
  68. package/ai.plurnk/dist/vendor/PICO-LICENSE.txt +21 -0
  69. package/ai.plurnk/dist/vendor/PICO-LICENSE.txt.br +0 -0
  70. package/ai.plurnk/dist/vendor/PICO-LICENSE.txt.gz +0 -0
  71. package/ai.plurnk/documents.js +483 -0
  72. package/ai.plurnk/http.js +69 -0
  73. package/ai.plurnk/index-document.js +5 -0
  74. package/ai.plurnk/module.js +326 -0
  75. package/ai.plurnk/session.js +319 -0
  76. package/ai.plurnk/static-assets.js +40 -0
  77. package/package.json +92 -5
  78. package/plugin.json +13 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Plurnk contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,338 @@
1
- # Temporary Holding Version
1
+ # plurnk-web
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Plurnk daemon plugin serving native OTM documents at `/`.
4
+ Pico Classless Green supplies the styling, with #148800 brand accents and system
5
+ monospace typography. It follows the browser's light/dark preference.
6
+ Conversation cards use a subtle green user tint, ordinary cards for model replies,
7
+ and neutral backgrounds for operations, notes and reasoning.
8
+ Requires a current stable browser; no legacy-browser compatibility layer is shipped.
9
+ No additional server. When signed in (or on an anonymous daemon), the root `/`
10
+ shows a live daemon-wide session overview ordered by most recent delivered reply,
11
+ and links to the daemon's workspace directory at `/~/`; workspace pages show the
12
+ same live session overview for that workspace, with native collapsed child
13
+ branches, on-demand completed-reply disclosures and a “Background work” section
14
+ for branches beneath internal actors. Bookkeeping sessions are omitted. Session pages show identity,
15
+ breadcrumbs, their actual parent, live status and conversation, without a
16
+ duplicate page heading. A native message form sends text; Stop explicitly cancels
17
+ the session's current work. Pending operations have native Accept/Reject review forms;
18
+ questions and MCP input share the same review and resume path.
19
+ The message box also accepts `/help`, `/models [search]`, `/model [selector] [effort]`,
20
+ `/effort [policy]`, `/capabilities [json]`, `/yolo`, and `/stop`. Commands never go
21
+ to the model: their results appear separately from conversation. Model, effort
22
+ and capability changes use daemon actions and retain its validation and active-work
23
+ restrictions. `/capabilities` shows service, workspace and effective policy; a JSON
24
+ argument replaces the workspace policy. `/yolo` or the 🔥/🐹 status toggle changes only this page's automatic
25
+ approval preference, including pending proposals; questions still require answers.
26
+ Reload restores the page's starting mode. Commands remain available during pending review.
27
+ `/models` lists aliases and a bounded catalog page; narrow its search for more matches.
28
+ Click the status model or effort to open its form below the conversation. Model
29
+ search offers native suggestions, or accepts an exact alias/provider-model.
30
+ Select Model / Select Effort applies the choice and closes the form; Cancel dismisses it.
31
+ Session cards on `/` and workspace pages show the session's lifecycle, parent/name
32
+ and workspace, ordered with the most recently delivered reply first, and stay live
33
+ over the ACP overview subscription while preserving open disclosures.
34
+ Click the ant to visit the session, or expand the card to see its child tree and
35
+ latest completed reply (loaded on demand when opened); parent and workspace links
36
+ navigate independently. Leaves without a completed reply have no empty disclosure.
37
+ The workspace list marks folderless workspaces with ☁️ and folder-backed ones with 🗂️.
38
+ On a workspace page, matching workspace/folder labels share one 🗂️ breadcrumb;
39
+ differing labels keep ☁️ on the workspace name and show 🗂️ on a separate folder line.
40
+ Folder labels use the daemon's home abbreviation, not the browser's.
41
+ The workspace list, workspace pages and session pages offer native header actions alongside the breadcrumbs:
42
+ - On `/~/`: `🗃️` followed by `➕` opens a native `<dialog>` modal to create a new folderless workspace; left unnamed, the daemon names it ({§web-workspace-creation}).
43
+ - On `/<workspace>/`: `🗃️` › `☁️ <workspace>` followed by `✏️`, `🗑️` and `➕` open native `<dialog>` modals to rename the current workspace ({§web-workspace-rename}), archive the workspace ({§web-workspace-archive}), or create an independent model session; left unnamed, the daemon names it ({§web-session-creation}).
44
+ - On `/<workspace>@<session>/`: breadcrumb navigation carries `🗑️` to archive the session and its descendants ({§web-session-archive}).
45
+ Modals offer Cancel and Confirm, dismiss on Escape or backdrop click without mutating state, and render in normal flow at the end of `<main>` when JavaScript is disabled; with JavaScript a closed modal shows nothing.
46
+ Creation forms carry a hidden token so a retried submission converges on the created place instead of re-executing.
47
+ Anonymous daemons refuse cross-site form posts, but a page that makes the browser reach
48
+ the daemon by a name of its choosing (DNS rebinding) passes that guard: run `PLURNK_AUTH=0` only where trusted parties alone reach the listener.
49
+ Sessions share full-width article lists and a stationary header around scrolling
50
+ main content. Session status and the Stop / multiline message / Send row stay at
51
+ the bottom. The message field and buttons grow together; Enter remains a newline,
52
+ and Ctrl+Enter sends through the same form as Send.
53
+ Page titles, breadcrumbs and empty/error documents support ordinary browser
54
+ navigation with or without JavaScript.
55
+ Header controls: 🗃️ workspace breadcrumb, ⚙️ Account, ✖ Sign out.
56
+ Breadcrumbs fit between the mascot and menu, or center below them when space is short.
57
+
58
+ Directories select their `index.html`: `/` and `/index.html` serve the same
59
+ document, as do the two spellings for any admitted nested index. Directory links
60
+ include the trailing slash. Missing documents and assets return 404, not the
61
+ home page. Navigation never creates workspaces or sessions, claims control, or
62
+ starts inference. Folderless workspaces work the same way as project workspaces.
63
+
64
+ The [directory address contract](SPEC.md#web-directory-addresses-named-directory-addresses)
65
+ maps workspace names to `/~/<workspace>/` and sessions to `/~/<workspace>@<session>/`,
66
+ preserving slashes and encoding name punctuation. Names remain separate from
67
+ project folders; URLs do not publish workspace files.
68
+
69
+ ## Sign-in
70
+
71
+ Web uses the daemon's shared `PLURNK_AUTH` and `PLURNK_PASS` configuration.
72
+ There is one local operator account and no username setting. With authentication
73
+ enabled, set a password; an empty password does not admit a browser,
74
+ including on localhost. The daemon owns the sign-in form and credential validation.
75
+ Set `PLURNK_AUTH_ISSUER` to the public origin when a proxy fronts the daemon.
76
+ `PLURNK_AUTH=0` explicitly makes directories anonymous too.
77
+
78
+ The daemon's native sign-in and setup/error pages use the same compiled Pico
79
+ stylesheet as web. Authentication remains daemon-owned; web supplies no login form.
80
+
81
+ The browser receives an HttpOnly session cookie; no access token, refresh token,
82
+ password or operator secret is embedded in a page or browser storage. **Sign out**
83
+ ends this browser's sign-in and its module credentials together. Revisiting requires
84
+ sign-in. **Account → Sessions** lists the authority's live sign-ins, marks this session,
85
+ and offers native forms to revoke one or all other sessions. No device or last-activity
86
+ information is invented. Other sessions and independently authorized clients remain
87
+ intact. A daemon restart invalidates sessions. Remotely, use HTTPS.
88
+
89
+ The same-origin `POST /auth/token` connection primitive supplies short-lived
90
+ ACP access credentials to a signed-in browser. OAuth refresh credentials stay
91
+ server-side; rotation and revocation use the daemon's authority.
92
+
93
+ Session pages passively connect through the official ACP client. A compact footer
94
+ above the message field shows approval mode, lifecycle, model/effort, latest-loop
95
+ elapsed time, and cumulative session-and-descendant tokens and cost. Unknown totals
96
+ stay `?`, not zero; known partial amounts retain `+ ?`. Field descriptions preserve
97
+ exact accounting behind the compact figures. A finished loop leaves the page watching
98
+ for the next one. Closing, refreshing or navigating away never cancels work.
99
+ Connection failure stops observation and marks the displayed values as stale;
100
+ use **Reconnect** or, after revocation, **Sign in**. Reconnect preserves the current
101
+ draft without resubmitting it; full page reloads do not persist drafts.
102
+ No automatic work retries occur.
103
+ Context occupancy/capacity, pending input, preparation, indexing and the daemon's `wait`
104
+ countdown appear only when relevant. The countdown is an observation deadline, not
105
+ a promise of completion. A collapsed session-count disclosure opens the live
106
+ descendant tree; no empty tree or metadata inventory occupies the page.
107
+ Workspace names and folder labels
108
+ follow supplied state without changing the observed session or rewriting the URL.
109
+ Daemon notices and nonterminal Problems appear separately under **Observed diagnostics**,
110
+ as literal messages and recovery guidance. Distinct observations remain through reconnects
111
+ on that page; they are not an archive or an indication that the session has stopped.
112
+ **Observed activity** exposes non-conversation log rows and text emitted outside
113
+ operations, retaining source coordinates and supplied session attribution as entry
114
+ semantics and accessible names. These observations survive reconnects on the same page, not
115
+ reloads; they are not replies or a complete activity history.
116
+ Operations show their glyph and aside above the addressed selection, returned count
117
+ and any failure. An immediate receipt completes that summary rather than adding
118
+ another article. Authored bodies appear below it; `read` results are not fetched
119
+ automatically. Reasoning stays collapsed until opened. Fetched bodies are literal text and are never added
120
+ to client-uploaded conversation history or saved in browser storage.
121
+
122
+ **Observed stream outcomes** retains conclusions seen on this page, separately
123
+ from session completion and conversation receipts. **Read output** explicitly
124
+ fetches a literal snapshot of all channels; click again to refresh it. Neither
125
+ inspection nor navigation cancels execution.
126
+
127
+ Live reasoning appears in one temporary plain-text preview, cleared when the
128
+ attempt ends or delivery disconnects. `PLURNK_WEB_REASONING_PREVIEW_CHARS` bounds
129
+ its retained tail (4096 Unicode code points by default). Completed turns keep
130
+ their reasoning in collapsed disclosures headed by `<session> > Loop <loop> > Turn <turn>`;
131
+ opening one retrieves the full trace when visible, without adding it to later prompt submissions.
132
+ Loops in the conversation transcript use open `Loop <loop>` disclosures with folded operation runs and a live open-task checklist at the tail of an active loop.
133
+
134
+ **Send** also supports follow-ups while the session is running. The draft clears
135
+ only after the daemon acknowledges it; newer typing is preserved. A failed or
136
+ uncertain submission is never retried automatically. **Stop** preserves unsent
137
+ text; closing the page is not Stop. Browser approval defaults to manual review
138
+ (`PLURNK_WEB_YOLO=0`). `PLURNK_WEB_YOLO=1` accepts proposals automatically, subject to daemon
139
+ permissions, on a session page the person opened themselves; one reached from another site
140
+ starts in manual review, as does every page on a plain-HTTP origin other than localhost (its
141
+ browser reports no navigation source). The session page shows the active mode. In manual mode, each approval
142
+ names its requesting session and shows the exact operation and review evidence.
143
+ Choose Accept or Reject for every presented operation, then **Submit responses**;
144
+ neither choice is preselected. Questions and MCP input always need a human answer.
145
+ Other authorized clients can settle the same request; every view withdraws the
146
+ answered controls and follows the work. Refresh rediscovers what remains pending.
147
+ An answer overtaken by another answer is reported as refused while observation
148
+ continues. Failed or uncertain decisions are reported, never automatically retried;
149
+ an actual connection failure requires **Reconnect**. Unimplemented interactions remain pending with
150
+ an explicit notice, never an automatic decision.
151
+
152
+ Questions show their request, full response schema, and requesting session ID when
153
+ the daemon supplies it. Simple fields use native
154
+ controls; nested or conditional answers use an **Answer (JSON)** editor without
155
+ discarding schema rules. Optional fields have an inclusion checkbox. Answers are
156
+ validated before submission and by the daemon; local errors preserve your input.
157
+ **Cancel this request** cancels that question, not the session's other work.
158
+ Refresh rediscovers pending questions but does not retain unsubmitted answers.
159
+
160
+ The daemon supplies the recent conversation window. **Load previous loop** reads
161
+ older history without adding it to model requests. Loaded pages remain for this
162
+ visit; any gap after reconnection has the same explicit loading control. Prepending
163
+ history preserves your reading position. New output follows the bottom only while
164
+ you are there. Reloading starts from the current daemon window.
165
+
166
+ Messages preserve daemon chronology; unknown history is not shown as empty.
167
+ Content and calls are direct semantic entries with accessible names and
168
+ presentation-relevant metadata, not nested protocol inspectors. Bodies use native
169
+ disclosures: user messages and delivered replies start open; notes, tool evidence
170
+ and completed reasoning start closed. Headers, diagnostics and operation details
171
+ remain visible. Opening a body does not start work or add `read` receipt readback.
172
+ Markdown is sanitized, raw
173
+ HTML stays text and images become links. `send` bodies use that same Markdown renderer;
174
+ `note`, reasoning and ordinary operation evidence remain literal preformatted text.
175
+ Long lines wrap and settled entries grow with their content; explicit code, tables,
176
+ formulas and diagrams scroll locally when wider than the viewport.
177
+ `note` retains attribution semantics without an enclosing assistant
178
+ article, a duplicate arguments view or a body copy in attributes.
179
+ The ACP client owns live messages; archival pages are read-only presentation. Neither
180
+ is saved in browser storage.
181
+
182
+ Markdown footnotes use native reference and back-links within each rich message or `send`.
183
+ Repeated references retain separate return links; definitions never leak between
184
+ messages. Code and unresolved references remain readable source.
185
+
186
+ Math uses locally bundled Temml and native MathML: `$…$`, `$$…$$`, `\(…\)` and
187
+ `\[…\]` outside code. Unsupported formulas keep their source and a short error;
188
+ raw HTML and TeX link/media extensions cannot create controls or fetch content.
189
+ The math renderer and its styles load only when needed.
190
+
191
+ Mermaid fences become static SVG images compiled by the web module's pinned
192
+ [Ondine](https://github.com/wevm/ondine) dependency, within its supported syntax.
193
+ The compiler runs in bounded Node threads; no compiler or WebAssembly is
194
+ sent to the browser. Compilation uses the existing browser session (or the daemon's
195
+ explicitly unauthenticated mode). `PLURNK_WEB_DIAGRAM_*` limits are documented in
196
+ `ai.plurnk/.env.defaults`.
197
+ The renderer loads only when needed. Source remains available in a disclosure;
198
+ unsupported or invalid diagrams keep their source and a short error. There are no
199
+ interactive diagrams, remote rendering services or external font requests.
200
+ Prose reflows on narrow screens; wide code, tables, formulas and diagrams scroll locally
201
+ without shrinking the text or widening the page. Native zoom remains available.
202
+
203
+ English is rendered in the document, not filled in by JavaScript. The current
204
+ page needs no duplicate English dictionary or browser storage, and has no language
205
+ picker or stored preferences. Ordinary navigation and forms
206
+ remain native HTML; live session status and conversation require JavaScript.
207
+
208
+ ## Install
209
+
210
+ Requires Node.js 26+ and Plurnk service 5.3+ with its module-client credential and
211
+ workspace-label APIs
212
+ ([credential authority](https://github.com/plurnk/plurnk-service/blob/main/plurnk-core/SPEC.md),
213
+ including sign-in inventory, shared browser/module revocation, password-only login,
214
+ and registered authorization-page styles, plus daemon-supplied folder labels).
215
+ `@plurnk/plurnk-service` includes `@plurnk/plurnk-web` by default; it can also be
216
+ installed beside the service directly from npm or a local build:
217
+
218
+ ```sh
219
+ npm install --no-audit --no-fund @plurnk/plurnk-web
220
+ ```
221
+
222
+ To build and verify a candidate archive from source:
223
+
224
+ ```sh
225
+ npm ci --no-audit --no-fund
226
+ npm run test:candidate -- /path/to/packed-platform-packages
227
+ npm pack
228
+ npm install --no-audit --no-fund /path/to/plurnk-plurnk-web-1.1.0.tgz
229
+ ```
230
+
231
+ Restart that daemon and visit its root URL, normally `http://localhost:1066/`.
232
+ Native extension trust applies; the standard policy admits `@plurnk/*` packages.
233
+ The plugin inherits the daemon's host and port, and leaves more-specific routes
234
+ alone. It does not install or start a service.
235
+
236
+ Set `PLURNK_WEB_ENABLED=0` in the service environment and restart to disable the
237
+ module without uninstalling it. Module settings, browser approval mode and the sign-in time limit are documented in
238
+ [ai.plurnk/.env.defaults](ai.plurnk/.env.defaults).
239
+
240
+ ## Install as an app
241
+
242
+ Use the browser's **Install app**, **Add to Home Screen** or **Add to Dock** action,
243
+ where supported. The manifest supplies Plurnk's name, icons and standalone window.
244
+ Localhost works over HTTP; remote installation requires HTTPS, normally supplied
245
+ by your reverse proxy. Installation does not install or start the daemon.
246
+
247
+ The app requires its daemon to be reachable. It does not register an offline cache
248
+ or provide offline operation. Browser and OS support varies.
249
+
250
+ ## Development
251
+
252
+ Read [ARCHITECTURE.md](ARCHITECTURE.md) for ownership, configuration and verification
253
+ boundaries. Web follows the platform's engineering discipline through public contracts.
254
+
255
+ ```sh
256
+ npm ci --no-audit --no-fund
257
+ npm run dev
258
+ ```
259
+
260
+ Open the loopback URL Vite prints (normally `http://127.0.0.1:5173/`).
261
+ HTML edits reload the page; CSS edits update it in place. This serves the frontend
262
+ only and neither starts nor restarts the daemon. Directory and sign-in requests
263
+ need the daemon; Vite does not emulate its data or authentication.
264
+
265
+ `npm run build` compiles the frontend into `ai.plurnk/dist/`. The plugin serves
266
+ that build on the daemon's existing port; restart the daemon to load a new build.
267
+ `npm run preview` previews the last build locally without a daemon. It is not the
268
+ production server. Vite is development tooling only.
269
+
270
+ `npm test` builds the frontend and checks specification tags and witnesses using
271
+ Plurnk's shared development checker, server and browser JavaScript, OTM's
272
+ HTML/JavaScript/CSS contracts, the module, and the three-engine browser component
273
+ suites. `npm run test:installation` packs and installs the plugin with packed
274
+ platform packages and the terminal client in a temporary directory. These may be published
275
+ archives obtained with `npm pack` or locally built candidates. `test:candidate`
276
+ gates the committed HEAD in a throwaway clone: `npm ci` from the lockfile, the
277
+ archives installed beside it without saving, then `npm test` and
278
+ `npm run test:installation` with `PLURNK_WEB_TEST_PACKAGES` naming the archive directory. The checkout is only read.
279
+ The installed test uses an
280
+ isolated configuration, database and dynamically assigned port, plus Playwright
281
+ Chromium. Component coverage also exercises native conversation behavior in Firefox
282
+ and WebKit. Install the test browsers with `npx playwright install chromium firefox webkit`
283
+ if necessary. The operator's daemon and configuration are not used.
284
+ The gate exercises the shipped session page and native input/review forms against that daemon and a
285
+ deterministic local model; no paid inference is involved and no ACP traffic is mocked.
286
+ Shared review journeys drive the installed terminal through a PTY alongside the
287
+ browser, covering both clients' manual/YOLO combinations and either manual responder.
288
+ The terminal emulator and PTY driver are test-only dependencies.
289
+ It exercises synchronization, observer/control handoff with one ACP history,
290
+ admitted work surviving request abortion, navigation and page close, explicit
291
+ cancellation, approval and question interruption and rediscovery,
292
+ shutdown and the real browser sign-in/access-token lifecycle. Held directory and OAuth
293
+ requests verify that module shutdown settles admitted work before releasing resources.
294
+ Cross-client prompt
295
+ delivery, exact `read` operands and real command stdout/stderr results are compared live and after
296
+ refresh through the client's messages, without custom-row reconstruction. Standard success,
297
+ cancellation, failure and reported token usage are checked separately. No ACP traffic is
298
+ mocked and no client-owned replay cache hides differences from the daemon's history.
299
+ Stream journeys also exercise silent executors, never-written channels, explicit
300
+ authenticated output inspection, completion during absence, cancellation and nonzero exit.
301
+ Browser journeys also check console warnings/errors and uncaught exceptions;
302
+ deliberately tested failures require explicit diagnostic expectations.
303
+ Adversarial content checks exercise rich-text roles, `note` bodies, literal tool data,
304
+ streaming and replay against the production content policy in all three engines.
305
+
306
+ `web/index.html` and `web/app.js` are the document and browser entry point.
307
+ Vite bundles OTM from its npm entry and compiles the CSS. Build preparation copies
308
+ Pico's readable pinned substrate and dependency licenses, and derives a maskable SVG
309
+ from the mascot. The ACP client loads only on session and overview pages; Vite includes the bundled
310
+ dependencies' license notices. Production prunes unused CSS rules, variables and keyframes from
311
+ the browser and server-rendered document sources, plus the authority's publicly
312
+ exported document specimens, before minification. The auth package supplies those
313
+ specimens at build time only; no daemon code enters the browser bundle.
314
+ Browser type checking uses the public runtime-neutral client entry without Node
315
+ globals. The module's server-side port types use their separate public entry;
316
+ type declarations are erased, not bundled.
317
+ The generated asset inventory tells the plugin which production files to serve.
318
+ Static assets negotiate build-time Brotli/gzip representations when smaller, with
319
+ identity delivery available. Private documents and ACP streams are not compressed
320
+ by the plugin; no request-time compression or additional browser dependency is added.
321
+ Build output is packed but not committed. [SPEC.md](SPEC.md) defines the contract.
322
+
323
+ The `web/styles/index.css` import entry composes the four source layers in order
324
+ before production pruning, so references between layers remain visible:
325
+
326
+ | File | Concern |
327
+ |---|---|
328
+ | `app-vendor.css` | Generated, unmodified Pico substrate. |
329
+ | `app-structure.css` | Reading width, viewport grid, composer sizing, gutters and wrapping. |
330
+ | `app-style.css` | Substrate token overrides; brand accents, system monospace and conversation surface colors. |
331
+ | `app-state.css` | Directory/session width, semantic background selection, glyphs, connection state and native focus visibility. |
332
+
333
+ Authored CSS is checked with OTM's Stylelint rules. Content-renderer dependencies
334
+ load their own vendor styles separately; sign-in uses only the page stylesheet.
335
+ No remote fonts are required.
336
+
337
+ Code: MIT. The hamster artwork derives from Google's Noto Emoji under Apache-2.0,
338
+ with glasses and a green bow added. See [artwork attribution](web/public/icons/NOTO-NOTICE.txt).