Skip to content

Commit d0abe90

Browse files
authored
feat: Persist Studio manual edits via manifest (#593)
## Summary Studio manual geometry edits now persist as a project-local manifest instead of being baked into composition source on each gesture. The manifest lives at: ```text .hyperframes/studio-manual-edits.json ``` It is the source of truth for manual drag, resize, rotation, inspector geometry edits, group moves, and selected-layer reset. ## Architecture - **Manifest-backed edits**: each edit stores a kind (`path-offset`, `box-size`, `rotation`), a source-scoped target, and the edit values. - **Source-scoped resolution**: targets include `sourceFile`, `id`, `selector`, and `selectorIndex`, so duplicate selectors in nested compositions resolve against the owning source file. - **Additive CSS layer**: move uses CSS `translate`, resize writes stable dimensions/flex sizing, and rotation uses CSS `rotate` over the authored base. - **Shared replay runtime**: Studio preview, thumbnails, frame capture, producer renders, and CLI Studio renders/thumbnails all use the same core manual-edit render script. - **Animation-safe replay**: Studio reapplies the manual layer after load, refresh, timeline seeks, player operations, playback frames, thumbnail seeks, and render seeks instead of rewriting GSAP timelines. - **History and handoff**: the manifest is a normal project file, so undo/redo and agent edits can preserve, modify, or remove manual visual edits explicitly. ## User Impact Users can move, resize, rotate, group-move, and reset supported layers from the canvas or inspector, then refresh, capture thumbnails/screenshots, play animated compositions, and render videos without manual edits drifting away from the edited state. ## Main Files - `packages/studio/src/components/editor/manualEdits.ts` - `packages/studio/src/components/editor/DomEditOverlay.tsx` - `packages/studio/src/components/editor/PropertyPanel.tsx` - `packages/studio/src/App.tsx` - `packages/core/src/studio-api/helpers/manualEditsRenderScript.ts` - `packages/studio/vite.config.ts` - `packages/cli/src/server/studioServer.ts` - `packages/core/src/compiler/htmlBundler.ts` - `packages/producer/src/services/htmlCompiler.ts` - `packages/core/src/studio-api/routes/thumbnail.ts` - `packages/producer/src/services/fileServer.ts` - `packages/producer/src/services/renderOrchestrator.ts` ## Test Plan ```bash volta run --node 22.20.0 bun run build volta run --node 22.20.0 bun run --filter @hyperframes/core test -- src/studio-api/helpers/manualEditsRenderScript.test.ts volta run --node 22.20.0 bun run --filter @hyperframes/core typecheck volta run --node 22.20.0 bun run --filter @hyperframes/studio typecheck volta run --node 22.20.0 bun run --filter @hyperframes/cli typecheck volta run --node 22.20.0 bunx oxlint <changed files> volta run --node 22.20.0 bunx oxfmt --check <changed files> git diff --check ```
1 parent 1d15845 commit d0abe90

82 files changed

Lines changed: 15392 additions & 758 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.

docs/contributing.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ bun run build # Build all packages
5353
bun run --filter '*' typecheck # Type-check all packages
5454
```
5555

56+
### Studio Editing Work
57+
58+
If you are changing Studio's visual editing surface, read
59+
[Studio Manual DOM Editing](/contributing/studio-manual-dom-editing) before
60+
editing code. The inspector intentionally exposes only interactions it can
61+
persist safely back to HTML, so changes should preserve the capability gates,
62+
source patching model, and documented limitations.
63+
5664
### Running Tests
5765

5866
<CodeGroup>
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
---
2+
title: Studio Manual DOM Editing
3+
description: What the Studio manual DOM editing inspector ships today, including capabilities, UX, and constraints.
4+
---
5+
6+
This page documents the current manual DOM editing surface in HyperFrames Studio. It reflects the implementation that ships in the Studio inspector today, not the earlier design draft that explored third-party transform engines.
7+
8+
## What Shipped
9+
10+
Studio now supports a direct DOM editing workflow inside the preview:
11+
12+
- select supported elements directly in the preview
13+
- see an editor-owned overlay around the current selection
14+
- move and resize supported elements on canvas when geometry is safe
15+
- detach eligible layout-controlled layers with an explicit `Make movable` action
16+
- edit style properties from the right-side `Design` inspector
17+
- edit text layers for safe text-bearing selections, including empty text values
18+
- add and remove child text layers for multi-text selections
19+
- edit solid fills, gradients, project-asset image fills, external image fills, opacity, radius, flex metadata, typography, and blend mode
20+
- drill into nested compositions from master view instead of pretending every inner node is editable in place
21+
- generate an element-scoped `Ask agent` prompt bundle from the right inspector
22+
23+
The important rule is conservative: Studio only exposes interactions it can round-trip back to authored HTML with deterministic behavior.
24+
25+
## Current User Experience
26+
27+
### Preview selection
28+
29+
- Single click selects a patchable element in the preview.
30+
- The selection overlay is rendered in Studio chrome, not injected into authored content.
31+
- The overlay is cleared when:
32+
- the `Inspector` panel is closed
33+
- the user clicks an empty area in the preview
34+
- the underlying element disappears after a source refresh
35+
36+
### Overlay behavior
37+
38+
The overlay provides:
39+
40+
- selection bounds
41+
- drag behavior for supported elements
42+
- a resize handle when width and height are safely patchable
43+
- blocked-drag feedback for unsupported movement
44+
45+
The overlay intentionally does not include a floating action toolbar. `Ask agent` lives in the right inspector header, and style controls live in the `Design` panel.
46+
47+
The current implementation uses Studio-owned pointer handling in `DomEditOverlay.tsx`. It does **not** use `Moveable`.
48+
49+
### Inspector behavior
50+
51+
The `Design` panel currently includes:
52+
53+
- `Layout`
54+
- X / Y / W / H fields
55+
- wheel and arrow-key numeric scrubbing
56+
- `Make movable` for block-ish layout-controlled layers that can be detached safely
57+
- `Flex`
58+
- direction, justify, align, gap, clip content
59+
- `Radius`
60+
- slider + live readout
61+
- `Blending`
62+
- opacity slider + live readout
63+
- blend mode
64+
- `Fill`
65+
- solid color
66+
- multi-stop gradient editing
67+
- project asset image fills
68+
- inline image upload into the project assets list
69+
- external image URL fill
70+
- text color
71+
- `Color picker`
72+
- viewport-clamped floating picker
73+
- saturation / brightness crosshair
74+
- hue and alpha sliders
75+
- hex input
76+
- `Text`
77+
- direct text layer editing when the selection is safe to patch
78+
- add / remove text layers for child text selections
79+
- font size, weight, and family controls
80+
- `Selection colors`
81+
- a summary of detected colors for the current selection
82+
83+
The inspector is intentionally split from `Renders` with a `Design / Renders` tab control in the right panel. Switching to `Renders` does not mean the header-level `Inspector` panel is closed.
84+
85+
## What Counts As Editable
86+
87+
Studio builds a `DomEditSelection` and `DomEditCapabilities` object for each selection.
88+
89+
### Selection requirements
90+
91+
A node is only useful to Studio if it can be identified with a stable patch target, for example:
92+
93+
- `id`
94+
- stable selector
95+
- selector index scoped to the correct source file
96+
- composition host mapping when master view is involved
97+
98+
### Move support
99+
100+
Move is allowed only when the selected element:
101+
102+
- has a stable patch target
103+
- is `absolute` or `fixed`
104+
- has `left` and `top` values that resolve to pixel values
105+
- is not transform-driven (`transform: none`)
106+
107+
### Resize support
108+
109+
Resize is allowed only when move is already allowed and Studio can also safely patch pixel `width` and/or `height`.
110+
111+
### Detach from layout support
112+
113+
Some block-ish layers are selectable and style-editable, but cannot be moved directly because flex, grid, or normal document flow owns their position.
114+
115+
For those layers, Studio can expose `Make movable` instead of silently converting on drag. The action measures the current visual rect relative to the composition root and writes conservative inline geometry:
116+
117+
- `position: absolute`
118+
- `left`, `top`, `width`, and `height` in pixels
119+
- `margin: 0`
120+
121+
The UI explains that this detaches the layer from flex/grid flow and preserves the current visual position. Inline text nodes are not detached directly.
122+
123+
### Text editing support
124+
125+
Text editing is allowed only for safe text-bearing selections:
126+
127+
- supported text-bearing tags such as `div`, `span`, `p`, `strong`, and headings
128+
- self text selections or leaf child text layers
129+
- empty text values after a user clears the content
130+
- not a composition host
131+
132+
For multi-text selections, Studio shows a text-layer list. Users can select a specific text layer, edit content live, change size, weight, and font family, add a sibling text layer, or remove the active layer.
133+
134+
### Unsupported examples
135+
136+
Studio intentionally withholds direct geometry editing for:
137+
138+
- flex/grid children whose position is emergent from layout, unless the user chooses `Make movable`
139+
- transform-driven geometry
140+
- nested composition internals while the user is still in master view
141+
- nodes without a stable patch target
142+
- inline text spans as geometry targets
143+
144+
When geometry is blocked but style edits are still safe, the inspector shows the selection and the reason direct geometry editing is unavailable.
145+
146+
If the user tries to drag a blocked layer, Studio shows a toast. Layout-owned layers point users to `Make movable`; transform-driven or unsafe targets explain that direct move/resize is limited to absolute or fixed pixel geometry with no transform-driven layout.
147+
148+
## Nested Composition Rules
149+
150+
Nested compositions are handled explicitly.
151+
152+
### In master view
153+
154+
- clicking content inside a nested composition maps back to the composition host
155+
- supported composition hosts can move as a whole when their host geometry is safe
156+
- Studio does not expose direct inner-node geometry edits from the master preview
157+
- double click drills into the subcomposition
158+
159+
### After drill-down
160+
161+
- Studio resolves selections inside that composition normally
162+
- direct move/resize becomes available again if the selected inner node meets the capability rules
163+
- text, fill, gradient, image, radius, opacity, and typography edits apply to the selected inner node
164+
165+
This keeps Studio honest about what it can patch safely from the current editing context.
166+
167+
## Source Patching Model
168+
169+
Studio still uses authored HTML as the source of truth.
170+
171+
The manual DOM editing flow patches source through the existing patch pipeline in `packages/studio/src/utils/sourcePatcher.ts`.
172+
173+
Current patch types used by the inspector include:
174+
175+
- inline style patches
176+
- attribute patches for timeline-linked editing paths
177+
- text-content patches
178+
- detach-from-layout style patches
179+
180+
The flow is:
181+
182+
1. user selects or manipulates an element in the preview
183+
2. Studio resolves a stable target
184+
3. the preview is updated optimistically for interaction feedback
185+
4. the patch is written back to source
186+
5. the preview refreshes and selection is reattached
187+
188+
## Gradient Editing
189+
190+
The current gradient editor is a structured Studio control, not a raw CSS text field.
191+
192+
It supports:
193+
194+
- `linear`, `radial`, and `conic` gradients
195+
- repeating variants
196+
- multiple stops
197+
- stop insertion by clicking the preview strip
198+
- stop removal
199+
- angle control
200+
- radial shape and size controls
201+
- radial/conic center controls
202+
203+
The editor still serializes back to CSS `background-image`, but the inspector works with a parsed gradient model instead of forcing the user to type raw gradient syntax.
204+
205+
## Image Fill Editing
206+
207+
The image fill editor is no longer just a raw `background-image` input.
208+
209+
It supports:
210+
211+
- selecting an existing project image asset
212+
- uploading an image from the fill panel, which also adds it to the Assets tab
213+
- previewing the selected project asset in the panel
214+
- entering an external URL when the image is not a project asset
215+
216+
Studio serializes project asset selections back to `background-image: url(...)`, and rewrites asset URLs so nested subcomposition previews still resolve the image correctly.
217+
218+
## Color Editing
219+
220+
The color editor is a custom Studio popover instead of the native browser color dialog.
221+
222+
It supports:
223+
224+
- opening from the whole color row
225+
- staying inside the viewport near the clicked color
226+
- saturation / brightness picking with visible crosshair guides
227+
- hue and alpha controls with visible handles
228+
- a current color swatch, readout, and hex input
229+
230+
The picker writes CSS `rgb(...)` or `rgba(...)` values and preserves alpha through edits.
231+
232+
## Numeric Scrubbing
233+
234+
Numeric layout/detail inputs support lightweight design-tool-style nudging:
235+
236+
- mouse wheel over the focused field
237+
- `ArrowUp` / `ArrowDown`
238+
- `Shift` for larger steps
239+
- `Alt` for finer steps
240+
241+
This is currently used across the numeric commit fields in the inspector, including layout metrics and other numeric text inputs that parse cleanly as values plus units.
242+
243+
## Files That Own The Feature
244+
245+
The main implementation lives in:
246+
247+
- `packages/studio/src/App.tsx`
248+
- overall inspector wiring
249+
- selection lifecycle
250+
- preview hit testing
251+
- persistence hooks
252+
- detach-from-layout commit flow
253+
- `packages/studio/src/components/editor/DomEditOverlay.tsx`
254+
- overlay box, drag, resize, blocked-drag feedback
255+
- `packages/studio/src/components/editor/PropertyPanel.tsx`
256+
- right-side inspector UI
257+
- `packages/studio/src/components/editor/domEditing.ts`
258+
- selection resolution
259+
- capability gating
260+
- text field modeling
261+
- prompt generation
262+
- `packages/studio/src/components/editor/colorValue.ts`
263+
- color parsing, HSV conversion, and CSS color serialization
264+
- `packages/studio/src/components/editor/floatingPanel.ts`
265+
- viewport-safe floating panel placement for color picking
266+
- `packages/studio/src/components/editor/fontAssets.ts`
267+
- imported font asset helpers
268+
- `packages/studio/src/components/editor/fontCatalog.ts`
269+
- Google font catalog metadata and stylesheet URLs
270+
- `packages/studio/src/components/editor/gradientValue.ts`
271+
- gradient parsing, serialization, and stop editing helpers
272+
- `packages/studio/src/utils/sourcePatcher.ts`
273+
- source patch persistence
274+
275+
Supporting Studio shell changes also landed in:
276+
277+
- `packages/studio/src/components/nle/NLELayout.tsx`
278+
- `packages/studio/src/components/nle/NLEPreview.tsx`
279+
- `packages/studio/src/components/sidebar/CompositionsTab.tsx`
280+
- `packages/studio/src/components/sidebar/LeftSidebar.tsx`
281+
- `packages/studio/src/player/components/Player.tsx`
282+
- `packages/studio/src/player/components/Timeline.tsx`
283+
- `packages/studio/src/player/components/TimelineClip.tsx`
284+
- `packages/studio/src/player/hooks/useTimelinePlayer.ts`
285+
- `packages/studio/src/utils/mediaTypes.ts`
286+
287+
## Current Constraints
288+
289+
This feature is intentionally **not** a full general-purpose visual builder.
290+
291+
Still out of scope today:
292+
293+
- rotation
294+
- arbitrary transforms
295+
- snapping and alignment guides
296+
- multi-select
297+
- marquee selection
298+
- freeform editing of every DOM node regardless of layout model
299+
- editing nested subcomposition internals directly from the master preview without drill-down
300+
- automatic conversion to absolute positioning on drag without user confirmation
301+
- direct geometry editing of inline text spans
302+
303+
## Bottom Line
304+
305+
Studio manual DOM editing is now a narrow, deterministic visual editing layer over authored HTML.
306+
307+
It does **not** try to make the whole DOM freely editable. Instead it:
308+
309+
- keeps source HTML as the source of truth
310+
- exposes only patchable interactions
311+
- uses a Studio-owned overlay layer for direct manipulation
312+
- gives users a real inspector for safe style and text edits
313+
- treats nested compositions as drill-down boundaries instead of flattening them into an unsafe editing surface
314+
315+
That tradeoff is the reason the current feature feels reliable instead of deceptive.

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@
202202
"pages": [
203203
"contributing",
204204
"contributing/release-channels",
205-
"contributing/testing-local-changes"
205+
"contributing/testing-local-changes",
206+
"contributing/studio-manual-dom-editing"
206207
]
207208
},
208209
{

0 commit comments

Comments
 (0)