HiveJournal in VR — handoff / map of the immersive layer
Start here to continue the WebXR work. Over one session (2026-07-12) HiveJournal grew a small family of browser-based VR surfaces. This doc maps what exists, the shared pattern they all follow, the one caveat that applies to all of them, and where to go next. Front door for users: /dashboard/vr.
Everything here is WebXR — runs in the Meta Quest 3 / Apple Vision Pro browser (tap "Enter VR") and previews fully on desktop (drag to look). No native app, no store review.
The surfaces
| Surface | Route | Scene component | What it is |
|---|---|---|---|
| The Cockpit | /dashboard/cockpit | components/cockpit/CockpitScene.tsx | Write under an open sky on a Bluetooth keyboard; your journal hangs above as an openable constellation (recent entries as stars — now with photo thumbnails); saving launches a new star. Sky themes, ambient hum, rotating writing spark, notebook-focused constellation, console readout. |
| Entry VR | /dashboard/entries/[id]/vr | components/entry-vr/EntryVrScene.tsx | "Step inside this entry" — sit inside the note's background art (or its first attached photo) with the text on a book-paged reading panel; attached photos framed above it (pager + tap-to-enlarge); reads itself aloud if a voice render exists. |
| Notebook VR | /dashboard/notebooks/vr | components/notebook-vr/NotebookVrScene.tsx | Your notebooks as a library in the round — sit at the centre of a ring of your journals (cover, colour, entry count); open one and its cover floats large ahead while its entries fan out overhead as a constellation of stars, each opening that entry. "← all notebooks" returns to the ring. Collection-scoped sibling of Entry VR. |
| Immersive Stream | /dashboard/stream/immersive | components/stream-vr/StreamXRScene.tsx | Your notes drift past on a river; catch one to open + hear it. (Pre-session; the shared VrImage boundary was extracted from here.) |
| Your Dome | /dashboard/dome | components/dome/DomeScene.tsx | Your room, rendered (real dome.glb). (Pre-session.) |
| Graphene Story Player | /vr → /seasons/[id]/vr | components/story-vr/StoryVrScene.tsx | Stand inside a published story's art with narration + hidden passages. (Pre-session.) |
| Wall Breaker | /dashboard/circles/vr | components/circles-vr/WallBreakerScene.tsx | Break a wall to nudge a circle member. (Pre-session.) |
| VR hub | /dashboard/vr | (plain 2D) | Front door listing every room; headset-detected badge. |
Discoverable via the VR hub, the pinned Apps strip in the logo feature menu (🥽 HiveJournal in VR), and the ⌘K command palette.
The shared pattern (follow it for any new room)
Every WebXR scene here obeys the same rules — copy an existing one:
- Scene is a
dynamic(ssr:false)import. NOTHING from@react-three/*may be imported at a page's module scope — it pulls the fiber/reconciler chain into the server bundle and crashesnext build's prerender ("ReactCurrentBatchConfig"). The page fetches data + stays XR-free; the scene ownscreateXRStore/<XR>/<XROrigin>+ the Enter-VR button. - Comfort rule (load-bearing): the user never moves. Fix everything around a seated origin — zero locomotion, zero nausea. (Walk-mode rooms are the deliberate exception and add teleport/thumbstick.)
- Page owns state; scene is a pure render. Keyboard capture, journal saves, audio, fetches live in the page/hook; the scene renders props.
- fiber v9 is required (Next 15 = React 19). See IMMERSIVE_3D_HANDOFF.md.
- Shared image component:
components/vr-common/VrImage.tsx—TextureBoundary(broken-image guard) +VrImage(framed photo / multi-image pager / optional tap-to-select). Reuse it for any photo-in-3D; the Stream + Entry VR + Cockpit all do.
Text is troika (@react-three/drei <Text>, needs a font file — the PremiumUltra .ttfs in /public/fonts). Images are drei <Image> behind TextureBoundary. Photo-sphere backdrops use a manual THREE.TextureLoader with setCrossOrigin('anonymous') (see PhotoSphere) so a CORS/expired-URL failure degrades instead of throwing.
⚠️ The one caveat that applies to everything
It was all built blind — no headset available during the build. Every in-scene position, scale, and distance (cockpit star placement, the entry sphere + reading panel, the framed-photo sizes) is a first guess. They read fine on desktop; they want one tuning pass on an actual Quest 3 / Vision Pro. Nothing here has been verified in-headset. When you put it on:
- Cockpit: star radius/height, panel arc distance, sky-screen size, keyboard-in-VR actually delivering
keydown(the experimental bet). - Entry VR: sphere tint/scale, reading-panel distance + font size, framed-photo placement, focus-overlay size.
- Everything: comfortable focal distance + text legibility at headset DPI.
Where to go next (ideas, unbuilt)
- On-device tuning pass — the highest-value next step; turns "built blind" into "feels right." (Now includes Notebook VR: ring radius/row spacing, book size + cover legibility, opened-notebook entry-star radius.)
- A consistent 🥽 affordance on more surfaces — the 2D stream, search results (notebooks now have one — "View in VR" on
/dashboard/notebooks). - Hand-tracking / controller polish — pinch-to-select, a laser pointer, grab-to-move panels.
- Spatial audio — position the ambient hum / narration in 3D.
- Shared presence — see a JQ-Bridge connection's shared entries as stars in your sky (the Stream already visits neighbors; the Cockpit could too).
- Migrate the Stream's
ImageCardontoVrImage— it still has bespoke card rendering; the boundary is shared but the framed-image body isn't yet.
Session provenance (2026-07-12): Cockpit (#1203) + two enhancement passes (#1205 constellation/themes, #1206 ambience/spark/notebook) + Entry VR (#1207) + attached-photos-in-VR & shared VrImage (#1209) + photo tap-to-enlarge & cockpit constellation photos & this handoff doc (#1210) + the VR hub (#1208) + the Apps-strip/palette discoverability (#1204) + Notebook VR — notebooks as a library in the round.