egaki 0.7.1 → 0.8.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cli/bpm-detect.d.ts +7 -0
  3. package/dist/cli/bpm-detect.d.ts.map +1 -0
  4. package/dist/cli/bpm-detect.js +123 -0
  5. package/dist/cli/cli.d.ts.map +1 -1
  6. package/dist/cli/cli.js +115 -0
  7. package/dist/cli/loudness-detect.d.ts +13 -0
  8. package/dist/cli/loudness-detect.d.ts.map +1 -0
  9. package/dist/cli/loudness-detect.js +96 -0
  10. package/dist/vite/code-block.d.ts.map +1 -1
  11. package/dist/vite/code-block.js +26 -5
  12. package/dist/vite/layout-editor.js +1 -1
  13. package/dist/vite/mdx-video.test.js +0 -79
  14. package/dist/vite/player-page.d.ts.map +1 -1
  15. package/dist/vite/player-page.js +6 -94
  16. package/dist/vite/store-hooks.d.ts +2 -0
  17. package/dist/vite/store-hooks.d.ts.map +1 -1
  18. package/dist/vite/store-hooks.js +8 -0
  19. package/dist/vite/store.d.ts +5 -0
  20. package/dist/vite/store.d.ts.map +1 -1
  21. package/dist/vite/store.js +5 -0
  22. package/dist/vite/tweakpane-hook.d.ts +6 -3
  23. package/dist/vite/tweakpane-hook.d.ts.map +1 -1
  24. package/dist/vite/tweakpane-hook.js +27 -16
  25. package/dist/vite/vite-plugin.d.ts +0 -6
  26. package/dist/vite/vite-plugin.d.ts.map +1 -1
  27. package/dist/vite/vite-plugin.js +1 -92
  28. package/package.json +9 -9
  29. package/skills/egaki/SKILL.md +26 -224
  30. package/src/cli/bpm-detect.ts +165 -0
  31. package/src/cli/cli.ts +149 -0
  32. package/src/cli/loudness-detect.ts +129 -0
  33. package/src/vite/code-block.tsx +28 -6
  34. package/src/vite/layout-editor.tsx +1 -1
  35. package/src/vite/mdx-video.test.tsx +0 -94
  36. package/src/vite/player-page.tsx +28 -117
  37. package/src/vite/store-hooks.ts +11 -0
  38. package/src/vite/store.ts +10 -0
  39. package/src/vite/tweakpane-hook.tsx +71 -24
  40. package/src/vite/vite-plugin.ts +1 -91
@@ -17,26 +17,13 @@ import { AbsoluteFill, Freeze, Sequence, Series, useCurrentFrame, useDelayRender
17
17
  import { renderInBrowser } from "./render-client.js";
18
18
  import { createSpiceflowFetch } from 'spiceflow/client';
19
19
  import { egakiStore } from "./store.js";
20
- import { useGenerationStatus, useGenerationErrors } from "./store-hooks.js";
20
+ import { useGenerationStatus, useGenerationErrors, useSidebarOpen } from "./store-hooks.js";
21
21
  import { egakiSDK } from "./sdk.js";
22
22
  import { LayoutEditor } from "./layout-editor.js";
23
23
  import { TweakpaneRoot, SIDEBAR_WIDTH } from "./tweakpane-hook.js";
24
24
  import { Fill, LayoutAnimationLayer, LayoutGhost, LayoutTransitionProvider, MotionTimingSync, ServerSlotsContext, } from "./mdx-video.js";
25
25
  import { SectionIndexContext } from "./media-duration-store.js";
26
26
  // ---------------------------------------------------------------------------
27
- // HMR scene-changed: store pending seek at module scope so it survives
28
- // component remounts caused by rsc:update (spiceflow payload swap).
29
- // The vite plugin sends egaki:scene-changed with { sectionIndex } when
30
- // the user edits a specific section of the entry MDX.
31
- // ---------------------------------------------------------------------------
32
- let pendingSceneSeek = null;
33
- /** Cancels the auto-pause listener from a previous scene-seek, so a new
34
- * HMR during playback doesn't leave stale listeners that pause the wrong scene. */
35
- let cancelPreviousAutoPlay = null;
36
- /** Monotonic counter to invalidate stale requestAnimationFrame callbacks
37
- * when rapid successive HMR edits arrive before the first rAF fires. */
38
- let sceneSeekVersion = 0;
39
- // ---------------------------------------------------------------------------
40
27
  // Generation progress client — fetches /api/generation-progress on page
41
28
  // load and on every rsc:update using the typed Spiceflow fetch client.
42
29
  // The route is an async generator that yields progress events as SSE;
@@ -107,9 +94,6 @@ function connectToProgress() {
107
94
  if (typeof window !== 'undefined')
108
95
  connectToProgress();
109
96
  if (import.meta.hot) {
110
- import.meta.hot.on('egaki:scene-changed', (data) => {
111
- pendingSceneSeek = data;
112
- });
113
97
  // Reconnect on rsc:update — new generations may have started from
114
98
  // MDX edits that trigger server-side rendering of <Server> blocks.
115
99
  import.meta.hot.on('rsc:update', () => {
@@ -291,79 +275,6 @@ export function PlayerPage({ sections, totalDuration, preamble, entryPath, hasUn
291
275
  playerContainerRef,
292
276
  });
293
277
  }, [Component, totalDuration, sections, fps, width, height, scale]);
294
- // Consume pending scene seek after the RSC refetch delivers new sections.
295
- // The module-level pendingSceneSeek is set by import.meta.hot.on before
296
- // React re-renders; this effect fires after the commit with fresh data.
297
- // Seeks to the changed section's start frame, plays it, and pauses at
298
- // the section's end so the user sees exactly the content they edited.
299
- useEffect(() => {
300
- if (pendingSceneSeek == null)
301
- return;
302
- // Ignore scene changes from a different entry MDX file.
303
- if (pendingSceneSeek.file !== entryPath) {
304
- pendingSceneSeek = null;
305
- return;
306
- }
307
- const idx = pendingSceneSeek.sectionIndex;
308
- pendingSceneSeek = null;
309
- // Wait one frame for the Remotion Player to initialize after remount.
310
- // Version token ensures stale rAF callbacks from rapid edits are ignored.
311
- const version = ++sceneSeekVersion;
312
- requestAnimationFrame(() => {
313
- if (version !== sceneSeekVersion)
314
- return;
315
- const player = playerRef.current;
316
- if (!player || idx < 0 || idx >= sections.length)
317
- return;
318
- // Compute section boundaries from current sections
319
- let startFrame = 0;
320
- for (let i = 0; i < idx; i++) {
321
- startFrame += sections[i].durationInFrames;
322
- }
323
- const endFrame = startFrame + sections[idx].durationInFrames - 1;
324
- player.seekTo(startFrame);
325
- // Cancel any auto-pause from a previous HMR scene-seek so stale
326
- // listeners don't pause playback in the wrong section.
327
- cancelPreviousAutoPlay?.();
328
- cancelPreviousAutoPlay = null;
329
- // Only auto-play if currently paused (after rsc:update remount
330
- // the player always starts paused at frame 0)
331
- if (!player.isPlaying()) {
332
- player.play();
333
- // Pause at section end so only the edited section plays.
334
- // If the user manually seeks (clicks timeline, arrow keys, etc.)
335
- // cancel the auto-pause so we don't randomly stop playback later.
336
- let cancelled = false;
337
- const cleanup = () => {
338
- if (cancelled)
339
- return;
340
- cancelled = true;
341
- cancelPreviousAutoPlay = null;
342
- player.removeEventListener('frameupdate', onFrame);
343
- player.removeEventListener('seeked', onSeek);
344
- };
345
- cancelPreviousAutoPlay = cleanup;
346
- const onFrame = () => {
347
- if (cancelled)
348
- return;
349
- if (player.getCurrentFrame() >= endFrame) {
350
- player.pause();
351
- cleanup();
352
- }
353
- };
354
- const onSeek = (e) => {
355
- // User seeked away — cancel auto-pause. Ignore seeks within
356
- // the target section range (could be our own seekTo settling).
357
- const frame = e?.detail?.frame ?? player.getCurrentFrame();
358
- if (frame < startFrame || frame > endFrame) {
359
- cleanup();
360
- }
361
- };
362
- player.addEventListener('frameupdate', onFrame);
363
- player.addEventListener('seeked', onSeek);
364
- }
365
- });
366
- }, [sections]);
367
278
  // Defer Player mount to client only. Remotion's Player and all composition
368
279
  // components (Series, AbsoluteFill, Audio, Video) use React context provided
369
280
  // by Player at runtime. During SSR there's no Remotion context, so hooks like
@@ -373,6 +284,7 @@ export function PlayerPage({ sections, totalDuration, preamble, entryPath, hasUn
373
284
  const mounted = useSyncExternalStore(subscribeNoop, getClientMounted, getServerMounted);
374
285
  const generationStatus = useGenerationStatus();
375
286
  const generationErrors = useGenerationErrors();
287
+ const sidebarOpen = useSidebarOpen();
376
288
  const [editing, setEditing] = useState(false);
377
289
  const [resetKey, setResetKey] = useState(0);
378
290
  const [rendering, setRendering] = useState(false);
@@ -702,9 +614,9 @@ export function PlayerPage({ sections, totalDuration, preamble, entryPath, hasUn
702
614
  margin: 0,
703
615
  lineHeight: 1.5,
704
616
  }, children: error.stack || error.message })] }) })) }, resetKey)) : (_jsx("div", { className: 'aspect-video bg-[#050505]' })) }) }), _jsx(TweakpaneRoot, { playerRef: playerRef, fps: fps, sections: sections, entryPath: entryPath }), _jsxs("div", { className: 'fixed bottom-6 flex items-center gap-1.5 rounded-full bg-[#1c1c1c] border border-white/10 px-2 py-1.5 shadow-2xl', style: {
705
- left: `calc((100% - ${SIDEBAR_WIDTH}px - 0.75rem) / 2)`,
617
+ left: sidebarOpen ? `calc((100% - ${SIDEBAR_WIDTH}px - 0.75rem) / 2)` : '50%',
706
618
  transform: 'translateX(-50%)',
707
- }, children: [availableEntries.length > 1 && (_jsxs(_Fragment, { children: [_jsx("select", { value: currentRoute, onChange: (e) => {
619
+ }, children: [_jsx("button", { onClick: goToPrevScene, disabled: !hasPrevScene, className: 'flex items-center justify-center rounded-full w-7 h-7 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-zinc-400', title: 'Previous scene (\u2193)', children: _jsx(ChevronDownIcon, {}) }), _jsx(ToolbarSeparator, {}), availableEntries.length > 1 && (_jsxs(_Fragment, { children: [_jsx("select", { value: currentRoute, onChange: (e) => {
708
620
  const route = e.target.value;
709
621
  const base = import.meta.env.BASE_URL ?? '/';
710
622
  window.location.href = `${base}${route}`;
@@ -714,7 +626,7 @@ export function PlayerPage({ sections, totalDuration, preamble, entryPath, hasUn
714
626
  const next = PLAYBACK_RATES[(idx + 1) % PLAYBACK_RATES.length];
715
627
  return next;
716
628
  });
717
- }, className: 'flex items-center rounded-full px-2.5 py-1.5 text-[13px] font-medium tabular-nums text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer min-w-[3rem] justify-center', title: 'Playback speed (click to cycle)', children: playbackRate === 1 ? '1x' : `${playbackRate}x` }), _jsx(ToolbarSeparator, {}), _jsxs("div", { className: 'flex items-center', children: [_jsx("button", { onClick: goToPrevScene, disabled: !hasPrevScene, className: 'flex items-center justify-center rounded-full w-7 h-7 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-zinc-400', title: 'Previous scene (\u2193)', children: _jsx(ChevronDownIcon, {}) }), _jsx("button", { onClick: goToNextScene, disabled: !hasNextScene, className: 'flex items-center justify-center rounded-full w-7 h-7 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-zinc-400', title: 'Next scene (\u2191)', children: _jsx(ChevronUpIcon, {}) })] }), _jsx(ToolbarSeparator, {}), _jsx(LayoutEditor, { playerContainerRef: playerContainerRef, playerRef: playerRef, editing: editing, onEditingChange: setEditing, onReset: () => setResetKey((k) => k + 1), sections: sections, fps: fps, entryPath: entryPath }), generationStatus && (_jsxs(_Fragment, { children: [_jsx(ToolbarSeparator, {}), _jsxs("div", { className: 'flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] font-medium text-amber-400/90', children: [_jsx(SpinnerIcon, {}), _jsx("span", { children: formatGenerationStatus(generationStatus) })] })] })), generationErrors.length > 0 && (_jsxs(_Fragment, { children: [_jsx(ToolbarSeparator, {}), _jsxs("div", { className: 'flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] font-medium text-red-400/90', children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1 }, children: "\u2715" }), _jsx("span", { children: generationErrors.length === 1
629
+ }, className: 'flex items-center rounded-full px-2.5 py-1.5 text-[13px] font-medium tabular-nums text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer min-w-[3rem] justify-center', title: 'Playback speed (click to cycle)', children: playbackRate === 1 ? '1x' : `${playbackRate}x` }), _jsx(ToolbarSeparator, {}), _jsx(LayoutEditor, { playerContainerRef: playerContainerRef, playerRef: playerRef, editing: editing, onEditingChange: setEditing, onReset: () => setResetKey((k) => k + 1), sections: sections, fps: fps, entryPath: entryPath }), generationStatus && (_jsxs(_Fragment, { children: [_jsx(ToolbarSeparator, {}), _jsxs("div", { className: 'flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] font-medium text-amber-400/90', children: [_jsx(SpinnerIcon, {}), _jsx("span", { children: formatGenerationStatus(generationStatus) })] })] })), generationErrors.length > 0 && (_jsxs(_Fragment, { children: [_jsx(ToolbarSeparator, {}), _jsxs("div", { className: 'flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] font-medium text-red-400/90', children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1 }, children: "\u2715" }), _jsx("span", { children: generationErrors.length === 1
718
630
  ? `${generationErrors[0].namespace} failed: ${generationErrors[0].error.slice(0, 60)}`
719
- : `${generationErrors.length} generations failed` })] })] }))] })] }));
631
+ : `${generationErrors.length} generations failed` })] })] })), _jsx(ToolbarSeparator, {}), _jsx("button", { onClick: goToNextScene, disabled: !hasNextScene, className: 'flex items-center justify-center rounded-full w-7 h-7 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-zinc-400', title: 'Next scene (\u2191)', children: _jsx(ChevronUpIcon, {}) })] })] }));
720
632
  }
