Conversation
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (0.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## sy-3833-2 #2291 +/- ##
=============================================
+ Coverage 64.67% 64.88% +0.21%
=============================================
Files 2595 2594 -1
Lines 112925 112313 -612
Branches 8334 8246 -88
=============================================
- Hits 73029 72875 -154
+ Misses 33769 33359 -410
+ Partials 6127 6079 -48
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Lays the server-side foundation for granular schematic mutations. Oracle codegen: - Adds the 'action' grammar production to the oracle DSL alongside fields, domains, and field omissions inside struct bodies. - Adds resolution.Action and analyzer.collectAction. resolveTypeRefs now walks action payload field types so cross-references inside action bodies resolve correctly to their qualified names. - New oracle/plugin/go/actions plugin generates a discriminated-union codec (Action envelope, Reduce, ReduceAll, NewXxxAction constructors) for any struct that declares actions. Optional fields use the same pointer-prefix logic as gotypes (skip slice / map / msgpack.EncodedJSON). - Formatter learns to emit action blocks so 'oracle fmt' / 'oracle sync' preserves them. Schematic schema and runtime: - schemas/schematic.oracle declares six actions: SetNodePosition, AddNode, RemoveNode, SetEdge, RemoveEdge, SetProps. Hand-written Handle methods live in core/pkg/service/schematic/actions.go alongside the ScopedAction envelope used for cluster broadcast. - Writer.Dispatch applies a sequence of actions atomically inside a single gorp transaction, rejects snapshots, and notifies actionObserver on success. SetData remains in place; no callers are migrated yet. - Service wires actionObserver to a signals translator that publishes scoped action sequences to sy_schematic_set / sy_schematic_delete when cfg.Signals is non-nil. - API exposes Dispatch as a new freighter endpoint (/api/v1/schematic/dispatch) registered alongside the existing SetData endpoint.
Wires the schematic action codec from Part 2 through to the consumer
side: Pluto now owns schematic graph state via flux + dispatch, and
Console renders the new self-contained Schematic component.
## Oracle codegen (TS actions)
- New `oracle/plugin/ts/types/actions.go` mirrors the Go actions plugin
for TypeScript output: emits `actions.gen.ts` with payload schemas, a
discriminated union `actionZ`, action constructors, and `reduce` /
`reduceAll` reducers backed by `structuredClone` (no immer dependency).
- Wired into `(*Plugin).Generate` so `oracle sync` produces TS actions
alongside the existing Go ones.
## Client SDK
- `client/ts/src/schematic/actions.gen.ts` — generated payload schemas
and action constructors for the six schematic actions defined in Part 2.
- `actions.ts` — hand-written `handle*` mutators called by the generated
reducer; semantics mirror the Go `Handle` methods byte-for-byte
(Add/Remove/SetEdge/SetProps/SetNodePosition).
- `scoped.ts` — `scopedActionZ` schema for decoding `sy_schematic_set`
signal channel frames.
- `client.schematics.dispatch(key, sessionKey, actions)` calls the
`/schematic/dispatch` endpoint added in Part 2.
## Pluto
- `queries.ts` gains: `useDispatch` (optimistic + rollback + augment
edge segments on node moves), `useSelectProps`, `useSelectEdge`,
`useSelectElementDigests`, `useSelectElementsInfo`,
`useSelectElementNames`, `useSelectSnapshot`, `useSelectAuthority`,
`useAddNode`, plus a `sy_schematic_set` listener that re-applies
remote action batches to the flux store and self-dedups via session key.
- `Schematic.tsx` is now self-contained: no factory, no hook injection.
It pulls graph state with `useRetrieve` and dispatches actions for
node/edge changes and drops. Stubs `onUndo` / `onRedo` (deferred).
- New `node/Node.tsx` extracted from the old `create()` factory; reads
per-node props via `useSelectProps`, dispatches `setProps` on changes.
- `edge/Edge.tsx` reads via `useSelectProps` and dispatches directly;
`EdgeProps` extension dropped.
## Console
- `console/src/schematic/Schematic.tsx` shrinks 521 → 100 lines. Drops
`Base.create(hooks)`, `useUndoableDispatch`, `useSyncComponent`,
`useLoadRemote`, `useAddSymbol`, and the in-component graph-state
effect handlers. Renders `<Base.Schematic resourceKey={layoutKey}>`
directly. Re-exports `HAUL_TYPE` from pluto for the channel ontology
drag-and-drop wiring.
The console schematic slice still carries graph fields (nodes/edges/
props/legend.colors/etc.) for now — those become unread state since the
canvas reads through Pluto. Slice gut + v6 simplification + toolbar
migration are scoped to a follow-up so this PR stays reviewable.
Undo/redo, off-page-reference cross-schematic navigation, and the
deferred-upload (`pendingUpload`) path are not in this PR.
- Add useAutoUpload hook in console/src/schematic/useUpload.ts.
On schematic mount, if the v6 migration parked a pendingUpload,
ensure the schematic exists on the server (lazy create against the
user's active workspace, falling back to no workspace) and clear
the pending entry.
- Restore navigateToLinkedSchematic / useHandleNodeClickAction in
console Schematic.tsx, reading off-page-reference props through the
pluto flux store instead of the deleted slice graph state.
- Drop the useSelectRequiredViewportMode hardcoded shim and the
matching slice setViewportMode no-op; viewport mode now lives as
component-local state in Schematic.tsx.
- Drop the useSelectRequired ZERO_STATE-fallback shim from
selectors.ts; nothing reads it now.
- Switch all Schematic.create call sites that previously spread server
Schematic into CreateArg to pass { key, name } only — graph state is
server-owned and nothing else belongs in the layout payload.
| ); | ||
| }; | ||
|
|
There was a problem hiding this comment.
control always resolves to "released" — edit lock and toggle button always wrong
Both branches of the ternary produce the same string literal "released":
const control =
(doc?.snapshot ?? false) ? "released" : ("released" as Control.Status);As a result, Diagram.Controls.ToggleEdit disabled={control === "acquired"} is never disabled and ControlToggleButton never renders as acquired, even when a user holds control authority. The original code read state.control from the Redux store. This should read the control status from the Redux selector (useSelectControlStatus) or from the server-returned authority.
09d8ff2 to
beb52eb
Compare
# Conflicts: # client/ts/src/schematic/actions.gen.ts # client/ts/src/schematic/actions.ts # client/ts/src/schematic/client.ts # client/ts/src/schematic/external.ts # console/src/range/overview/Snapshots.tsx # console/src/schematic/Schematic.tsx # console/src/schematic/services/link.ts # console/src/schematic/services/ontology.tsx # console/src/workspace/services/ontology.tsx # core/pkg/service/schematic/writer_test.go # oracle/cmd/plugins.go
The merge from sy-3833-2 deleted actions.go from plugin/ts/types/ (it now lives in plugin/ts/actions/), but the call site in types.go's Generate() was missed and references an undefined method.
| void state; | ||
| // create with an undefined key so we do not have to worry about the key that was from | ||
| // the imported data overwriting existing schematics in the cluster | ||
| placeLayout(create({ ...state, key: layout?.key, ...layout, type: LAYOUT_TYPE })); | ||
| placeLayout(create({ ...layout, key: layout?.key, type: LAYOUT_TYPE })); |
There was a problem hiding this comment.
Imported schematic content silently discarded
void state throws away every field from the exported file (nodes, edges, props, legend, etc.) — the create() call that follows only accepts key and name. A user who exports a schematic and then imports it gets an empty canvas. The previous code spread state into the create call; that mechanism has been removed without a replacement (the pendingUpload / useAutoUpload path only handles the v5→v6 migration and is never set here).
- Drop redundant <div> wrapper in Schematic.tsx; Diagram already provides its own container with ref forwarding and onDoubleClick handling. - Default workspace to uuid.ZERO in useCreate when none is provided so client.schematics.create receives a valid string. Matches Go's uuid.Nil handling in service/schematic/writer.go. - Run prettier on the four files CI flagged.
Resolves the merge of sy-3833-2 into sy-3833-3: - Adopts sy-3833-2's oracle action rename: SetProps -> SetConfig, props field -> config, props map -> configs map. Propagates the rename to all hand-written code in pluto/src/schematic and the console schematic toolbar. - Adopts sy-3833-2's per-variant Spec architecture for nodes and edges (Node.resolveSpec / Edge.resolve). Deletes the legacy pluto/src/schematic/edge/Edge.tsx; rewrites Schematic.tsx and Node.tsx to dispatch rendering through the registry while still using Pluto-owned state (useRetrieve / useDispatch / useSelectConfig / useAddNode). - Keeps sy-3833-3's gutted console slice / selectors / Schematic shell but combines it with sy-3833-2's migration helpers (migrateEdge, migratePropsToConfigs, migrateLegendColors). The v6 pendingUpload field now stores fully-typed v6 data so useUpload can hand it straight to client.schematics.create.
| onNodeDoubleClick={handleNodeDoubleClick} | ||
| fitViewOnResize={state.fitViewOnResize} | ||
| setFitViewOnResize={handleSetFitViewOnResize} | ||
| fitViewOnResize={false} |
There was a problem hiding this comment.
fitViewOnResize is hardcoded to false here, but the handleFitViewOnResizeChange callback still dispatches to the store. The user can toggle "Fit View on Resize" in the toolbar — the store updates — but the diagram always receives false, so the setting has no visible effect. The value should be read from the store, just as editable is read via useSelectFitViewOnResize.
| fitViewOnResize={false} | |
| fitViewOnResize={useSelectFitViewOnResize(layoutKey)} |
| @@ -320,27 +380,24 @@ const MultiElementProperties = ({ | |||
| key: K, | |||
| value: Schematic.Node.Label.Config[K], | |||
| ): void => { | |||
| selectedConfigs.forEach((cfg, i) => { | |||
| if (!("label" in cfg) || cfg.label == null) return; | |||
| onChange(selected[i], { label: { ...cfg.label, [key]: value } }); | |||
| elements.forEach((e) => { | |||
| if (e.type !== "node") return; | |||
| const config = e.config as NodeProps; | |||
| if (config.label == null) return; | |||
| onChange(e.key, { label: { ...config.label, [key]: value } }); | |||
| }); | |||
There was a problem hiding this comment.
Unbatched multi-element operations trigger N separate server requests
handleRotateIndividual, handleLabelProp, and the color swatch onChange each call the local onChange helper once per selected element, which invokes dispatchSchematic N separate times. Every invocation registers its own optimistic update and makes an independent server round-trip. Contrast with applyNodePositions, which correctly batches all position actions into a single dispatchSchematic call.
The divergence risk: each optimistic update pushes its own rollback entry. If intermediate request i fails, its rollback reverts the flux store to the state after request i−1, while requests i+1 through N may have already succeeded on both the client and the server. The ACTION_LISTENER self-dedup (changed.sessionKey === client.key) prevents the server's acknowledged state from being re-applied locally, so client and server diverge silently on any partial failure.
# Conflicts: # console/src/schematic/slice.ts
| placeLayout: Layout.Placer, | ||
| ) => { | ||
| const schematic = await client.schematics.retrieve({ key }); | ||
| placeLayout( | ||
| Schematic.create({ ...Schematic.fromRemote(schematic), editable: false }), | ||
| ); | ||
| placeLayout(Schematic.create({ key: schematic.key, name: schematic.name })); |
There was a problem hiding this comment.
Schematics from ontology now open in edit mode instead of read-only
loadSchematic previously called Schematic.create({ ...Schematic.fromRemote(schematic), editable: false }), which forced read-only mode when a user clicked a schematic in the ontology panel. The new call passes only { key, name }, so the Redux entry is initialized with ZERO_STATE, where editable defaults to true. Users who single-click a schematic in the ontology panel will now land in edit mode, which is the opposite of the previous behaviour.
Issue Pull Request
Linear Issue
SY-3833
Description
Third PR in the SY-3833 schematic split series. Wires the schematic action codec from #2290 through to the consumer side: Pluto now owns schematic graph state via flux + dispatch, and Console renders the new self-contained Schematic component.
Stacked on #2290. Targets
sy-3833-2. Flatten in order.What changes
Oracle codegen — TS actions plugin
oracle/plugin/ts/types/actions.gomirrors the Go actions plugin. Generatesactions.gen.tsper schema-with-actions: payload zod schemas, discriminated unionactionZ, action constructors,reduce/reduceAll(the latter usingstructuredClone, no immer dependency).(*Plugin).Generate.Client SDK
client/ts/src/schematic/actions.gen.ts(generated) — payload schemas + constructors for the six actions.actions.ts— hand-writtenhandle*mutators called by the generated reducer; semantics mirror the GoHandlemethods byte-for-byte.scoped.ts—scopedActionZfor decodingsy_schematic_setsignal frames.client.schematics.dispatch(key, sessionKey, actions)— calls the/schematic/dispatchendpoint added in Part 2.Pluto
queries.tsgains:useDispatch(optimistic + rollback + augment edge segments on node moves viaconnector.updateSegmentsForPositionChanges),useSelectProps,useSelectEdge,useSelectElementDigests,useSelectElementsInfo,useSelectElementNames,useSelectSnapshot,useSelectAuthority,useAddNode, plus asy_schematic_setlistener that re-applies remote action batches and self-dedups via session key.Schematic.tsxbecomes self-contained: drops thecreate(hooks)factory. Pulls graph state withuseRetrieve, dispatches actions for node / edge changes and drops. StubsonUndo/onRedo.node/Node.tsxextracted from the old factory; reads per-node props viauseSelectProps, dispatchessetPropson changes.edge/Edge.tsxreads viauseSelectPropsand dispatches directly.EdgePropsextension dropped.Console
console/src/schematic/Schematic.tsxshrinks 521 → 100 lines. DropsBase.create(hooks),useUndoableDispatch,useSyncComponent,useLoadRemote,useAddSymbol, and the in-component graph-state effect handlers. Renders<Base.Schematic resourceKey={layoutKey}>directly. Re-exportsHAUL_TYPEfrom Pluto for the channel ontology drag-and-drop wiring.Out of scope (follow-ups)
Schematic.useRetrievein the toolbar follow-up).pendingUploaddeferred-import path (depends on imex which isn't in v2 Pluto).SetDataendpoint (kept alive alongsideDispatchuntil console toolbar is fully migrated).Basic Readiness
Greptile Summary
This PR wires schematic graph state out of the Console Redux slice and into Pluto-owned flux state, with optimistic dispatch going to the new server dispatch endpoint and a channel listener re-applying remote action batches for real-time replication. The Console renderer shrinks from 521 to ~100 lines, delegating nodes/edges/configs to
Base.Schematicwhile keeping only UI state (editable, selected, viewport, legend) in Redux.queries.tsaddsuseDispatchwith optimistic updates, rollback, and edge-segment augmentation, plus a suite ofuseSelect*hooks for node and edge renderers.Schematic.tsxdrops sync, undo, and load-remote logic in favor ofBase.Schematicwith aresourceKeyprop that fetches its own data.useUpload.tsmigrates pre-v6 local schematics to the server via apendingUploadfield in the Redux slice.Confidence Score: 2/5
Multiple regressions across core user-facing paths make this unsafe to merge as-is.
The import regression means a user who exports a schematic and re-imports it gets a blank canvas. The always-released control string means the edit-lock toggle renders incorrectly for every schematic under any control authority. The viewport reset discards the user saved pan/zoom on every tab open. The ontology loadSchematic change means schematics that previously opened read-only now open in edit mode.
console/src/schematic/Schematic.tsx (control status, viewport, fitViewOnResize all hard-coded), console/src/schematic/services/import.ts (imported content silently discarded), console/src/schematic/services/ontology.tsx (editable flag dropped), console/src/schematic/useUpload.ts (create not awaited), pluto/src/schematic/queries.ts (missing guard in useAddNode, silent data loss when store entry absent)
Important Files Changed
Comments Outside Diff (3)
console/src/schematic/Schematic.tsx, line 496 (link)viewport={{ position: { x: 0, y: 0 }, zoom: 1 }}is hard-coded, so every time the schematic tab is opened or the component remounts, the view resets to origin at 1× zoom regardless of where the user had panned/zoomed. Changes are still persisted to Redux viahandleViewportChange, but the initial value is never restored.useSelectViewportalready exists inselectors.tsand returns the stored viewport; it just isn't called here.pluto/src/schematic/queries.ts, line 1098-1116 (link)When
currentisnull(schematic not yet loaded in the flux store), the optimisticreduceAllupdate is skipped and no rollback is pushed, but the network call still fires. After the server writes the action, theACTION_LISTENERself-deduplicates viachanged.sessionKey === client.keyand also returns early — so the local store is never updated. The canvas will silently show stale state until the next fulluseRetrievecycle. This can happen ifuseDispatchis called (e.g. from a toolbar interaction) before the initial retrieve has resolved.pluto/src/schematic/Schematic.tsx, line 771 (link)handleClearSelectionmissingonSelectionChangein its deps arrayhandleClearSelectioncloses overonSelectionChangebut theuseCallbackdependency array is empty ([]). If the parent re-renders with a newonSelectionChangereference (e.g. because a prop changed), the stale callback will be kept — Ctrl+A deselect will call the old handler. AddonSelectionChangeto the dependency array.Reviews (10): Last reviewed commit: "Drop unused eslint-disable directive on ..." | Re-trigger Greptile