Skip to content

Plain python export - #672

Merged
Edwardvaneechoud merged 11 commits into
mainfrom
plain-python-export
Aug 17, 2026
Merged

Plain python export#672
Edwardvaneechoud merged 11 commits into
mainfrom
plain-python-export

Conversation

@Edwardvaneechoud

Copy link
Copy Markdown
Owner

Adding an old idea. Making flowfile a tool for teaching basic python and how you can work with plain python and data

Edwardvaneechoud and others added 2 commits August 14, 2026 13:11
A fourth mode next to FlowFrame/Polars/Project that rewrites a flow with
no dataframe library: every table is a list[dict] and every node is an
explicit loop. Meant for learning the patterns a dataframe hides - the
accumulator dict behind a group by, the hash index behind a join.

Structural bits worth knowing:
- FlowGraphToPlainPythonConverter subclasses the existing converter, so
  the Polars path is untouched. PLAIN_PYTHON_NODE_TYPES is derived from
  the mixin's _handle_* methods, never hand-written, so an inherited
  Polars emitter can't leak pl. into the output.
- Chain fusion is off; you can't pipe two for-loops together.
- Nodes with no loop equivalent (formula, polars_code, sql, pivot) become
  exercise stubs that raise NotImplementedError instead of failing the
  whole export.

Also adds GET /editor/code_to_plain_python and /editor/explain_node, plus
the "How would I write this myself?" panel in the settings drawer, which
shows the loop for that node's own settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports the teaching flavour to the WASM build as an opt-in second mode in
the Code panel (Polars stays the default, choice persists), plus the same
per-node "How would I write this myself?" panel. Embedders can hide both
with teachingMode={false}.

The reason this belongs in Lite more than anywhere else: the browser
already has a Python runtime, so the generated script can run where you
read it. The run button execs it in its own namespace - no execute_*
bridge, no engine state - stages the Read File contents into Pyodide's FS
and prints the rows underneath. An exercise stub becomes something you
fill in and immediately re-run.

Semantics are matched against src/pyodide/engine, not flowfile_core; the
two differ on the aggregate set, join suffixing and unique strategies.
tests/unit/plain-python-parity.test.ts pins that by running 37 fixtures
through the real engine.build_* calls and through the generated script
and diffing the rows. It reports skipped, not passed, when there's no
CPython with Polars around.

Two fixes fell out along the way: toPythonValue emitted JSON null/true/
false into generated Polars code (invalid Python for any manual_input
holding a null or bool), and FlowToPolarsConverter's members are now
protected so the plain flavour can subclass it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 14, 2026

Copy link
Copy Markdown

Deploy Preview for flowfile-wasm ready!

Name Link
🔨 Latest commit a573891
🔍 Latest deploy log https://app.netlify.com/projects/flowfile-wasm/deploys/6a829e5d51e5c5000817b7c1
😎 Deploy Preview https://deploy-preview-672--flowfile-wasm.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Edwardvaneechoud
Edwardvaneechoud marked this pull request as ready for review August 14, 2026 13:29
@claude

claude Bot commented Aug 14, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 1m 22s —— View job


Claude is reviewing this PR

Todo list

  • Read repo CLAUDE.md and package-level CLAUDE.md files (flowfile_core, flowfile_wasm)
  • Gather PR diff (git diff origin/main...HEAD)
  • Review backend: plain_python.py, plain_python_handlers.py, code_generator.py, routes, output_model (in progress)
  • Review frontend/wasm: usePlainPythonGeneration.ts, CodeGenerator.vue, NodeExplainer.vue, related composables (in progress)
  • Review tests (backend + wasm unit/parity tests)
  • Post findings

The inline # comments were passive - something you read past on the way to
the code, and they cannot show you anything. This adds the layer that can.

Learning mode is a user setting (graduation cap in the icon rail). With it
on, the Code panel opens on a third mode that steps through the flow one
node at a time and shows three things per step:

- the pattern behind it, in prose. Not what the line does, but why the
  shape is what it is and where else you will write it: the accumulator
  dict, the hash index, why sort takes a key, why stability matters.
- that node's own loop, for your columns and your operators.
- the real rows going in and coming out, from one instrumented run.

The data comes from buildTraceCode(), the same pipeline with a capture
after each block. __steps__ is module-level on purpose: an unfilled
exercise raises, and the steps before it still have to be readable.

