No description
  • TypeScript 92.2%
  • CSS 7.2%
  • JavaScript 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dikkadev 07289962f6 docs: document temporary UI redesign guidance
Documents the temporary presentation authority, placeholder-specific redesign intent, preservation rules, validation requirements, and cleanup steps so future UI work does not restore rejected historical layouts or backend-driven placement.
2026-08-30 16:53:59 +02:00
.dev docs: document temporary UI redesign guidance 2026-08-30 16:53:59 +02:00
extension fix(dev): terminate supervised processes by process group 2026-08-29 19:05:36 +02:00
hub build: declare Pi coding agent as a peer dependency 2026-08-29 20:58:07 +02:00
scripts fix(dev): terminate supervised processes by process group 2026-08-29 19:05:36 +02:00
shared feat(operative-status): add provider-neutral operative status projections 2026-08-29 10:09:03 +02:00
systemd feat: productionize persistent workspace 2026-08-24 10:40:00 +02:00
web feat(production): add workspace redesign placeholder destinations 2026-08-30 15:42:53 +02:00
.gitignore feat: productionize persistent workspace 2026-08-24 10:40:00 +02:00
AGENTS.md docs: document temporary UI redesign guidance 2026-08-30 16:53:59 +02:00
index.html feat: add adaptive mobile workspace access 2026-08-27 15:28:30 +02:00
package.json build: declare Pi coding agent as a peer dependency 2026-08-29 20:58:07 +02:00
pnpm-lock.yaml build: declare Pi coding agent as a peer dependency 2026-08-29 20:58:07 +02:00
pnpm-workspace.yaml feat: productionize persistent workspace 2026-08-24 10:40:00 +02:00
README.md fix(dev): terminate supervised processes by process group 2026-08-29 19:05:36 +02:00
tsconfig.hub.json build: add unified development workspace supervisor 2026-08-24 11:19:31 +02:00
tsconfig.json feat: add persistent workspace hub foundation 2026-08-23 13:46:01 +02:00
vite.config.ts feat(terminal): add persistent tmux-backed project terminals 2026-08-28 13:35:55 +02:00

Pilcrow

Pilcrow is one persistent local browser workspace for all interactive Pi sessions in a Linux or WSL user environment. Interactive sessions register with an independently running foreground hub; the browser can switch among live and retained sessions, review finalized responses, annotate canonical rendered text, and send confirmed feedback back through the owning Pi extension.

The hub does not launch a browser, and Pi does not launch the hub. The project + control launches headless pi --mode rpc sessions that the current hub directly owns. Independently launched TUI and RPC sessions also register; JSON and print modes do not.

Requirements

  • @earendil-works/pi-coding-agent 0.84.3 or newer
  • Node.js 22.19.0 or newer (the minimum required by Pi 0.84.3 for integrated Pilcrow use)
  • Linux or WSL with Unix sockets, tmux, and /bin/bash
  • x86-64 glibc for the currently selected prebuilt @replit/ruspty backend (the package also publishes macOS x86-64/arm64 builds; other Linux architectures and musl are not currently packaged)
  • A browser able to reach the environment's loopback listener (WSL localhost forwarding is normally sufficient for a Windows browser)

Install

Install the Pi package from Git:

pi install ssh://git@git.dikka.dev:2222/lab/pilcrow.git

Pi installs dependencies and runs the package build. Restart Pi or run /reload after installation. For isolated integration testing from a checkout without disabling the normal Pi profile, use scripts/pilcrow-dev-pi.

Run the regular workspace

The regular hub serves the built browser from dist/ and uses the default production state directory and producer socket. It is separate from the development stack described below.

Start one regular hub in a foreground terminal from a source or Pi-managed package checkout:

vp run hub

pnpm run hub is equivalent. When the pilcrow-hub package binary is installed on PATH, use:

pilcrow-hub

Successful startup prints the fixed browser URL and producer socket, then remains in the foreground. If local-router is available, the hub silently asks it to register the bound port as pilcrow.localhost. If local-router is absent or registration is unavailable, startup continues without a warning.

Open the friendly route when local-router accepted the registration:

http://pilcrow.localhost/

The fixed loopback URL always remains available:

http://127.0.0.1:17067/

