pi-web-casual 0.0.0-stage → 0.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ### Added
6
+
7
+ - **Casual in its own browser tab.** **⁂** from a project chat now opens the casual conversation in a separate named window (`pi-web-casual-chat`), leaving the project chat exactly where it is; pressing **⁂** again focuses that window, and inside it the button closes the window to return. The card gained **Open in its own tab**, and the palette gained **Open Casual Chat in Its Own Tab**. If a popup blocker intervenes, or the home has no session yet, Casual falls back to opening in the current tab.
8
+ - **The Casual tab is the chat, with no toolbar.** The embedded frame fills the tab and nothing else renders around it; **New chat**, **Reset link**, and **Change home** no longer sit under the conversation. They are palette actions now, alongside **Open**, **Open in Its Own Tab**, and **Set Up**.
9
+ - **The Casual tab embeds the chat.** Once a conversation is remembered, the tab renders a same-origin frame on the casual chat route with that frame's navigation, header, and workspace column trimmed away, so the tab is a chat interface rather than a card. Trimming is measured and rolled back when the chat does not come out usable. **Open chat** and **Open in its own tab** stay as the full-view paths.
10
+ - **⁂ toggles between your project chat and Casual.** From a project chat it opens Casual; from Casual it returns to the exact project, workspace, and session you left, remembered per machine. With no return point — a fresh install, or a project that was removed — it opens the Projects list instead. The button's tooltip and accessible label follow the side you are on.
11
+ - `repo-docs` project skill at [`.agents/skills/repo-docs`](.agents/skills/repo-docs/SKILL.md): user-first README and docs standard, ADR and glossary contracts, a fill-in README skeleton, and a dependency-free link/anchor checker.
12
+
13
+ ### Changed
14
+
15
+ - **Removed the header ⁂ button.** The Casual tab and the palette are the entry points now, so nothing is injected beside **Actions** on desktop or mobile. The button's toggle, the per-machine return point, and the close-on-⁂ behavior in the separate window are gone with it; the separate window stays as a palette action and behaves like any other tab.
16
+ - **The Casual tab is its own view.** Clicking it renders the Casual card and leaves you there; it no longer redirects into the chat. The conversation opens from **Open chat** on the card, **⁂**, or the palette, and the card now shows the remembered session's name and location instead of a generic greeting. Mount-time auto-open is gone (`shouldAutoOpenOnMount` and the tab-click intent listener are removed); only an explicit control sets a short-lived open intent.
17
+ - Documentation is now organized by reader: `README.md` for install, first run, task table, limits, and a decisions index; `docs/using-casual.md` for the user path; `docs/how-it-works.md` for internals; `GLOSSARY.md` and `docs/adr/` for vocabulary and decisions.
18
+
19
+ ## 0.1.0 — 2026-10-21
20
+
21
+ Initial public release.
22
+
23
+ ### Added
24
+
25
+ - **⁂** button beside the PI WEB **Actions** button on desktop and mobile.
26
+ - **Casual** application tab with the Asterism icon.
27
+ - One persistent conversation opened in PI WEB's native chat.
28
+ - Automatic first-run setup: creates and trusts `~/.casual-pi-web`, creates the first session, and opens it.
29
+ - The Casual project is hidden from the sidebar **Projects** list, including the project count and collapsed heading.
30
+ - **New chat**, **Reset link**, and **Change home** controls.
31
+ - Palette actions: **Open Casual Chat**, **New Casual Chat**, and **Set Up Casual Chat**.
32
+ - Optional discovery of the latest casual session through the PI WEB catalog on newer hosts.
33
+ - Browser API v4 compatibility, including PI WEB `1.202610.3`.
34
+ - Documentation, screenshots, CI, issue templates, and a manual npm publish workflow.
35
+
36
+ ### Notes
37
+
38
+ - The extra button and hidden project row use PI WEB's private shadow DOM and are best-effort.
39
+ - The package is browser-only and needs no session-daemon restart.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Edward Champion
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,158 @@
1
- # Temporary Holding Version
1
+ # pi-web-casual
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 PI WEB plugin that adds one long-running conversation that is not attached to a repository.
4
+
5
+ **Status:** implemented (v1). Browser-only plugin: one project-free chat, opened from the Casual tab or the action palette. It has no server entry and needs no session-daemon restart.
6
+
7
+ ![Casual on desktop: the Casual tab, a native chat, and a sidebar with the Casual project hidden](docs/assets/desktop.png)
8
+
9
+ <sub>The screenshots predate this change: the ⁂ button they show beside **Actions** has been removed.</sub>
10
+
11
+ ## What Casual does
12
+
13
+ PI WEB organizes conversations around projects and workspaces. That is the right shape for code and a poor fit for the rest of the day: what to cook, naming a playlist, explaining an article, planning a weekend. Casual gives those conversations one home by reusing PI WEB's own project machinery: a hidden project at `~/.casual-pi-web` and a normal session inside it.
14
+
15
+ The chat is PI WEB's **native chat**, not a plugin reimplementation. Streaming, Markdown, tools, images, model selection, and compaction behave exactly as they do anywhere else, and the conversation is stored where PI WEB stores every other session.
16
+
17
+ It contributes:
18
+
19
+ - a **Casual** application tab;
20
+ - four palette actions: **Open Casual Chat**, **Open Casual Chat in Its Own Tab**, **New Casual Chat**, **Set Up Casual Chat**.
21
+
22
+ It behaves like this:
23
+
24
+ - **One conversation.** The **Casual** tab and **Open Casual Chat** reopen the remembered session from any project.
25
+ - **The Casual tab is only a chat.** The tab embeds the real PI WEB chat in a same-origin frame with that frame's navigation, header, and workspace column trimmed away, so a project chat in the main column keeps working next to it. There is no toolbar and no card around it; every control lives in the palette.
26
+ - **Hidden home.** The `Casual` project is filtered out of the **Projects** sidebar, including its row, the project count, and the collapsed-heading summary. It remains a real project for PI WEB: it appears in the **Change home** chooser, and its sessions appear in **Sessions** while it is selected.
27
+ - **New chat keeps history.** **New Casual Chat** creates another session in the same home and makes it the remembered one. The previous conversation stays in **Sessions**; Casual never deletes.
28
+ - **Self-repair.** Opening Casual resolves the home's workspaces and sessions and prefers the remembered session, so a stale or archived pointer is replaced by the newest live casual session instead of failing.
29
+ - **Automatic first run.** The first **Open chat** or palette open creates and trusts `~/.casual-pi-web`, starts a session, and reloads the page once into it. There is no **Add Project** step.
30
+
31
+ ## Install
32
+
33
+ ### Requirements
34
+
35
+ - PI WEB `1.202610.3` or newer (browser plugin API v4). Tested against `@jmfederico/pi-web` 1.202610.3.
36
+ - Node.js 22.19 or newer only if you build from source. Installing the published package needs no toolchain.
37
+ - No session-daemon restart at any point.
38
+
39
+ ### From GitHub
40
+
41
+ 1. Open PI WEB → **Settings → Pi packages**.
42
+ 2. Install `https://github.com/paradise-runner/pi-web-casual`.
43
+ 3. Enable **Casual** under **Settings → PI WEB plugins** if it is not already on.
44
+ 4. Reload the browser page.
45
+
46
+ ### From a release tarball
47
+
48
+ Download `pi-web-casual-0.1.0.tgz` from [Releases](https://github.com/paradise-runner/pi-web-casual/releases) and install that file under **Settings → Pi packages**, then enable **Casual** and reload.
49
+
50
+ ### From npm
51
+
52
+ The package is prepared as `pi-web-casual` but is not published yet. Use GitHub or a tarball until it is. Maintainers: see [docs/how-it-works.md](docs/how-it-works.md#releasing).
53
+
54
+ ### From source
55
+
56
+ See [CONTRIBUTING.md](CONTRIBUTING.md#development-setup). PI WEB does not compile TypeScript for local packages, so a checkout must be built before it is linked.
57
+
58
+ ## First run
59
+
60
+ 1. Open the **Casual** tab, or **Actions → Casual → Open Casual Chat**.
61
+ 2. Casual creates and trusts `~/.casual-pi-web`, starts the first session, and reloads the page once into the chat.
62
+ 3. Type in the normal composer.
63
+
64
+ The reload is one-time. PI WEB has no plugin API for registering a project, so Casual uses the host's own same-origin project and session endpoints for setup ([docs/how-it-works.md](docs/how-it-works.md#first-run)).
65
+
66
+ If those requests are unreachable, the panel shows a setup card instead: add `~/.casual-pi-web` yourself through **Add Project**, or choose an already-registered project as the home.
67
+
68
+ ![Casual on mobile: the Casual tab in the mobile tab bar and a native chat](docs/assets/mobile.png)
69
+
70
+ ## Use
71
+
72
+ | Task | Where |
73
+ | --- | --- |
74
+ | Chat casually next to your project | The **Casual** tab — it embeds the chat and leaves the project column alone |
75
+ | Keep a project chat and Casual open as separate tabs | **Actions → Casual → Open Casual Chat in Its Own Tab** |
76
+ | Open the conversation in this tab, full view | **Open chat** on the Casual card, or **Actions → Casual → Open Casual Chat** |
77
+ | Start a new conversation | **Actions → Casual → New Casual Chat**, or **New chat** on the Casual card |
78
+ | Recover a stale link | **Reset link** on the Casual card |
79
+ | Pick a different home folder | **Change home** on the Casual card |
80
+
81
+ The **Casual** tab never redirects you into the host chat view. It shows the card, and once a conversation is remembered it embeds that conversation directly. Setup and error states keep their own card with the controls needed to recover.
82
+
83
+ The **Casual** tab is a view of its own: it always shows the card and never redirects you into the chat. Nothing opens the conversation except **Open chat** on the card or a palette action. The card reports where you left off — the remembered session's name and location — and carries the controls that only make sense there.
84
+
85
+ While the conversation is open, the **Casual** project is the selected project, so its sessions are visible in **Sessions** like any other project's.
86
+
87
+ ## Limits (v1)
88
+
89
+ - **One conversation.** No topics, folders, pins, or per-topic sessions. A new chat is a sibling session in the same home, not a new space.
90
+ - **The separate window is a browser tab.** The palette action opens a named window and reuses it; a popup blocker, or no casual session yet, falls back to opening the conversation in the current tab, and the window is closed like any other tab.
91
+ - **One home per machine.** The home folder is fixed at `~/.casual-pi-web` unless you run **Change home**, and the remembered project/workspace/session pointer is stored per machine in `localStorage` under `pi-web-casual:store:v2`. A second machine finds or creates its own casual session.
92
+ - **No settings pane.** PI WEB's plugin API has no Settings contribution, so the home folder and pointer are changed through the card's controls, not through configuration.
93
+ - **Controls live in the palette.** Once a conversation is remembered the Casual tab has no buttons at all, so **Open**, **Open in Its Own Tab**, **New**, **Reset Casual Link**, **Change Casual Home**, and **Set Up** are palette actions. The card's buttons appear only in the states that show the card.
94
+ - **The embedded chat is framed, not native to the panel.** PI WEB's plugin API exposes no transcript component, so the Casual tab embeds the app's chat route in a same-origin frame and hides that frame's chrome. The frame is supervised, not trimmed once: it boots its own SPA, so the chrome keeps being hidden as the frame renders. A frame is also a second client instance with its own daemon connection.
95
+ - **Best-effort chrome.** PI WEB has no contribution point for hiding a project row or for a chat-only layout, so the hidden row and the embedded frame's chrome trimming depend on PI WEB's private shadow DOM. If an update changes that markup, the Casual tab and the palette keep working and the selectors need work in [`src/browser/dom.ts`](src/browser/dom.ts).
96
+ - **Project registration is not public API.** First run uses the host's own endpoints. If PI WEB changes them, first run falls back to the setup card.
97
+ - **API v4 only.** PI WEB `1.202610.6+` logs a deprecation warning for every API-v4 plugin, including its own bundled plugins. Moving to API v5 would drop `1.202610.3` support.
98
+ - **No deletion.** Casual does not delete sessions, workspaces, or the home project. Manage history in **Sessions**.
99
+ - **No telemetry.** No analytics, no third-party calls, no server process. Messages go to the model provider you configured for PI WEB.
100
+
101
+ ## How it runs
102
+
103
+ A browser-only PI WEB plugin package. There is no server entry, no Pi extension, and no agent client: the plugin resolves the home project, remembers a session for each side of the toggle, and navigates into PI WEB's native chat.
104
+
105
+ - Browser bundle in `dist/browser`, served by PI WEB directly from the installed package.
106
+ - Browser API v4, with the public **catalog** used on `1.202610.6+` and the selection service on `1.202610.3`.
107
+ - Per-machine pointer in `localStorage` (`pi-web-casual:store:v2`), holding the casual project, workspace, and session.
108
+ - Shadow-DOM integration isolated in `src/browser/dom.ts`.
109
+
110
+ | Change | Action |
111
+ | --- | --- |
112
+ | Browser bundle or panel | `npm run build`, reload the page |
113
+ | `package.json` `piWeb` metadata | Reload the page; no daemon restart |
114
+ | Separate Casual window | Already open — reload it, or close it and open Casual again |
115
+ | Home folder or remembered session | Nothing to rebuild — use the card controls or `localStorage` |
116
+
117
+ Detail, including the bootstrap requests, discovery order, explicit-open intent, and shadow-DOM selectors: [docs/how-it-works.md](docs/how-it-works.md).
118
+
119
+ ## Development
120
+
121
+ ```sh
122
+ npm install --ignore-scripts
123
+ npm run typecheck
124
+ npm run build
125
+ ```
126
+
127
+ `--ignore-scripts` skips building the native dependencies of the PI WEB host package; this project only consumes its TypeScript declarations. There is no test suite yet; the manual checklist lives in [CONTRIBUTING.md](CONTRIBUTING.md#manual-test-checklist).
128
+
129
+ The generated `dist/browser` files are committed because PI WEB serves them from the installed package. CI verifies they match source.
130
+
131
+ ## Contributing
132
+
133
+ Issues and pull requests are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md), follow the [Code of Conduct](CODE_OF_CONDUCT.md), and report security issues as described in [SECURITY.md](SECURITY.md).
134
+
135
+ ## Decisions
136
+
137
+ - [ADR 0001: One long-running conversation](docs/adr/0001-one-long-running-conversation.md)
138
+ - [ADR 0002: The chat is PI WEB's native chat](docs/adr/0002-native-chat-not-an-agent-client.md)
139
+ - [ADR 0003: Browser-only, with first-run bootstrap through host endpoints](docs/adr/0003-browser-only-bootstrap-through-host-endpoints.md)
140
+ - [ADR 0004: Stay on browser plugin API v4](docs/adr/0004-stay-on-browser-plugin-api-v4.md)
141
+ - [ADR 0005: Best-effort private shadow-DOM integration](docs/adr/0005-best-effort-private-shadow-dom.md)
142
+ - [ADR 0006: The Asterism toggles between the project chat and Casual](docs/adr/0006-asterism-toggles-between-project-and-casual.md) *(superseded)*
143
+ - [ADR 0007: Casual opens in its own browser tab](docs/adr/0007-casual-opens-in-its-own-window.md)
144
+ - [ADR 0008: The Casual panel embeds the chat in a same-origin frame](docs/adr/0008-panel-embeds-the-chat-in-a-same-origin-frame.md)
145
+ - [ADR 0009: The Casual tab replaces the header button](docs/adr/0009-the-tab-replaces-the-header-button.md)
146
+ - [Vocabulary](GLOSSARY.md)
147
+ - [Using Casual](docs/using-casual.md)
148
+ - [How Casual works](docs/how-it-works.md)
149
+
150
+ ## Related
151
+
152
+ - [PI WEB](https://pi-web.dev/) — the web UI that hosts Casual.
153
+ - [PI WEB plugin documentation](https://pi-web.dev/plugins) — supported contracts, installation, and reload rules.
154
+ - [Pi coding agent](https://pi.dev/) — the agent that powers PI WEB conversations.
155
+
156
+ ## License
157
+
158
+ [MIT](LICENSE) © 2026 Edward Champion.
@@ -0,0 +1,123 @@
1
+ /** Created and trusted automatically on the machine hosting the plugin. */
2
+ export const CASUAL_HOME_PATH = "~/.casual-pi-web";
3
+ export const CASUAL_PROJECT_NAME = "Casual";
4
+ export const CASUAL_PANEL_TOOL = "pi-web-casual:casual";
5
+ async function requestJson(url, init) {
6
+ try {
7
+ const response = await fetch(url, {
8
+ ...init,
9
+ credentials: "same-origin",
10
+ headers: {
11
+ "content-type": "application/json",
12
+ ...(init?.headers ?? {}),
13
+ },
14
+ });
15
+ if (!response.ok) {
16
+ const body = await response.text().catch(() => "");
17
+ console.warn(`[pi-web-casual] ${init?.method ?? "GET"} ${url} failed: ${response.status}`, body);
18
+ return undefined;
19
+ }
20
+ if (response.status === 204)
21
+ return {};
22
+ return await response.json();
23
+ }
24
+ catch (error) {
25
+ console.warn(`[pi-web-casual] ${init?.method ?? "GET"} ${url} failed`, error);
26
+ return undefined;
27
+ }
28
+ }
29
+ function asRecord(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value)
31
+ ? value
32
+ : undefined;
33
+ }
34
+ function stringField(record, key) {
35
+ const value = record?.[key];
36
+ return typeof value === "string" && value !== "" ? value : undefined;
37
+ }
38
+ function records(value) {
39
+ if (!Array.isArray(value))
40
+ return [];
41
+ const output = [];
42
+ for (const item of value) {
43
+ const record = asRecord(item);
44
+ if (record !== undefined)
45
+ output.push(record);
46
+ }
47
+ return output;
48
+ }
49
+ /**
50
+ * First-run bootstrap. The public plugin API cannot register a project, so this
51
+ * uses the host's own same-origin project/session endpoints. The folder is
52
+ * expanded and created by the host, which also means `~` resolves on the
53
+ * machine that owns the conversation.
54
+ */
55
+ export async function bootstrapCasual(machineId) {
56
+ const base = `api/machines/${encodeURIComponent(machineId)}`;
57
+ const projectRaw = await requestJson(`${base}/projects`, {
58
+ method: "POST",
59
+ body: JSON.stringify({
60
+ path: CASUAL_HOME_PATH,
61
+ name: CASUAL_PROJECT_NAME,
62
+ create: true,
63
+ }),
64
+ });
65
+ const projectRecord = asRecord(projectRaw);
66
+ const projectId = stringField(projectRecord, "id");
67
+ const projectPath = stringField(projectRecord, "path");
68
+ if (projectId === undefined || projectPath === undefined)
69
+ return undefined;
70
+ const project = {
71
+ id: projectId,
72
+ name: stringField(projectRecord, "name") ?? CASUAL_PROJECT_NAME,
73
+ path: projectPath,
74
+ };
75
+ const resolution = asRecord(await requestJson(`${base}/projects/${encodeURIComponent(projectId)}/workspaces`));
76
+ const workspaces = records(resolution?.["workspaces"]);
77
+ const workspace = workspaces.find((candidate) => candidate["isMain"] === true) ?? workspaces[0];
78
+ const workspaceId = stringField(workspace, "id");
79
+ const workspacePath = stringField(workspace, "path") ?? projectPath;
80
+ if (workspaceId !== undefined) {
81
+ // The casual folder is a user-owned scratch space; trust it so Pi does not
82
+ // ask about it and can load any settings the user later places there.
83
+ await requestJson(`${base}/projects/${encodeURIComponent(projectId)}/workspaces/${encodeURIComponent(workspaceId)}/trust`, { method: "PUT", body: JSON.stringify({ trusted: true }) });
84
+ }
85
+ const sessionRaw = await requestJson(`${base}/sessions`, {
86
+ method: "POST",
87
+ body: JSON.stringify({ cwd: workspacePath }),
88
+ });
89
+ const sessionId = stringField(asRecord(sessionRaw), "id");
90
+ return {
91
+ project,
92
+ ...(workspaceId === undefined ? {} : { workspaceId }),
93
+ workspacePath,
94
+ ...(sessionId === undefined ? {} : { sessionId }),
95
+ };
96
+ }
97
+ /** Reload straight into the native chat for the bootstrapped conversation. */
98
+ export function reloadToCasualChat(machineId, projectId, workspaceId, sessionId) {
99
+ window.location.assign(casualChatUrl(machineId, projectId, workspaceId, sessionId));
100
+ }
101
+ /** Route URL for the casual chat; used both for the one-time reload and for its own window. */
102
+ export function casualChatUrl(machineId, projectId, workspaceId, sessionId) {
103
+ const params = routeParams(machineId, projectId, workspaceId);
104
+ params.set("session", sessionId);
105
+ params.set("view", "chat");
106
+ return `${window.location.pathname}?${params.toString()}`;
107
+ }
108
+ /** Reload into the Casual panel so its normal flow can create the session. */
109
+ export function reloadToCasualPanel(machineId, projectId, workspaceId) {
110
+ const params = routeParams(machineId, projectId, workspaceId);
111
+ params.set("tool", CASUAL_PANEL_TOOL);
112
+ params.set("view", "workspace");
113
+ window.location.assign(`${window.location.pathname}?${params.toString()}`);
114
+ }
115
+ function routeParams(machineId, projectId, workspaceId) {
116
+ const params = new URLSearchParams();
117
+ if (machineId !== "local")
118
+ params.set("machine", machineId);
119
+ params.set("project", projectId);
120
+ if (workspaceId !== undefined && workspaceId !== "")
121
+ params.set("workspace", workspaceId);
122
+ return params;
123
+ }