Skip to content

feat(studio): update to v0.1.3 with v2 design and timeline improvements#56

Closed
miguel-heygen wants to merge 2 commits intomainfrom
feat/studio-v0.1.3
Closed

feat(studio): update to v0.1.3 with v2 design and timeline improvements#56
miguel-heygen wants to merge 2 commits intomainfrom
feat/studio-v0.1.3

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen commented Mar 26, 2026

New Features

  • CompositionThumbnail: Server-rendered JPEG film strips for timeline clips via shared Puppeteer instance
  • TimelineToolbar: Split, Delete, and Zoom controls (Split/Delete edit the HTML source directly)
  • TimelineClip: Draggable clips with move, trim-left, trim-right, and track-switch via @use-gesture/react
  • VideoThumbnail: Client-side video frame extraction for <video> clips
  • htmlEditor.ts: Hybrid DOMParser + surgical regex for format-preserving HTML split/delete operations
  • Standalone dev mode: pnpm dev runs full editor with all API endpoints via Vite plugin — no separate backend

Design Updates

  • v2 teal color scheme via CSS custom property --hf-accent (customizable by consumers)
  • Teal playhead line with glow shadow, always visible above clips (z-100)
  • Progress thumb: always-visible teal dot with hover scale
  • Auto-scroll timeline to follow playhead during seek and playback

Architecture

  • Shared Puppeteer browser singleton for thumbnails (not per-request)
  • Render job storage via typed Map with TTL cleanup (replaces globalThis)
  • parseTimelineFromDOM() — shared helper for DOM → TimelineElement[] (deduplicated from 3 locations)
  • Removed all magic-edit-* postMessage references — only hf-parent/hf-preview now
  • Removed dead exported stubs (clearThumbnailCache, disposeThumbnailIframe, requestThumbnail)

Test Coverage

  • Added comprehensive test suites for Timeline.tsx tick generation and formatting
  • Added test coverage for playerStore.ts state management and liveTime subscription system
  • Added test coverage for formatTime utility function edge cases

Version Updates

  • Bumped all packages from 0.1.1 to 0.1.2 (studio to 0.1.3)
  • Updated zustand from v4 to v5 with peer dependency changes
  • Added development dependencies: puppeteer-core, vitest, @use-gesture/react

@miguel-heygen miguel-heygen force-pushed the feat/studio-v0.1.3 branch 11 times, most recently from 80f7c52 to 2ab150a Compare March 26, 2026 16:36
@vanceingalls
Copy link
Copy Markdown
Collaborator

Code Review: feat(studio) v0.1.3

Critical

1. XSS via unsanitized ID injection in htmlEditor.ts

patchAttrInTag injects values into HTML attributes via regex without escaping quotes:

const re = new RegExp(`(${attrName}=["'])([^"']*)(["'])`);
return tag.replace(re, `$1${newValue}$3`);

A crafted element ID like foo" onload="alert(1) breaks the attribute boundary. Needs quote escaping or ID validation (reject non-alphanumeric beyond -_).

2. patchAttrInTag regex isn't quote-aware

The ["'] pattern doesn't ensure opening and closing quotes match. Also matches attribute names that appear inside other attribute values (e.g. <div title="data-start=old" data-start="real">).

3. No request body size limit on PUT /api/projects/:id/files/:path

Accepts arbitrary content with no cap. Add a size limit (e.g. 10MB).

Important

4. Zero tests for htmlEditor.ts — 475 lines of surgical regex HTML manipulation with no test coverage. Functions are pure string→string — ideal for unit testing. This is the highest-risk code in the PR.

5. Zero tests for styleCapture.ts — 257 lines, also untested.

6. Timeline.test.ts duplicates logic instead of importing — Tests validate a replicated copy of generateTicks/formatTick, not the production code. The copies are already out of sync (missing the !Number.isFinite(duration) || duration > 7200 guard, the minorInterval floor, and the maxTicks safety cap).

7. Puppeteer browser singleton never closes — No cleanup handler on dev server shutdown. Leaves orphaned Chrome processes. Fix:

server.httpServer?.on('close', async () => {
  if (_browser) await _browser.close().catch(() => {});
});

8. Render job TTL parses timestamps from keyparseInt(key.split("-").pop()) is fragile when project IDs contain hyphens. Store createdAt in the job object instead.

9. applyBakedStyles positional pass destroys formatting — Falls back to DOMParser.innerHTML which loses the format-preserving behavior the regex approach was designed for.

10. reset() doesn't reset zoom — Switching projects preserves zoom level from the previous project. A 5s project's zoom applied to a 120s project is confusing. Consider resetting zoomMode to "fit".

11. Removed agentId/agentColor/ActiveEdits — Breaking change if external consumers depend on these fields from @hyperframes/studio.

Suggestions

12. VideoThumbnailextractingRef not reset when videoSrc changes (same component instance, new prop).

13. CompositionThumbnailsetRef walks up DOM to find [data-clip] parent. Tight coupling to TimelineClip DOM structure.

14. Speed menu has no click-outside-to-close.

15. formatTime(-1) returns "-1:-1", formatTime(NaN) returns "NaN:NaN". Guard with if (!Number.isFinite(s) || s < 0) return "0:00".

16. Auto-scroll RAF in Timeline.tsx — if pointer leaves window without pointerup, scroll loop continues.

What's well done

  • liveTime pub-sub bypassing React state for 60fps playhead updates — exactly right
  • isSafePath consistently applied on all file-serving endpoints
  • findElementBlock depth counting with comment/quote awareness — robust
  • ResizeObserver cleanup on unmount in both Timeline and CompositionThumbnail
  • Progressive video frame extraction via seeked event
  • Lean Zustand store with individual selectors preventing unnecessary re-renders

- v2 teal color scheme (buttons, playhead, progress bar)
- CompositionThumbnail: server-rendered JPEG film strips for timeline clips
- TimelineToolbar: Split, Delete, and Zoom controls
- TimelineClip: draggable clips with resize handles
- VideoThumbnail: client-side video frame extraction
- Teal playhead line with glow, always visible above clips
- Progress thumb: always-visible teal dot
- Auto-scroll timeline to follow playhead during seek
- Vite plugin: thumbnail endpoint, render proxy, file watcher
- Standalone dev mode with full editor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the Split and Delete toolbar buttons which had many edge cases
(style capture, media offset, depth counting, DOMParser + regex hybrid).

Replace with an Edit button that:
1. Opens a modal with time range selection (start/end sliders)
2. Shows all elements that overlap the selected range
3. Provides a prompt textarea for describing the desired change
4. "Copy to Agent" button copies structured context to clipboard

The AI agent handles the actual HTML mutation — which is what it's
good at. This eliminates ~300 lines of fragile split/delete code.

Removed: splitElement, deleteElement, applyBakedStyles, styleCapture.ts
Kept: parseStyleString, mergeStyleIntoTag, findElementBlock (useful utilities)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants