diff --git a/bun.lock b/bun.lock index c94440c4b..84d73f6db 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@hyperframes/player": "workspace:*", "@types/node": "^25.0.10", "concurrently": "^8.2.0", + "fallow": "^2.75.0", "happy-dom": "^20.9.0", "knip": "^6.0.3", "lefthook": "^2.1.4", @@ -517,6 +518,22 @@ "@exodus/bytes": ["@exodus/bytes@1.15.0", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ=="], + "@fallow-cli/darwin-arm64": ["@fallow-cli/darwin-arm64@2.75.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-izJTjYPNdiWPbf7QjVwje0JwJeVQJAUuncjPkuAO9hRM3t4oJ9fqAvyEXyyRq4dAJS4NO4DMEsPsN82QOF8fGw=="], + + "@fallow-cli/darwin-x64": ["@fallow-cli/darwin-x64@2.75.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-hZzaSidHYKFC82bjP/L+xKOp67ESgNjSUX3U2HzaBg/dKFVcyMFxub3O7r3SBFBU2zY5wsZK1qmfpw/oC01vsg=="], + + "@fallow-cli/linux-arm64-gnu": ["@fallow-cli/linux-arm64-gnu@2.75.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-K5ymLWqR6NJUV+wVDFK0+bKK4YSzYo9Lr2Xla5YptW6FnlpEAvRDdcr3lGdf3Ge1T14fKU3ys8dKHDHkJSeyeA=="], + + "@fallow-cli/linux-arm64-musl": ["@fallow-cli/linux-arm64-musl@2.75.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-FRBz19XQ6pPjnhsIry0w0cRqUEYGZH6rEDCau3SGa2DNgts/i4573HJOn7ZRAzgSNMtgoh3KKTyUNOEzNLTl0A=="], + + "@fallow-cli/linux-x64-gnu": ["@fallow-cli/linux-x64-gnu@2.75.0", "", { "os": "linux", "cpu": "x64" }, "sha512-81xmIf9G8hVTKbQGRGV6oTbu9W5XQPpKFpfXS3KFu16bbdgfwEaKzr5E7Y7dxECYsskhIiff5zdHcEPOFv29Wg=="], + + "@fallow-cli/linux-x64-musl": ["@fallow-cli/linux-x64-musl@2.75.0", "", { "os": "linux", "cpu": "x64" }, "sha512-lVPjmM+dGy3NG6nJ9PaZfkUClZC6OyfwyqVZdQFhq+6Xl/2c2jZI9vgmwIDBIGBhL9TU+V8NdSM7Xy85ifeOyw=="], + + "@fallow-cli/win32-arm64-msvc": ["@fallow-cli/win32-arm64-msvc@2.75.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-HbIXLbte8pzNX0XlbyRlRQA2+y8kfJ4O6XxB2VR2dE6q5LrNhG4fl0QRR/P9x8r3CNn9fw/25kk+WxULfbHu1w=="], + + "@fallow-cli/win32-x64-msvc": ["@fallow-cli/win32-x64-msvc@2.75.0", "", { "os": "win32", "cpu": "x64" }, "sha512-h8W+qEOPvyolBQO2Y0H4/pz2XXw/1yHA8/XRPo3/tYRovmN2zoNjDrvaGjjodzFt6fBVLus+T0uo+U4pMgOu/w=="], + "@fontsource/archivo-black": ["@fontsource/archivo-black@5.2.8", "", {}, "sha512-3zNj/o9LzWyDl/UEpY5IOHpAQyUtFr3hQaFS7NSKwCLLkXOfH/CMCt1L2b2Z+OF25OURtOYenCadgAebALz7/A=="], "@fontsource/eb-garamond": ["@fontsource/eb-garamond@5.2.7", "", {}, "sha512-V42tTlHDbnDo0+lENnXKWMx63Llq6Gfl2l7ozkoeRQN60S0jm6hEgCLlOT/5YyKAN9QZ0e2ofpy0rGKlz9jBrw=="], @@ -1243,6 +1260,8 @@ "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + "fallow": ["fallow@2.75.0", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "@fallow-cli/darwin-arm64": "2.75.0", "@fallow-cli/darwin-x64": "2.75.0", "@fallow-cli/linux-arm64-gnu": "2.75.0", "@fallow-cli/linux-arm64-musl": "2.75.0", "@fallow-cli/linux-x64-gnu": "2.75.0", "@fallow-cli/linux-x64-musl": "2.75.0", "@fallow-cli/win32-arm64-msvc": "2.75.0", "@fallow-cli/win32-x64-msvc": "2.75.0" }, "bin": { "fallow": "bin/fallow", "fallow-lsp": "bin/fallow-lsp", "fallow-mcp": "bin/fallow-mcp" } }, "sha512-0/2cquNI/cDLP/LzcCbkwI4hMzkX4tE0VY3/69n3PBBeqFpbM2oai+2Cb0sB8dXB8MDUGPVoPJjDW5GiUo7a1A=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], diff --git a/lefthook.yml b/lefthook.yml index 237bd82ca..b2ea46992 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -12,6 +12,14 @@ pre-commit: typecheck: glob: "*.{ts,tsx}" run: cd packages/core && bunx tsc --noEmit && cd ../studio && bunx tsc --noEmit + # Mirrors the CI gate (same `--base origin/main` so the local hook can't + # pass on a branch CI would fail). Audits the working tree, which means + # unstaged WIP in `packages/**` is part of the diff — stash before + # committing if that surprises you. `--gate new-only` (the default) only + # fails on issues introduced by the branch, not inherited findings. + fallow: + glob: "packages/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}" + run: bunx fallow audit --base origin/main --fail-on-issues filesize: # Scoped to packages/studio — the 500 LOC limit is a studio architecture # standard enforced as part of the App.tsx decomposition work. Player and diff --git a/package.json b/package.json index 11ac3ade0..4c8c81423 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@hyperframes/player": "workspace:*", "@types/node": "^25.0.10", "concurrently": "^8.2.0", + "fallow": "^2.75.0", "happy-dom": "^20.9.0", "knip": "^6.0.3", "lefthook": "^2.1.4", diff --git a/packages/aws-lambda/src/s3Transport.ts b/packages/aws-lambda/src/s3Transport.ts index 988ae2a62..0c74d378f 100644 --- a/packages/aws-lambda/src/s3Transport.ts +++ b/packages/aws-lambda/src/s3Transport.ts @@ -22,11 +22,10 @@ import { createWriteStream, existsSync, mkdirSync, - readdirSync, rmSync, statSync, } from "node:fs"; -import { dirname, join } from "node:path"; +import { dirname } from "node:path"; import { pipeline } from "node:stream/promises"; import { GetObjectCommand, PutObjectCommand, type S3Client } from "@aws-sdk/client-s3"; import * as tar from "tar"; @@ -138,19 +137,3 @@ export async function untarDirectory(tarballPath: string, destDir: string): Prom mkdirSync(destDir, { recursive: true }); await tar.extract({ file: tarballPath, cwd: destDir }); } - -/** List all regular files under a directory, sorted, returned as absolute paths. */ -export function listFilesInDirectory(dir: string): string[] { - const out: string[] = []; - function walk(d: string): void { - for (const entry of readdirSync(d, { withFileTypes: true }).sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0, - )) { - const full = join(d, entry.name); - if (entry.isDirectory()) walk(full); - else if (entry.isFile()) out.push(full); - } - } - walk(dir); - return out; -} diff --git a/packages/cli/src/browser/manager.ts b/packages/cli/src/browser/manager.ts index fc1c9999a..a29c943e4 100644 --- a/packages/cli/src/browser/manager.ts +++ b/packages/cli/src/browser/manager.ts @@ -13,12 +13,6 @@ const CACHE_DIR = join(homedir(), ".cache", "hyperframes", "chrome"); // too or it silently picks system Chrome over a perfectly good headless-shell. const PUPPETEER_CACHE_DIR = join(homedir(), ".cache", "puppeteer", "chrome-headless-shell"); -/** Override browser path via --browser-path flag. Takes priority over env var. */ -let _browserPathOverride: string | undefined; -export function setBrowserPath(path: string): void { - _browserPathOverride = path; -} - export type BrowserSource = "env" | "cache" | "system" | "download"; export interface BrowserResult { @@ -61,10 +55,6 @@ function whichBinary(name: string): string | undefined { } function findFromEnv(): BrowserResult | undefined { - // --browser-path flag takes priority - if (_browserPathOverride && existsSync(_browserPathOverride)) { - return { executablePath: _browserPathOverride, source: "env" }; - } const envPath = process.env["HYPERFRAMES_BROWSER_PATH"]; if (envPath && existsSync(envPath)) { return { executablePath: envPath, source: "env" }; diff --git a/packages/cli/src/capture/assetCataloger.ts b/packages/cli/src/capture/assetCataloger.ts index be174548e..3bcd7e7da 100644 --- a/packages/cli/src/capture/assetCataloger.ts +++ b/packages/cli/src/capture/assetCataloger.ts @@ -288,76 +288,3 @@ function getWidthParam(url: string): number { return 0; } } - -/** - * Format cataloged assets as markdown for the DESIGN.md Assets section. - * Matches Aura.build's format: grouped by type, named from file paths. - */ -export function formatAssetCatalog(assets: CatalogedAsset[]): string { - if (assets.length === 0) return "No assets detected.\n"; - - // Group by type - const groups: Record = {}; - for (const a of assets) { - const group = a.type; - if (!groups[group]) groups[group] = []; - groups[group]!.push(a); - } - - const lines: string[] = []; - - // Output in order: Fonts, Images, Videos, Icons, Background, Other - const order: CatalogedAsset["type"][] = ["Font", "Image", "Video", "Icon", "Background", "Other"]; - for (const type of order) { - const group = groups[type]; - if (!group || group.length === 0) continue; - - const sectionName = - type === "Font" - ? "Fonts" - : type === "Image" - ? "Images" - : type === "Video" - ? "Videos" - : type === "Icon" - ? "Icons" - : type === "Background" - ? "Backgrounds" - : "Other"; - lines.push(`### ${sectionName}`); - - for (const a of group) { - const name = a.notes || deriveAssetName(a.url); - const contexts = a.contexts.join(", "); - lines.push(`- **${name}**: ${a.url} — contexts: ${contexts}`); - } - lines.push(""); - } - - return lines.join("\n"); -} - -/** - * Derive a human-readable name from a URL's file path. - * E.g., "ConnectBentoBackground.jpg" → "Connect Bento Background" - */ -function deriveAssetName(url: string): string { - try { - const u = new URL(url); - const path = u.pathname; - // Get filename without extension - const filename = path.split("/").pop() || ""; - const nameWithoutExt = filename.replace(/\.[^.]+$/, ""); - // Remove hash suffixes (e.g., "Sohne.cb178166" → "Sohne") - const cleaned = nameWithoutExt.replace(/\.[a-f0-9]{6,}$/, ""); - // Convert camelCase/PascalCase to spaces - const spaced = cleaned - .replace(/([a-z])([A-Z])/g, "$1 $2") - .replace(/[-_]/g, " ") - .replace(/\s+/g, " ") - .trim(); - return spaced || filename; - } catch { - return "Asset"; - } -} diff --git a/packages/core/src/parsers/htmlParser.ts b/packages/core/src/parsers/htmlParser.ts index 384b65f53..23fae3a2b 100644 --- a/packages/core/src/parsers/htmlParser.ts +++ b/packages/core/src/parsers/htmlParser.ts @@ -10,7 +10,6 @@ import type { StageZoomKeyframe, CompositionVariable, } from "../core.types"; -import { CANVAS_DIMENSIONS } from "../core.types"; import { parseGsapScript, validateCompositionGsap, @@ -902,5 +901,3 @@ function extractGsapScript(doc: Document): string | null { } return null; } - -export { CANVAS_DIMENSIONS }; diff --git a/packages/engine/src/services/hdrCapture.ts b/packages/engine/src/services/hdrCapture.ts index 91494244b..2d2975780 100644 --- a/packages/engine/src/services/hdrCapture.ts +++ b/packages/engine/src/services/hdrCapture.ts @@ -158,46 +158,6 @@ export async function initHdrReadback(page: Page, width: number, height: number) // ── HDR frame conversion ────────────────────────────────────────────────────── -/** - * Convert raw rgba64le pixels (from FFmpeg) to a base64 string for FFmpeg encoding. - * - * For HLG sources: the pixel values are already HLG-encoded. We pass them through - * as-is (normalized to 16-bit) and tag the output as HLG. No OETF conversion needed — - * the HLG signal values ARE the correct encoding. Converting to linear and back to - * PQ produces worse results because every viewer's PQ→display tone-mapping differs - * from its HLG→display tone-mapping. - * - * The WebGPU round-trip is skipped for pass-through — the pixels go directly from - * FFmpeg extraction to FFmpeg encoding. WebGPU is only needed when transforms - * (scale, rotate, opacity from GSAP) must be applied to the HDR pixels. - */ -export function convertHdrFrameToRgb48le( - rawRgba64le: Buffer, - width: number, - height: number, -): Buffer { - const input = new Uint16Array( - rawRgba64le.buffer, - rawRgba64le.byteOffset, - rawRgba64le.byteLength / 2, - ); - - // Convert RGBA → RGB (drop alpha) for rgb48le output - const output = Buffer.alloc(width * height * 6); - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const srcIdx = (y * width + x) * 4; - const dstIdx = (y * width + x) * 6; - output.writeUInt16LE(input[srcIdx] ?? 0, dstIdx); - output.writeUInt16LE(input[srcIdx + 1] ?? 0, dstIdx + 2); - output.writeUInt16LE(input[srcIdx + 2] ?? 0, dstIdx + 4); - } - } - - return output; -} - // ── Frame upload + readback ─────────────────────────────────────────────────── /** diff --git a/packages/shader-transitions/src/webgl.ts b/packages/shader-transitions/src/webgl.ts index a36ffe51f..1a2c3584e 100644 --- a/packages/shader-transitions/src/webgl.ts +++ b/packages/shader-transitions/src/webgl.ts @@ -147,16 +147,6 @@ export function createTexture(gl: WebGLRenderingContext): WebGLTexture { return tex; } -export function uploadTexture( - gl: WebGLRenderingContext, - tex: WebGLTexture, - canvas: HTMLCanvasElement, -): void { - uploadTextureSource(gl, tex, canvas); - canvas.width = 0; - canvas.height = 0; -} - export function uploadTextureSource( gl: WebGLRenderingContext, tex: WebGLTexture, diff --git a/packages/studio/src/components/editor/TimelineLayerPanel.tsx b/packages/studio/src/components/editor/TimelineLayerPanel.tsx index a87e2ee07..3a0447a0f 100644 --- a/packages/studio/src/components/editor/TimelineLayerPanel.tsx +++ b/packages/studio/src/components/editor/TimelineLayerPanel.tsx @@ -1,14 +1,5 @@ -import { memo } from "react"; import type { DomEditLayerItem } from "./domEditing"; -interface TimelineLayerPanelProps { - clipLabel: string; - layers: DomEditLayerItem[]; - selectedLayerKey: string | null; - onSelectLayer: (layer: DomEditLayerItem) => void; - onClose: () => void; -} - const MEDIA_LAYER_TAGS = new Set(["audio", "canvas", "img", "picture", "svg", "video"]); export function getTimelineLayerPanelSummary(layers: readonly DomEditLayerItem[]): string { @@ -22,92 +13,3 @@ export function getTimelineLayerPanelSummary(layers: readonly DomEditLayerItem[] ? "Single selectable media layer" : "Single selectable layer"; } - -export const TimelineLayerPanel = memo(function TimelineLayerPanel({ - clipLabel, - layers, - selectedLayerKey, - onSelectLayer, - onClose, -}: TimelineLayerPanelProps) { - return ( -
-
-
-
- Clip layers -
-
{clipLabel}
-
- -
-
- {getTimelineLayerPanelSummary(layers)} -
-
- {layers.map((layer) => { - const selected = layer.key === selectedLayerKey; - return ( - - ); - })} -
-
- ); -}); diff --git a/packages/studio/src/components/editor/domEditingElement.ts b/packages/studio/src/components/editor/domEditingElement.ts index 96d33ea12..4a339c859 100644 --- a/packages/studio/src/components/editor/domEditingElement.ts +++ b/packages/studio/src/components/editor/domEditingElement.ts @@ -12,9 +12,7 @@ import type { import { buildStableSelector, escapeCssString, - findClosestByAttribute, getElementDepth, - getPreferredClassSelector, getSelectorIndex, getSourceFileForElement, isHtmlElement, @@ -60,7 +58,7 @@ function isEmptyVisualContainer(el: HTMLElement): boolean { return true; } -export function hasRenderedBox(el: HTMLElement): boolean { +function hasRenderedBox(el: HTMLElement): boolean { const rect = el.getBoundingClientRect(); if (rect.width <= 1 || rect.height <= 1) return false; if (!isElementComputedVisible(el)) return false; @@ -324,7 +322,3 @@ export function getDirectLayerChildren( isHtmlElement(child) && getDomLayerPatchTarget(child, options.activeCompositionPath) !== null, ); } - -// ─── Composition source helpers ─────────────────────────────────────────────── - -export { findClosestByAttribute, getPreferredClassSelector, getSourceFileForElement }; diff --git a/packages/studio/src/components/editor/manualEditingAvailability.test.ts b/packages/studio/src/components/editor/manualEditingAvailability.test.ts index 7fde62f30..aa8968bb6 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.test.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.test.ts @@ -23,37 +23,15 @@ describe("manual editing availability", () => { expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(true); expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true); expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(false); - expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true); }); - it("keeps explicit truthy inspector env flags enabled", async () => { - const availability = await loadAvailabilityWithEnv({ - VITE_STUDIO_ENABLE_INSPECTOR_PANELS: "1", - VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "true", - }); - - expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true); - expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true); - }); - - it("allows explicit env flags to disable default-on inspector layers", async () => { - const availability = await loadAvailabilityWithEnv({ - VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "off", - }); - - expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true); - expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(false); - }); - - it("keeps timeline layer inspection off when the parent inspector flag is off", async () => { + it("disables preview selection when the inspector panel flag is explicitly off", async () => { const availability = await loadAvailabilityWithEnv({ VITE_STUDIO_ENABLE_INSPECTOR_PANELS: "0", - VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "true", }); expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(false); expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(false); - expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(false); }); it("enables feature flags with explicit truthy env values", () => { diff --git a/packages/studio/src/components/editor/manualEditingAvailability.ts b/packages/studio/src/components/editor/manualEditingAvailability.ts index 703211a13..bd3b59c5b 100644 --- a/packages/studio/src/components/editor/manualEditingAvailability.ts +++ b/packages/studio/src/components/editor/manualEditingAvailability.ts @@ -1,10 +1,8 @@ export type StudioFeatureFlagEnv = Record; -export const STUDIO_PREVIEW_MANUAL_DRAGGING_ENV = "VITE_STUDIO_ENABLE_PREVIEW_MANUAL_DRAGGING"; -export const STUDIO_INSPECTOR_PANELS_ENV = "VITE_STUDIO_ENABLE_INSPECTOR_PANELS"; -export const STUDIO_MOTION_PANEL_ENV = "VITE_STUDIO_ENABLE_MOTION_PANEL"; -export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENV = "VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR"; - +const STUDIO_PREVIEW_MANUAL_DRAGGING_ENV = "VITE_STUDIO_ENABLE_PREVIEW_MANUAL_DRAGGING"; +const STUDIO_INSPECTOR_PANELS_ENV = "VITE_STUDIO_ENABLE_INSPECTOR_PANELS"; +const STUDIO_MOTION_PANEL_ENV = "VITE_STUDIO_ENABLE_MOTION_PANEL"; const TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on", "enabled"]); const FALSY_ENV_VALUES = new Set(["0", "false", "no", "off", "disabled"]); @@ -52,14 +50,6 @@ export const STUDIO_MOTION_PANEL_ENABLED = resolveStudioBooleanEnvFlag( false, ); -export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED = - STUDIO_INSPECTOR_PANELS_ENABLED && - resolveStudioBooleanEnvFlag( - env, - [STUDIO_TIMELINE_LAYER_INSPECTOR_ENV, "VITE_STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED"], - true, - ); - export const STUDIO_BLOCKS_PANEL_ENABLED = resolveStudioBooleanEnvFlag( env, ["VITE_STUDIO_ENABLE_BLOCKS_PANEL", "VITE_STUDIO_BLOCKS_PANEL_ENABLED"], @@ -68,6 +58,4 @@ export const STUDIO_BLOCKS_PANEL_ENABLED = resolveStudioBooleanEnvFlag( export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED; -export const STUDIO_MANUAL_EDITING_ENABLED = STUDIO_PREVIEW_MANUAL_EDITING_ENABLED; - export const STUDIO_MANUAL_EDITING_DISABLED_TITLE = "Manual editing is temporarily disabled"; diff --git a/packages/studio/src/components/editor/manualEditsSnapshot.ts b/packages/studio/src/components/editor/manualEditsSnapshot.ts index ea14f255d..1cf840ff5 100644 --- a/packages/studio/src/components/editor/manualEditsSnapshot.ts +++ b/packages/studio/src/components/editor/manualEditsSnapshot.ts @@ -11,7 +11,6 @@ import { STUDIO_HEIGHT_PROP, STUDIO_ROTATION_PROP, STUDIO_PATH_OFFSET_ATTR, - STUDIO_MANUAL_EDIT_GESTURE_ATTR, STUDIO_BOX_SIZE_ATTR, STUDIO_ROTATION_ATTR, STUDIO_ORIGINAL_TRANSLATE_ATTR, @@ -33,7 +32,6 @@ import { STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, STUDIO_ROTATION_DRAFT_ATTR, - STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, } from "./manualEditsTypes"; import type { StudioBoxSizeSnapshot, @@ -187,38 +185,6 @@ export function restoreStudioPathOffset( ); } -/* ── DOM element collection ───────────────────────────────────────── */ -export function collectStudioManualEditElements(doc: Document): HTMLElement[] { - const htmlElement = doc.defaultView?.HTMLElement; - if (!htmlElement) return []; - - const elements = [doc.documentElement, ...Array.from(doc.getElementsByTagName("*"))].filter( - (element): element is HTMLElement => element instanceof htmlElement, - ); - - return elements.filter( - (element) => - element.hasAttribute(STUDIO_PATH_OFFSET_ATTR) || - element.hasAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR) || - element.hasAttribute(STUDIO_BOX_SIZE_ATTR) || - element.hasAttribute(STUDIO_ROTATION_ATTR) || - element.hasAttribute(STUDIO_ROTATION_DRAFT_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_ROTATE_ATTR) || - element.hasAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR) || - Boolean(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) || - Boolean(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) || - Boolean(element.style.getPropertyValue(STUDIO_WIDTH_PROP)) || - Boolean(element.style.getPropertyValue(STUDIO_HEIGHT_PROP)) || - Boolean(element.style.getPropertyValue(STUDIO_ROTATION_PROP)), - ); -} - /* ── Clear functions ──────────────────────────────────────────────── */ type BoxSizeProperty = | "width" diff --git a/packages/studio/src/icons/SystemIcons.tsx b/packages/studio/src/icons/SystemIcons.tsx index 5058fce5f..3a43f29fb 100644 --- a/packages/studio/src/icons/SystemIcons.tsx +++ b/packages/studio/src/icons/SystemIcons.tsx @@ -1,54 +1,18 @@ import { - WarningCircle, - Warning, - ArrowLeft as PhArrowLeft, Check as PhCheck, - CheckCircle as PhCheckCircle, - Circle as PhCircle, Clock as PhClock, - Code as PhCode, - DownloadSimple, - Pencil as PhPencil, - ArrowSquareOut, Eye as PhEye, - EyeClosed, - File as PhFile, - FileCode as PhFileCode, - FileText as PhFileText, FilmStrip, - Heart as PhHeart, - Image as PhImage, - Info as PhInfo, Stack, - SpinnerGap, - ArrowsOut, - CornersOut, - ChatCircle, ChatCenteredText, - Cursor, ArrowsOutCardinal, MusicNote, Palette as PhPalette, - Paperclip as PhPaperclip, - Pause as PhPause, - Play as PhPlay, Plus as PhPlus, - MagnifyingGlass, - PaperPlaneRight, - SkipBack as PhSkipBack, - SkipForward as PhSkipForward, Square as PhSquare, - Trash, TextT, - UploadSimple, - User as PhUser, - UsersThree, - VideoCamera, X as PhX, Lightning, - MagnifyingGlassPlus, - MagnifyingGlassMinus, - Terminal as PhTerminal, CaretDown, CaretRight, ClipboardText, @@ -69,60 +33,21 @@ const makeIcon = (Icon: PhosphorIcon) => { }; // Lucide name → Phosphor equivalent -export const AlertCircle = makeIcon(WarningCircle); -export const AlertTriangle = makeIcon(Warning); -export const ArrowLeft = makeIcon(PhArrowLeft); export const Check = makeIcon(PhCheck); -export const CheckCircle = makeIcon(PhCheckCircle); -/** CheckCircle2 in lucide is visually identical to CheckCircle */ -export const CheckCircle2 = makeIcon(PhCheckCircle); -export const Circle = makeIcon(PhCircle); export const Clock = makeIcon(PhClock); -export const Code = makeIcon(PhCode); -export const Download = makeIcon(DownloadSimple); -export const Edit2 = makeIcon(PhPencil); -export const ExternalLink = makeIcon(ArrowSquareOut); export const Eye = makeIcon(PhEye); -export const EyeOff = makeIcon(EyeClosed); -export const File = makeIcon(PhFile); -export const FileCode = makeIcon(PhFileCode); -export const FileText = makeIcon(PhFileText); export const Film = makeIcon(FilmStrip); -export const Heart = makeIcon(PhHeart); -export const Image = makeIcon(PhImage); -export const Info = makeIcon(PhInfo); export const Layers = makeIcon(Stack); -export const Loader2 = makeIcon(SpinnerGap); -export const Maximize = makeIcon(ArrowsOut); -export const Maximize2 = makeIcon(CornersOut); -export const MessageCircle = makeIcon(ChatCircle); export const MessageSquare = makeIcon(ChatCenteredText); -export const MousePointer = makeIcon(Cursor); export const Move = makeIcon(ArrowsOutCardinal); export const Music = makeIcon(MusicNote); export const Palette = makeIcon(PhPalette); -export const Paperclip = makeIcon(PhPaperclip); -export const Pause = makeIcon(PhPause); -export const Pencil = makeIcon(PhPencil); -export const Play = makeIcon(PhPlay); export const Plus = makeIcon(PhPlus); -export const Search = makeIcon(MagnifyingGlass); -export const Send = makeIcon(PaperPlaneRight); -export const SkipBack = makeIcon(PhSkipBack); -export const SkipForward = makeIcon(PhSkipForward); export const Square = makeIcon(PhSquare); -export const Trash2 = makeIcon(Trash); export const Type = makeIcon(TextT); -export const Upload = makeIcon(UploadSimple); -export const User = makeIcon(PhUser); -export const Users = makeIcon(UsersThree); -export const Video = makeIcon(VideoCamera); export const X = makeIcon(PhX); export const Zap = makeIcon(Lightning); -export const ZoomIn = makeIcon(MagnifyingGlassPlus); -export const ZoomOut = makeIcon(MagnifyingGlassMinus); // Extra icons used in this project (not in lucide's default mapping above) -export const Terminal = makeIcon(PhTerminal); export const ChevronDown = makeIcon(CaretDown); export const ChevronRight = makeIcon(CaretRight); export const ClipboardList = makeIcon(ClipboardText); diff --git a/packages/studio/src/player/components/useTimelineRangeSelection.ts b/packages/studio/src/player/components/useTimelineRangeSelection.ts index 6bfc2a555..6183057e7 100644 --- a/packages/studio/src/player/components/useTimelineRangeSelection.ts +++ b/packages/studio/src/player/components/useTimelineRangeSelection.ts @@ -144,19 +144,3 @@ export function useTimelineRangeSelection({ handlePointerUp, }; } - -/* ── Seek + scroll utilities (used in Timeline only) ──────────────── */ -export function seekTimeFromScrollX( - scrollEl: HTMLDivElement, - clientX: number, - effectiveDuration: number, - pps: number, - onSeek?: (time: number) => void, -): void { - const rect = scrollEl.getBoundingClientRect(); - const x = clientX - rect.left + scrollEl.scrollLeft - GUTTER; - if (x < 0) return; - const time = Math.max(0, Math.min(effectiveDuration, x / pps)); - liveTime.notify(time); - onSeek?.(time); -} diff --git a/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts b/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts index 54c28732f..06dd91221 100644 --- a/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts +++ b/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts @@ -18,7 +18,6 @@ import { findTimelineDomNodeForClip, createImplicitTimelineLayersFromDOM, buildStandaloneRootTimelineElement, - mergeTimelineElementsPreservingDowngrades, getTimelineElementSelector, } from "../lib/timelineDOM"; import { @@ -26,7 +25,6 @@ import { autoHealMissingCompositionIds, buildMissingCompositionElements, } from "../lib/timelineIframeHelpers"; -import { getTimelineElementIdentity } from "../lib/timelineElementHelpers"; interface UseTimelineSyncCallbacksParams { iframeRef: React.RefObject; @@ -288,7 +286,3 @@ export function useTimelineSyncCallbacks({ onIframeLoad, }; } - -// Re-export the merge helper so the hook can use it via this module (avoids -// adding another import line to the already-large useTimelinePlayer.ts). -export { mergeTimelineElementsPreservingDowngrades, getTimelineElementIdentity }; diff --git a/packages/studio/src/utils/studioHelpers.ts b/packages/studio/src/utils/studioHelpers.ts index 3790dee6f..b193364c4 100644 --- a/packages/studio/src/utils/studioHelpers.ts +++ b/packages/studio/src/utils/studioHelpers.ts @@ -23,13 +23,7 @@ export function getTimelineElementLabel(element: TimelineElement): string { return element.label || element.id || element.tag; } -export function confirmElementDelete(label: string, kind: "timeline clip" | "element"): boolean { - return window.confirm( - `Delete ${kind} "${label}"?\n\nThis removes it from the project source. You can use Undo to restore it.`, - ); -} - -export function normalizeProjectAssetPath(value: string): string { +function normalizeProjectAssetPath(value: string): string { const trimmed = value.trim(); const maybeUrl = /^[a-z]+:\/\//i.test(trimmed) ? new URL(trimmed).pathname : trimmed; return decodeURIComponent(maybeUrl) @@ -51,7 +45,7 @@ export function toRelativeProjectAssetPath(sourceFile: string, assetPath: string return [...fromParts.map(() => ".."), ...targetParts].join("/") || assetPath; } -export function isAbsoluteFilePath(value: string): boolean { +function isAbsoluteFilePath(value: string): boolean { return /^(?:\/|[A-Za-z]:[\\/]|\\\\)/.test(value); } @@ -181,7 +175,7 @@ export function collectHtmlIds(source: string): string[] { return Array.from(source.matchAll(/\bid="([^"]+)"/g), (match) => match[1] ?? ""); } -export const DEFAULT_TIMELINE_ASSET_DURATION: Record = { +const DEFAULT_TIMELINE_ASSET_DURATION: Record = { image: 3, video: 5, audio: 5, diff --git a/packages/studio/src/utils/timelineDiscovery.ts b/packages/studio/src/utils/timelineDiscovery.ts index b449a4f1e..f4027953e 100644 --- a/packages/studio/src/utils/timelineDiscovery.ts +++ b/packages/studio/src/utils/timelineDiscovery.ts @@ -1,6 +1,4 @@ export const TIMELINE_TOGGLE_SHORTCUT_LABEL = "Shift+T"; -const TIMELINE_EDITOR_HINT_STORAGE_KEY = "hf-studio-timeline-editor-hint-dismissed"; - type TimelineToggleHotkeyEvent = Pick< KeyboardEvent, "key" | "shiftKey" | "metaKey" | "ctrlKey" | "altKey" | "target" @@ -41,17 +39,3 @@ export function shouldHandleTimelineToggleHotkey(event: TimelineToggleHotkeyEven export function getTimelineToggleTitle(timelineVisible: boolean): string { return `${timelineVisible ? "Hide" : "Show"} timeline editor (${TIMELINE_TOGGLE_SHORTCUT_LABEL})`; } - -export function getTimelineEditorHintDismissed(): boolean { - if (typeof window === "undefined") return false; - return window.localStorage.getItem(TIMELINE_EDITOR_HINT_STORAGE_KEY) === "1"; -} - -export function setTimelineEditorHintDismissed(dismissed: boolean): void { - if (typeof window === "undefined") return; - if (dismissed) { - window.localStorage.setItem(TIMELINE_EDITOR_HINT_STORAGE_KEY, "1"); - return; - } - window.localStorage.removeItem(TIMELINE_EDITOR_HINT_STORAGE_KEY); -}