The panel docks beside the canvas rather than covering it, and highlights
the node it is describing - that is the canvas-to-code link. Below 1240px
it falls back to a modal. The Plain Python editor is now writable too, so
an exercise stub is something you fill in and re-run, and edits survive a
trip to another tab.

Validated by a fleet of inspection agents driving the live app (beginner
comprehension, layout across four viewports, light mode, the no-auto-run
contract, the edit/run loop, and fact-checking every concept claim against
real Python). The explicit-run-only contract came back clean. Fixes that
came out of it: the pivot exercise omitted the index column and the
aggregation, so it could not be solved; a group by reported that it
"dropped" rows when it had folded them; three concept claims were wrong;
the docked panel buried the header's Run button; and edits were silently
discarded on a tab switch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 14, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 2m 54s —— View job


Claude's review

Todo list

  • Read repo CLAUDE.md and package-level CLAUDE.md files (flowfile_core, flowfile_frontend, flowfile_wasm)
  • Gather PR diff (git diff origin/main...HEAD)
  • Review backend: plain_python.py, plain_python_handlers.py, code_generator.py, routes, output_model
  • Review frontend/wasm: usePlainPythonGeneration.ts, CodeGenerator.vue, NodeExplainer.vue, related composables
  • Review tests (backend + wasm unit/parity tests)
  • Post findings

Summary

This is a large but well-scoped addition: a fourth code-generation flavour (flowfile_core/flowfile/code_generator/plain_python.py + plain_python_handlers.py) that emits dependency-free Python for teaching, mirrored by an independent TypeScript implementation in flowfile_wasm for the browser-only Lite editor, plus UI surfaces (per-node explainer drawer, walkthrough panel) on both sides. The design is careful about the two failure modes that matter most here:

  • No stealth Polars: PLAIN_PYTHON_NODE_TYPES is derived from the mixin's own _handle_* methods (plain_python.py:34-36) rather than hand-maintained, so a node type without a real emitter can never fall through to an inherited Polars handler. The wasm side mirrors this via PLAIN_HANDLERS/plainHandlerFor.
  • No stealth wrong answers: emitters raise PlainPythonUnsupported and the converter rolls back partial output and emits an exercise stub (raise NotImplementedError) rather than producing rows that silently diverge from the canvas (plain_python.py:181-199).

The code_generator.py refactor (_resolve_handler/_handle_unsupported extracted as overridable hooks) is a clean, minimal way to let the new flavour override dispatch without duplicating the whole node-walk loop.

Backend correctness spot-checks against the documented Polars semantics (nulls sort first in both directions, null join keys never match, sum-over-all-null is 0 vs other aggs are null) all check out in plain_python_handlers.py — the _sort_key, _handle_join, and _emit_value_lists/_agg_expression logic look right, and are backed by two dedicated test files (test_code_generator_plain_python.py, ..._edge_cases.py) plus a settings-validation-style approach of only supporting a safe subset (inner/left/semi/anti joins; a documented, explicit set of aggregations).

New routes (GET /editor/code_to_plain_python, GET /editor/explain_node) are added to the existing router in routes.py, which carries a router-level Depends(get_current_active_user) — consistent with the neighboring /editor/code_to_polars / /editor/code_to_flowframe endpoints, no new auth gap introduced.

Frontend/wasm: no v-html/innerHTML/eval usage in any of the new components (nodeExplainer.vue, wasm NodeExplainer.vue, PlainPythonWalkthrough.vue, RowTable.vue, CodeGenerator.vue) — the generated code snippets are rendered through a read-only CodeMirror instance, not raw HTML, so there's no XSS surface even though the snippets embed user-controlled column names/paths. flowfile_wasm/CLAUDE.md was updated in-PR to document the new subsystem, per the repo's docs-maintenance convention.

Test coverage looks proportionate: backend has dedicated unit + edge-case test files; wasm has unit tests plus a differential parity test (plain-python-parity.test.ts) that runs each fixture through both the real Pyodide-engine executor and the generated script and diffs rows — and skips cleanly (not "passes") when no local CPython+Polars is available, which is the right failure mode for CI portability.

Minor findings

  1. Unused fieldflowfile_frontend/src/renderer/app/types/node.types.ts adds NodeTitleInfo.item, and GenericNode.vue:114 populates it (item: props.nodeData?.item), but nothing in this diff reads it — nodeTitle.vue and NodeSettingsDrawer.vue don't consume it. If it's meant for a follow-up, worth a one-line comment; otherwise it's dead code. Low severity.
  2. No Alembic migration needed (no database/models.py changes), no public flowfile_frame API changed (no make check_stubs implications) — consistent with this being a codegen/UI-only addition.

Not verified

I wasn't able to execute poetry run pytest / npm run test:unit in this sandbox (commands requiring subprocess execution were blocked by the environment's approval gate), so I could not confirm the new test suites actually pass, only that they read as well-constructed and semantically correct on inspection. Worth confirming green CI before merge if that hasn't already run.
· Branch: plain-python-export

Three things, all pulling the same way: stop showing code in isolation.

The walkthrough showed each node as a detached fragment, which told you
nothing about where the step sits or what it is handed. It now shows the
entire script with the current step's lines lit up, scrolling to them as
you step. The converter resolves each step's line range against the
finished script, accounting for the helpers above the body.

Hovering any name in any of the three editors now explains it - the
builtins the generated code leans on, the methods, and the helpers this
generator writes for you. A test fails if the glossary drifts from the
vocabulary the codegen actually emits.

Pivot is no longer an exercise. It generates the two-level accumulator it
really is: one pass to collect the column set, one to fill the grid.
Matched against execute_pivot by probing the real engine - the value_agg
naming rule, nulls-first labels, and the empty-cell-versus-absent-cell
distinction that decides sum 0 from None. Seven parity fixtures, mutation
tested.

Also rewrote the sort background. "Missing values come first because that
is simply what the engine does" explained nothing; it now shows that
None < 5 raises rather than returning False, which is the actual reason
the key carries a boolean flag, and reframes stability as something you
use rather than a fact you are told.

Fixed along the way: a tooltip anchored to the top visible line rendered
and was then clipped invisible, because the panel's backdrop-filter makes
it a containing block for the fixed-position tooltip. Bounding
tooltipSpace at the editor makes CodeMirror flip it below instead.

Known gap: the parity harness sorts object keys before comparing, so
pivot's column ORDER is not differentially proven - only the column set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 14, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 5m 42s —— View job


Claude's review

Todo list

  • Read repo CLAUDE.md and package-level CLAUDE.md (flowfile_core, flowfile_wasm, flowfile_frontend)
  • Diff against origin/main to see what changed since the last review
  • Review backend changes (unchanged since the previous round — plain_python.py/plain_python_handlers.py/routes.py were not touched by the 3 new commits)
  • Review frontend/wasm changes (Lite plain-Python mode, Learning-mode walkthrough, whole-script view + hover glossary)
  • Review new tests
  • Post findings

Summary

Three new commits (9b8fe8a, ac070f4, 8ca15f5) landed since the last review round, all scoped to flowfile_wasm + docs — the flowfile_core code generator (plain_python.py, plain_python_handlers.py, routes.py) is unchanged and was already reviewed. This adds:

  1. A second Code-panel mode in Lite that runs the generated plain-Python script in-browser via Pyodide (own namespace, no execute_* engine bridge).
  2. A "Learning mode" walkthrough panel: step-by-step, per-node concept explanation + real traced input/output rows (buildTraceCode() / module-level __steps__).
  3. A follow-up showing the whole script with the current step's lines highlighted, hover tooltips explaining names across all three CodeMirror editors, and turning pivot from an exercise stub into a real two-pass accumulator implementation.

I traced the line-range/offset arithmetic in usePlainPythonGeneration.ts by hand against plain-python-walkthrough.test.ts and it's correct, including across the two buildFinalCode() calls per walkthrough build. No v-html/innerHTML/eval/Function() in the new Vue/TS layer — RowTable.vue, NodeExplainer.vue, and PlainPythonWalkthrough.vue all use text interpolation; the only exec() is inside the Pyodide-side Python string, which is the intended sandboxed browser-tab execution this feature is built on. Guard logic around empty __steps__, stale step index after a flow edit, and tooltip clipping (the backdrop-filter containing-block fix) all check out as real fixes, not band-aids.

