feat(studio): render queue, layout restructure, home page, hover preview (#95)

## Summary
- Add render queue panel with progress tracking, download, and delete actions
- Restructure App layout: home page with project picker, session-based routing
- Add ExpandOnHover component for preview-on-hover interactions (uses motion/react)
- CompositionsTab now supports hover preview with expanded iframe view
- Vite config: guard setInterval cleanup to dev-only (fixes CI build timeout)
- Add favicon and update studio package deps

## Test plan
- [x] Render queue shows progress, completes, and allows download
- [x] Home page lists projects and navigates to session view
- [x] ExpandOnHover shows expanded preview on mouse hover with spring animation
- [x] `vite build` exits cleanly (no hanging process from setInterval)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Miguel Ángel
2026-03-28 21:28:43 +01:00
committed by GitHub
parent 9ae239d177
commit 40bd159103
24 changed files with 2495 additions and 796 deletions
@@ -1,4 +1,5 @@
import { memo, useState, useCallback, useEffect } from "react";
import { memo, useState, useCallback } from "react";
import { useMountEffect } from "../../hooks/useMountEffect";
import { CompositionsTab } from "./CompositionsTab";
import { AssetsTab } from "./AssetsTab";
@@ -12,6 +13,7 @@ function getPersistedTab(): SidebarTab {
}
interface LeftSidebarProps {
width?: number;
projectId: string;
compositions: string[];
assets: string[];
@@ -21,6 +23,7 @@ interface LeftSidebarProps {
}
export const LeftSidebar = memo(function LeftSidebar({
width = 240,
projectId,
compositions,
assets,
@@ -36,7 +39,7 @@ export const LeftSidebar = memo(function LeftSidebar({
}, []);
// Keyboard shortcuts: Cmd+1 for Compositions, Cmd+2 for Assets
useEffect(() => {
useMountEffect(() => {
const handler = (e: KeyboardEvent) => {
if (!e.metaKey && !e.ctrlKey) return;
if (e.key === "1") {
@@ -50,12 +53,12 @@ export const LeftSidebar = memo(function LeftSidebar({
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [selectTab]);
});
return (
<div
className="flex flex-col h-full bg-neutral-950 border-r border-neutral-800/50"
style={{ width: 240 }}
style={{ width }}
>
{/* Tabs */}
<div className="flex border-b border-neutral-800/50 flex-shrink-0">