The root production URL is the persistent workspace. It remains available with no Pi sessions. Each independently launched TUI or RPC session that loads the extension creates or refreshes an observed workspace record. Browser-created and observed records remain stopped and selectable across hub restarts. Pi owns each conversation file and session name; Pilcrow stores only its opaque workspace identity, lifecycle, and bounded last-known projection. The extension shows ¶ hub, ¶ hub…, or ¶ hub! in Pi's status area; hub absence does not stop normal Pi use.

The left sidebar is a project tree that merges hub-saved paths with projects discovered from current Active sessions by normalized working-directory identity. Saved projects remain visible and startable with zero sessions; discovered projects are ephemeral until explicitly saved. Active sessions appear inside collapsible project groups. Hub-owned organization can move a session to Later for the configured default duration, Parked, or Archived, and return it to Active. Placement is durable workspace metadata shared by every browser page and remains independent of Pi runtime, lifecycle, activity, and launcher authority. Later stores one absolute wall-clock deadline; the hub returns it to Active at expiry, including after clock jumps or restart. Archived sessions have a dedicated sidebar view. Search covers session titles, working directories, summaries, and response text. The resizable bottom dock keeps the selected session's current line in its collapsed strip. Its expanded surface places overview and Turn timing in a resizable information column beside an xterm.js project terminal; narrow layouts stack the information above the terminal.

Add project opens one path field and saves a validated empty project without starting Pi. Absolute Linux/WSL paths work directly; copied Windows drive and UNC paths are converted through WSL's native wslpath, and conversion failures are definite and actionable. Add does not imply Pi trust or pass approval. Saved rows use Panels Top Left, discovered rows use Eye, and the project icon or header context menu offers Save project or Forget project. Forget removes only saved membership and never changes sessions, drafts, or placements. Each project has a separate collapse control and + control; + first persists and opens a Starting workspace record, then starts pi --mode rpc after saved/current-session authority and a fresh directory check. Input becomes usable only after the process registers with the exact launch binding. The hub directly owns the child process group and its open RPC pipe. A currently running browser-created session alone exposes a separated red Stop session action with confirmation. Stop carries the record's opaque per-binding management token, so a stale request cannot target a replacement binding; location remains display-only. Stopping retains the record, Pi history, drafts, and hub-owned placement. Continue resumes the exact canonical Pi session path, or starts a fresh process for an untouched empty record, without changing workspace identity. If that exact path is already live independently, Continue selects it without adopting or duplicating the process. Start failures retain the stopped record with actionable Continue and Forget controls. Session Forget is stopped-only, removes Pilcrow metadata, and never deletes or rewrites Pi JSONL. For a known persisted session, Stream shows Pi's current active branch as one chronological conversation of user messages, assistant answers, thinking, tool and shell work, visible custom messages, and meaningful boundaries. A cooperating supervision extension may add compact current-turn operative rows containing only display name, lifecycle, and current activity. Persisted TUI-visible operative messages remain ordinary chronological stream entries; Pilcrow does not copy their report text into live rows. The running turn is its provisional newest entry and settles in place when its persisted Pi identity appears; interrupted and failed turns keep partial content and their outcome. Completed technical work is one collapsed section whose expansion shows retained details. The newest bounded page opens first; scrolling toward the top loads opaque hub-issued earlier cursors while retaining the current fixed-prefix visit and preserving the visible entry's pixel position. A short viewport automatically loads older pages until it is filled or history is exhausted. A failed page has an in-place Retry action, and leaving the stream cancels and closes its bounded visit. Pilcrow uses the exact path and Pi identity already recorded for that session and never exposes raw branch leaves, abandoned branches, or tree navigation. Lost project/start/stop responses are reported as unconfirmed rather than definite failures, with project membership resolved from authoritative workspace GET/SSE when possible, and concurrent start clicks retain separate notices. Sidebar controls use a locally shipped Lucide SVG icon set rather than text glyph approximations; the pilcrow brand mark and session-state indicators remain product-specific.

The extension retries an unavailable or restarted hub for a bounded window. After it reports exhaustion, run:

/pilcrow-reconnect

/pilcrow-reconnect is Pilcrow's only user-facing Pi command. The extension also registers /pilcrow-reload-resources as an internal browser-to-Pi bridge; users should not invoke it directly. There are no per-session URLs, capability tokens, browser-open commands, terminal shortcuts, or sidecar listeners. The dock terminal is created only after Initialize terminal is selected for a retained/current session. It is keyed by the hub-resolved canonical project cwd, so sessions in that cwd share one ordinary Bash shell. Saved-only project paths cannot initialize one.