Tests are substantive, not superficial: the glossary-drift test (plain-python-walkthrough.test.ts:223-243) builds a real flow, generates the script, and asserts every emitted vocabulary term has a glossary entry — it will actually fail on drift. The pivot parity suite has the claimed 7 fixtures and an honest documented gap (column order isn't differentially proven, only the column set, since the harness sorts object keys before comparing).

Minor findings

  1. Left join against an empty right table drops columns entirelyflowfile_wasm/src/composables/usePlainPythonGeneration.ts:1595-1600 (plainJoin, how === 'left'): extras is derived from `${right}[0] if ${right} else {}`, so when the right-hand table is completely empty, unmatched left rows get no right-hand keys at all, rather than the schema-consistent null-filled columns Polars would emit for a left join against an empty-but-known-schema frame. No parity fixture exercises an empty right table. Narrow edge case, but a real semantic divergence — worth a fixture + falling back to a static column list from settings instead of right[0].
    Fix this →
  2. New multi-line JSDoc header comments (usePythonGlossary.ts, useStepHighlight.ts, and several new types/functions in usePlainPythonGeneration.ts such as Concept, PlainStep, buildTraceCode) drift from the repo-wide comment doctrine ("no long explanatory blocks or multi-line header comments — one short line at most"). Existing sibling files in the same package (useCodeGeneration.ts, pyodide-store.ts) keep to 1–3 line headers, so this is new style introduced by this PR rather than a pre-existing pattern being followed. Doesn't affect correctness — a lint/style nit, not a blocker.
  3. Small inconsistency: PlainPythonWalkthrough.vue:42 reads steps[current].lineStart without the optional chaining used everywhere else in the file for steps[current]. Currently unreachable (parent updates steps/current together synchronously) but worth matching the defensive pattern used elsewhere.
  4. From the earlier review round, still true and unaddressed: NodeTitleInfo.item (node.types.ts) is populated in GenericNode.vue:114 but nothing reads it yet — likely dead code unless there's a follow-up planned.

No Alembic migration needed (no database/models.py touch), no flowfile_frame public API changed, no backend routes/auth changes since the last round. Overall this is a well-tested, carefully-scoped addition — the only functional issue found is the narrow empty-right-table join edge case above.

Two tabs showed the same script, and the walkthrough lived on a screen of
its own - so reading the code and reading about it were different places.
Now there are two tabs, Polars and Python walkthrough, and the walkthrough
is the plain-Python view rather than a parallel one.

The editor is mounted once and never remounted. It sits in the middle of
the panel, keeps ~478px at 1600x1000, and stepping never resizes it: the
chips move a highlight through the script in place. Below it a margin
carries Data and Output, split by a draggable divider that remembers where
you put it. At modal widths the two flip to side-by-side columns.

The background prose is now OPT-IN and collapsed by default. An embedded
editor is a plain code view unless someone presses "Why does it look like
this?", which adds a Why tab that stays open as you step and is remembered
next session. That was the maintainer's call, and it also fixes the thing
that made the old layout wrong: four paragraphs and an ASCII sketch filled
the viewport and pushed the code below the fold.

The highlight now rides the text. Ranges are kept as document offsets that
map through edits (start assoc -1, end +1, so typing inside a block grows
it) instead of being dropped on any change, and the header label re-derives
its line numbers from the painted decoration - so label and paint cannot
disagree, which is how the old "lines 75-78" pointed at a viewport showing
line 48. scrollIntoView is verified against real geometry and retried once,
because line heights are estimated until measured and lineWrapping makes
the estimate worse.

The layout came out of a design workflow: four proposals from different
starting biases, adversarial judging that caught real arithmetic errors in
them, then a synthesis resolving the fatal flaws.

Known: uiStore.codePanelMode is now written but unread (Canvas moved to
codePanelDocked). Re-seeding after a tab round-trip drifts against your own
edits, and trace staleness is disclosed rather than fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 15, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 2m 56s —— View job


Claude is reviewing this PR

New commit landed since the last review round (6b0edd9 — "Merge the two learning tabs into one Python walkthrough"). Re-reviewing the incremental diff.

Todo list

  • Read repo CLAUDE.md / flowfile_wasm CLAUDE.md
  • Diff 8ca15f5...6b0edd9 (the new commit since the last review round)
  • Review the merged-tabs UI changes for correctness/regressions (stores, highlight offsets, RowTable/Canvas, docs)
  • Deep-dive CodeGenerator.vue's rewritten state machine (edited/draft/trace/run logic) (in progress via subagent)
  • Check whether prior findings (empty-right-table join, JSDoc comment style, steps[current].lineStart, unused NodeTitleInfo.item) were addressed
  • Post findings

Branch: plain-python-export

It was pinned at 620px docked right, with a modal fallback below 1240px
and no way to change either. Now it is a DraggableItem, the same overlay
the node-settings panel already uses: drag it, resize it from any edge,
take it fullscreen, minimise it, and it remembers where you left it.

Reusing that system rather than hand-rolling a second one means the
bespoke geometry goes away entirely - the .docked CSS, the 1240px media
query, the click-through backdrop, Z_INDEX.DOCKED_PANEL, and the
codePanelDocked/codePanelMode store fields (the latter was write-only).
Persistence rides the existing intent record; no new localStorage key and
no derived geometry stored.

Two opt-in additions to DraggableItem, following the precedent of the
existing wasm-only extras: an `actions` header slot (run/reset/refresh/
export live there now, and mousedown.stop keeps a button press from
starting a drag) and a `flushContent` prop for a full-bleed body. Plus a
`topOffset` prop so embed mode clears the in-canvas toolbar, replacing a
hardcoded 100px of padding.

The panel joins the normal panel stack rather than keeping its own 30000,
so last-clicked wins between Code and Settings instead of Code permanently
burying it, and the layout-controls widget stays reachable - which now
matters more, since Reset Panel Layout is the way back from a panel
dragged somewhere awkward.

Because the panel is freely resizable, the inner two-column flip is now
reachable at real widths; CodeMirror re-measures on container change so it
does not render stale geometry. The outer edge drag and the inner
bench/margin divider do not fight.

Also fixed a console error this uncovered in showStep: the retry dispatch
ran inside CodeMirror's measure cycle, which refuses updates. It hops out
via rAF now, guarded against a detached view. Verified gone.

Known: below ~1240px the panel no longer becomes a modal, so it docks at
620 over a narrow canvas - draggable and minimisable, which the modal was
not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 15, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 2m 14s —— View job


Claude's review

Todo list

  • Read repo CLAUDE.md / flowfile_wasm CLAUDE.md
  • Diff 6b0edd9..6a14f0f (the new commit since the last review round)
  • Review the resizable/draggable/fullscreen CodeGenerator.vue rewrite for correctness/regressions
  • Check whether prior findings (empty-right-table join, JSDoc comment style, steps[current].lineStart, unused NodeTitleInfo.item) were addressed
  • Post findings

Summary

Commit 6a14f0f ("Make the code panel resizable, draggable and fullscreen-able") retires the Code panel's bespoke modal/dock CSS (.code-generator-overlay, .code-generator-panel, the 1241px viewport media-query fallback, Z_INDEX.DOCKED_PANEL) and rebuilds it as a DraggableItem instance, the same shared overlay-panel system already used elsewhere in the app (components/common/DraggableItem/). This is a real simplification, not just a restyle: geometry (drag/resize/fullscreen/minimize/persistence) now comes from one system instead of two, and designer-ui-store.ts sheds the codePanelMode/codePanelDocked fields it used only to let Canvas.vue know to hide LayoutControls under the old docked panel — LayoutControls is now unconditionally rendered and relies on general panel z-index stacking (FLOATING_WIDGET above PANEL_BASE..FULLSCREEN) instead. I grepped for leftover references to the removed store fields and Z_INDEX.DOCKED_PANEL — none remain.

DraggableItem.vue gained two small, well-scoped extensions to support this: an actions slot (header buttons right of the tab strip, @mousedown.stop so pressing one doesn't start a drag) and a flushContent prop (hands the whole content box to the slot with no padding/scroller, for panels like this one that own their own internal scrollers). Both are documented in the updated flowfile_wasm/CLAUDE.md bullet, consistent with the repo's doc-maintenance convention.

Two correctness details I traced by hand:

  • The bodyEl/ResizeObserver rewire (panelElbodyEl) is consistent — the watcher's releaseEditor() call on the body unmounting (which happens on minimize) correctly tears down the CodeMirror scroll listener before the DOM node goes away.
  • useStepHighlight.ts's showStep fix (dispatching a scroll-into-view from inside a CodeMirror requestMeasure write callback previously risked "update in progress"; now deferred via requestAnimationFrame with an isConnected guard) is a real fix for a plausible race, not just cosmetic.

Status of prior findings

  • Empty-right-table left join (usePlainPythonGeneration.ts:1565, `${right}[0] if ${right} else {}`) — still present, not addressed in this or the merge-tabs commit. Still a narrow edge case, still worth a follow-up fixture + fix.
  • steps[current].lineStart missing optional chaining — moot; PlainPythonWalkthrough.vue no longer exists after the tab-merge commit (6b0edd9), its logic was folded into CodeGenerator.vue.
  • Multi-line JSDoc headers (usePythonGlossary.ts, useStepHighlight.ts) — still present, unchanged from the last round. Minor style drift from the repo's "one short line at most" comment doctrine, not a blocker.
  • Unused NodeTitleInfo.item (node.types.ts) — still unread outside GenericNode.vue:114's assignment. Confirmed via grep that no consumer of that NodeTitleInfo (the one in node.types.ts, distinct from the unrelated same-named interface in nodeInterfaces.ts) reads .item. Still low severity, still open.

Not verified

Same limitation as previous rounds: this sandbox's approval gate blocks npm run test:run (flowfile_wasm) and poetry run pytest, so I could not execute the test suites — only trace the diff by hand against the existing DraggableItem prop contract and CSS. The panel-geometry unit tests (layout-geometry.test.ts, layout-intent.test.ts, components/DraggableItem.test.ts) were not modified by this commit, consistent with it reusing rather than changing the underlying geometry system.
· Branch: plain-python-export

@claude

claude Bot commented Aug 16, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 2m 3s —— View job


Claude's review

Todo list

  • Read repo CLAUDE.md / flowfile_wasm CLAUDE.md
  • Diff 6a14f0f..a7a9702 (the two new commits since the last review round)
  • Review the walkthrough/data-tracing changes for correctness/regressions
  • Check whether prior findings (empty-right-table join, JSDoc comment style, unused NodeTitleInfo.item) were addressed
  • Post findings

Summary

Two commits landed since the last review round:

  1. 7ded530 "Revert changes to core" — removes the entire flowfile_core backend implementation (plain_python.py, plain_python_handlers.py, the code_generator.py hook refactor, the two new routes, the backend test files, the nodeExplainer.vue/node.api.ts/GenericNode.vue frontend surfaces). This PR is now WASM-only — plain-Python export/learning mode lives exclusively in Flowfile Lite (flowfile_wasm), not the desktop/web app backed by core. All prior review rounds' findings about plain_python_handlers.py/routes.py/code_generator.py are moot; those files no longer exist on this branch. docs/users/deployment/lite.md's comparison table now correctly shows "Plain-Python (learning) export: ✗ (full app) / ✓ (Lite)" — consistent with the revert.
  2. a7a9702 "Enhance Learning Mode" — reworks data-tracing so it instruments the buffer as the learner has it (usePlainTrace.ts::instrumentScript, replacing the old separate traceScript), adds a real by-value comparison (compareTables) instead of the previous row-count-only check, syncs the canvas viewport to the current walkthrough step (Canvas.vue::panToNode), makes Learning mode fully opt-in with a Code-panel affordance, and closes the panel on canvas double-click.

I traced the new tracing/comparison logic by hand:

  • instrumentScript inserts captures bottom-up so earlier insertions never shift later line numbers — verified against plain-trace.test.ts's ordering test, correct.
  • compareTables checks row count → column set → cell values in order, with a cellsEqual helper that tolerates the two known serialization divergences (float noise, T-vs-space datetime separator) without being loose enough to let a real type mismatch (e.g. "true" vs true) pass — backed by 13 well-targeted test cases in plain-trace.test.ts.
  • runTrace's capture-range computation correctly reads live line ranges via allStepLines(view.state) rather than the generator's static step.lineStart/lineEnd, so a solved exercise stub still gets its own per-step data — consistent with the useStepHighlight design documented in flowfile_wasm/CLAUDE.md.
  • The double-gate (teachingMode prop × learning.enabled store) for both the Code-panel tab and the per-node explainer is real and test-enforced (NodeSettingsWrapper.test.ts), not just documented.
  • refreshCode's draft handling correctly scopes the stash/clear to isWalkthrough.value, so refreshing from the Polars tab leaves a stashed walkthrough draft untouched, matching the comment's stated intent.

No .collect()/pandas/security-relevant changes — this diff is confined to flowfile_wasm (browser-only, no backend calls) and one docs page.

Status of prior findings

  • Empty-right-table left join (usePlainPythonGeneration.ts:1594-1605, `${right}[0] if ${right} else {}`) — still present, untouched by this round. Still worth a follow-up fixture + fix, but low severity.
  • Multi-line JSDoc header comments — the new usePlainTrace.ts continues this pattern (module header + 3 function-level blocks), still a departure from the repo's "one short line at most" comment doctrine noted in root CLAUDE.md. Given how consistently this style is now used across every new composable in this PR (usePythonGlossary.ts, useStepHighlight.ts, usePlainStep.ts, usePlainTrace.ts), it reads as a deliberate, consistent choice for this subsystem rather than drift — worth a maintainer call on whether to codify an exception rather than keep flagging it each round.
  • Unused NodeTitleInfo.item — moot; node.types.ts and GenericNode.vue were removed entirely by the core revert.

Not verified

Same limitation as previous rounds: this sandbox's approval gate blocks npm run test:run / poetry run pytest, so I traced the new logic by hand against the (substantial) new test files rather than executing them. Worth confirming green CI, particularly the pyodide-smoke job given the trace/compare-to-canvas changes touch the JS↔Pyodide bridge.
· Branch: plain-python-export

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 2m 22s —— View job


Claude's review

Todo list

  • Read repo CLAUDE.md / flowfile_wasm CLAUDE.md
  • Diff e5f36c3..b1e6f63 (the new commit since the last review round)
  • Review the refactor for correctness/regressions
  • Check status of prior open findings (empty-right-table join, JSDoc comment style)
  • Post findings

Summary

Commit b1e6f63 ("refactor: simplify walkthrough structure and improve code readability") is a pure cleanup pass, no new functionality. It:

  1. Tightens FlowToPolarsConverter's visibility (useCodeGeneration.ts) — most fields/methods (edges, unsupportedNodes, formulaCode, passthrough, currentNodeId, all the handle* node emitters, etc.) go from protected to private. I checked this against the subclass, FlowToPlainPythonConverter (usePlainPythonGeneration.ts:585) — it only touches dispatchNodeCode, renderBody, collectMainInputs, addCode, addComment, planBoundaryNames, applyRenames, buildFinalCode, nodes, nodeVarMapping, imports, codeLines, nodeSpans, all of which correctly remain protected. Grepped for this.<privatized-member> in the subclass file — no hits, so this narrows the base class's real surface area without breaking the one subclass that exists.
  2. Removes dead fields: NodeExplanation.{nodeId,nodeType,supported}, PlainWalkthrough.{traceScript,snippets}, usePlainStep()'s {step,stepLabel} (now a standalone exported stepLabel(), imported directly by CodeGenerator.vue:212), usePlainPythonGeneration()'s re-exported {PLAIN_PYTHON_NODE_TYPES,NODE_EXPLANATIONS,CONCEPTS} (now imported directly where needed, e.g. usePlainStep.ts:4), and GLOSSARY_TERMS. Grepped the whole src/tests tree for each removed identifier — no dangling references.
  3. Fixes a theme-coupling smell flagged implicitly by the old code: the .cm-glossary* CSS was previously defined inside useStepHighlight.ts's stepTheme, with NodeExplainer.vue including stepHighlight() "to carry the tooltip theme" even though it doesn't want step highlighting. Now usePythonGlossary.ts owns its own glossaryTheme and NodeExplainer.vue no longer imports stepHighlight at all — a real decoupling, not just a move.
  4. RowTable.vue: drops the flush prop/mode (sticky header, second scroller) since both call sites (StepMargin.vue:96,123) always used it — makes limit a required prop instead of defaulted, and both call sites pass it explicitly. StepMargin.vue also replaces its bespoke run-table markup/CSS with the shared RowTable, removing ~50 lines of duplicated table styling.
  5. CodeGenerator.vue:485: simplifies update.docChanged || update.geometryChanged || ... to update.geometryChanged || ... with a comment that geometryChanged subsumes docChanged — correct per CodeMirror's ViewUpdate.geometryChanged semantics (true when the doc changed or the editor/element geometry changed).
  6. New tests/helpers/{flow-builder,python-runtime}.ts extract fixture-building code that was previously duplicated across the plain-Python test files; confirmed both are actually imported by plain-python-generation.test.ts, plain-python-parity.test.ts, and plain-python-walkthrough.test.ts rather than being unused additions.

No behavioral/logic changes, no new security surface, nothing here touches flowfile_core (still absent since the earlier revert) or crosses the core/worker contract.

Status of prior findings

  • Empty-right-table left join (usePlainPythonGeneration.ts:1605, `${right}[0] if ${right} else {}`) — still present, untouched by this commit. Still a narrow edge case, still worth a follow-up fixture + fix.
  • Multi-line JSDoc-style header comments — largely unchanged, though this commit trims one (usePlainStep.ts's 5-line header down to 1 line), a small step in the direction of the repo's "one short line at most" comment doctrine.

Not verified

Same limitation as previous rounds: this sandbox's approval gate blocks npm run test:run (flowfile_wasm), so I traced the visibility/removal changes by hand via targeted greps rather than compiling/running the suite. Given the refactor is mechanical (visibility tightening + dead-field removal + prop consolidation) and every removed identifier was confirmed unreferenced, I'd expect vue-tsc/vitest to pass, but that's not confirmed — worth checking CI is green before merge.

· Branch: plain-python-export

@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

Claude finished @Edwardvaneechoud's task in 1m 15s —— View job


Claude's review

Todo list

  • Read repo CLAUDE.md / flowfile_wasm CLAUDE.md
  • Diff b1e6f63..a573891 (the new commit since the last review round)
  • Review the refactor for correctness/regressions
  • Check status of prior open findings (empty-right-table join, JSDoc comment style)
  • Post findings

Summary

Commit a573891 (same title as b1e6f63, "refactor: simplify walkthrough structure and improve code readability") is test-only + one CLAUDE.md wording tweak — no production logic changed. It's worth flagging what it actually fixes, though: b1e6f63 left the test suite broken, and this commit repairs it.

Specifically, b1e6f63 removed NodeExplanation.supported and PlainWalkthrough.{traceScript,snippets} from usePlainPythonGeneration.ts (confirmed via git diff e5f36c3..b1e6f63), but three test files still referenced the removed members:

  • plain-python-generation.test.ts asserted explanation.supported
  • plain-python-walkthrough.test.ts read walk.traceScript and indexed snippets[step.nodeId]

Between b1e6f63 and a573891, npm run test:run / vue-tsc in flowfile_wasm would have failed to compile (TS2339, property doesn't exist) — so if CI ran on b1e6f63 directly it should have been red. a573891 fixes this correctly:

  • Drops the .supported assertions (the field no longer exists; the remaining assertions on .explanation/.code still cover the same behavior).
  • Replaces walk.traceScript with a new local traceScriptFor(flow) helper that constructs FlowToPlainPythonConverter directly and calls its (public) buildTraceCode() — I confirmed buildTraceCode() (usePlainPythonGeneration.ts:738) and FlowToPlainPythonConverter (usePlainPythonGeneration.ts:585) are both exported/public, so this compiles.
  • Replaces snippets[step.nodeId] with explainNode(flow, step.nodeId).codeexplainNode is exported from the usePlainPythonGeneration() hook (usePlainPythonGeneration.ts:1828,1844), confirmed.

Also finishes the helper-extraction started in b1e6f63: plain-python-generation.test.ts now imports makeNode/flowWith from tests/helpers/flow-builder.ts instead of a third duplicate copy, and both plain-python-parity.test.ts and plain-python-walkthrough.test.ts now share the CPython-discovery logic via a new tests/helpers/python-runtime.ts::findPython({ requirePolars? }) (I checked the two call sites use the option correctly — parity needs Polars, walkthrough's traceScript path is Polars-free so it calls findPython() with no option, matching its original probe of -c ''). Net effect is a real simplification (215 lines removed, mostly duplicated fixture/probe code) with no behavior change once the compile breakage is fixed.

The CLAUDE.md edit is just prose (documents the narrower protected surface from b1e6f63 more precisely, and points the Plain-Python bullet at the Learning-mode bullet instead of re-describing the double-gate) — consistent with the doc-maintenance convention.

Status of prior open findings

  • Empty-right-table left join (usePlainPythonGeneration.ts plainJoin, `${right}[0] if ${right} else {}`) — still present, untouched by this commit. Still a narrow edge case, low severity.
  • Multi-line JSDoc-style header comments — unchanged.

Not verified

Same sandbox limitation as previous rounds: npm run test:run / vue-tsc --noEmit are blocked by the environment's approval gate, so I traced the compile/type correctness by hand (grepping for the exported members the tests now reference) rather than actually compiling. Given the fix directly addresses concrete TS property-not-found errors and I confirmed every replacement member is genuinely exported, I'd expect this to go green — worth confirming CI is actually passing on a573891 given b1e6f63 likely wasn't.
· Branch: plain-python-export

@Edwardvaneechoud
Edwardvaneechoud merged commit cc46139 into main Aug 17, 2026
27 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