@popmelt.com/core 0.18.0 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -158,6 +158,40 @@ export function Providers({ children }: { children: React.ReactNode }) {
158
158
 
159
159
  `navigate` is optional for single-page use. Pass your router's navigation function when Popmelt needs to restore annotations across routes.
160
160
 
161
+ ### Add application context to comments
162
+
163
+ Use `getCommentMeta` to snapshot application-owned context whenever a human
164
+ submits annotations or replies in a thread. Popmelt validates and detaches the
165
+ JSON, includes it as contextual evidence for the model, and persists the same
166
+ snapshot with the stored human turn.
167
+
168
+ ```tsx
169
+ <PopmeltProvider
170
+ getCommentMeta={({ kind }) => ({
171
+ 'my-app/video-ui/v1': {
172
+ kind,
173
+ generation: {
174
+ prompt: generation.prompt,
175
+ dataVersion: store.getState().version,
176
+ },
177
+ frame: {
178
+ assetId: video.id,
179
+ timeMs: Math.round(video.currentTime * 1000),
180
+ },
181
+ interactions: recorder.snapshot(),
182
+ },
183
+ })}
184
+ >
185
+ {children}
186
+ </PopmeltProvider>
187
+ ```
188
+
189
+ The hook may be synchronous or asynchronous. Return a plain JSON object; use a
190
+ namespaced, versioned key so multiple integrations can coexist. Comment
191
+ metadata is limited to 64 KiB per human turn. Keep large binaries and long
192
+ event logs in external storage or attachments and place their stable IDs,
193
+ hashes, and timestamps in `meta`.
194
+
161
195
  ### Next.js
162
196
 
163
197
  Next.js needs both the command wrapper and the config wrapper.
@@ -243,6 +277,71 @@ If the toolbar opens but cannot reach an AI, check that:
243
277
  - The framework plugin or config wrapper is installed
244
278
  - The development server was restarted after installation
245
279
 
280
+ ## React Three Fiber
281
+
282
+ Popmelt can target objects rendered by React Three Fiber while Core continues to
283
+ own the familiar hover, annotation, and thread presentation. Install the R3F
284
+ peers alongside Core:
285
+
286
+ ```bash
287
+ npm install @popmelt.com/core @react-three/fiber three
288
+ ```
289
+
290
+ Keep `PopmeltProvider` outside the canvas and mount `PopmeltThreeAdapter` inside
291
+ it. The adapter renders nothing; it registers the current scene, camera, and
292
+ renderer with the nearest provider.
293
+
294
+ ```tsx
295
+ import { Canvas } from '@react-three/fiber';
296
+ import { PopmeltProvider } from '@popmelt.com/core';
297
+ import { PopmeltThreeAdapter } from '@popmelt.com/core/react-three-fiber';
298
+
299
+ export function ProductPreview() {
300
+ return (
301
+ <PopmeltProvider>
302
+ <Canvas>
303
+ <PopmeltThreeAdapter />
304
+ <ProductScene />
305
+ </Canvas>
306
+ </PopmeltProvider>
307
+ );
308
+ }
309
+ ```
310
+
311
+ Named objects receive readable labels automatically. Add stable application
312
+ identity when object names or scene ordering may change:
313
+
314
+ ```tsx
315
+ <mesh
316
+ name="Bottle"
317
+ userData={{ popmeltId: 'hero-bottle', popmeltLabel: 'Hero bottle' }}
318
+ >
319
+ {/* geometry and material */}
320
+ </mesh>
321
+ ```
322
+
323
+ Use a distinct `id` for each adapter when a page owns multiple canvases. Pass
324
+ `root` to target only one scene subtree, `captureFrame={false}` to omit renderer
325
+ screenshots, or `select`, `key`, `label`, and `describe` to adapt an application's
326
+ scene semantics.
327
+
328
+ For integrations that need the registered adapter instance, use the hook inside
329
+ the canvas instead of the component:
330
+
331
+ ```tsx
332
+ import { usePopmeltThreeAdapter } from '@popmelt.com/core/react-three-fiber';
333
+
334
+ function ProductPopmeltAdapter() {
335
+ const adapter = usePopmeltThreeAdapter({ id: 'product-preview' });
336
+ // Optional application-specific coordination with adapter.
337
+ void adapter;
338
+ return null;
339
+ }
340
+ ```
341
+
342
+ The component is the recommended default; the hook exposes the same lifecycle
343
+ for advanced composition.
344
+
246
345
  ## Media annotation
247
346
 
248
347
  Manual image and video annotation are enabled by default in `PopmeltProvider`,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "popmelt-core",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "description": "Ambient project memory and shared understanding for Codex",
5
5
  "author": {
6
6
  "name": "Popmelt",
@@ -0,0 +1,36 @@
1
+ import { Camera, Object3D, Raycaster } from 'three';
2
+ import { P as PopmeltSubjectAdapter } from './subjects-CeqtFged.mjs';
3
+
4
+ type ThreeSubjectAdapterOptions = {
5
+ /** Unique adapter identity when a page owns more than one Three.js surface. */
6
+ id?: string;
7
+ canvas: HTMLCanvasElement;
8
+ camera: Camera;
9
+ scene: Object3D;
10
+ raycaster?: Raycaster;
11
+ /** Select a meaningful parent from the raw intersected render object. */
12
+ select?: (object: Object3D) => Object3D | null;
13
+ /** Override durable identity. Explicit application IDs are preferred. */
14
+ key?: (object: Object3D) => string | null | undefined;
15
+ label?: (object: Object3D) => string;
16
+ /** Add application-specific provenance or source hints to the model context. */
17
+ describe?: (object: Object3D) => Record<string, string | undefined>;
18
+ /** Capture the renderer's current frame for DOM-cloned screenshots. */
19
+ captureFrame?: () => string | null | Promise<string | null>;
20
+ };
21
+
22
+ type PopmeltThreeAdapterOptions = Omit<ThreeSubjectAdapterOptions, 'canvas' | 'camera' | 'scene' | 'captureFrame'> & {
23
+ /** Scene root Popmelt should target. Defaults to the complete R3F scene. */
24
+ root?: Object3D;
25
+ /** Override frame capture, or set false to omit renderer screenshots. */
26
+ captureFrame?: ThreeSubjectAdapterOptions['captureFrame'] | false;
27
+ };
28
+ /**
29
+ * Register an R3F-owned Three.js scene with the nearest PopmeltProvider.
30
+ * Must run inside Canvas so React Three Fiber's renderer state is available.
31
+ */
32
+ declare function usePopmeltThreeAdapter(options?: PopmeltThreeAdapterOptions): PopmeltSubjectAdapter;
33
+ /** Declarative, zero-render integration for use inside an R3F Canvas. */
34
+ declare function PopmeltThreeAdapter(props: PopmeltThreeAdapterOptions): null;
35
+
36
+ export { PopmeltThreeAdapter, type PopmeltThreeAdapterOptions, usePopmeltThreeAdapter };
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ "use client";import{b as g}from"./chunk-J3UIBBU2.mjs";import{useThree as M}from"@react-three/fiber";import{useEffect as C,useMemo as E}from"react";import{Box3 as x,Raycaster as D,Vector2 as T,Vector3 as P}from"three";function h(e){return[e.x,e.y,e.z].map(r=>Number(r.toFixed(4))).join(", ")}function y(e){let r=e.material;return r&&(Array.isArray(r)?r:[r]).map(n=>n.name?.trim()||n.type).filter(Boolean).join(", ")||void 0}function j(e,r,s){let c=e.domElement.width,n=e.domElement.height;if(c<=0||n<=0)return null;let l=e.getRenderTarget(),t=e.xr.enabled;try{return e.xr.enabled=!1,e.setRenderTarget(null),e.render(r,s),e.domElement.toDataURL("image/png")}catch{return null}finally{e.setRenderTarget(l),e.xr.enabled=t}}function A(e){let c=(e.parent?.children??[]).filter(t=>t.type===e.type&&t.name===e.name),n=Math.max(0,c.indexOf(e)),l=e.name.trim()||e.type;return`${encodeURIComponent(l)}[${n}]`}function f(e){let r=[],s=e;for(;s?.parent;)r.push(A(s)),s=s.parent;return r.reverse().join("/")}function v(e){let r=e.userData.popmeltId;return typeof r=="string"&&r.trim()?r.trim():f(e)}function S(e){let r=e.userData.popmeltLabel;return typeof r=="string"&&r.trim()?r.trim():e.name.trim()||e.type}function F(e){let r=e;for(;r.parent;){let s=r.name.trim(),c=s.match(/^(.*)_(?:alpha|normal|material)(?:_\d+)+$/i),n=r.parent;for(;n.parent&&!n.name.trim();)n=n.parent;let l=n.name.trim();if(!l||!s.startsWith(`${l}_`)&&!c)break;r=n}return r}function R(e,r,s){let c=new x().setFromObject(e,!0);if(c.isEmpty())return null;let n=s.getBoundingClientRect(),l=[];for(let p of[c.min.x,c.max.x])for(let d of[c.min.y,c.max.y])for(let b of[c.min.z,c.max.z])l.push(new P(p,d,b).project(r));let t=l.map(p=>n.left+(p.x+1)/2*n.width),a=l.map(p=>n.top+(1-p.y)/2*n.height),i=Math.max(n.left,Math.min(...t)),o=Math.min(n.right,Math.max(...t)),m=Math.max(n.top,Math.min(...a)),u=Math.min(n.bottom,Math.max(...a));return o<=i||u<=m?null:{left:i,top:m,width:o-i,height:u-m}}function O(e){let r=e.raycaster??new D,s=new T,c=t=>e.key?.(t)??v(t),n=(t,a)=>{let i=c(t);if(!i)return null;let o=R(t,e.camera,e.canvas);return o?{key:i,label:e.label?.(t)??S(t),sourceElement:e.canvas,bounds:o,context:f(t),data:{renderer:"Three.js",objectType:t.type,...t.name?{objectName:t.name}:{},scenePath:f(t),...a&&a!==t?{renderObjectName:a.name.trim()||a.type,renderObjectType:a.type,renderScenePath:f(a)}:{},position:h(t.position),rotation:h(t.rotation),scale:h(t.scale),visible:String(t.visible),...y(t)?{material:y(t)}:{},...Object.fromEntries(Object.entries(e.describe?.(t)??{}).filter(m=>typeof m[1]=="string"&&m[1].length>0))}}:null},l=t=>{let a=null;return e.scene.traverse(i=>{!a&&c(i)===t&&(a=i)}),a};return{id:e.id?.trim()||"three",hitTest({x:t,y:a}){let i=e.canvas.getBoundingClientRect();if(t<i.left||t>i.right||a<i.top||a>i.bottom)return null;s.set((t-i.left)/i.width*2-1,-((a-i.top)/i.height)*2+1),r.setFromCamera(s,e.camera);for(let o of r.intersectObject(e.scene,!0)){let m=e.select?e.select(o.object):F(o.object);if(!m||!m.visible)continue;let u=n(m,o.object);if(u)return u}return null},resolve(t){let a=l(t);return a?n(a):null},...e.captureFrame?{async captureSurfaces(){let t=await e.captureFrame?.();return t?[{element:e.canvas,dataUrl:t}]:[]}}:{}}}function w(e={}){let{registerSubjectAdapter:r}=g(),{gl:s,camera:c,scene:n}=M(),{root:l=n,captureFrame:t,describe:a,id:i,key:o,label:m,raycaster:u,select:p}=e,d=E(()=>O({id:i,canvas:s.domElement,camera:c,scene:l,raycaster:u,select:p,key:o,label:m,describe:b=>({framework:"@react-three/fiber",...a?.(b)}),...t===!1?{}:{captureFrame:t??(()=>j(s,n,c))}}),[c,t,a,s,i,o,m,u,l,n,p]);return C(()=>r(d),[d,r]),d}function U(e){return w(e),null}export{U as PopmeltThreeAdapter,w as usePopmeltThreeAdapter};
@@ -152,6 +152,8 @@ type PopmeltSubjectAdapter = {
152
152
  };
153
153
 
154
154
  type ThreeSubjectAdapterOptions = {
155
+ /** Unique adapter identity when a page owns more than one Three.js surface. */
156
+ id?: string;
155
157
  canvas: HTMLCanvasElement;
156
158
  camera: Camera;
157
159
  scene: Object3D;
@@ -1 +1 @@
1
- import{Box3 as y,Raycaster as x,Vector2 as O,Vector3 as j}from"three";function b(e){return[e.x,e.y,e.z].map(n=>Number(n.toFixed(4))).join(", ")}function h(e){let n=e.material;return n&&(Array.isArray(n)?n:[n]).map(r=>r.name?.trim()||r.type).filter(Boolean).join(", ")||void 0}function M(e,n,c){let s=e.domElement.width,r=e.domElement.height;if(s<=0||r<=0)return null;let l=e.getRenderTarget(),t=e.xr.enabled;try{return e.xr.enabled=!1,e.setRenderTarget(null),e.render(n,c),e.domElement.toDataURL("image/png")}catch{return null}finally{e.setRenderTarget(l),e.xr.enabled=t}}function D(e){let s=(e.parent?.children??[]).filter(t=>t.type===e.type&&t.name===e.name),r=Math.max(0,s.indexOf(e)),l=e.name.trim()||e.type;return`${encodeURIComponent(l)}[${r}]`}function f(e){let n=[],c=e;for(;c?.parent;)n.push(D(c)),c=c.parent;return n.reverse().join("/")}function v(e){let n=e.userData.popmeltId;return typeof n=="string"&&n.trim()?n.trim():f(e)}function T(e){let n=e.userData.popmeltLabel;return typeof n=="string"&&n.trim()?n.trim():e.name.trim()||e.type}function R(e){let n=e;for(;n.parent;){let c=n.name.trim(),s=c.match(/^(.*)_(?:alpha|normal|material)(?:_\d+)+$/i),r=n.parent;for(;r.parent&&!r.name.trim();)r=r.parent;let l=r.name.trim();if(!l||!c.startsWith(`${l}_`)&&!s)break;n=r}return n}function P(e,n,c){let s=new y().setFromObject(e,!0);if(s.isEmpty())return null;let r=c.getBoundingClientRect(),l=[];for(let p of[s.min.x,s.max.x])for(let d of[s.min.y,s.max.y])for(let g of[s.min.z,s.max.z])l.push(new j(p,d,g).project(n));let t=l.map(p=>r.left+(p.x+1)/2*r.width),a=l.map(p=>r.top+(1-p.y)/2*r.height),i=Math.max(r.left,Math.min(...t)),u=Math.min(r.right,Math.max(...t)),m=Math.max(r.top,Math.min(...a)),o=Math.min(r.bottom,Math.max(...a));return u<=i||o<=m?null:{left:i,top:m,width:u-i,height:o-m}}function C(e){let n=e.raycaster??new x,c=new O,s=t=>e.key?.(t)??v(t),r=(t,a)=>{let i=s(t);if(!i)return null;let u=P(t,e.camera,e.canvas);return u?{key:i,label:e.label?.(t)??T(t),sourceElement:e.canvas,bounds:u,context:f(t),data:{renderer:"Three.js",objectType:t.type,...t.name?{objectName:t.name}:{},scenePath:f(t),...a&&a!==t?{renderObjectName:a.name.trim()||a.type,renderObjectType:a.type,renderScenePath:f(a)}:{},position:b(t.position),rotation:b(t.rotation),scale:b(t.scale),visible:String(t.visible),...h(t)?{material:h(t)}:{},...Object.fromEntries(Object.entries(e.describe?.(t)??{}).filter(m=>typeof m[1]=="string"&&m[1].length>0))}}:null},l=t=>{let a=null;return e.scene.traverse(i=>{!a&&s(i)===t&&(a=i)}),a};return{id:"three",hitTest({x:t,y:a}){let i=e.canvas.getBoundingClientRect();if(t<i.left||t>i.right||a<i.top||a>i.bottom)return null;c.set((t-i.left)/i.width*2-1,-((a-i.top)/i.height)*2+1),n.setFromCamera(c,e.camera);for(let u of n.intersectObject(e.scene,!0)){let m=e.select?e.select(u.object):R(u.object);if(!m||!m.visible)continue;let o=r(m,u.object);if(o)return o}return null},resolve(t){let a=l(t);return a?r(a):null},...e.captureFrame?{async captureSurfaces(){let t=await e.captureFrame?.();return t?[{element:e.canvas,dataUrl:t}]:[]}}:{}}}export{M as captureThreeRendererFrame,C as createThreeSubjectAdapter,R as selectThreeNamedPrimitiveParent,f as threeObjectPath};
1
+ import{Box3 as y,Raycaster as x,Vector2 as O,Vector3 as j}from"three";function b(e){return[e.x,e.y,e.z].map(n=>Number(n.toFixed(4))).join(", ")}function d(e){let n=e.material;return n&&(Array.isArray(n)?n:[n]).map(r=>r.name?.trim()||r.type).filter(Boolean).join(", ")||void 0}function M(e,n,c){let s=e.domElement.width,r=e.domElement.height;if(s<=0||r<=0)return null;let l=e.getRenderTarget(),t=e.xr.enabled;try{return e.xr.enabled=!1,e.setRenderTarget(null),e.render(n,c),e.domElement.toDataURL("image/png")}catch{return null}finally{e.setRenderTarget(l),e.xr.enabled=t}}function D(e){let s=(e.parent?.children??[]).filter(t=>t.type===e.type&&t.name===e.name),r=Math.max(0,s.indexOf(e)),l=e.name.trim()||e.type;return`${encodeURIComponent(l)}[${r}]`}function f(e){let n=[],c=e;for(;c?.parent;)n.push(D(c)),c=c.parent;return n.reverse().join("/")}function v(e){let n=e.userData.popmeltId;return typeof n=="string"&&n.trim()?n.trim():f(e)}function T(e){let n=e.userData.popmeltLabel;return typeof n=="string"&&n.trim()?n.trim():e.name.trim()||e.type}function R(e){let n=e;for(;n.parent;){let c=n.name.trim(),s=c.match(/^(.*)_(?:alpha|normal|material)(?:_\d+)+$/i),r=n.parent;for(;r.parent&&!r.name.trim();)r=r.parent;let l=r.name.trim();if(!l||!c.startsWith(`${l}_`)&&!s)break;n=r}return n}function P(e,n,c){let s=new y().setFromObject(e,!0);if(s.isEmpty())return null;let r=c.getBoundingClientRect(),l=[];for(let p of[s.min.x,s.max.x])for(let h of[s.min.y,s.max.y])for(let g of[s.min.z,s.max.z])l.push(new j(p,h,g).project(n));let t=l.map(p=>r.left+(p.x+1)/2*r.width),a=l.map(p=>r.top+(1-p.y)/2*r.height),i=Math.max(r.left,Math.min(...t)),u=Math.min(r.right,Math.max(...t)),m=Math.max(r.top,Math.min(...a)),o=Math.min(r.bottom,Math.max(...a));return u<=i||o<=m?null:{left:i,top:m,width:u-i,height:o-m}}function C(e){let n=e.raycaster??new x,c=new O,s=t=>e.key?.(t)??v(t),r=(t,a)=>{let i=s(t);if(!i)return null;let u=P(t,e.camera,e.canvas);return u?{key:i,label:e.label?.(t)??T(t),sourceElement:e.canvas,bounds:u,context:f(t),data:{renderer:"Three.js",objectType:t.type,...t.name?{objectName:t.name}:{},scenePath:f(t),...a&&a!==t?{renderObjectName:a.name.trim()||a.type,renderObjectType:a.type,renderScenePath:f(a)}:{},position:b(t.position),rotation:b(t.rotation),scale:b(t.scale),visible:String(t.visible),...d(t)?{material:d(t)}:{},...Object.fromEntries(Object.entries(e.describe?.(t)??{}).filter(m=>typeof m[1]=="string"&&m[1].length>0))}}:null},l=t=>{let a=null;return e.scene.traverse(i=>{!a&&s(i)===t&&(a=i)}),a};return{id:e.id?.trim()||"three",hitTest({x:t,y:a}){let i=e.canvas.getBoundingClientRect();if(t<i.left||t>i.right||a<i.top||a>i.bottom)return null;c.set((t-i.left)/i.width*2-1,-((a-i.top)/i.height)*2+1),n.setFromCamera(c,e.camera);for(let u of n.intersectObject(e.scene,!0)){let m=e.select?e.select(u.object):R(u.object);if(!m||!m.visible)continue;let o=r(m,u.object);if(o)return o}return null},resolve(t){let a=l(t);return a?r(a):null},...e.captureFrame?{async captureSurfaces(){let t=await e.captureFrame?.();return t?[{element:e.canvas,dataUrl:t}]:[]}}:{}}}export{M as captureThreeRendererFrame,C as createThreeSubjectAdapter,R as selectThreeNamedPrimitiveParent,f as threeObjectPath};