Stop a foreground hub with Ctrl+C or SIGTERM. It waits for each owned process group to disappear, using graceful termination and then bounded force termination if necessary, before closing HTTP/SSE and producer connections and removing only the Unix socket it owns. It still completes hub/socket cleanup when child cleanup fails, then surfaces an aggregate failure rather than reporting a clean shutdown. Browser-created Pi processes do not survive a hub restart, but their records do: restart projects them as stopped and Continue-able. Independently launched sessions retain observed records and remain independent. Project terminals are different: WebSocket loss and hub shutdown close only the current PTY tmux client, leaving the isolated tmux session available for reattachment after browser or hub return. Ordinary shell exit terminates that project terminal.

Configuration

Defaults are intentionally stable:

Setting Default Environment Command option
Loopback HTTP port 17067 PILCROW_HUB_PORT --port <1-65535>
Producer socket $XDG_RUNTIME_DIR/pilcrow/hub.sock, otherwise /tmp/pilcrow-$UID/hub.sock PILCROW_HUB_SOCKET --socket <absolute-path>
Pilcrow state directory $XDG_STATE_HOME/pilcrow, otherwise ~/.local/state/pilcrow PILCROW_STATE_DIR --state-dir <absolute-path>
Additional write origin unset PILCROW_HUB_ALLOWED_ORIGIN none

Run pilcrow-hub --help for the command summary. Command options override environment values; in particular, a valid --state-dir overrides PILCROW_STATE_DIR without consulting the lower-priority value. State-directory input must be absolute, contain no control characters, and fit within 4096 UTF-8 bytes. Pilcrow reports invalid input without echoing it.

The state directory is the root for Pilcrow-owned persistent data. Saved projects use <state-directory>/projects.json; durable observed/browser-created workspace records, including placement and Later deadlines, use exact catalog v3 at <state-directory>/workspace-sessions.json; workspace-wide organization preferences use <state-directory>/settings.json; unfinished ordinary and response-bound drafts plus accepted-dispatch fingerprints use bounded version 1 state at <state-directory>/feedback-state.json. These private files use atomic replacement. Draft writes carry a workspace revision, so a stale page receives a conflict instead of replacing newer durable content. This setting does not change Pi's settings, models, resource directories, or session-file resolution.

For Pi session metadata listing, the history adapter follows the resolution available inside the hub process: an explicit resolved adapter override first, then PI_CODING_AGENT_SESSION_DIR, then Pi's configured session directory. A Pi --session-dir command option affects that Pi process and is not discoverable by an independently started hub unless the same resolved directory is also supplied through the environment. Listing never overrides the exact producer/catalog Pi identity and session path used for history reads and Continue authority. Project terminals use an isolated tmux server socket named terminal-tmux.sock beside the producer socket, and the terminal session identity includes the state directory. Separate state roots therefore cannot claim the same project-terminal session. Pilcrow never kills the tmux server during ordinary hub cleanup. Malformed catalog state is reported rather than overwritten. A configured socket directory must be private to the current Unix user; Pilcrow creates its default directory with mode 0700 and the socket with mode 0600. Apply PILCROW_HUB_SOCKET to both the hub and the Pi process so they use the same transport authority.

The HTTP listener is always 127.0.0.1; configuration cannot expose it remotely. PILCROW_HUB_ALLOWED_ORIGIN may name one exact additional HTTP or HTTPS browser origin when a trusted same-machine reverse proxy exposes Pilcrow, for example https://pilcrow.ppc.dikka.dev. It expands only the browser write-origin check and does not provide authentication, authorize a proxy, or broaden the listener. A port or socket collision is a startup error with an actionable message. Pilcrow never searches for another port, steals a live socket, or broadens the listener.

Optional systemd --user operation

Systemd is optional. Pilcrow ships systemd/pilcrow-hub.service as a maintained template but never installs, enables, or starts it.

The template uses /usr/bin/env pilcrow-hub, so first ensure pilcrow-hub is installed in the service manager's PATH. Then, if user-service operation is desired, copy the template yourself:

mkdir -p ~/.config/systemd/user
cp systemd/pilcrow-hub.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now pilcrow-hub.service

Optional service configuration belongs at ~/.config/pilcrow/hub.env:

