Repository navigation
A group can be laid out in rows - #169
Merged
Merged
Conversation
An edge whose end names a group's scope ends on the group's frame instead of at one of its members: a transition into a state, where the state is a group of what can happen in it. The transform resolves a scope id as an end; `splitEdgesAtGroups` gives every such edge one place on the frame (`GROUP_END`, left for edges ending there, right for edges starting there), shared by all of them, crossing the frames around the group as any edge does. The client keeps the arrowhead of an edge that ends there and draws no dot. An edge between a group and something inside it (or itself) goes nowhere the frame does not already hold. It is taken out of the model and the group is marked (`GROUP_LOOP`): one loop on the frame for all of them. A group whose label is empty has no title and no header band; only a scope with no label at all is named by its id. A scope's label now reaches the document on the sidecar-export path, which dropped it. Reopening a saved layout puts the place where edges end back where the edge outside the group reaches it, there being no piece inside to go by. Claude-Session: https://claude.ai/code/session_01EToj6RJRMSSHQiqGNMU9wv
A producer puts the members of a group in rows with `meta.row` (a whole number from zero, the first row on top). The members of a row sit side by side in the order the producer gave them, the rows one under another; a member with no row comes after the last. The layers of a layout that runs downward are rows, and a member's row is its partition: such a group is laid out with `elk.direction: DOWN`, partitioning on, and its members as one component (on their own, members with nothing between them would each be a component, packed wherever it fits). The engine forces every nested layout to the right; a group in rows is the exception. The pass that pins sources and sinks to the outer layers when a diagram opens leaves a row's members alone: a layer constraint would contradict their row. It also no longer pins a node that has no port at all, which is neither a source nor a sink. Claude-Session: https://claude.ai/code/session_01EToj6RJRMSSHQiqGNMU9wv
Reopening a saved layout: a port where edges end at a group has no member to be level with, and the layout may put it below the members. The frame was rebuilt around the members alone, so such a port was clamped to the new bottom edge and its edges turned diagonal. The frame now reaches down past every port its saved routes meet. A real lay out, save and reopen of such a document is tested. The selection trace took the port where edges end at a group for one more port to pass through, with nothing behind it: the group was never on a path. That port is the group's own; the group is on the path, and what it holds is left as it is instead of faded. A group that holds nothing was laid out 20 by 20, its title running over its edges. Groups have a minimum size, on layout and on reopening. The loop of a group keeps what its edges said: their captions and error messages are its tooltip, and it is drawn in the error colour when one of them is in error. Before, they went with the edges. Where an edge starts at a group the dot stays (the line began 4 px off the frame without it); only where edges arrive does the arrowhead replace it. The property panel names the group an edge ends at by its title. Tests: the loop from a group to a member and to itself, sharing on the way out, group to group, an empty group's size, the trace through a group. Claude-Session: https://claude.ai/code/session_01EToj6RJRMSSHQiqGNMU9wv
Rows are for members that relate only to what is outside their group. With an edge between two members the layering puts its ends in different layers, which split a row, and keeping the producer's order within a row sent the edge through the members between its ends. A group with such an edge now keeps the plain layout, laid out by its edges as before. Which groups lay out in rows is decided once the groups are nested and the edges cut (`arrangeGroupRows`), not while each group is built. A group nested in a rows group is a member with a row of its own (a scope's `row`), or after the last; a member opened in place keeps its row on the frame it becomes. Before, both landed in the top row. A group laid out in rows says so with `GROUP_ROWS`; `GROUP_ROW` is the row of a member only. A row past what a layout can count is no row. The changelog entry had been merged into the middle of its neighbour. Left for later: a short row is not left-aligned over a long one, and a frame grows by a track for every member's leaving edge. Claude-Session: https://claude.ai/code/session_01EToj6RJRMSSHQiqGNMU9wv
endrix
added a commit
that referenced
this pull request
Nov 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second platform piece for the actor view of streamblocks-ide, stacked on #168 (the base of this PR is that branch; retarget to
mainonce #168 is merged). Generic: any producer can use it.What changes
meta.row(a whole number from zero, the first row on top). The members of a row sit side by side in the order the producer gave them, the rows one under another. A member with no row comes after the last row. A group none of whose members carries a row is laid out as before.elk.direction: DOWN,elk.partitioning.activateandelk.separateConnectedComponents: false(members with nothing between them would otherwise each be a component, packed wherever it fits).applyDirectionToChildrenforced every nested layout to the right; a group in rows is now the exception.applyBoundaryFlowLayerConstraints, run when a diagram opens) leaves a row's members alone, since a layer constraint would contradict their row. It also no longer pins a node with no port at all: such a node is neither a source nor a sink. That second part changes where a port-less node of any product lands on a fresh layout.Checked
test/group-rows.test.ts: what the transform hands the layout, the pinning pass, and two runs of the real ELK (members of a row at one height in document order, rows in order, all inside the frame; also with no edges at all).elk-layout-port-order.test.ts, which timed out once under load and passes on its own.scripts/render-svgfrom the CalPy sidecar's real actor export: a state whose four actions share a priority shows them side by side; states with a priority chain show one action per row in order; the 53-state MPEG-4 parser lays out in 1.2 s.https://claude.ai/code/session_01EToj6RJRMSSHQiqGNMU9wv