refactor(web): migrate the Web workbench UI to React - #384
Conversation
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
|
@tt-a1i Could you please review the technical direction and implementation of this React Web MVP? Thanks. |
|
来了
涂少坤
***@***.***
…---Original---
From: ***@***.***>
Date: Fri, Sep 4, 2026 17:53 PM
To: ***@***.***>;
Cc: ***@***.******@***.***>;
Subject: Re: [openpi-dev/openpi] refactor(web): migrate the Web workbench UI to React (PR #384)
6iKUN6 left a comment (openpi-dev/openpi#384)
@tt-a1i 麻烦帮忙 review 一下这个 React Web MVP 的技术方向和实现,谢谢。
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
- disable npm peer resolution for Pi-provided host packages - guard the Git install contract and record the production smoke rationale
tt-a1i
left a comment
There was a problem hiding this comment.
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.
| if (startupFailure) throw startupFailure; | ||
| try { | ||
| const response = await fetch(endpoint); | ||
| const response = await fetch(endpoint, { |
There was a problem hiding this comment.
[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.
| } | ||
| }, | ||
| setQuery(query) { | ||
| set({ query: query.trim() }); |
There was a problem hiding this comment.
[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.
| return ( | ||
| <div className="markdown"> | ||
| <ReactMarkdown | ||
| remarkPlugins={[remarkGfm]} |
There was a problem hiding this comment.
[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.
|
|
||
| afterEach(() => { | ||
| sessionStorage.clear(); | ||
| localStorage.clear(); |
There was a problem hiding this comment.
[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.
| "undici": "8.9.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@astryxdesign/core": "0.5.2", |
There was a problem hiding this comment.
[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.
|
Reviewed at exact head 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 |
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.
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
1b340f2is 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
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
eventsource-parserreact-markdown,remark-gfm, andrehype-sanitizeAstryx owns generic interaction primitives only. OpenPI-specific transcript evidence, capability projections, and the animated logo remain local React components.
Development workflow
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 testSee
docs/development/OPENPI_WEB_DEVELOPMENT.mdfor lifecycle and HMR boundaries.Build and release
bun run build:webwrites deterministic production assets toweb/dist/. The built directory is committed as a package runtime asset, andWebHostserves only its fixed allowlist. npm and GitHub installs therefore start Web without Vite, a CDN, or remote fonts.CI rebuilds and rejects
web/distdrift, checks Node 22.19.0 and Node 24, runs browser E2E, smoke-tests the packed npm CLI, and smoke-tests a source-onlygit archivecheckout. Seedocs/design/OPENPI_WEB_REACT_MVP.mdfor the complete design and evidence record.Validation
bun run checkbun run test: 1,228 Node tests passed, one platform-specific skip; 54 Vitest tests passedbun run test:web:e2e: 3 Playwright tests passed using local Chrome/weband real-provider interaction smokenpm packand installed-package static asset smokegit diff --checkpi listreports only the current checkoutThe PR remains Draft while these items are resolved:
THIRD_PARTY_NOTICES.mdImpact
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.