Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ The harness, models, and settings exist only once `#open` has set them up, after

Extensions never see `PocketApp`. They get `PocketHost` (`host.ts`), whose functions call into the app lazily. The HTTP layer parses a request, checks it, calls a part, and writes the answer; it holds no state of its own beyond invites and the event streams' pollers.

A `PocketExtension` may include optional `uiLocales`: locale metadata plus plain `strings` and `templates` dictionaries. `ExtensionLoader` validates these data, rejects duplicate locale registrations, and reports packs only from modules currently installed. A failed reload does not replace the previous module or its locale data. Locale dictionaries are not executable browser code.

## Startup

`PocketApp.#open` runs in this order, and the order matters:
Expand Down Expand Up @@ -112,12 +114,16 @@ Each browser tab is a `Client` (`room.ts`), fed by an event stream or by long po
3. then, when it shows a conversation, the room's full view;
4. then only what changed.

For an authenticated client, `hello.server.uiLocales` carries the data packs of enabled extensions; the authenticated `/api/me` response carries the same server metadata. There is no public dictionary endpoint. Without an authenticated hello or without an enabled pack, the web app uses English.

## The web app

Plain ES modules, served as they are, with no build. Editing a file under `web/` reloads every open tab, and a syntax error blanks the screen for everyone.

**Loading order matters.** `store.js` is the root, and a few modules do work when they load (listed in [map.md](map.md#web-web-preact-and-htm-no-build)). `theme.js` must load before anything that reads `prefs()` as it loads. `store.set` calls subscribers in the order they subscribed. When moving code between modules, keep both orders. `back.js` sits under `store.js` and imports nothing of the app's, so anything may use it.

`store.js` installs the available locale data from `hello` before updating shared state. `i18n.js` exports `t()`, but it translates only explicit source strings passed by UI code; it does not scan the DOM or user content. Calls with parameters use the matching template dictionary, and parameter values are interpolated once without recursive translation. The browser chooses `default: true` first and then sorts by locale string; with no pack it stays in English. The choice is not stored and users do not select a per-client language.

**History is the back stack (`back.js`).** Only `back.js` writes history. Each route (`/s/12`) and each layer over it (a sheet, the drawer, the launcher, the Files tile or Browser panel where they cover the screen, a file over the tile's tree) is an entry, and each entry says how many layers it stands for and how many steps back the session list's entry is. The app's state is the truth and history follows it a moment later: opening a layer pushes an entry, closing one with a tap goes back past it, and a new route first goes back past the layers above the current one. Back that the person makes closes the layers above the entry it lands on; back to another route reaches `store.js` through `onRoute`. Traversals `back.js` makes itself reach no one. So:

- Navigate with `navigate()` (which calls `pushRoute`); do not call `history.pushState` or `history.back()` elsewhere. To drop a query from the address, use `replaceAddress`, which keeps the entry's marks.
Expand All @@ -128,7 +134,7 @@ Plain ES modules, served as they are, with no build. Editing a file under `web/`

**Contracts with the server.**

- Event names and shapes (`hello`, `view`, `chat`, `peek`, `subagents`, …) and the sheet types the server names (`sheet: "chat"`).
- Event names and shapes (`hello`, `view`, `chat`, `peek`, `subagents`, …) and the sheet types the server names (`sheet: "chat"`). `hello.server.uiLocales` is authenticated connection metadata for enabled extension packs, not a public dictionary API.
- The `pocket.*` keys in local and session storage.
- `ATTACHMENTS_HEADING`, which `ui.js` repeats from `entry-format.ts`.

Expand Down
47 changes: 47 additions & 0 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,53 @@ The file:
- has a file name no built-in module has (a drop-in named `browser.ts` is not loaded). Files that start with `_` or end in `.test.ts` are not modules: use them for helpers and tests. A helper is not reloaded when it changes, even when the module that imports it is: a change to it takes a restart. Keep what you will change often in the module itself.
- starts with a `/** … */` comment whose first sentence says what it does.

### UI locale packs (data only)

A Pocket extension may also return optional `uiLocales` data alongside its ordinary tools, sections, hooks, or tasks. The shape is:

```typescript
type PocketUiLocale = {
locale: string; // a canonical BCP 47 language tag, such as "zh-CN"
label: string;
default?: boolean;
strings: Record<string, string>;
templates?: Record<string, string>;
};
type PocketExtension = Extension & { uiLocales?: PocketUiLocale[] };
```

Use exact English source strings as keys. `strings` handles a key with no parameters; `templates` handles a source template with named placeholders:

```typescript
import { defineExtension } from "@earendil-works/pi-durable";

export default function createUiLocale() {
return {
...defineExtension({ name: "example-ui-locale" }),
uiLocales: [
{
locale: "zh-CN",
label: "简体中文",
default: true,
strings: { Files: "文件" },
templates: {
"Back to sessions, {{count}} waiting for you":
"返回会话列表,{{count}} 项待你处理",
},
},
],
};
}
```

The web app translates only where its code explicitly calls `t("Files")` or `t("Back to sessions, {{count}} waiting for you", { count })`. Missing keys stay in English. A source template and its translation must contain the same set of placeholder names; for example, both templates above contain `{{count}}`. Interpolated values are inserted as-is; they are not looked up or translated again. This is not a DOM-wide translation pass: user messages, names, paths, model names, logs, and other dynamic content are not translated automatically.

The loader accepts locale packs only as validated data; their strings and templates are sent to the browser, not extension JavaScript. The same locale cannot be registered by multiple loaded extensions; a conflicting or invalid module is rejected. A drop-in still starts off. Its locale data follows the extension lifecycle: turning it off or removing it removes the pack, editing an enabled module reloads it, and a failed reload leaves the previous extension and pack running.

Only signed-in clients receive the available packs, as `hello.server.uiLocales` (also through the authenticated `/api/me` response). There is no public dictionary endpoint. An unauthenticated page, or a signed-in client with no enabled locale packs, uses English. The browser prefers a pack marked `default: true`, then sorts by the locale string; `default` is only a preference among packs already enabled, not a switch that enables an extension. There is no per-user language choice or saved locale.

The loader's canonical-tag and data validation rules may evolve; write canonical BCP 47 tags and do not rely on undocumented size or entry limits.

### Names

Installing an extension with a name already installed replaces it, and a tool with the name of another replaces it in every session. So Pi Pocket refuses a drop-in that would take a name it keeps for itself, or one another module already has, and says so in Menu → Extensions:
Expand Down
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The full tour. For setup, see the [README](../README.md).
- **Docs for agents.** Pi's system prompt says where Pi Pocket's documentation is ([index.md](index.md)), to read when someone asks about Pi Pocket itself: changing how Pi works without code, writing extensions (with tested examples), and changing Pi Pocket's code while it runs, checked first on a throwaway copy (`node test/serve.ts`).
- **Drop-in extensions.** Put your own extension module (a `.ts` file that default-exports `(host) => Extension`) in `~/.pi-pocket/extensions/`. It shows in the Extensions sheet, stays off until the owner turns it on, can import Pi Durable and pi-ai like the built-in modules, and reloads when edited. One that would take the name of a built-in extension or tool, or of another drop-in's, is not loaded, and the sheet says why: installing it would replace that one.
- **Extensions sheet.** Menu → **Extensions** (or tap "guard" in the status line) lists Pi Pocket's extensions with their tools and any load error. The owner can turn Artifacts, Browser, Subagents, Scheduled messages, Done when, Plan mode, Codemode, Lancet Guard, or a drop-in off and on, or reload one; the change applies to every session at once, stays after restarts (`disabledExtensions` and `enabledExtensions` in `~/.pi-pocket/config.json`), and everyone in a session sees who changed what. Turning Lancet Guard off here leaves Pi's own setting (`~/.pi/lancet-guard.json`) alone. The system prompt cannot be turned off.
- **UI locale packs.** An extension can provide a data-only language pack for exact UI strings and templates. The web app translates only strings its code explicitly passes to `t()`; missing labels remain English, and dynamic values, user messages, names, and paths are not translated automatically. Enabled packs reach signed-in clients in `hello.server.uiLocales`; there is no public dictionary endpoint. With no pack the UI is English. The pack marked `default: true` wins, then locale strings are sorted; this does not enable the extension, and there is no per-user language selector or saved language choice. Drop-ins stay off until the owner turns them on; disabling, editing, or removing a module manages its locale data with the rest of that extension. See [extensions.md](extensions.md#ui-locale-packs-data-only).
- **Uploads.** Attach files (or paste images). Files are saved on the server and their paths are given to the agent; images also go straight to models that accept them.
- **Rich replies.** Code blocks in Pi's replies show their language and a Copy button, with syntax colors for the common languages (no dependencies, so it works offline), and a block of more than 40 lines folds until you ask for the rest. Some show more than code, with the code a tab away: an `html` block (or a reply that is a whole page) is previewed as a page, inline, with its scripts off and nothing loaded from anywhere; **▶ Run** runs its scripts in the artifacts' sandbox, where it can reach the internet but not the app; an `svg` block is drawn as an image (tap it for full screen); and a `diff` or `patch` block opens in the diff viewer. While Pi is still writing, they show as code. Write cards color the file they write, and the file viewer previews `.html` files the same way, with Run.
- **Images inline.** Attached images show as thumbnails (also in the composer before you send). When Pi writes `![description](path)` with a path on the server (absolute, `~/…`, or relative to the session's folder), the image shows in its reply. Images a tool returns and SVG artifacts show inline too. Tap any image to see it full screen. Only real image files are shown: the server checks a file's bytes, not just its name.
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ A drop-in extension survives updates to Pi Pocket and can be turned off from the
## The documents

- [customizing.md](customizing.md): changing how Pi works without code.
- [extensions.md](extensions.md): writing extensions (tools, prompt sections, hooks), with working examples in [examples/](examples/).
- [extensions.md](extensions.md): writing extensions (tools, prompt sections, hooks, and UI locale packs), with working examples in [examples/](examples/).
- [self-editing.md](self-editing.md): changing Pi Pocket's code while it runs, checking the change, and getting a broken one back.
- [map.md](map.md): where each part of the code is, and how to add the common things.
- [architecture.md](architecture.md): how the parts depend on each other, and the orderings they rely on.
Expand Down
Loading