diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 617ea79..de3131a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -27,7 +27,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: openpi-e2e + name: pidex-e2e path: | /tmp/pidex-*.png test-results/ diff --git a/README.md b/README.md index bfdf72c..03f4832 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **A desktop workbench for the Pi coding agent.** **Workspace-first, not chat-first.** -[Quick Start](#quick-start) · [Architecture](docs/openpi-architecture.md) · [Design tokens](#design-tokens) · [Docs](docs/openpi-project-brief.md) +[Quick Start](#quick-start) · [Architecture](docs/pidex-architecture.md) · [Design tokens](#design-tokens) · [Docs](docs/pidex-project-brief.md) [![CI](https://github.com/echohello-dev/pidex/actions/workflows/ci.yml/badge.svg)](https://github.com/echohello-dev/pidex/actions/workflows/ci.yml) [![Electron 41](https://img.shields.io/badge/electron-41-blue)](https://www.electronjs.org/) @@ -37,7 +37,7 @@ For a production build: `mise run build` then `mise run start`. ┌────────────────────────────────────────┐ │ Renderer (React 19) │ ← dashboard, session tabs, timeline └───────────────────┬────────────────────┘ - │ typed IPC (window.openpi) + │ typed IPC (window.pidex) ┌───────────────────▼────────────────────┐ │ Main process (Electron) │ ← workspace registry, event normalization └───────────────────┬────────────────────┘ @@ -47,13 +47,13 @@ For a production build: `mise run build` then `mise run start`. └────────────────────────────────────────┘ ``` -Pi runs out-of-process over RPC so the UI survives a runtime crash. Pretext handles text measurement so virtualized lists stay smooth in long sessions. Full detail in [docs/openpi-architecture.md](docs/openpi-architecture.md). +Pi runs out-of-process over RPC so the UI survives a runtime crash. Pretext handles text measurement so virtualized lists stay smooth in long sessions. Full detail in [docs/pidex-architecture.md](docs/pidex-architecture.md). ## Built on | Layer | Choice | |---|---| -| Shell | Electron 41, context-isolated, typed `window.openpi` preload bridge | +| Shell | Electron 41, context-isolated, typed `window.pidex` preload bridge | | UI | React 19.2, Vite 8 with HMR | | Text | [`@chenglou/pretext`](https://github.com/chenglou/pretext), DOM-free | | Language | TypeScript 6 | @@ -96,9 +96,9 @@ uv run --with fonttools python scripts/build-screenshot.py # docs/assets/scree | Doc | What it covers | |---|---| -| [docs/openpi-project-brief.md](docs/openpi-project-brief.md) | Thesis, pain points, positioning, MVP slice | -| [docs/openpi-architecture.md](docs/openpi-architecture.md) | Process boundaries, IPC schema, SDK vs RPC | -| [docs/openpi-pretext-deep-dive.md](docs/openpi-pretext-deep-dive.md) | Why DOM-free text measurement | +| [docs/pidex-project-brief.md](docs/pidex-project-brief.md) | Thesis, pain points, positioning, MVP slice | +| [docs/pidex-architecture.md](docs/pidex-architecture.md) | Process boundaries, IPC schema, SDK vs RPC | +| [docs/pidex-pretext-deep-dive.md](docs/pidex-pretext-deep-dive.md) | Why DOM-free text measurement | ## License diff --git a/docs/adr/0002-pierre-diffs-renderer.md b/docs/adr/0002-pierre-diffs-renderer.md index 8357e8b..7c00a18 100644 --- a/docs/adr/0002-pierre-diffs-renderer.md +++ b/docs/adr/0002-pierre-diffs-renderer.md @@ -3,7 +3,7 @@ - **Status**: Accepted (2026-07-19) - **Context**: The workbench surfaces code changes from agent runs (file-level diffs from `bash`/`edit`/`write` tools). The change surface needs a renderer that handles large files, performs syntax highlighting consistently with code blocks, and supports future merge-conflict UI. The repo's research note (`2026-06-26 - Spanner Layer - Search, Syntax, Markdown, Diff.md`) ranked candidates: `@pierre/diffs`, `react-diff-viewer`, `react-virtualized-diff`, raw `jsdiff`. Pierre was the recommended primary — same engine used by Claude Code, Codex, Cursor, and OpenChamber — and pairs naturally with `shiki` for syntax highlighting. The earlier "Pidex - Pi Ecosystem Technology Stack Research" note (`2026-05-31`) flagged that `openchamber` already uses `@pierre/diffs 1.1.0-beta.13`. -- **Decision**: Adopt `@pierre/diffs` as the primary diff engine. Wrap its React `` component in `` to provide a small API (`oldFile`, `newFile`, `filename`) and a unified-patch generator. Use `theme: 'pierre-dark'` to match the OpenPi palette. +- **Decision**: Adopt `@pierre/diffs` as the primary diff engine. Wrap its React `` component in `` to provide a small API (`oldFile`, `newFile`, `filename`) and a unified-patch generator. Use `theme: 'pierre-dark'` to match the pidex palette. - **Consequences**: - Diff renderer scales to 10k+ lines via per-line virtualisation in Shadow DOM. diff --git a/docs/adr/0003-pretext-measurement.md b/docs/adr/0003-pretext-measurement.md index 248ae8a..538dd29 100644 --- a/docs/adr/0003-pretext-measurement.md +++ b/docs/adr/0003-pretext-measurement.md @@ -1,7 +1,7 @@ # ADR 0003 — Use @chenglou/pretext for text measurement only - **Status**: Accepted (2026-07-19) -- **Context**: The workbench renders long session timelines (10k+ messages in long sessions). The renderer needs a way to know row heights for the status-bar measurement counter and, eventually, for virtualised row sizing before mounting DOM. `@chenglou/pretext` (Cheng Lou) provides Canvas-2D-based text measurement that is ~300× faster than DOM-based measurement for re-layout. The original OpenPi architecture (`docs/openpi-architecture.md`) called for Pretext specifically as a "layout dispatch" primitive. +- **Context**: The workbench renders long session timelines (10k+ messages in long sessions). The renderer needs a way to know row heights for the status-bar measurement counter and, eventually, for virtualised row sizing before mounting DOM. `@chenglou/pretext` (Cheng Lou) provides Canvas-2D-based text measurement that is ~300× faster than DOM-based measurement for re-layout. The original pidex architecture (`docs/pidex-architecture.md`) called for Pretext specifically as a "layout dispatch" primitive. - **Decision**: Use `@chenglou/pretext` strictly for text measurement — row counting for the status bar today, and (future) row heights for virtualised lists. Do not use it for glyph rendering; rely on `markdown-it` + browser text layout for that. diff --git a/docs/adr/0005-oxlint.md b/docs/adr/0005-oxlint.md index c32f67e..046efc7 100644 --- a/docs/adr/0005-oxlint.md +++ b/docs/adr/0005-oxlint.md @@ -1,7 +1,7 @@ # ADR 0005 — Use oxlint as the lint tool - **Status**: Accepted (2026-07-19) -- **Context**: The repo had no lint gate. ESLint is the default but slow to install and configure, and many of its plugins don't work cleanly with React 19 + Vitest. The OpenPi project has a single-language (TypeScript) codebase and needs correctness guarantees, not style debates. +- **Context**: The repo had no lint gate. ESLint is the default but slow to install and configure, and many of its plugins don't work cleanly with React 19 + Vitest. The pidex project has a single-language (TypeScript) codebase and needs correctness guarantees, not style debates. - **Decision**: Adopt `oxlint` for CI lint. Configure it via `.oxlintrc.json` with the `correctness` category as error and the rest allowed. Style formatting is left to manual conventions. diff --git a/docs/adr/0006-pi-runtime-rpc.md b/docs/adr/0006-pi-runtime-rpc.md index df75709..893d2ac 100644 --- a/docs/adr/0006-pi-runtime-rpc.md +++ b/docs/adr/0006-pi-runtime-rpc.md @@ -1,9 +1,9 @@ # ADR 0006 — Pi runtime integration via JSONL RPC - **Status**: Accepted (2026-07-19) -- **Context**: OpenPi is an Electron workbench for the Pi coding agent. Pi supports four runtime modes (`interactive`, `print/JSON`, `RPC`, `SDK`). The renderer needs streaming events and prompt submission. Embedding the SDK in-process bloats the renderer with Node-only dependencies; interactive mode is TUI-only. RPC mode is the documented headless protocol — JSON lines over stdin/stdout, `prompt`/`get_state`/`get_messages`/`get_commands` commands, `agent_start`/`message_update`/`tool_execution_*` events. +- **Context**: pidex is an Electron workbench for the Pi coding agent. Pi supports four runtime modes (`interactive`, `print/JSON`, `RPC`, `SDK`). The renderer needs streaming events and prompt submission. Embedding the SDK in-process bloats the renderer with Node-only dependencies; interactive mode is TUI-only. RPC mode is the documented headless protocol — JSON lines over stdin/stdout, `prompt`/`get_state`/`get_messages`/`get_commands` commands, `agent_start`/`message_update`/`tool_execution_*` events. -- **Decision**: Spawn `pi --mode rpc` from the Electron main process per active session tab. Line-buffer stdout on `\n` only (no `readline` — the protocol forbids U+2028/U+2029 splitting). Forward events to the renderer over `webContents.send`. Re-emit Pi errors as `openpi:session:exit`. Validate `sessionFile` is under `~/.pi/agent/sessions` before resume. +- **Decision**: Spawn `pi --mode rpc` from the Electron main process per active session tab. Line-buffer stdout on `\n` only (no `readline` — the protocol forbids U+2028/U+2029 splitting). Forward events to the renderer over `webContents.send`. Re-emit Pi errors as `pidex:session:exit`. Validate `sessionFile` is under `~/.pi/agent/sessions` before resume. - **Consequences**: - Strong process boundary; renderer never sees Node APIs beyond the typed bridge. diff --git a/docs/adr/0007-native-tab-bar.md b/docs/adr/0007-native-tab-bar.md index 4c3e4e7..6987297 100644 --- a/docs/adr/0007-native-tab-bar.md +++ b/docs/adr/0007-native-tab-bar.md @@ -1,7 +1,7 @@ # ADR 0007 — Native tab bar with hidden-inset title bar - **Status**: Accepted (2026-07-19) -- **Context**: The workbench has a workspace/session model with multiple open sessions. macOS users expect Chrome-style native tabs (`NSWindow tabbingMode`) OR a custom drag region with native traffic-light controls. The original OpenPi brief calls for "few clicks, fast switching, high awareness" and a "workspace-first" mental model — both benefit from a tab strip at the top of the window. +- **Context**: The workbench has a workspace/session model with multiple open sessions. macOS users expect Chrome-style native tabs (`NSWindow tabbingMode`) OR a custom drag region with native traffic-light controls. The original pidex brief calls for "few clicks, fast switching, high awareness" and a "workspace-first" mental model — both benefit from a tab strip at the top of the window. - **Decision**: Use `titleBarStyle: 'hiddenInset'` on the `BrowserWindow`. Reserve `84px` of left padding on the renderer tab strip to clear the macOS traffic lights at the inset position. Mark the tab strip with `-webkit-app-region: drag` so the user can drag the window by the strip; mark every interactive child with `no-drag` so buttons remain clickable. Render the tab strip in-app with custom styles. diff --git a/docs/adr/0008-pi-dev-design-system.md b/docs/adr/0008-pi-dev-design-system.md index 6d03131..315c43d 100644 --- a/docs/adr/0008-pi-dev-design-system.md +++ b/docs/adr/0008-pi-dev-design-system.md @@ -3,10 +3,10 @@ - **Status**: Accepted (2026-07-19) - **Context**: The workbench is a desktop wrapper around the Pi coding agent. The Pi product (pi.dev) has its own distinctive visual language: deep ink canvas `#0d1116`, moonstone text, tidal-blue accent, terracotta rust, Departure Mono labels, Commit Mono code, bracketed buttons `[ ACTION ]`, corner-bracketed figure frames, lowercase `›` and `$` shell prompts. Building a separate design system would feel disjointed. -- **Decision**: Mirror the pi.dev design system in OpenPi. Bundle `Commit Mono` (OFL) and `Departure Mono` (OFL) locally; fall back to system serifs/mono for the licensed Plantin MT Pro. Tokens live in `src/renderer/design-system/tokens.css` and components in `components.css`. +- **Decision**: Mirror the pi.dev design system in pidex. Bundle `Commit Mono` (OFL) and `Departure Mono` (OFL) locally; fall back to system serifs/mono for the licensed Plantin MT Pro. Tokens live in `src/renderer/design-system/tokens.css` and components in `components.css`. - **Consequences**: - - Screenshots from OpenPi feel like native Pi product surfaces. + - Screenshots from pidex feel like native Pi product surfaces. - Designers familiar with Pi can transfer work without re-learning a design system. - Token names match Pi's published names (`--bg-deep`, `--accent`, `--panel`) — easy to grep and update. - Local font assets ship ~850KB; acceptable for desktop. diff --git a/docs/adr/0009-calver-releases.md b/docs/adr/0009-calver-releases.md index d11af5c..932fc4b 100644 --- a/docs/adr/0009-calver-releases.md +++ b/docs/adr/0009-calver-releases.md @@ -9,5 +9,5 @@ - Predictable, sortable, machine-friendly tags. - No "is this a breaking change" ceremony — every push to main tags a release. - Dropped `release-please` (semver, PR-based) for a small workflow that just calls `gh release create --generate-notes`. - - Inconsistent with Pi CLI semver — keep documentation clear that OpenPi tags are CalVer. + - Inconsistent with Pi CLI semver — keep documentation clear that pidex tags are CalVer. - If we ever publish a public API, we revisit and add semver alongside. \ No newline at end of file diff --git a/docs/adr/README.md b/docs/adr/README.md index b6afd1f..fe6e7fa 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -1,6 +1,6 @@ # Architecture Decision Records -ADRs document significant architectural choices made in OpenPi. +ADRs document significant architectural choices made in pidex. Each ADR follows this shape: diff --git a/docs/openpi-architecture.md b/docs/pidex-architecture.md similarity index 85% rename from docs/openpi-architecture.md rename to docs/pidex-architecture.md index 9c7d877..5f8ccfd 100644 --- a/docs/openpi-architecture.md +++ b/docs/pidex-architecture.md @@ -1,4 +1,4 @@ -# OpenPi Architecture Proposal +# pidex Architecture Proposal This document captures the first-pass architecture for an Electron desktop app that wraps the Pi coding agent runtime and stays responsive under long sessions, diffs, and workspace switching. @@ -55,7 +55,7 @@ Owns the narrow, typed bridge between renderer and main. Suggested responsibilities: -- Expose a minimal `window.openpi` API +- Expose a minimal `window.pidex` API - Keep IPC channels explicit and typed - Prevent renderer code from touching Node APIs directly @@ -101,28 +101,28 @@ The IPC layer should be event-driven, typed, and intentionally small. | Channel | Direction | Purpose | |---|---|---| -| `openpi:workspace/open` | renderer -> main | Open or focus a workspace | -| `openpi:workspace/refresh` | renderer -> main | Re-read repo and session state | -| `openpi:session/start` | renderer -> main | Start a new Pi session | -| `openpi:session/resume` | renderer -> main | Resume an existing session | -| `openpi:session/fork` | renderer -> main | Fork a session branch | -| `openpi:agent/send` | renderer -> main | Send a prompt or command to Pi | -| `openpi:diff/open` | renderer -> main | Open a file diff or patch view | -| `openpi:file/open` | renderer -> main | Open a file in the workspace | -| `openpi:git/checkout` | renderer -> main | Switch branches or worktrees | +| `pidex:workspace/open` | renderer -> main | Open or focus a workspace | +| `pidex:workspace/refresh` | renderer -> main | Re-read repo and session state | +| `pidex:session/start` | renderer -> main | Start a new Pi session | +| `pidex:session/resume` | renderer -> main | Resume an existing session | +| `pidex:session/fork` | renderer -> main | Fork a session branch | +| `pidex:agent/send` | renderer -> main | Send a prompt or command to Pi | +| `pidex:diff/open` | renderer -> main | Open a file diff or patch view | +| `pidex:file/open` | renderer -> main | Open a file in the workspace | +| `pidex:git/checkout` | renderer -> main | Switch branches or worktrees | ### Event channels | Channel | Direction | Purpose | |---|---|---| -| `openpi:workspace/state` | main -> renderer | Current workspace snapshot | -| `openpi:session/state` | main -> renderer | Session lifecycle and metadata | -| `openpi:agent/event` | main -> renderer | Normalized runtime events | -| `openpi:agent/token` | main -> renderer | Streamed text deltas | -| `openpi:agent/tool` | main -> renderer | Tool call start / progress / result | -| `openpi:git/state` | main -> renderer | Repo, branch, dirty, commit state | -| `openpi:fs/change` | main -> renderer | File tree or watch updates | -| `openpi:ui/notify` | main -> renderer | Toasts, errors, approvals, prompts | +| `pidex:workspace/state` | main -> renderer | Current workspace snapshot | +| `pidex:session/state` | main -> renderer | Session lifecycle and metadata | +| `pidex:agent/event` | main -> renderer | Normalized runtime events | +| `pidex:agent/token` | main -> renderer | Streamed text deltas | +| `pidex:agent/tool` | main -> renderer | Tool call start / progress / result | +| `pidex:git/state` | main -> renderer | Repo, branch, dirty, commit state | +| `pidex:fs/change` | main -> renderer | File tree or watch updates | +| `pidex:ui/notify` | main -> renderer | Toasts, errors, approvals, prompts | ### Core payload shapes @@ -187,7 +187,7 @@ type AgentEvent = - Use **Pi SDK** for the first implementation if the priority is rich integration and fastest product iteration. - Use **Pi RPC** if the priority is runtime isolation, restartability, and keeping the Electron main process lightweight. -### Practical choice for OpenPi +### Practical choice for pidex Start with **SDK** if: diff --git a/docs/openpi-pretext-deep-dive.md b/docs/pidex-pretext-deep-dive.md similarity index 95% rename from docs/openpi-pretext-deep-dive.md rename to docs/pidex-pretext-deep-dive.md index 64dfd33..01b496d 100644 --- a/docs/openpi-pretext-deep-dive.md +++ b/docs/pidex-pretext-deep-dive.md @@ -1,4 +1,4 @@ -# Pretext Deep-Dive for OpenPi +# Pretext Deep-Dive for pidex ## What Pretext Actually Is @@ -16,7 +16,7 @@ Package name is `@chenglou/pretext`. The npm registry has it under this scoped n ##Core API -Pretext serves two use cases. Both matter for OpenPi. +Pretext serves two use cases. Both matter for pidex. ### Use case 1: Measure a paragraph's height without the DOM @@ -167,11 +167,11 @@ Designed to be narrow: `white-space: normal` only, caller-owned extraWidth for p - On every render frame - Inside a React render function -## Caveats That Matter for OpenPi +## Caveats That Matter for pidex 1. **Font loading**: Fonts must be loaded before `prepare()`. Not a problem in Electron since you ship fonts locally. -2. **`system-ui` is unsafe on macOS** for layout() accuracy. Use a named font. OpenPi should bundle Inter and JetBrains Mono. +2. **`system-ui` is unsafe on macOS** for layout() accuracy. Use a named font. pidex should bundle Inter and JetBrains Mono. 3. **Not a bidi layout engine**. Segment widths are canvas widths for line breaking only. Not precise enough for custom Arabic or mixed-direction text reconstruction. Adequate for most coding-tool UI. @@ -181,7 +181,7 @@ Designed to be narrow: `white-space: normal` only, caller-owned extraWidth for p 6. **Version note**: The API changed significantly between early 2026 previews and the current npm release. The current API uses: `prepare(text, font)` returning opaque handle, `layout(prepared, maxWidth, lineHeight)` with `lineHeight` as a layout-time input (not in prepare). -## Where Pretext Fits in OpenPi +## Where Pretext Fits in pidex ### High-impact areas @@ -248,19 +248,19 @@ Key point: **this lives in a view-model layer, not in React render**. Computed o | DOM `getBoundingClientRect` | ~0ms (skip prepare) | ~1ms+ | Yes | No | Ground truth (but slow) | | Heuristic/guesswork | ~0ms | ~0ms | No | N/A | Unreliable (causes jank) | -For OpenPi's workload (thousands of messages, dozens of workspaces, frequent updates), the one-time prepare cost pays off immediately. The alternative — repeated DOM reads on scroll, resize, or data update — compounds into the exact kind of lag that OpenPi is trying to eliminate. +For pidex's workload (thousands of messages, dozens of workspaces, frequent updates), the one-time prepare cost pays off immediately. The alternative — repeated DOM reads on scroll, resize, or data update — compounds into the exact kind of lag that pidex is trying to eliminate. ## What Pretext Enables That CSS Can't Pretext uniquely enables **shrinkwrap text measurement**: finding the minimum container width that fits a paragraph without changing line count. CSS has `fit-content` which gives the widest line's width, but no equivalent for "find the narrowest width that still produces exactly N lines". Pretext's `walkLineRanges()` + binary search gives you this in pure arithmetic. -In OpenPi, this means: +In pidex, this means: - Workspace status cards that size to content without wasted space - Tool output cards that fit text exactly, even with variable-length content - Balanced text layout for session summaries - Multiline "shrinkwrap" that has been missing from the web -## Recommended Implementation Order for OpenPi +## Recommended Implementation Order for pidex 1. Session timeline cards (most text, most updates) 2. Workspace summary list diff --git a/docs/openpi-project-brief.md b/docs/pidex-project-brief.md similarity index 83% rename from docs/openpi-project-brief.md rename to docs/pidex-project-brief.md index e52f333..62e58ab 100644 --- a/docs/openpi-project-brief.md +++ b/docs/pidex-project-brief.md @@ -1,17 +1,17 @@ -# OpenPi Project Brief +# pidex Project Brief -## What is OpenPi +## What is pidex -OpenPi is an Electron desktop app that wraps the [Pi coding agent runtime](https://pi.dev/) and gives developers a daily workbench. It is not a commercial product or a clone of OpenCode. It is a personal project — learn by building, improve day-to-day DX, and share a stronger local-first desktop harness pattern for coding agents. +pidex is an Electron desktop app that wraps the [Pi coding agent runtime](https://pi.dev/) and gives developers a daily workbench. It is not a commercial product or a clone of OpenCode. It is a personal project — learn by building, improve day-to-day DX, and share a stronger local-first desktop harness pattern for coding agents. -OpenPi is one half of a two-product split: +pidex is one half of a two-product split: | Product | Role | |---|---| -| **OpenPi** | Daily-use coding workbench centred on the Pi runtime | +| **pidex** | Daily-use coding workbench centred on the Pi runtime | | **Weldable** | Installer, launcher, updater, and provider/gateway config utility | -Weldable handles install, BYOK, provider bootstrap, SSO, and enterprise gateway wiring. OpenPi relies on Weldable for setup and focuses entirely on the daily coding workflow. +Weldable handles install, BYOK, provider bootstrap, SSO, and enterprise gateway wiring. pidex relies on Weldable for setup and focuses entirely on the daily coding workflow. ## The Mental Model @@ -62,7 +62,7 @@ These are the concrete workflow problems driving the idea, all observed while us 5. **Local and extensible** Pi's minimal runtime philosophy stays intact. Richer UI and orchestration layered on top without bloating the engine. -## What OpenPi is NOT +## What pidex is NOT - **Not a multi-runtime orchestrator.** It is Pi-first, with adapters for Claude Code or Codex possible later as optional extensions. - **Not a bundler or installer.** That role belongs to Weldable. @@ -74,7 +74,7 @@ These are the concrete workflow problems driving the idea, all observed while us - **Main process** owns lifecycle, Pi runtime, workspace registry, persistence, and IPC routing - **Renderer** owns presentation only — React UI, virtualized lists, and text measurement via Pretext -- **Preload** exposes a typed `window.openpi` API as a narrow bridge between processes +- **Preload** exposes a typed `window.pidex` API as a narrow bridge between processes - All agent events are normalized in main before they reach the renderer ### Pi Runtime Integration @@ -88,9 +88,9 @@ Initial recommendation: start with **SDK** for the simplest prototype; evaluate ### Pretext for Rendering Performance -[Pretext](https://github.com/chenglou/pretext) is a ~15KB TypeScript library by Cheng Lou (ex-Meta React Core, now Midjourney) that measures multiline text dimensions without touching the DOM. It is 600x faster than browser-based text measurement. See the [Pretext deep-dive](./openpi-pretext-deep-dive.md) for details. +[Pretext](https://github.com/chenglou/pretext) is a ~15KB TypeScript library by Cheng Lou (ex-Meta React Core, now Midjourney) that measures multiline text dimensions without touching the DOM. It is 600x faster than browser-based text measurement. See the [Pretext deep-dive](./pidex-pretext-deep-dive.md) for details. -In OpenPi, Pretext is: +In pidex, Pretext is: - Applied to text-heavy virtualised lists (session timelines, workspace summaries, diff previews) - Used to precompute row heights, line counts, and text layouts off the DOM hot path @@ -98,7 +98,7 @@ In OpenPi, Pretext is: ### Harnss as UX Reference -[Harnss](https://github.com/OpenSource03/harnss) is an Electron wrapper for Claude Code and Codex that renders agent output as interactive cards. It is the strongest design reference for how OpenPi should present agent activity — rich tool cards, multi-session awareness,24ith explicit repo and task context surfacing. +[Harnss](https://github.com/OpenSource03/harnss) is an Electron wrapper for Claude Code and Codex that renders agent output as interactive cards. It is the strongest design reference for how pidex should present agent activity — rich tool cards, multi-session awareness,24ith explicit repo and task context surfacing. ### Tooling @@ -113,16 +113,16 @@ See the [README](../README.md) for setup instructions. Tools overlapping with the space: -| Tool | Strength | How OpenPi relates | +| Tool | Strength | How pidex relates | |---|---|---| | OpenCode | Breadth, adoption, IDE integration | Source of the pain points; baseline to surpass in UX quality | -| Harnss | Multi-agent desktop harness | Strong UI reference; OpenPi aims for stronger workspace visibility and context recovery | +| Harnss | Multi-agent desktop harness | Strong UI reference; pidex aims for stronger workspace visibility and context recovery | | CodePilot | Provider switching, general desktop UX | Reference for provider management | -| CC Switch | Config management | Weldable’s domain, not OpenPi’s | +| CC Switch | Config management | Weldable’s domain, not pidex’s | | 1Code | Worktree isolation, kanban-style thinking | Reference for operational workbench | | Superset | multi-agent orchestration, worktrees | Reference for heavy parallel worktree workflows | -OpenPi does NOT need to compete across every axis. The distinctiveness is: +pidex does NOT need to compete across every axis. The distinctiveness is: 1. OpenCode's breadth without its friction points 2. Superset-level worktree awareness without swarm-first complexity diff --git a/package.json b/package.json index cc443a8..a71d6df 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "openpi", + "name": "pidex", "version": "0.0.1", "private": true, "repository": "https://github.com/echohello-dev/pidex", diff --git a/src/main/index.ts b/src/main/index.ts index 3dd8d93..ab22f1f 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -40,7 +40,7 @@ function createWindow() { height: 840, minWidth: 900, minHeight: 600, - title: 'OpenPi', + title: 'pidex', backgroundColor: '#0d1116', titleBarStyle: 'hiddenInset', trafficLightPosition: { x: 14, y: 14 }, @@ -63,9 +63,9 @@ function createWindow() { }); } -ipcMain.handle('openpi:workspaces', () => listWorkspaces(readCustomWorkspaces())); +ipcMain.handle('pidex:workspaces', () => listWorkspaces(readCustomWorkspaces())); -ipcMain.handle('openpi:workspace:add', async () => { +ipcMain.handle('pidex:workspace:add', async () => { if (!mainWindow) return { error: 'no window' }; const result = await dialog.showOpenDialog(mainWindow, { title: 'Add workspace', @@ -77,13 +77,13 @@ ipcMain.handle('openpi:workspace:add', async () => { return { cwd }; }); -ipcMain.handle('openpi:sessions', (_e, dirName: string) => { +ipcMain.handle('pidex:sessions', (_e, dirName: string) => { if (typeof dirName !== 'string') return []; return listSessions(dirName); }); ipcMain.handle( - 'openpi:session/open', + 'pidex:session/open', async (_e, req: { cwd: string; sessionFile?: string }) => { const cwd = typeof req?.cwd === 'string' ? req.cwd : ''; const sessionFile = typeof req?.sessionFile === 'string' ? req.sessionFile : undefined; @@ -96,8 +96,8 @@ ipcMain.handle( const id = `s${++sessionSeq}`; const session = new RpcSession(id, cwd, sessionFile); - session.onEvent = (sessionId, event) => sendToRenderer('openpi:session:event', { sessionId, event }); - session.onExit = sessionId => sendToRenderer('openpi:session:exit', { sessionId }); + session.onEvent = (sessionId, event) => sendToRenderer('pidex:session:event', { sessionId, event }); + session.onExit = sessionId => sendToRenderer('pidex:session:exit', { sessionId }); sessions.set(id, session); const [state, messages] = await Promise.all([session.getState(), session.getMessages()]); @@ -110,7 +110,7 @@ ipcMain.handle( ); ipcMain.handle( - 'openpi:session/send', + 'pidex:session/send', async (_e, req: { sessionId: string; text: string; streaming: boolean }) => { const session = sessions.get(req?.sessionId ?? ''); if (!session) return { error: 'no such session' }; @@ -119,11 +119,11 @@ ipcMain.handle( }, ); -ipcMain.handle('openpi:session/abort', (_e, sessionId: string) => { +ipcMain.handle('pidex:session/abort', (_e, sessionId: string) => { sessions.get(sessionId)?.abort(); }); -ipcMain.handle('openpi:session/commands', async (_e, sessionId: string) => { +ipcMain.handle('pidex:session/commands', async (_e, sessionId: string) => { const session = sessions.get(sessionId); if (!session) return { commands: [] }; const response = await session.getCommands(); @@ -131,7 +131,7 @@ ipcMain.handle('openpi:session/commands', async (_e, sessionId: string) => { return { commands: response.success ? data?.commands ?? [] : [] }; }); -ipcMain.handle('openpi:session/close', (_e, sessionId: string) => { +ipcMain.handle('pidex:session/close', (_e, sessionId: string) => { const session = sessions.get(sessionId); if (session) { sessions.delete(sessionId); diff --git a/src/preload/index.ts b/src/preload/index.ts index 501a37f..012ceff 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -5,31 +5,31 @@ type SessionEventPayload = { event: Record & { type: string }; }; -contextBridge.exposeInMainWorld('openpi', { +contextBridge.exposeInMainWorld('pidex', { platform: process.platform, versions: { electron: process.versions.electron, node: process.versions.node, }, - workspaces: () => ipcRenderer.invoke('openpi:workspaces'), - addWorkspace: () => ipcRenderer.invoke('openpi:workspace:add'), - sessions: (dirName: string) => ipcRenderer.invoke('openpi:sessions', dirName), + workspaces: () => ipcRenderer.invoke('pidex:workspaces'), + addWorkspace: () => ipcRenderer.invoke('pidex:workspace:add'), + sessions: (dirName: string) => ipcRenderer.invoke('pidex:sessions', dirName), openSession: (req: { cwd: string; sessionFile?: string }) => - ipcRenderer.invoke('openpi:session/open', req), + ipcRenderer.invoke('pidex:session/open', req), sendPrompt: (req: { sessionId: string; text: string; streaming: boolean }) => - ipcRenderer.invoke('openpi:session/send', req), - abortSession: (sessionId: string) => ipcRenderer.invoke('openpi:session/abort', sessionId), + ipcRenderer.invoke('pidex:session/send', req), + abortSession: (sessionId: string) => ipcRenderer.invoke('pidex:session/abort', sessionId), sessionCommands: (sessionId: string) => - ipcRenderer.invoke('openpi:session/commands', sessionId), - closeSession: (sessionId: string) => ipcRenderer.invoke('openpi:session/close', sessionId), + ipcRenderer.invoke('pidex:session/commands', sessionId), + closeSession: (sessionId: string) => ipcRenderer.invoke('pidex:session/close', sessionId), onSessionEvent: (cb: (payload: SessionEventPayload) => void) => { const listener = (_event: unknown, payload: SessionEventPayload) => cb(payload); - ipcRenderer.on('openpi:session:event', listener); - return () => ipcRenderer.removeListener('openpi:session:event', listener); + ipcRenderer.on('pidex:session:event', listener); + return () => ipcRenderer.removeListener('pidex:session:event', listener); }, onSessionExit: (cb: (payload: { sessionId: string }) => void) => { const listener = (_event: unknown, payload: { sessionId: string }) => cb(payload); - ipcRenderer.on('openpi:session:exit', listener); - return () => ipcRenderer.removeListener('openpi:session:exit', listener); + ipcRenderer.on('pidex:session:exit', listener); + return () => ipcRenderer.removeListener('pidex:session:exit', listener); }, }); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 6392eae..d4468be 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -101,10 +101,10 @@ function App() { const activeTab = tabs.find(t => t.runtimeId === activeId) ?? null; useEffect(() => { - window.openpi.workspaces().then(async list => { + window.pidex.workspaces().then(async list => { setWorkspaces(list); const entries = await Promise.all( - list.map(ws => window.openpi.sessions(ws.dirName).then(s => [ws.dirName, s] as const)), + list.map(ws => window.pidex.sessions(ws.dirName).then(s => [ws.dirName, s] as const)), ); const map: Record = {}; for (const [dirName, s] of entries) map[dirName] = s; @@ -121,7 +121,7 @@ function App() { }, []); useEffect(() => { - const offEvent = window.openpi.onSessionEvent(({ sessionId, event }) => { + const offEvent = window.pidex.onSessionEvent(({ sessionId, event }) => { setTabs(prev => prev.map(tab => { if (tab.runtimeId !== sessionId) return tab; @@ -130,7 +130,7 @@ function App() { }), ); }); - const offExit = window.openpi.onSessionExit(({ sessionId }) => { + const offExit = window.pidex.onSessionExit(({ sessionId }) => { setTabs(prev => prev.map(tab => tab.runtimeId === sessionId ? { ...tab, exited: true, running: false } : tab, @@ -166,16 +166,16 @@ function App() { if (!next) return; let sessions = wsSessions[next]; if (!sessions) { - sessions = await window.openpi.sessions(next); + sessions = await window.pidex.sessions(next); setWsSessions(prev => ({ ...prev, [next]: sessions })); } if (sessions[0]) openSession(ws, sessions[0]); }; const addWorkspace = async () => { - const result = await window.openpi.addWorkspace(); + const result = await window.pidex.addWorkspace(); if (result.error || !result.cwd) return; - const list = await window.openpi.workspaces(); + const list = await window.pidex.workspaces(); setWorkspaces(list); const added = list.find(w => w.cwd === result.cwd); if (added) { @@ -194,7 +194,7 @@ function App() { } setOpening(true); try { - const result = await window.openpi.openSession({ + const result = await window.pidex.openSession({ cwd: ws.cwd, sessionFile: info?.file, }); @@ -203,7 +203,7 @@ function App() { } const model = result.state?.model?.id ?? result.state?.model?.name ?? info?.model ?? 'default'; - const { commands } = await window.openpi.sessionCommands(result.sessionId); + const { commands } = await window.pidex.sessionCommands(result.sessionId); const tab: SessionTab = { runtimeId: result.sessionId, cwd: ws.cwd, @@ -224,7 +224,7 @@ function App() { openSessionRef.current = openSession; const closeTab = (runtimeId: string) => { - window.openpi.closeSession(runtimeId); + window.pidex.closeSession(runtimeId); setTabs(prev => { const next = prev.filter(t => t.runtimeId !== runtimeId); if (activeId === runtimeId) setActiveId(next[next.length - 1]?.runtimeId ?? null); @@ -241,7 +241,7 @@ function App() { ); setDraft(''); setSlashIndex(0); - await window.openpi.sendPrompt({ + await window.pidex.sendPrompt({ sessionId: activeTab.runtimeId, text, streaming: activeTab.running, @@ -323,8 +323,8 @@ function App() { ))} - electron {window.openpi?.versions.electron ?? '—'} · node{' '} - {window.openpi?.versions.node ?? '—'} + electron {window.pidex?.versions.electron ?? '—'} · node{' '} + {window.pidex?.versions.node ?? '—'} @@ -416,7 +416,7 @@ function App() { {activeTab.exited ? 'exited' : activeTab.running ? 'running' : 'idle'}
- window.openpi.abortSession(activeTab.runtimeId)}> + window.pidex.abortSession(activeTab.runtimeId)}> Abort
@@ -555,7 +555,7 @@ function App() { {tabs.length} tabs - $ openpi — {measuredRows} rows measured · 0 dom reads + $ pidex — {measuredRows} rows measured · 0 dom reads diff --git a/src/renderer/api.ts b/src/renderer/api.ts index b9b1181..89e1e50 100644 --- a/src/renderer/api.ts +++ b/src/renderer/api.ts @@ -60,7 +60,7 @@ type OpenPiApi = { declare global { interface Window { - openpi: OpenPiApi; + pidex: OpenPiApi; } } diff --git a/src/renderer/index.html b/src/renderer/index.html index 61f6025..38ba627 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -3,7 +3,7 @@ - OpenPi + pidex