PILCROW_HUB_PORT=17067
PILCROW_HUB_SOCKET=/run/user/1000/pilcrow/hub.sock
PILCROW_STATE_DIR=/home/example/.local/state/pilcrow
# Optional trusted reverse-proxy browser origin:
# PILCROW_HUB_ALLOWED_ORIGIN=https://pilcrow.example.internal

Use the actual runtime directory and UID for the account. If pilcrow-hub is not visible to the user manager, replace ExecStart in your copied unit with the stable absolute launcher path rather than editing the packaged template. Upgrades then require only updating the installed package and restarting the service:

systemctl --user restart pilcrow-hub.service
journalctl --user -u pilcrow-hub.service

Browser and transport behavior

  • The hub serves the built dist/ workspace on /, with immutable caching for generated assets and no-store for HTML and APIs.
  • Every browser page joins the hub's shared multiplexed SSE workspace stream and first receives a complete authoritative snapshot, including saved-project paths, workspace Settings, and durable placement, followed by session and full-workspace updates. Unfinished ordinary input, annotations, comments, and validated raster attachments persist under the configured state root. Cursor position, native selection geometry, open menus, editor placement, routes, and browser selection remain page-local. Workspace state uses each record's opaque workspaceId; the exact piSessionId remains Pi identity. Multiple pages share hub state while keeping independent navigation and show a presence notice. Overview assigns each session once, in priority order, to Needs review or attention, Working now, Active workspace, Later, Parked, or Archived. It also totals working operatives and operatives needing attention across connected exact-session projections without adding child rows. Only direct interaction with a loaded finalized-response area advances the durable reviewed position; navigation, hydration, background arrival, viewport visibility, and programmatic scrolling do not.
  • Project terminals use the separate /api/terminal WebSocket endpoint. Upgrade Origin, retained-session authority, server-derived canonical cwd, exact tmux argv, message and output sizes, resize dimensions, and backpressure are validated without changing the producer protocol.
  • Pi extensions connect over bounded framed JSON on one user-scoped Unix socket and send exact protocol-v10 state/events. Registration carries a required nullable opaque launch ID. Every producer state carries Pi's exact nullable canonical session path, a bounded active-branch revision (leaf identity and count, never branch chunks), current and available model/thinking facts, and the normalized presentation state.
  • Protocol v10 retains the typed model/thinking controls and live-turn facts from v9, but removes completion-notification projection. Finalized response identity and the hub-owned reviewed position now drive Overview attention; queue_notification remains visible only as ordinary technical tool work. Optional operative status is an additive v10 presentation fact rather than a new required producer field. Pilcrow requests its version-1 provider on operative-status:api:request:v1 through Pi's shared extension event bus. The request supplies the exact sessionId and an accept callback; a provider exposes version: 1, getSnapshot(sessionId), and subscribe(onChange). Pilcrow validates bounded exact-session snapshots and retains the last valid snapshot when a provider is absent, malformed, throwing, replaced, or disposed. The extension is the only raw-Pi adapter and applies accepted changes through Pi's public APIs; the hub never consumes raw Pi lifecycle objects or imports an operative provider's implementation or registry.
  • The narrow browser fallback for an already-running older hub marks each missing session path, history, model, or thinking projection with an explicit read-only { status: "unknown" } sentinel; it does not invent a null path, empty model catalog, or off thinking state. The fallback also normalizes the older missing extensionStatuses, hubManaged, managementToken, and savedProjectPaths fields. Producer messages have no compatibility path: after a producer-protocol build, restart the hub first, then reload or restart active Pi sessions, and finally reload browser pages.
  • Browser writes require a built-in loopback page origin or the one exact additional origin explicitly configured through PILCROW_HUB_ALLOWED_ORIGIN. Render captures, request bodies, identities, images, and submission-result caches are bounded.
  • Feedback can be sent only while its Pi session is connected. The browser clears only the accepted editable payload and allows a new draft immediately. The hub retains bounded immutable submission and payload fingerprints until matching real Pi user history appears; it never fabricates a waiting conversation row. Disconnects and timeouts preserve honest indeterminate retry state.
  • Workspace records persist atomically in exact catalog version 3 at workspace-sessions.json beneath the configured Pilcrow state directory. They retain origin, starting | ready | stopped | blocked lifecycle, bounded start failure, durable placement and absolute Later deadline, exact nullable Pi identity/path, Pi-owned names, project directories, monotonic workspace creation timestamps, and bounded last-known producer and presentation facts. Workspace Settings persist separately in private settings.json; the atomically replaced, revisioned reviewed position for each workspace record persists in private reviewed-positions.json; durable drafts and accepted-dispatch fingerprints persist in private feedback-state.json. A restart projects every formerly live or Starting record as stopped and immediately reconciles overdue Later deadlines. Render captures and launch ownership still clear on hub restart.

