Skip to content

Author editor layout from Python: with_position and flow_direction (v0.1.22) - #65

Merged
Volv-G merged 1 commit into
masterfrom
piforge/tangle-pipeline-crud/tangle-cli-v0-1-21-editor-layout-0f263bc
Sep 25, 2026
Merged

Volv-G merged 1 commit into
masterfrom
piforge/tangle-pipeline-crud/tangle-cli-v0-1-21-editor-layout-0f263bc

Conversation

@Volv-G

@Volv-G Volv-G commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

(AI-assisted)

What

The pipeline editor stores graph layout in ordinary annotations, so a Python author can already write it — as a hand-serialized JSON string under a key they have to remember:

train.with_annotations({"editor.position": '{"x": 300, "y": 120}'})
@pipeline("P", annotations={"editor.flow-direction": "left-to-right"})

This adds the typed spelling. .with_position(x, y) on task and subpipeline handles, and flow_direction= on @pipeline:

@pipeline("Daily Pulse", flow_direction="left-to-right")
def daily_pulse() -> Out[str]:
    scrape = SCRAPE.named("Scrape").with_position(0, 0)()
    judge = JUDGE.named("Judge").with_position(300, 0)(rows=scrape.rows)
    return judge.report

Both spellings stay valid and compile to the same bytes. First step toward a YAML→Python decompiler: editor.position appears 1193 times and editor.flow-direction 54 times in our pipeline corpus, and a decompiler cannot emit Python for a document it has no way to express.

How

  • Pure sugar. .with_position(...) is a .with_annotations({"editor.position": ...}) call, and flow_direction is assigned into the annotations mapping. That is what makes the collision rule single: the last write wins, whichever spelling made it, with no shadowing and no error. flow_direction is assigned after the mapping, so the typed keyword wins on that key while an already-present key keeps its position in key order.
  • Value format is the editor's. editor.position is a JSON object string (JSON.stringify / JSON.parse on the editor side), keys x, y plus optional width/height, json.dumps spacing — the same bytes tangle sdk pipelines layout already writes. Negative coordinates are normal. Absent dimensions are omitted rather than written as null. The editor also reads w/h; we always write the long names it writes itself.
  • top-to-bottom is accepted although the editor renders left-to-right today, because the corpus contains it and a port of an existing document has to stay expressible.
  • New stdlib-only tangle_cli.editor_layout owns the key names, the format and the validation, and is shared by the authoring sugar, pipelines layout, and the runner's auto-layout gate, so the three cannot drift. It lives outside python_pipeline deliberately: importing that package pulls in the authoring/codegen stack and its optional dependencies, which the layout command and the submit-time gate must not require. Callers inject error_cls, the same pattern check_annotations uses; authoring injects InvalidEditorLayoutError (a CompileError).
  • Validated at the call. bool, non-numeric, NaN and ±inf coordinates and unknown flow directions are refused before anything is written. True would silently serialize as 1; non-finite values are not JSON and break the editor's JSON.parse. Messages name the coordinate or list the allowed directions and never echo the value.

Failure modes

  • Layout is descriptive. Positions are task annotations, never part of a componentRef, so they cannot move component digests, compile identity or cache behaviour. On a subpipeline handle the position applies to the parent task and the child sidecar is byte-identical either way — pinned by a test comparing child sidecar name and bytes.
  • Auto-layout interaction is the one behavioural edge. The runner relayouts only a graph where no task carries a non-zero position, so an explicit .with_position(...) now suppresses auto-layout (and --force-layout still overrides). A graph positioned entirely at (0, 0) still counts as unpositioned — pre-existing runner semantics, pinned here because the sugar makes (0, 0) easy to write.
  • Graph inputs/outputs also carry editor.position and the editor reads it there, but an In[T] parameter has nowhere to hang layout; that needs a graph_input() API and is follow-up work, noted in the README.

Review focus

  • That the sugar routes through with_annotations rather than competing with it — this is what keeps one collision rule instead of two.
  • The placement of editor_layout.py outside python_pipeline (two packaging guards assert a minimal no-deps install still imports; there is now a regression test pinning the light import).
  • The auto-layout semantics above, which are the only way a position changes behaviour.

Tophatting

uv run --frozen pytest tests/test_editor_layout.py

39 tests: byte identity of the whole compiled bundle against the hand-written annotation, the exact serialized value format, chaining/immutability, last-write-wins in both orders, typed-keyword precedence, 12 hostile coordinate/direction cases asserting the rejected value never appears, fail-before-write, subpipeline parent-task positioning with child sidecar identity, componentRef equality, both auto-layout gates, and agreement with what pipelines layout writes.

Checklist

  • Full suite green locally on Python 3.13: 1823 passed.
  • Version bumped to 0.1.22 in pyproject.toml, packages/tangle-cli/src/tangle_cli/__init__.py, tests/test_packaging.py, and the uv.lock editable self-entry (lock diff is the one-line version change only).
  • README documents both spellings, the value format, the collision rule, and the auto-layout interaction.

…0.1.22)

The pipeline editor stores graph layout in ordinary annotations, so a
Python author can already write it — as a hand-serialized JSON string
under a key they have to remember. This adds the typed spelling:
`.with_position(x, y)` on task and subpipeline handles, and
`@pipeline(flow_direction=...)` for the root.

Both are pure sugar: they route through `with_annotations` / the
`annotations` mapping, so the compiled bundle is byte-identical to the
hand-written form and the last write to a key wins regardless of which
spelling made it.

The key names, value format and validation live in a new stdlib-only
`tangle_cli.editor_layout`, shared with `pipelines layout` and the
runner's auto-layout gate so the three cannot drift. It sits outside
`python_pipeline` because importing that package pulls in the
authoring/codegen stack, which the layout and submit paths must not
require; callers inject their own `error_cls`.
@Volv-G
Volv-G requested a review from Ark-kun as a code owner September 25, 2026 04:45
@Volv-G
Volv-G merged commit a5dfe5e into master Sep 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant