You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RFC / exploration, not a rewrite request. Persona should remain the source of truth for Codex/MCP integration, voice policy, behavior, model catalog, settings schema, and product UX. The proposal is to make the VRM renderer replaceable first, then evaluate an Electron-free shell only if the renderer experiment succeeds.
I built a PocketJS-native Persona compatibility POC and refreshed it against Persona bb7ef2455b23aee685f68c9e83a185347d257964 (0.1.0-beta.0). The current implementation and reproducible acceptance/benchmark tooling are in pocket-stack/pocket-character#3.
Visual receipt
persona-vs-pocket-matched-motion-30fps.mp4
Correction: the original hero clip advanced an eight-frame deterministic receipt at 2 FPS, which unintentionally made Pocket look choppy; the first live-capture replacement then showed different speaking gestures on each side. The current receipt fixes both problems.
Both panels are separate live-window captures encoded at 30 FPS and composited at the same 430×680 logical window size. They use the same avatar, the exact same Persona HEAD packaged motion (speaking-chunk2.vrma), and the same pulsed speaking/viseme event timeline. Persona's clip was warmed once to remove its one-time asynchronous asset-load delay; the remaining two-frame capture offset was removed during composition. Motion QA measured 30.0 effective FPS on Persona and 27.8 on Pocket, with no detected freeze longer than 100 ms and a 67 ms worst-case changing-frame interval on Pocket. This is a matched-action motion and retargeting receipt, not a resource benchmark or a pixel-parity claim.
The initial POC had visibly incorrect wrists/hands and ankles/feet because it treated raw local VRMA rotations as normalized humanoid rotations. That result and its old CPU headline were invalid. The current capture is post-retarget; the before/fixed receipts are below.
Suggested adoption path
Phase 1: replace only the VRM renderer
Keep Electron and all current Persona behavior. Add an experimental renderer interface so Persona can send the existing avatar/voice state to either:
the current Three.js/@pixiv/three-vrm renderer, or
a PocketJS native renderer host running as a child process/native window.
This is the low-risk path: the existing renderer remains a fallback, and no Settings, Codex, voice, catalog, tray, import, or protocol behavior has to move. It also avoids placing Persona-specific code in PocketJS core. The POC keeps that boundary today: Persona compatibility lives in pocket-character; PocketJS only needs generic window/lifecycle and normalized-humanoid animation capabilities (PocketJS #204, PocketJS #207).
For downstream reuse, this should not become a long-lived Persona fork. A practical distribution shape would be:
a small JS/TypeScript adapter and versioned renderer-state protocol that Persona can depend on;
platform-specific native binaries delivered as optional npm dependencies (or downloaded release artifacts, if preferred);
the existing Electron renderer as an always-available fallback.
The exact package name/API should be agreed with Persona maintainers before publishing it.
Important: this phase does not reduce package size. A measured “current Persona + Pocket renderer sidecar” payload was 3.4% larger installed and 3.0% larger compressed. It buys renderer CPU/memory headroom and an integration boundary, not an Electron size reduction.
Phase 2: optional Electron-free shell
If Phase 1 is useful, a later native shell could host both the avatar and self-drawn settings surfaces. Pocket Note demonstrates that the required UI/runtime primitives do not intrinsically require a DOM or Chromium: layout, rich text, editing/caret, drag selection, clipboard, IME/CJK glyph baking, undo/redo, menus, themes, scrolling, live resize/reflow, and persistence all run as a PocketJS DrawList over wgpu/winit.
pocket-note-native-ui-proof.mp4
The recording above is the real native Pocket Note window. This exact current-target headless native proof also shows editable text, a caret, scrolling, and the one-process host:
And these deterministic DrawList captures cover preview, edit/input, and a Portal menu:
Pocket Note is architectural proof of the UI substrate, not Persona Settings parity. A Persona port would still need reusable sliders/selects/checkboxes/tabs/lists, native file-dialog adapters, accessibility/focus QA, an embedded VRM preview, and every existing Settings/catalog workflow.
Current performance evidence
Measured on an Apple M3 Max / 120 Hz display. Persona and Pocket ran sequentially with the same packaged VRM/VRMA catalog, a 30-second settle, and nine 5-second process-tree samples. 100% CPU means one logical core.
The strongest architecture comparison is the near-refresh-matched lane:
Metric
Persona
Pocket POC
Pocket delta
Observed FPS
120.013
111.077
7.4% lower
Median CPU
21.196%
9.002%
57.5% lower
CPU / delivered frame
0.1766
0.0810
54.1% lower
Summed median RSS
1,197,728 KiB
121,920 KiB
89.8% lower
Process count
4
1
75% fewer
This is deliberately labeled near-refresh-matched, not 120-vs-120: Pocket missed the target by 7.4%. Pocket also rendered a 1.78× larger backing surface, but it does not yet implement Persona's complete MToon/HDR/material behavior. Summed process RSS can double-count shared pages, so it should be read as a process-tree pressure estimate rather than unique physical bytes.
30 FPS / dynamic-frame-rate nuance
Lane
Observed FPS, Persona / Pocket
Median CPU, Persona / Pocket
Absolute CPU delta
CPU / delivered frame
Production idle
120.021 / 28.358
17.603% / 4.600%
73.9% lower
10.6% worse
Sustained speaking
120.011 / 29.062
17.999% / 3.000%
83.3% lower
31.2% lower
Persona would also use less power if it capped or dynamically gated frames. Therefore the 30 FPS absolute-CPU result is a useful product policy, but it is not the fair renderer headline. The idle per-frame regression is the metric where Pocket currently gets worse. The near-refresh lane and speaking per-frame result are the stronger evidence for the native renderer itself.
The full post-retarget benchmark conclusion and validation commands are in this POC comment.
Package-size measurement
All numbers below are measured macOS arm64 payloads containing the same 27.35 MiB VRM + full VRMA catalog. “Same ZIP” uses the same ditto settings for every row.
Persona's Frameworks directory is 254.20 MiB, of which the Electron Framework is 252.50 MiB. Removing the identical shared media leaves 267.89 MiB of Persona non-media payload versus 10.08 MiB for the Pocket VRM runtime, or 19.61 MiB for renderer + the complete unshared Pocket Note host/JS/pak.
The 47.01 MiB row is a conservative, measured renderer + settings-type UI substrate envelope, not a release-size forecast. It intentionally combines two independent native binaries without link-time sharing, but it still omits full Persona Settings/catalog migration, tray, deep links, dialogs, the audio helper, icons, signing/notarization, and other product logic. The Pocket rows are verified payload stagings, not signed .app installers. Persona's official electron-builder ZIP is 123.68 MiB; the table uses a 128.84 MiB re-ZIP only to keep compression identical across rows.
Cross-platform boundary
The renderer/UI architecture is based on portable wgpu/winit primitives, so it provides a path to cross-platform native settings. It is not already a completed cross-platform Persona shell:
these package and runtime measurements are macOS arm64 only;
Pocket Note's current registered desktop target is macos-widget;
Windows/Linux window, clipboard, dialogs, tray, packaging, and accessibility adapters still need implementation and validation.
Current POC gaps / no-regression bar
The POC is not a drop-in replacement yet. In particular, it lacks complete MToon/HDR/material parity and does not own Persona's Settings/import/CRUD, audio, tray, deep-link, or Codex/MCP product flows. Its Persona-compatible bridge is a renderer/protocol test, not a proposal to move Persona's agent logic out of this repository.
For Phase 1, I would propose this acceptance bar:
existing Persona behavior remains authoritative and unchanged;
the native renderer is opt-in and the current renderer is the fallback;
model/animation import and voice-state transitions use a documented, versioned boundary;
benchmarks compare the same upstream revision, assets, activity, frame policy, and visible window state.
Questions for maintainers
Would you be open to an experimental renderer abstraction behind a feature flag, while retaining the current Electron renderer?
For a first integration, would a native child-process/window boundary be acceptable, or is in-window embedding required?
Which visual/material cases should be treated as blockers before testing this in Persona itself?
If Phase 1 works, is a later native settings shell worth exploring, or would you prefer Electron to remain the permanent product shell?
If there is interest, the next concrete step can be a small renderer protocol/package proposal in pocket-character, with Persona-specific changes kept minimal and reviewed here before any implementation PR.
I built a PocketJS-native Persona compatibility POC and refreshed it against Persona
bb7ef2455b23aee685f68c9e83a185347d257964(0.1.0-beta.0). The current implementation and reproducible acceptance/benchmark tooling are in pocket-stack/pocket-character#3.Visual receipt
persona-vs-pocket-matched-motion-30fps.mp4
Correction: the original hero clip advanced an eight-frame deterministic receipt at 2 FPS, which unintentionally made Pocket look choppy; the first live-capture replacement then showed different speaking gestures on each side. The current receipt fixes both problems.
Both panels are separate live-window captures encoded at 30 FPS and composited at the same 430×680 logical window size. They use the same avatar, the exact same Persona HEAD packaged motion (
speaking-chunk2.vrma), and the same pulsed speaking/viseme event timeline. Persona's clip was warmed once to remove its one-time asynchronous asset-load delay; the remaining two-frame capture offset was removed during composition. Motion QA measured 30.0 effective FPS on Persona and 27.8 on Pocket, with no detected freeze longer than 100 ms and a 67 ms worst-case changing-frame interval on Pocket. This is a matched-action motion and retargeting receipt, not a resource benchmark or a pixel-parity claim.The initial POC had visibly incorrect wrists/hands and ankles/feet because it treated raw local VRMA rotations as normalized humanoid rotations. That result and its old CPU headline were invalid. The current capture is post-retarget; the before/fixed receipts are below.
Suggested adoption path
Phase 1: replace only the VRM renderer
Keep Electron and all current Persona behavior. Add an experimental renderer interface so Persona can send the existing avatar/voice state to either:
@pixiv/three-vrmrenderer, orThis is the low-risk path: the existing renderer remains a fallback, and no Settings, Codex, voice, catalog, tray, import, or protocol behavior has to move. It also avoids placing Persona-specific code in PocketJS core. The POC keeps that boundary today: Persona compatibility lives in
pocket-character; PocketJS only needs generic window/lifecycle and normalized-humanoid animation capabilities (PocketJS #204, PocketJS #207).For downstream reuse, this should not become a long-lived Persona fork. A practical distribution shape would be:
The exact package name/API should be agreed with Persona maintainers before publishing it.
Important: this phase does not reduce package size. A measured “current Persona + Pocket renderer sidecar” payload was 3.4% larger installed and 3.0% larger compressed. It buys renderer CPU/memory headroom and an integration boundary, not an Electron size reduction.
Phase 2: optional Electron-free shell
If Phase 1 is useful, a later native shell could host both the avatar and self-drawn settings surfaces. Pocket Note demonstrates that the required UI/runtime primitives do not intrinsically require a DOM or Chromium: layout, rich text, editing/caret, drag selection, clipboard, IME/CJK glyph baking, undo/redo, menus, themes, scrolling, live resize/reflow, and persistence all run as a PocketJS DrawList over
wgpu/winit.pocket-note-native-ui-proof.mp4
The recording above is the real native Pocket Note window. This exact current-target headless native proof also shows editable text, a caret, scrolling, and the one-process host:
And these deterministic DrawList captures cover preview, edit/input, and a Portal menu:
Pocket Note is architectural proof of the UI substrate, not Persona Settings parity. A Persona port would still need reusable sliders/selects/checkboxes/tabs/lists, native file-dialog adapters, accessibility/focus QA, an embedded VRM preview, and every existing Settings/catalog workflow.
Current performance evidence
Measured on an Apple M3 Max / 120 Hz display. Persona and Pocket ran sequentially with the same packaged VRM/VRMA catalog, a 30-second settle, and nine 5-second process-tree samples.
100% CPUmeans one logical core.The strongest architecture comparison is the near-refresh-matched lane:
This is deliberately labeled near-refresh-matched, not 120-vs-120: Pocket missed the target by 7.4%. Pocket also rendered a 1.78× larger backing surface, but it does not yet implement Persona's complete MToon/HDR/material behavior. Summed process RSS can double-count shared pages, so it should be read as a process-tree pressure estimate rather than unique physical bytes.
30 FPS / dynamic-frame-rate nuance
Persona would also use less power if it capped or dynamically gated frames. Therefore the 30 FPS absolute-CPU result is a useful product policy, but it is not the fair renderer headline. The idle per-frame regression is the metric where Pocket currently gets worse. The near-refresh lane and speaking per-frame result are the stronger evidence for the native renderer itself.
The full post-retarget benchmark conclusion and validation commands are in this POC comment.
Package-size measurement
All numbers below are measured macOS arm64 payloads containing the same 27.35 MiB VRM + full VRMA catalog. “Same ZIP” uses the same
dittosettings for every row.bb7ef245signed.appPersona's
Frameworksdirectory is 254.20 MiB, of which the Electron Framework is 252.50 MiB. Removing the identical shared media leaves 267.89 MiB of Persona non-media payload versus 10.08 MiB for the Pocket VRM runtime, or 19.61 MiB for renderer + the complete unshared Pocket Note host/JS/pak.The 47.01 MiB row is a conservative, measured renderer + settings-type UI substrate envelope, not a release-size forecast. It intentionally combines two independent native binaries without link-time sharing, but it still omits full Persona Settings/catalog migration, tray, deep links, dialogs, the audio helper, icons, signing/notarization, and other product logic. The Pocket rows are verified payload stagings, not signed
.appinstallers. Persona's official electron-builder ZIP is 123.68 MiB; the table uses a 128.84 MiB re-ZIP only to keep compression identical across rows.Cross-platform boundary
The renderer/UI architecture is based on portable
wgpu/winitprimitives, so it provides a path to cross-platform native settings. It is not already a completed cross-platform Persona shell:macos-widget;Current POC gaps / no-regression bar
The POC is not a drop-in replacement yet. In particular, it lacks complete MToon/HDR/material parity and does not own Persona's Settings/import/CRUD, audio, tray, deep-link, or Codex/MCP product flows. Its Persona-compatible bridge is a renderer/protocol test, not a proposal to move Persona's agent logic out of this repository.
For Phase 1, I would propose this acceptance bar:
Questions for maintainers
If there is interest, the next concrete step can be a small renderer protocol/package proposal in
pocket-character, with Persona-specific changes kept minimal and reviewed here before any implementation PR.