Pilcrow has no remote listener, account/token layer, offline feedback queue, branch-tree browser, submitted-anchor repository, operative management controls, operative navigation, or durable process supervisor. Its bounded local history reader supports the continuous active-branch stream, project History search, cold inspection, and explicit import while preserving exact Pi path and identity authority; it never writes Pi JSONL. Valid persisted Pilcrow TOON remains one real chronological Pi user entry and renders as quoted targets, comments, an overall reply, and attachment counts. Pilcrow adds no model-visible marker, paired custom entry, or duplicate transcript row. Live work is the provisional newest stream entry and reconciles to persisted Pi identity. Completed technical work is derived from Pi history and collapsed as one chronological detail section rather than stored or summarized by Pilcrow. The current hub supervises only its own browser-created children in memory; placement never affects launcher behavior, and an observed record never grants Stop or signal authority.

Development

Development from a source checkout requires Vite+ (vp) on PATH. Start the complete watched development stack with:

vp run dev

pnpm dev is equivalent. Do not use vp dev: that starts only Vite and omits the watched hub.

Regular usage and development are deliberately isolated:

Regular usage Development
Browser http://127.0.0.1:17067/ http://127.0.0.1:17167/ or http://pilcrow-dev.localhost/
Hub API same process and port as the browser http://127.0.0.1:17168/; Vite proxies /api to this port
Producer socket $XDG_RUNTIME_DIR/pilcrow/hub.sock $XDG_RUNTIME_DIR/pilcrow/development/hub.sock
Pilcrow-owned state $XDG_STATE_HOME/pilcrow $XDG_STATE_HOME/pilcrow/development
Browser assets built dist/ served by the hub source modules served by Vite

vp run dev supervises Vite, the TypeScript hub build watch, and the development hub as one command. The development hub restarts silently after successful backend compilation; type errors leave the last good hub running. Its API port never serves or redirects to browser assets. Production can remain active because development uses separate Pilcrow state and transport. Do not run a second development stack beside it.

For an isolated integration test, use the checkout launcher after the development socket exists:

./scripts/pilcrow-dev-pi

The launcher preserves the normal Pi profile and replaces only Pilcrow with this checkout's extension. It selects the development producer socket and forwards additional Pi arguments unchanged. From another working directory, invoke it by repository path so Pi keeps that directory:

~/projs/pilcrow/scripts/pilcrow-dev-pi --name "Pilcrow smoke"

The launcher fails clearly when the development socket is unavailable. An older installed Pilcrow extension that does not support exact source selection must be updated before using this launcher.

The development-only studio remains available with ?studio, and the multi-session browser fixture with ?workspace-fixture; both run without Pi. Fixture/studio modules are guarded by import.meta.env.DEV and are absent from the production bundle.

Focused verification commands remain separate:

vp test
vp exec tsc --noEmit
vp build
pnpm run build

Source boundaries:

  • extension/ is the Pi-only lifecycle adapter and hub client.
  • hub/ owns the foreground service, session registry, browser API, project terminal PTY/tmux lifecycle, static assets, and producer protocol endpoint.
  • shared/ owns Pi/browser-independent domain and protocol types.
  • web/production/WorkspaceApp.tsx owns the persistent browser shell, project-tree sidebar, hub placement controls, page-local navigation/drafts, and selected-session stage.
  • web/dev/, web/studies/, and web/studio/ are development-only fixtures and studies.

Build and packaging

pnpm run build type-checks the complete source tree, builds current browser assets into dist/, and compiles the Node hub runtime into runtime/. For Git-based Pi installs, prepare builds the production browser and hub runtime using production dependencies; prepack runs the complete checked build before packaging. Use pnpm pack --pack-destination /tmp to inspect a disposable tarball; it should contain:

  • the pilcrow-hub launcher and compiled Node runtime;
  • production dist/ browser assets;
  • the hub client, framed transport, shared protocol/domain source, and Pi extension;
  • font licenses, documentation, and the optional systemd unit.