296 lines
12 KiB
Markdown
296 lines
12 KiB
Markdown
# Template V2 direct component rendering
|
|
|
|
This document describes the Template V2 presentation runtime on branch
|
|
`dipesh/pre-159-move-new-editor-code-to-main-codebase-for-proper-integration`.
|
|
|
|
The governing rule is:
|
|
|
|
> Select a layout → write the complete layout to `slide.ui` → render `slide.ui`
|
|
> directly → edit `slide.ui` → persist `slide.ui`.
|
|
|
|
`slide.ui` is the only frontend source of truth for Template V2 layout and
|
|
content. The presentation renderer does not read Template V2 content from
|
|
`slide.content` and does not maintain a second canonical `Slide` copy.
|
|
|
|
## Active flow
|
|
|
|
```text
|
|
selected Template V2 layout
|
|
|
|
|
v
|
|
slide.ui = raw 1280 x 720 layout JSON
|
|
|
|
|
v
|
|
V1ContentRender
|
|
|
|
|
v
|
|
TemplateV2KonvaSlide
|
|
|
|
|
+-- render raw ui.components with react-konva
|
|
+-- edit an immutable, structurally shared raw UI tree
|
|
+-- dispatch updateSlideUi({ index, ui })
|
|
|
|
|
v
|
|
Redux presentationData.slides[index].ui
|
|
|
|
|
v
|
|
debounced presentation autosave
|
|
```
|
|
|
|
The presentation runtime no longer performs this conversion loop:
|
|
|
|
```text
|
|
raw slide.ui
|
|
-> canonical editor Slide
|
|
-> layout resolver / SlideSurface
|
|
-> reverse serialization
|
|
-> slide.ui plus hidden slide.content state
|
|
```
|
|
|
|
This removes redundant object construction, schema translation, Jotai editor
|
|
state, layout resolution, and reverse serialization from the render/edit path.
|
|
|
|
## Primary implementation files
|
|
|
|
| File | Responsibility |
|
|
| --- | --- |
|
|
| [`TemplateV2KonvaSlide.tsx`](../servers/nextjs/components/slide-editor/surface/TemplateV2KonvaSlide.tsx) | The preserved Dipesh component, now responsible for raw UI rendering, selection, transforms, existing element toolbars, inline editing, insertions, chart/image integration, history, and UI commits. |
|
|
| [`V1ContentRender.tsx`](../servers/nextjs/app/(presentation-generator)/components/V1ContentRender.tsx) | Routes Template V2 presentation slides directly to the raw renderer. |
|
|
| [`events.ts`](../servers/nextjs/components/slide-editor/events/events.ts) | Shared insert, chart, and active-surface event contracts. |
|
|
| [`presentationGeneration.ts`](../servers/nextjs/store/slices/presentationGeneration.ts) | Owns `updateSlideUi`, which updates only the selected slide's `ui`. |
|
|
| [`NewSlide.tsx`](../servers/nextjs/app/(presentation-generator)/presentation/components/NewSlide.tsx) | Writes a selected Template V2 layout directly into a new slide's `ui`. |
|
|
| [`blank-slide.ts`](../servers/nextjs/app/(presentation-generator)/_shared/blank-slide.ts) | Supplies an empty raw UI with `components: []`. |
|
|
| [`useAutoSave.tsx`](../servers/nextjs/app/(presentation-generator)/presentation/hooks/useAutoSave.tsx) | Persists the Redux presentation after its debounce. |
|
|
|
|
There are two `NewSlide.tsx` entry points in the presentation-generator tree.
|
|
Both now use the same `slide.ui` behavior.
|
|
|
|
## Raw UI contract
|
|
|
|
The renderer consumes the backend Template V2 shape without converting units:
|
|
|
|
```json
|
|
{
|
|
"id": "layout_id",
|
|
"background": "#FFFFFF",
|
|
"components": [
|
|
{
|
|
"id": "hero",
|
|
"position": { "x": 80, "y": 80 },
|
|
"size": { "width": 1120, "height": 560 },
|
|
"elements": [
|
|
{
|
|
"type": "text",
|
|
"name": "title",
|
|
"position": { "x": 0, "y": 0 },
|
|
"size": { "width": 900, "height": 140 },
|
|
"runs": [{ "text": "Title" }]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Geometry is interpreted in the native `1280 x 720` stage coordinate system:
|
|
|
|
- component positions are slide-relative;
|
|
- top-level element positions are component-relative;
|
|
- nested element positions are parent-relative;
|
|
- positions, sizes, font sizes, gaps, padding, strokes, and radii remain raw
|
|
UI values.
|
|
|
|
The loose raw boundary is intentional. Alias readers handle known snake-case
|
|
and camel-case variants without rewriting the entire tree.
|
|
|
|
## Rendering and editing
|
|
|
|
`TemplateV2KonvaSlide` creates one fixed-size Konva Stage and renders
|
|
each raw component as a clipped Group. Raw elements are traversed recursively
|
|
through `children`, `elements`, `child`, or repeated `item` structures.
|
|
|
|
Supported visuals include:
|
|
|
|
- text and text lists;
|
|
- images and SVG;
|
|
- rectangles, ellipses, lines, groups, containers, flex, and grid;
|
|
- tables;
|
|
- bar, line, area, and pie-style charts;
|
|
- infographic/progress primitives.
|
|
|
|
Interaction behavior:
|
|
|
|
- components can be selected, dragged, resized, and rotated;
|
|
- a selected element can be dragged, resized, and rotated;
|
|
- the first pointer interaction on an unselected child continues to select or
|
|
move its component; a selected child receives its own drag;
|
|
- layout-managed children receive `__presenton_manual_position` after a manual
|
|
transform so the layout engine does not immediately overwrite the edit;
|
|
- text, text-list, table, and SVG values use inline overlays;
|
|
- images use the upload API and save the returned source in the raw element;
|
|
- charts use the shared chart-panel event bridge;
|
|
- the existing `ElementToolbar` controls are retained as a selected-element
|
|
editor boundary; only that selected element is projected into editor units,
|
|
while rendering and persistence remain raw `slide.ui` operations;
|
|
- component-level design-variable controls are retained through the same
|
|
editor boundary and write their changes back into the raw component tree;
|
|
- undo and redo store raw UI snapshots, not canonical editor Slides.
|
|
|
|
## Backend-shape compatibility
|
|
|
|
The renderer reads and preserves the backend's current data shapes:
|
|
|
|
- TextList items may be arrays of text runs, direct text records, or legacy
|
|
strings.
|
|
- Table cells may contain `runs`, a direct `text`, and either `color` or `fill`.
|
|
Editing preserves the existing cell and first-run style metadata.
|
|
- Chart series read `values`, with `data` accepted as a compatibility alias.
|
|
|
|
This matters because a direct renderer must not silently depend on the canonical
|
|
editor adapter to normalize these values first.
|
|
|
|
## Commit and persistence behavior
|
|
|
|
Every edit ends at one mutation boundary:
|
|
|
|
```ts
|
|
updateSlideUi({
|
|
index: slideIndex,
|
|
ui: nextUi,
|
|
})
|
|
```
|
|
|
|
The reducer changes only:
|
|
|
|
```text
|
|
presentationData.slides[slideIndex].ui
|
|
```
|
|
|
|
It does not replace the whole slide and does not write an editor snapshot into
|
|
`slide.content`. The existing autosave observes the Redux presentation object
|
|
and persists the updated `ui` after its debounce.
|
|
|
|
New Template V2 slides are initialized as follows:
|
|
|
|
```text
|
|
layout selection -> newSlide.ui = selected raw layout
|
|
empty selection -> newSlide.ui = { background: "#FFFFFF", components: [] }
|
|
```
|
|
|
|
`content: {}` is currently retained on newly created slides for the shared API
|
|
shape, but it is not a Template V2 rendering or editing source.
|
|
|
|
## Adapter cleanup status
|
|
|
|
The active Template V2 presentation runtime has no calls to:
|
|
|
|
- `adaptTemplateV2LayoutToSlide`;
|
|
- `normalizeTemplateV2Slide`;
|
|
- `serializeTemplateV2LayoutFromSlide`;
|
|
- `serializeTemplateV2ContentFromSlide`;
|
|
- `applyGeneratedSlideContentToLayout`;
|
|
- the old normalized slide layout resolver.
|
|
|
|
`TemplateV2KonvaSlide` was preserved and changed in place. Its old whole-slide
|
|
adapter, Jotai deck hydration, `SlideSurface` route, reverse serializers, and
|
|
hidden `__template_v2_konva_slide__` content fallback were removed.
|
|
|
|
Some similarly named utilities remain for separate consumers:
|
|
|
|
- `TemplateV2LayoutPreview` still uses `adaptTemplateV2LayoutToSlide` to feed
|
|
the shared preview surface.
|
|
- The reusable slide editor still uses `normalizeTemplateV2Slide` for canonical
|
|
`Slide` model conversion where needed.
|
|
- The Blocks/palette boundary can still convert a selected library component
|
|
into an insertion payload. This conversion occurs only on insertion, not on
|
|
every render or edit.
|
|
|
|
Deleting these shared utilities globally would break template previews and the
|
|
standalone canonical editor without improving presentation-render performance.
|
|
They are not mounted in the direct presentation path.
|
|
|
|
## Backend generation boundary
|
|
|
|
The FastAPI generator currently creates semantic `slide.content` and then calls
|
|
`_apply_template_v2_content_to_ui()` during generation/streaming. The result is
|
|
a hydrated `slide.ui`, which is what the frontend renders and persists.
|
|
|
|
That server operation is generation-time work, not a browser render adapter.
|
|
The stricter final architecture should generate named element values directly
|
|
into a copied UI tree and keep `slide.content` empty for Template V2. Removing
|
|
the server hydrator before replacing the generator output would produce blank
|
|
template values, so it is intentionally outside this frontend runtime cleanup.
|
|
|
|
## Performance analysis
|
|
|
|
The direct path is structurally cheaper than the old presentation path because
|
|
it removes full-tree model conversion and reverse conversion from mounts and
|
|
edits. It also avoids initializing the standalone slide editor's state graph for
|
|
each presentation surface.
|
|
|
|
The editor hot path is optimized as follows:
|
|
|
|
- the incoming immutable UI becomes the initial draft by reference; no
|
|
full-tree startup clone is performed;
|
|
- edits use structural sharing and copy only the UI/component/element path that
|
|
changed;
|
|
- unchanged components, nested elements, and visual nodes are memoized;
|
|
- undo/redo stores immutable UI references in a bounded 50-entry history
|
|
instead of deep-cloning the complete UI for every edit;
|
|
- inline typing keeps its draft inside the textarea component, so keystrokes do
|
|
not rerender the Stage;
|
|
- repeated `item` rendering reuses the immutable source object rather than
|
|
cloning it per occurrence and render;
|
|
- view-only Layers disable Konva hit testing;
|
|
- global deck history runs at the debounced autosave boundary and stores
|
|
immutable structurally shared snapshots instead of cloning/stringifying the
|
|
complete deck on every editor commit;
|
|
- autosave change detection uses Redux object identity rather than serializing
|
|
the complete presentation merely to compare it.
|
|
|
|
Costs that remain include redraw of the affected Konva Layer,
|
|
selected-element toolbar projection, and request serialization performed by the
|
|
API client when autosave executes.
|
|
|
|
The next performance work should be evidence-driven:
|
|
|
|
1. Profile initial render, drag-end commit, inline-edit commit, and autosave on a
|
|
large real deck.
|
|
2. Split unusually large slides across carefully measured Konva Layers if canvas
|
|
redraw is still a bottleneck after React subtree memoization.
|
|
3. Move autosave comparison/persistence to slide-level patches if full-deck
|
|
JSON serialization is measurable.
|
|
4. Add browser performance tests before claiming a specific speedup.
|
|
|
|
The architecture eliminates known redundant work, but “blazing fast” still
|
|
requires measurements on representative decks.
|
|
|
|
## Verification checklist
|
|
|
|
For a Template V2 slide:
|
|
|
|
1. Confirm selecting a normal or blank layout immediately creates `slide.ui`.
|
|
2. Confirm `V1ContentRender` passes only `slide.ui` plus slide identity to the
|
|
direct renderer.
|
|
3. Render text, TextList, table, image, chart, flex, grid, and nested elements.
|
|
4. Drag, resize, and rotate both a component and a selected element.
|
|
5. Edit text/list/table values and verify their raw source shape is preserved.
|
|
6. Insert a palette element and a reusable block.
|
|
7. Replace an image and update a chart.
|
|
8. Undo/redo, reload, and confirm the saved values remain in `slide.ui`.
|
|
9. Confirm the presentation runtime does not create
|
|
`__template_v2_konva_slide__` in `slide.content`.
|
|
10. Confirm template previews and the standalone slide editor still work,
|
|
because their adapters were deliberately retained.
|
|
|
|
## Rules to preserve
|
|
|
|
1. Treat `slide.ui` as the only Template V2 frontend source of truth.
|
|
2. Do not add a renderer-side merge from `slide.content`.
|
|
3. Do not convert the whole UI to a canonical `Slide` for presentation display.
|
|
4. Preserve unknown raw fields while editing known fields.
|
|
5. Keep raw geometry in one coordinate system.
|
|
6. Keep preview/editor compatibility code outside the presentation runtime.
|
|
7. Persist through `updateSlideUi` and the existing autosave boundary.
|
|
8. Benchmark before adding caching complexity.
|