@@ -6,6 +6,8 @@ export declare function useMediaDurations(): Record<string, number>;
6
6
  export declare function useGenerationStatus(): GenerationStatus | null;
7
7
  /** Hook: recent generation errors (auto-cleared after 8s). */
8
8
  export declare function useGenerationErrors(): GenerationError[];
9
+ /** Hook: whether the right tweakpane sidebar is open. */
10
+ export declare function useSidebarOpen(): boolean;
9
11
  /** Hook: user modules from virtual:egaki-modules. */
10
12
  export declare function useModules(): EagerModules;
11
13
  export type { GenerationStatus, GenerationError };
@@ -1 +1 @@
1
- {"version":3,"file":"store-hooks.d.ts","sourceRoot":"","sources":["../../src/vite/store-hooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAuEvD,6CAA6C;AAC7C,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE1D;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAE7D;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD;AAED,qDAAqD;AACrD,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"store-hooks.d.ts","sourceRoot":"","sources":["../../src/vite/store-hooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AA6EvD,6CAA6C;AAC7C,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE1D;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAE7D;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD;AAED,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,qDAAqD;AACrD,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAA"}
@@ -69,6 +69,10 @@ const subscribeGenerationErrors = (callback) => egakiStore.subscribe((state) =>
69
69
  const getGenerationErrors = () => egakiStore.getState().serverGenerationErrors;
70
70
  const EMPTY_ERRORS = [];
71
71
  const getGenerationErrorsServer = () => EMPTY_ERRORS;
72
+ // Sidebar open state
73
+ const subscribeSidebarOpen = (callback) => egakiStore.subscribe((state) => state.sidebarOpen, callback);
74
+ const getSidebarOpen = () => egakiStore.getState().sidebarOpen;
75
+ const getSidebarOpenServer = () => false;
72
76
  // Modules
73
77
  const subscribeModules = (callback) => egakiStore.subscribe((state) => state.modules, callback);
74
78
  const getModules = () => egakiStore.getState().modules;
@@ -87,6 +91,10 @@ export function useGenerationStatus() {
87
91
  export function useGenerationErrors() {
88
92
  return useSyncExternalStore(subscribeGenerationErrors, getGenerationErrors, getGenerationErrorsServer);
89
93
  }
94
+ /** Hook: whether the right tweakpane sidebar is open. */
95
+ export function useSidebarOpen() {
96
+ return useSyncExternalStore(subscribeSidebarOpen, getSidebarOpen, getSidebarOpenServer);
97
+ }
90
98
  /** Hook: user modules from virtual:egaki-modules. */
91
99
  export function useModules() {
92
100
  return useSyncExternalStore(subscribeModules, getModules, getModules);
@@ -54,6 +54,9 @@ export interface EgakiClientState {
54
54
  /** Recent generation errors from the server progress stream.
55
55
  * Errors auto-clear after ERROR_DISPLAY_DURATION_MS. */
56
56
  serverGenerationErrors: GenerationError[];
57
+ /** Whether the right tweakpane sidebar is open. Closed by default so the
58
+ * video takes the full width. */
59
+ sidebarOpen: boolean;
57
60
  }
58
61
  export declare const egakiStore: Omit<import("zustand/vanilla").StoreApi<EgakiClientState>, "subscribe"> & {
59
62
  subscribe: {
@@ -64,6 +67,8 @@ export declare const egakiStore: Omit<import("zustand/vanilla").StoreApi<EgakiCl
64
67
  } | undefined): () => void;
65
68
  };
66
69
  };
70
+ /** Open or close the right tweakpane sidebar. */
71
+ export declare function setSidebarOpen(open: boolean): void;
67
72
  /**
68
73
  * Derive per-section max media durations.
69
74
  * Returns Record<string, number> (section-index-as-string → seconds).
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/vite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,2EAA2E;AAC3E,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IAErB;;;;OAIG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,oFAAoF;IACpF,sBAAsB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAE/C,mEAAmE;IACnE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;IAElD;6DACyD;IACzD,sBAAsB,EAAE,eAAe,EAAE,CAAA;CAC1C;AAMD,eAAO,MAAM,UAAU;;;;;;;;CAQtB,CAAA;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpF;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI,CAEvF"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/vite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,2EAA2E;AAC3E,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IAErB;;;;OAIG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,oFAAoF;IACpF,sBAAsB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAE/C,mEAAmE;IACnE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;IAElD;6DACyD;IACzD,sBAAsB,EAAE,eAAe,EAAE,CAAA;IAEzC;sCACkC;IAClC,WAAW,EAAE,OAAO,CAAA;CACrB;AAMD,eAAO,MAAM,UAAU;;;;;;;;CAStB,CAAA;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,QAE3C;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpF;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI,CAEvF"}
@@ -23,7 +23,12 @@ export const egakiStore = createStore()(subscribeWithSelector(() => ({
23
23
  serverGenerationStatus: null,
24
24
  serverGenerationEntries: [],
25
25
  serverGenerationErrors: [],
26
+ sidebarOpen: false,
26
27
  })));
28
+ /** Open or close the right tweakpane sidebar. */
29
+ export function setSidebarOpen(open) {
30
+ egakiStore.setState({ sidebarOpen: open });
31
+ }
27
32
  // ---------------------------------------------------------------------------
28
33
  // Selectors — derive values from state
29
34
  // ---------------------------------------------------------------------------
@@ -77,9 +77,12 @@ export declare function useTweakpane<T extends ParamSchema>(label: string, schem
77
77
  /** Sidebar width constant shared with player-page layout. */
78
78
  export declare const SIDEBAR_WIDTH = 280;
79
79
  /**
80
- * Right sidebar that hosts the tweakpane Pane. Always visible in the page
81
- * layout; the Pane mounts into the scrollable inner div. Also provides
82
- * player metadata (frame, fps, sections) to the copy prompt button.
80
+ * Right sidebar that hosts the tweakpane Pane. Collapsed by default so the
81
+ * video takes the full width; a fixed top-right button opens it. The sidebar
82
+ * container stays mounted while collapsed (hidden via display: none) because
83
+ * useTweakpane registers controls into it — unmounting would lose all
84
+ * registrations until components remount. Also provides player metadata
85
+ * (frame, fps, sections) to the copy prompt button.
83
86
  */
84
87
  export declare function TweakpaneRoot({ playerRef, fps, sections, entryPath, }: {
85
88
  playerRef: React.RefObject<PlayerRef | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"tweakpane-hook.d.ts","sourceRoot":"","sources":["../../src/vite/tweakpane-hook.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tweakpane-hook.d.ts","sourceRoot":"","sources":["../../src/vite/tweakpane-hook.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * Tweakpane integration for the egaki video player.
5
5
  *
@@ -21,6 +21,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
21
21
  */
22
22
  import { useCallback, useEffect, useRef, useState, useSyncExternalStore, createContext, useContext } from 'react';
23
23
  import { LayoutContainerContext, useIsPremounting } from "./mdx-video.js";
24
+ import { setSidebarOpen } from "./store.js";
25
+ import { useSidebarOpen } from "./store-hooks.js";
24
26
  export const TweakpaneContext = createContext(null);
25
27
  // ---------------------------------------------------------------------------
26
28
  // Global disable switch
@@ -410,10 +412,17 @@ export function useTweakpane(label, schema) {
410
412
  // ---------------------------------------------------------------------------
411
413
  /** Sidebar width constant shared with player-page layout. */
412
414
  export const SIDEBAR_WIDTH = 280;
415
+ /** Lucide-style panel-right icon used for both opening and closing the sidebar. */
416
+ function PanelRightIcon() {
417
+ return (_jsxs("svg", { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.5', strokeLinecap: 'round', strokeLinejoin: 'round', children: [_jsx("rect", { x: '3', y: '3', width: '18', height: '18', rx: '2' }), _jsx("line", { x1: '15', y1: '3', x2: '15', y2: '21' })] }));
418
+ }
413
419
  /**
414
- * Right sidebar that hosts the tweakpane Pane. Always visible in the page
415
- * layout; the Pane mounts into the scrollable inner div. Also provides
416
- * player metadata (frame, fps, sections) to the copy prompt button.
420
+ * Right sidebar that hosts the tweakpane Pane. Collapsed by default so the
421
+ * video takes the full width; a fixed top-right button opens it. The sidebar
422
+ * container stays mounted while collapsed (hidden via display: none) because
423
+ * useTweakpane registers controls into it — unmounting would lose all
424
+ * registrations until components remount. Also provides player metadata
425
+ * (frame, fps, sections) to the copy prompt button.
417
426
  */
418
427
  export function TweakpaneRoot({ playerRef, fps, sections, entryPath, }) {
419
428
  // Ref callback sets the container synchronously when the DOM node mounts,
@@ -435,16 +444,18 @@ export function TweakpaneRoot({ playerRef, fps, sections, entryPath, }) {
435
444
  contextRef = null;
436
445
  };
437
446
  }, [playerRef, fps, sections, entryPath]);
438
- return (_jsxs("div", { className: 'egaki-sidebar', style: {
439
- width: SIDEBAR_WIDTH,
440
- minWidth: SIDEBAR_WIDTH,
441
- height: '100vh',
442
- position: 'sticky',
443
- top: 0,
444
- overflowY: 'auto',
445
- overflowX: 'hidden',
446
- background: '#111',
447
- borderLeft: '1px solid rgba(255,255,255,0.08)',
448
- flexShrink: 0,
449
- }, children: [_jsx(CopyChangesButton, {}), _jsx("div", { ref: containerRefCallback, id: 'egaki-tweakpane', style: { width: '100%' } })] }));
447
+ const sidebarOpen = useSidebarOpen();
448
+ return (_jsxs(_Fragment, { children: [!sidebarOpen && (_jsx("button", { onClick: () => setSidebarOpen(true), title: 'Open controls sidebar', className: 'fixed top-4 right-4 z-10 flex items-center justify-center rounded-lg w-8 h-8 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer', children: _jsx(PanelRightIcon, {}) })), _jsxs("div", { className: 'egaki-sidebar', style: {
449
+ width: SIDEBAR_WIDTH,
450
+ minWidth: SIDEBAR_WIDTH,
451
+ height: '100vh',
452
+ position: 'sticky',
453
+ top: 0,
454
+ overflowY: 'auto',
455
+ overflowX: 'hidden',
456
+ background: '#111',
457
+ borderLeft: '1px solid rgba(255,255,255,0.08)',
458
+ flexShrink: 0,
459
+ display: sidebarOpen ? undefined : 'none',
460
+ }, children: [_jsx("div", { className: 'flex items-center justify-end px-2 pt-2', children: _jsx("button", { onClick: () => setSidebarOpen(false), title: 'Close controls sidebar', className: 'flex items-center justify-center rounded-lg w-8 h-8 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer', children: _jsx(PanelRightIcon, {}) }) }), _jsx(CopyChangesButton, {}), _jsx("div", { ref: containerRefCallback, id: 'egaki-tweakpane', style: { width: '100%' } })] })] }));
450
461
  }
@@ -18,11 +18,5 @@ export interface VideoPluginOptions {
18
18
  * All other .mdx files in the project become additional routes. */
19
19
  entry?: string;
20
20
  }
21
- /**
22
- * Compare old and new MDX sources section-by-section. Returns the index
23
- * of the first added or edited section, or null if nothing changed (or
24
- * only deletions occurred, which we skip per design).
25
- */
26
- export declare function findChangedSectionIndex(oldSource: string, newSource: string): number | null;
27
21
  export declare function video(options?: VideoPluginOptions): PluginOption[];
28
22
  //# sourceMappingURL=vite-plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AA4BhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAuCD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuB3F;AAoCD,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAwalE"}
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AA4BhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiDD,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAoYlE"}
@@ -46,60 +46,6 @@ function resolveSourceToFile(root, source) {
46
46
  }
47
47
  return undefined;
48
48
  }
49
- /**
50
- * Extract raw source text ranges per section directly from the mdast.
51
- * Works with heading nodes' positions to capture the FULL raw heading
52
- * text including props like `duration=3s` and `transition=20` that
53
- * `splitIntoSections` strips during parsing. Each section spans from
54
- * its heading's start offset to the next heading's start (or EOF).
55
- */
56
- function sectionRawTexts(source, mdast) {
57
- // Collect the start offsets of each heading node
58
- const headingStarts = [];
59
- for (const node of mdast.children) {
60
- if (node.type === 'heading' && node.position?.start?.offset != null) {
61
- headingStarts.push(node.position.start.offset);
62
- }
63
- }
64
- // Slice source between consecutive headings, trimming trailing whitespace
65
- // so boundary changes (adding/removing a following section) don't cause
66
- // false positives on the preceding section.
67
- return headingStarts.map((start, i) => {
68
- const end = i + 1 < headingStarts.length ? headingStarts[i + 1] : source.length;
69
- return source.slice(start, end).trimEnd();
70
- });
71
- }
72
- /**
73
- * Compare old and new MDX sources section-by-section. Returns the index
74
- * of the first added or edited section, or null if nothing changed (or
75
- * only deletions occurred, which we skip per design).
76
- */
77
- export function findChangedSectionIndex(oldSource, newSource) {
78
- try {
79
- const oldTexts = sectionRawTexts(oldSource, mdxParse(oldSource));
80
- const newTexts = sectionRawTexts(newSource, mdxParse(newSource));
81
- const maxLen = Math.max(oldTexts.length, newTexts.length);
82
- for (let i = 0; i < maxLen; i++) {
83
- if (i >= oldTexts.length)
84
- return i; // new section added
85
- if (i >= newTexts.length)
86
- return null; // section deleted, skip
87
- if (oldTexts[i] !== newTexts[i]) {
88
- // Distinguish edit from deletion: if the next old section matches
89
- // the current new section, a section was deleted (shifted down).
90
- // Look ahead to detect deletions at any position, not just the tail.
91
- if (newTexts.length < oldTexts.length && oldTexts[i + 1] === newTexts[i]) {
92
- return null; // deletion, skip
93
- }
94
- return i; // content changed
95
- }
96
- }
97
- return null; // no scene-level change (frontmatter/preamble only)
98
- }
99
- catch {
100
- return null; // parse error, don't seek
101
- }
102
- }
103
49
  /** Discover .mdx files in the project root directory (non-recursive).
104
50
  * Only root-level files become entries to avoid import resolution issues
105
51
  * with nested paths. Returns a map of routePath → absolutePath. */
@@ -146,8 +92,6 @@ export function video(options) {
146
92
  let defaultRoute = '';
147
93
  /** Whether the user's project has `motion` (framer-motion) installed. */
148
94
  let hasMotion = false;
149
- /** Cached previous MDX sources for section-level diff detection, keyed by abs path. */
150
- const previousMdxSources = new Map();
151
95
  /** Set of absolute paths of all entry MDX files for quick lookup. */
152
96
  let entryPathSet = new Set();
153
97
  /** Is this file referenced inside a <Server> block of any entry MDX?
@@ -193,13 +137,6 @@ export function video(options) {
193
137
  }
194
138
  defaultRoute = resolveDefaultRoute(mdxEntries, options?.entry, root);
195
139
  entryPathSet = new Set(mdxEntries.values());
196
- // Seed previous sources for section-level diff detection.
197
- for (const [, absPath] of mdxEntries) {
198
- try {
199
- previousMdxSources.set(absPath, fs.readFileSync(absPath, 'utf-8'));
200
- }
201
- catch { /* ignore read errors */ }
202
- }
203
140
  // Auto-generate egaki-env.d.ts so MDX LSP knows about built-in
204
141
  // components via the global MDXProvidedComponents type. Same
205
142
  // pattern Vite uses for vite-env.d.ts.
@@ -407,21 +344,7 @@ export function video(options) {
407
344
  mdxEntries.set(relPath.replace(/\.mdx$/, ''), absEntry);
408
345
  }
409
346
  defaultRoute = resolveDefaultRoute(mdxEntries, options?.entry, root);
410
- const nextEntryPathSet = new Set(mdxEntries.values());
411
- // Sync previousMdxSources: seed new entries, prune removed ones.
412
- for (const file of previousMdxSources.keys()) {
413
- if (!nextEntryPathSet.has(file))
414
- previousMdxSources.delete(file);
415
- }
416
- for (const file of nextEntryPathSet) {
417
- if (!previousMdxSources.has(file)) {
418
- try {
419
- previousMdxSources.set(file, fs.readFileSync(file, 'utf-8'));
420
- }
421
- catch { /* file may be mid-write */ }
422
- }
423
- }
424
- entryPathSet = nextEntryPathSet;
347
+ entryPathSet = new Set(mdxEntries.values());
425
348
  invalidateVirtual([RESOLVED_APP, RESOLVED_MDX, RESOLVED_MODULES]);
426
349
  if (this.environment.name === 'client') {
427
350
  ctx.server.environments.client?.hot.send({ type: 'full-reload' });
@@ -431,25 +354,11 @@ export function video(options) {
431
354
  if (isEntryMdx) {
432
355
  invalidateVirtual([RESOLVED_APP, RESOLVED_MDX]);
433
356
  if (this.environment.name === 'client') {
434
- // Section-level diff detection for the changed entry.
435
- const newMdxSource = fs.readFileSync(normalizedFile, 'utf-8');
436
- const prevSource = previousMdxSources.get(normalizedFile);
437
- const changedSection = prevSource != null
438
- ? findChangedSectionIndex(prevSource, newMdxSource)
439
- : null;
440
- previousMdxSources.set(normalizedFile, newMdxSource);
441
357
  ctx.server.environments.client?.hot.send({
442
358
  type: 'custom',
443
359
  event: 'rsc:update',
444
360
  data: { file: ctx.file },
445
361
  });
446
- if (changedSection != null) {
447
- ctx.server.environments.client?.hot.send({
448
- type: 'custom',
449
- event: 'egaki:scene-changed',
450
- data: { sectionIndex: changedSection, file: normalizedFile },
451
- });
452
- }
453
362
  }
454
363
  return [];
455
364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "egaki",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "AI image, video, and speech generation CLI with MDX-to-video framework",
6
6
  "main": "./dist/cli/main.js",
@@ -111,17 +111,18 @@
111
111
  "tweakpane": "^4.0.5",
112
112
  "yaml": "^2.9.0",
113
113
  "zod": "^4.3.6",
114
+ "@vitejs/plugin-react": "^6.0.2",
115
+ "spiceflow": "1.26.0-rsc.7",
114
116
  "zustand": "^5.0.14"
115
117
  },
116
118
  "peerDependencies": {
117
- "@vitejs/plugin-react": ">=6",
119
+ "vite": ">=6",
118
120
  "motion": ">=12",
119
121
  "motion-dom": ">=12",
120
- "motion-utils": ">=12",
121
- "spiceflow": ">=1.26.0-rsc.5"
122
+ "motion-utils": ">=12"
122
123
  },
123
124
  "peerDependenciesMeta": {
124
- "@vitejs/plugin-react": {
125
+ "vite": {
125
126
  "optional": true
126
127
  },
127
128
  "motion": {
@@ -132,15 +133,14 @@
132
133
  },
133
134
  "motion-utils": {
134
135
  "optional": true
135
- },
136
- "spiceflow": {
137
- "optional": true
138
136
  }
139
137
  },
138
+ "optionalDependencies": {
139
+ "node-web-audio-api": ">=1"
140
+ },
140
141
  "devDependencies": {
141
142
  "@types/js-yaml": "^4.0.9",
142
143
  "@types/react": "^19.2.17",
143
- "@vitejs/plugin-react": "^6.0.2",
144
144
  "vite": "^8.0.10",
145
145
  "vitest": "^4.1.8"
146
146
  },