Extract page editor controllers from PagePane/TreePane (FormController pattern) #113
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#113
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by an architecture review (deepening opportunities — turning shallow modules into deep ones).
Files:
web/src/components/PagePane.tsx(2,569 lines),web/src/components/TreePane.tsx(1,453 lines); contrastweb/src/components/FormPane.tsxand itsFormControllerinterface.Problem: the entire page-editing controller (publish/discard/undo/insert/apply-result, 32 drag handlers, optimistic
mutate()vs authoritativerefetch()) is fused with JSX. Every operation repeats the same busy/error/switch-on-tagged-result/refetch shape;TreePane'sfollowMutation/runwrappers re-implementPagePane'sapplyResultdance. The only way to test any of it is mounting the whole<App/>and driving the DOM (App.test.tsx,ElementCrud.test.tsx,Publish.test.tsx,LayoutGrid.test.tsxall boot router + tree + elements + git state per assertion).Solution: extract
createPageEditor/createTreeControllerstores holding the operation logic, leaving the panes as thin presentation — the patternFormPanealready proves: it exportsElementForm+ aFormControllerinterface so tests drive the controller directly (FormItems.test.tsx).Benefits: Locality — the "mutate → refetch → follow selection" dance lives once instead of per-pane. Tests shrink from whole-app DOM boots to controller-against-mock-api units; the drag/DOM layer stays thin enough to leave to e2e.
Agent Brief
Category: enhancement
Summary: Extract the page-editing and tree operation logic out of the
PagePaneandTreePanecomponents into testable controller/store units, following theFormControllerpattern thatFormPanealready establishes.Current behavior:
PagePaneandTreePaneare the two largest components in the web app. Each fuses its full operation logic (publish/discard/undo, element insert/move/delete, page create/rename/move/delete, drag-and-drop orchestration) with its JSX. Both independently implement the same mutation shape — set a busy flag, call the API, switch on the tagged result, optimisticallymutate()the resource orrefetch()for the authoritative state, then repair/follow the selection.PagePanecentralizes this in an internalapplyResulthelper;TreePanere-implements it with its ownrunwrapper plusfollowMutation. Because the logic lives inside the components, the only way to test any operation is mounting the whole<App/>shell via the test harness and driving the DOM — which is what the publish, element-CRUD, page-management, and layout test suites all do today, booting router + tree + elements + git state per assertion.Desired behavior:
The operation logic lives in dedicated controller/store factories (e.g.
createPageEditorfor element-level operations and publish/discard/undo,createTreeControllerfor page-tree operations) alongside the existing store modules (pageTree,gitState,hugoStatus,pagetypes). Each factory takes its dependencies (API surface, selection/navigation callbacks) as inputs and exposes reactive accessors (busy/error state, current document) plus operation methods, so a test can drive a controller directly against the mock API without mounting the shell. The panes become thin presentation: they consume a controller and render, keeping only DOM concerns (drag event wiring, focus, layout). The "mutate → refetch → follow selection" dance exists exactly once, shared by both controllers.Key interfaces:
FormPane's exportedFormControllerinterface (submit/dirty/saving/savedaccessors) — the proven precedent: a plain interface of stable accessors plus operation methods that a host or test drives directly. The new controllers should follow its spirit; they do not need to literally extend it.storesdirectory (created when the API layer was split into transport/domain/stores) — the new factories should live there and match its conventions.followMutationhelpers — selection-following after mutations belongs to the controllers, not the JSX layer.Acceptance criteria:
PagePaneandTreePaneno longer contain API-result branching, busy/error bookkeeping, or refetch/selection-repair logic — only presentation and DOM event wiring that delegates to a controller.createPageEditorandcreateTreeControllerdirectly against the mock API (no<App/>mount) covering at least: a successful mutation with optimistic update, a failed mutation surfacing an error state, and a mutation that changes the selected page/element path.Out of scope:
scaffold/site/AGENTS.mdupdates (nothing engine-facing changes here).FormPane/ElementFormitself, or other large components (MediaModule,fields).Suggested sequencing (advisory, not binding): this can land as two PRs — extract
createTreeControllerfirst (smaller surface), thencreatePageEditor— if a single change proves unwieldy.