Skip to content

refactor(web): migrate the Web workbench UI to React - #384

Open
6iKUN6 wants to merge 13 commits into
openpi-dev:mainfrom
6iKUN6:codex/web-react-mvp
Open

refactor(web): migrate the Web workbench UI to React#384
6iKUN6 wants to merge 13 commits into
openpi-dev:mainfrom
6iKUN6:codex/web-react-mvp

Conversation

@6iKUN6

@6iKUN6 6iKUN6 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

Related to #76.

The OpenPI Web UI is currently implemented as one imperative browser script. As workspace and Session management, streaming messages, tool evidence, capability activity, and responsive interactions grow, that structure lacks clear component boundaries, typed browser state, and focused tests.

This PR validates a React migration of the browser projection while preserving Pi/OpenPI runtime ownership and the existing Web protocol. PR #352 at head 1b340f2 is the frozen behavior and visual reference. This PR does not depend on #352 merging, but the maintainers should decide whether this migration follows or supersedes that implementation.

Value

  • Establish typed component, state, and protocol boundaries for the Web UI.
  • Make workspace, Session, composer, transcript, and activity behavior easier to test and extend.
  • Add UI HMR for day-to-day Web development.
  • Keep production Web delivery self-contained, without requiring users to install frontend tooling or load remote assets.
  • Provide a maintainable frontend base for the existing Web roadmap without introducing a second agent runtime.

Approach

Runtime boundary

Pi remains authoritative for Sessions, providers/models, tools, and execution lifecycle. PiWebRuntime, PiWebAdapter, TypeBox contracts, authenticated commands, and SSE cursor semantics remain the browser boundary. React consumes canonical snapshots/events and sends bounded commands; it does not infer terminal execution state from presentation.

The browser persists only process-scoped authentication and local navigation preferences in sessionStorage. It does not create a second Session, model, capability, or configuration source of truth.

Frontend stack

  • React 19 and TypeScript
  • Vite 8
  • Zustand vanilla store with React bindings
  • Tailwind CSS 4
  • Astryx Core with the neutral theme for Dialog, Menu, and Tooltip primitives
  • Lucide React
  • Fetch-based authenticated SSE with eventsource-parser
  • react-markdown, remark-gfm, and rehype-sanitize
  • i18next and react-i18next
  • Vitest, React Testing Library, Playwright, and axe-core

Astryx owns generic interaction primitives only. OpenPI-specific transcript evidence, capability projections, and the animated logo remain local React components.

Development workflow

bun install --frozen-lockfile
bun run dev:web
bun run dev:web -- /absolute/path/to/workspace

Vite provides HMR for web/ui/. Backend/runtime changes rebuild the Web-owned Pi runtime. The combined development command selects bounded fallback ports when defaults are occupied; explicitly requested occupied ports fail with an actionable diagnostic.

Validation entry points:

bun run check:web
bun run test:web:e2e
bun run check
bun run test

See docs/development/OPENPI_WEB_DEVELOPMENT.md for lifecycle and HMR boundaries.

Build and release

bun run build:web writes deterministic production assets to web/dist/. The built directory is committed as a package runtime asset, and WebHost serves only its fixed allowlist. npm and GitHub installs therefore start Web without Vite, a CDN, or remote fonts.

CI rebuilds and rejects web/dist drift, checks Node 22.19.0 and Node 24, runs browser E2E, smoke-tests the packed npm CLI, and smoke-tests a source-only git archive checkout. See docs/design/OPENPI_WEB_REACT_MVP.md for the complete design and evidence record.

Validation

  • bun run check
  • bun run test: 1,294 Node tests passed, one platform-specific skip; 57 Vitest tests passed
  • bun run test:web:e2e: 3 Playwright tests passed using local Chrome
  • Recorded axe checks found zero violations at desktop and 390 x 844 viewports
  • Vite HMR and production static delivery smoke
  • Basic Pi /web and real-provider interaction smoke
  • npm pack and installed-package static asset smoke
  • git diff --check
  • Runtime provenance: pi list reports only the current checkout
  • Removed the installed-CLI ready/signal-handler registration race exposed by a prior full test run
  • Added bundled frontend dependency notices to THIRD_PARTY_NOTICES.md

Optional follow-up work (not required to merge this PR):

Impact

User-visible behavior

The Web browser UI moves to React while retaining the #352 layout and interaction baseline, including the animated logo, workspace and Session navigation, transcript/tool evidence, activity projection, and responsive layout. Development mode gains HMR and bounded port avoidance.

Model-visible context and tools

None. This PR does not add or change model-facing tools, schemas, prompts, or child capability classifications.

Runtime and lifecycle

The canonical Pi/Web runtime and Session ownership boundaries remain unchanged. The development launcher and packaged static resource path are updated to serve the React build.

Persisted config/data

No canonical persisted configuration or Session data changes. Browser-local token and navigation preferences remain process/local UI state.

Compatibility and risk

Follow-up roadmap work remains tracked in #342, #343, #344, #345, #346, #347, #348, #349, #350, and #363. Pi-native slash command discovery and Web bundle/render performance should be proposed as separate follow-up issues after the migration direction is accepted.

Record the frozen PR openpi-dev#352 behavior baseline, Pi-native ownership boundaries, selected frontend stack, static delivery contract, and validation matrix for the local migration experiment.
Document default port fallback, strict explicit ports, fast startup diagnostics, and TUI error projection for the local React validation branch.
- replace the imperative browser UI with typed React and Zustand projections
- add Vite HMR, production asset builds, and bounded port conflict handling
- cover the Web store, packaged host, responsive browser flows, and artifact drift in CI
@github-actions github-actions Bot added documentation Improvements or additions to documentation area:github GitHub workflows, templates, ownership, or tests labels Sep 4, 2026
@6iKUN6

