Skip to content

Commit 7bff49e

Browse files
refactor(studio): simplify hooks, split contexts, remove dead code (#1416)
* fix(studio): guard Zustand no-op setters and fix useConsoleErrorCapture memory leak - Guard setIsPlaying to skip set() when value unchanged (eliminates 60 notifications/sec during reverse playback) - Guard caption store selectGroup to bail before set() when group missing (prevents empty Zustand notifications) - Guard clearSelection to skip when already empty - Fix useConsoleErrorCapture: restore original console.error, remove error event listener, and delete __hfErrorCapture flag on cleanup * fix(studio): delete dead files and unused exports Remove 7 dead files (audioBeatDetection, keyframeSnapping, timelineInspector, DopesheetStrip, StaggerControls, TimelineLayerPanel, TimelineEditorNotice) and their test companions. Delete unused computeFitToChildrenSize export from propertyPanelHelpers. Fix re-export indirection: useDomEditCommits and studioMotionOps.test now import patch builders directly from manualEditsDomPatches instead of the re-export passthrough in manualEditsDom. * fix(studio): eliminate effect-chain state mirroring for lint findings, hover, and GSAP fetch Move lint findingsByElement sync from App.tsx into useLintModal where the value is produced, removing the mirroring useEffect. Consolidate 4 hover-clearing effects in useDomSelection into 2 (one unconditional on context change, one conditional combining caption mode, selection match, and disconnected element checks). Fold the GSAP retry effect into the fetch effect in useGsapTweenCache, scheduling a single retry via setTimeout when the initial fetch returns 0 animations. Eliminates 3 unnecessary render cycles from effect chains. * fix(studio): memoize renderQueue, toolbar, and canvas rect to prevent re-render cascade - Wrap renderQueue object in useMemo so StudioContext consumers don't re-render on every App render - Memoize timelineToolbar JSX so NLELayout memo isn't defeated - Move canvasRect getBoundingClientRect() from render-time IIFE to a useLayoutEffect-backed ref, eliminating layout thrashing - Track and clear setTimeout handles in refreshPreviewDocumentVersion to prevent stale timer accumulation on rapid calls and unmount * refactor(studio): consolidate GSAP shared primitives — defaults, iframe access, keyframe parsing Extract duplicated PROPERTY_DEFAULTS, IframeGsap interface, iframe accessors (getIframeGsap, queryIframeElement), percentage keyframe parsing, and toAbsoluteTime into a single gsapShared.ts module. Removes ~120 lines of copy-pasted logic across 8 hook files, reducing drift risk between the duplicate implementations. * fix(studio): remove dead store fields, dead file, duplicate helper, and unsafe assertions * refactor(studio): deduplicate selector helpers, rounding utils, percentage computation, and iframe access * fix(studio): split StudioContext into Shell + Playback to prevent cascade re-renders * refactor(studio): decompose useGsapScriptCommits into focused mutation hooks * refactor(studio): decompose useFileManager into focused file operation hooks Extract useFileTree (tree loading, refresh, derived assets/compositions) and useEditorSave (debounced save with history tracking) from the 508-LOC useFileManager. The parent hook composes both and retains file I/O, click-to-source, upload/import, and CRUD — preserving the same public interface so no consumers change. * refactor(studio): decompose useDomEditCommits into focused commit hooks Extract geometry (path offset, box size, rotation) and element lifecycle (delete, z-index reorder) into useDomGeometryCommits and useElementLifecycleOps. Parent keeps persistDomEditOperations as core and composes all sub-hooks — public interface unchanged. * refactor(studio): simplify useAppHotkeys with declarative command table * refactor(studio): simplify useAppHotkeys with declarative command table Replace 15 individual useRef callback refs with a single cbRef object. Extract keydown dispatch into pure dispatchModifierKey/dispatchPlainKey functions. Merge duplicate undo/redo logic into shared applyHistory. Extract cross-origin listener boilerplate into safeAddListener/safeRemoveListener. Hook body: 204 LOC (down from 445). Public API unchanged. * fix(studio): remove unused getDomEditTargetKey import * refactor(studio): decompose useDomEditSession into focused editing hooks Extract GSAP-aware geometry intercepts (move/resize/rotation) and animated property commit into useGsapAwareEditing, and selection wiring, GSAP cache management, preview sync, and selection handlers into useDomEditWiring. The parent remains a pure composition shell. * style(studio): fix formatting in 5 files * fix(studio): trim App.tsx to 598 lines (under 600 limit) --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
1 parent 6f67729 commit 7bff49e

77 files changed

Lines changed: 3113 additions & 3520 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.fallowrc.jsonc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"packages/studio/src/components/nle/TimelineEditorNotice.tsx",
3636
// Zoom hook extracted for downstream razor-blade PRs (#1330, #1331).
3737
"packages/studio/src/player/components/useTimelineZoom.ts",
38+
// Preview helper consumed dynamically from the studio iframe bridge.
39+
"packages/studio/src/hooks/gsapRuntimePreview.ts",
3840
],
3941
"ignorePatterns": [
4042
"docs/**",

lefthook-local.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
commit-msg:
2+
commands:
3+
coauthor:
4+
run: |
5+
MSG_FILE="{1}"
6+
TRAILER1="Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>"
7+
if ! grep -qF "$TRAILER1" "$MSG_FILE"; then
8+
# Ensure blank line before trailers
9+
if [ -n "$(tail -c1 "$MSG_FILE")" ]; then
10+
echo "" >> "$MSG_FILE"
11+
fi
12+
echo "" >> "$MSG_FILE"
13+
echo "$TRAILER1" >> "$MSG_FILE"
14+
fi

packages/studio/src/App.tsx

Lines changed: 174 additions & 170 deletions
Large diffs are not rendered by default.

packages/studio/src/captions/store.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const initialState = {
5959
sourceFilePath: null,
6060
};
6161

62-
export const useCaptionStore = create<CaptionState>((set) => ({
62+
export const useCaptionStore = create<CaptionState>((set, get) => ({
6363
...initialState,
6464

6565
// Basic
@@ -82,15 +82,11 @@ export const useCaptionStore = create<CaptionState>((set) => ({
8282
return { selectedSegmentIds: new Set([id]), selectedGroupId: null };
8383
}),
8484

85-
selectGroup: (id) =>
86-
set((state) => {
87-
const group = state.model?.groups.get(id);
88-
if (!group) return {};
89-
return {
90-
selectedSegmentIds: new Set(group.segmentIds),
91-
selectedGroupId: id,
92-
};
93-
}),
85+
selectGroup: (id) => {
86+
const group = get().model?.groups.get(id);
87+
if (!group) return;
88+
set({ selectedSegmentIds: new Set(group.segmentIds), selectedGroupId: id });
89+
},
9490

9591
selectAll: () =>
9692
set((state) => {
@@ -101,7 +97,11 @@ export const useCaptionStore = create<CaptionState>((set) => ({
10197
};
10298
}),
10399

104-
clearSelection: () => set({ selectedSegmentIds: new Set(), selectedGroupId: null }),
100+
clearSelection: () => {
101+
const { selectedSegmentIds, selectedGroupId } = get();
102+
if (selectedSegmentIds.size === 0 && selectedGroupId === null) return;
103+
set({ selectedSegmentIds: new Set(), selectedGroupId: null });
104+
},
105105

106106
// Segment mutations
107107
updateSegmentStyle: (segmentId, style) =>

packages/studio/src/components/StudioHeader.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {
55
STUDIO_MANUAL_EDITING_DISABLED_TITLE,
66
} from "./editor/manualEditingAvailability";
77
import { getHistoryShortcutLabel } from "../utils/studioHelpers";
8-
import { useStudioContext } from "../contexts/StudioContext";
8+
import { useStudioShellContext } from "../contexts/StudioContext";
99
import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
10-
import { useDomEditContext } from "../contexts/DomEditContext";
10+
import { useDomEditActionsContext } from "../contexts/DomEditContext";
1111
import { trackStudioEvent } from "../utils/studioTelemetry";
1212

1313
export interface StudioHeaderProps {
@@ -150,9 +150,9 @@ export function StudioHeader({
150150
inspectorPanelActive,
151151
onExport,
152152
}: StudioHeaderProps) {
153-
const { projectId, editHistory, handleUndo, handleRedo } = useStudioContext();
153+
const { projectId, editHistory, handleUndo, handleRedo } = useStudioShellContext();
154154
const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
155-
const { clearDomSelection } = useDomEditContext();
155+
const { clearDomSelection } = useDomEditActionsContext();
156156

157157
return (
158158
<div className="flex items-center justify-between h-10 px-3 bg-neutral-900 border-b border-neutral-800 flex-shrink-0">

packages/studio/src/components/StudioLeftSidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { LeftSidebar, type LeftSidebarHandle } from "./sidebar/LeftSidebar";
44
import { MediaPreview } from "./MediaPreview";
55
import { isMediaFile } from "../utils/mediaTypes";
66
import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
7-
import { useStudioContext } from "../contexts/StudioContext";
7+
import { useStudioShellContext } from "../contexts/StudioContext";
88
import { useFileManagerContext } from "../contexts/FileManagerContext";
99
import { getPersistedRenderSettings } from "./renders/renderSettings";
1010
import type { BlockPreviewInfo } from "./sidebar/BlocksTab";
@@ -39,7 +39,7 @@ export function StudioLeftSidebar({
3939
handlePanelResizeMove,
4040
handlePanelResizeEnd,
4141
} = usePanelLayoutContext();
42-
const { projectId, renderQueue, waitForPendingDomEditSaves } = useStudioContext();
42+
const { projectId, renderQueue, waitForPendingDomEditSaves } = useStudioShellContext();
4343
const {
4444
compositions,
4545
assets,

0 commit comments

Comments
 (0)