6iKUN6 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@tt-a1i Could you please review the technical direction and implementation of this React Web MVP? Thanks.

@tt-a1i

tt-a1i commented Sep 4, 2026 via email

Copy link
Copy Markdown
Collaborator

- disable npm peer resolution for Pi-provided host packages
- guard the Git install contract and record the production smoke rationale
@github-actions github-actions Bot added the area:setup OpenPI setup, configuration, or setup documentation label Sep 4, 2026

@tt-a1i tt-a1i left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The React direction is sound and keeps Pi as the runtime source of truth, but this draft is not ready to merge yet. I found two user-visible parity regressions, an unenforced development-startup bound, a supported-Node test failure, and a distribution-notice gap.

I reviewed exact head 13f3032decdcc600e7d46d848b2cc0097fd54d7e. Locally, bun run check and bun run test:web:e2e pass (3/3). On Node 26.3.0, the focused Web store suite fails 21/21 because the tests access an unavailable unqualified localStorage. The required Node 22 CI job is also currently red: the packed standalone CLI smoke attempts to fetch unavailable @earendil-works/pi-tui@0.85.0 and receives 404, so the recorded installed-package validation is not current.

Please also update the design record's source boundary to identify this PR and an exact implementation head when the evidence is frozen, rather than only a mutable local branch plus the base and PR #352.

Comment thread scripts/dev-web.mjs Outdated
if (startupFailure) throw startupFailure;
try {
const response = await fetch(endpoint);
const response = await fetch(endpoint, {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Enforce the advertised readiness deadline on each probe. The outer 15-second loop does not bound this awaited fetch: a peer can accept the TCP connection and never return headers, leaving dev:web stuck here forever and preventing startup.waitForFailure() from being observed. Please abort each request at the remaining deadline (and when startup fails), and add a hanging-peer regression test.

Comment thread web/ui/src/store/web-store.ts Outdated
}
},
setQuery(query) {
set({ query: query.trim() });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve the raw value of this controlled input. Trimming in the setter rewrites "foo " to "foo" immediately; typing the next character then produces "foobar" instead of "foo bar". Keep the raw query in state and trim only the value used for matching, with a test covering spaces in a search term.

Comment thread web/ui/src/components/Markdown.tsx Outdated
return (
<div className="markdown">
<ReactMarkdown
remarkPlugins={[remarkGfm]}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve the frozen baseline's soft-line-break behavior. PR #352 configured Marked with breaks: true, while GFM alone renders "first line\nsecond line" without a <br>; this component sits under .message-content, so it does not inherit the user-message white-space: pre-wrap rule and the browser collapses the newline. Add remark-breaks (or an equivalent rendering rule) and a parity test.

Comment thread tests/web/web-store.spec.ts Outdated

afterEach(() => {
sessionStorage.clear();
localStorage.clear();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep this suite compatible with the declared node >=22.19.0 range. Under Node 26.3.0, the native Web Storage global shadows jsdom and unqualified localStorage is undefined, so all 21 store tests fail in this cleanup. I reproduced the same failure at this head. Use window.localStorage (including the assertion below) or explicitly configure the test environment, then cover a supported newer Node in CI.

Comment thread package.json
"undici": "8.9.0"
},
"devDependencies": {
"@astryxdesign/core": "0.5.2",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Add notices for the code now shipped in the committed production bundle. web/dist/app.js embeds React/ReactDOM, Zustand, Lucide, i18next, eventsource-parser, React Markdown/remark/rehype, Astryx, and related runtime code, but THIRD_PARTY_NOTICES.md still lists only the Sessions extension. The relevant MIT/ISC terms and retained copyright notices need to accompany distribution before this can leave draft.

@tt-a1i

tt-a1i commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Reviewed at exact head 13f3032decdcc600e7d46d848b2cc0097fd54d7e and submitted REQUEST_CHANGES.

The React/Pi-native direction looks good, but the draft still needs fixes for the unbounded development readiness probe, controlled-search whitespace regression, Markdown soft-break parity, Node 26 Web-store test compatibility, and bundled frontend notices. The required Node 22 packed-CLI smoke is also currently failing with an unavailable @earendil-works/pi-tui@0.85.0 tarball.

Use an explicitly referenced timer for each readiness probe so Node 22 does not let the test process or launcher exit while the fetch is still pending. Clear the timer after every probe.
Advance the design evidence boundary to the readiness timer fix and record the focused Node 22 and Node 24 regression runs.
Replace the superseded load-sensitive test note with the successful Node 24 full-suite result observed after the readiness fix.
Ensure the CLI cannot advertise a ready Web Host before its SIGINT and SIGTERM cleanup handlers are installed.
Advance the implementation boundary and distinguish focused CLI evidence from unrelated full-suite timing failures.
@6iKUN6
6iKUN6 marked this pull request as ready for review September 4, 2026 13:42
@6iKUN6
6iKUN6 requested a review from a team as a code owner September 4, 2026 13:42
@6iKUN6
6iKUN6 requested a review from tt-a1i September 4, 2026 13:46
@6iKUN6

6iKUN6 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@tt-a1i The review feedback has been addressed, the merge conflict is resolved, and CI is green. Could you please re-review when convenient?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:github GitHub workflows, templates, ownership, or tests area:setup OpenPI setup, configuration, or setup documentation documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants