Skip to content

feat(frontend): show node output reference hint in config modal - #23

Open
LukasHirt wants to merge 2 commits into
mainfrom
feat/node-output-reference-hint
Open

feat(frontend): show node output reference hint in config modal#23
LukasHirt wants to merge 2 commits into
mainfrom
feat/node-output-reference-hint

Conversation

@LukasHirt

Copy link
Copy Markdown
Collaborator

Summary

Users configuring any node in the workflow canvas had no reliable way to learn the exact {{...}} template syntax needed to reference that node's output from a downstream node. The only hints were scattered placeholder strings on a couple of specific fields (LLM prompt/comment/message), not a consistent, always-visible piece of UI, and their accuracy had never been verified against the executor.

Investigation (executor.go ground truth)

Read backend/pkg/executor/executor.go in full. Findings:

  • LLM node: runLLM() writes vars["llm.output"] = output (line 172) and sets result.Output on the NodeResult. So {{llm.output}} genuinely works today. Confirmed by the pre-existing TestRunTriggerLLMAction test, which already asserts a downstream action node receives the rendered {{llm.output}}.
  • Action nodes (tag/comment/move/copy/rename/notify): every case in runAction() set result.Output on the execution-result record, but none of them wrote anything into the shared vars map. I proved this with a new failing test (TestActionOutputIsReferenceableDownstream, added first per TDD) that chains a tag action into a comment action referencing {{tag.output}} — before the fix, the comment literally contained the unexpanded string {{tag.output}}. So action outputs were not referenceable at all prior to this PR, despite result.Output existing — a real trap for anyone assuming otherwise.
  • WorkflowNodeData.outputVariable (frontend/src/types/workflow.ts): grepped the whole repo — it's declared on the type but never read anywhere, frontend or backend. runLLM() unconditionally uses the literal key "llm.output" regardless of this field. It currently has zero effect on runtime behavior, so the new hint intentionally does not offer a "customized variable name" variant — that would have been fictional.
  • Trigger node: file.name / file.content are populated into vars once per run whenever a resourcePath is supplied (line 66-76), independent of trigger type. This is meaningfully "the trigger's output" and is surfaced the same way.

Changes

Backend (backend/pkg/executor/executor.go) — small, scoped fix so the new UI hint is true rather than aspirational: each action case now also writes its result into vars under "<actionType>.output" (tag.output, comment.output, move.output, copy.output, rename.output, notify.output), mirroring the existing llm.output convention (a single global key per node-kind, not per node-id — same characteristic the existing llm.output key already has). Added TestActionOutputIsReferenceableDownstream covering this end-to-end through a chained tag → comment workflow.

Frontend (frontend/src/components/NodeDetailsPanel.vue) — added a persistent "Output" section, shown for every node type, stating only verified-correct tokens:

  • LLM: {{llm.output}}
  • tag: {{tag.output}}, comment: {{comment.output}}, move: {{move.output}}, copy: {{copy.output}}, rename: {{rename.output}}, notify: {{notify.output}}
  • trigger: {{file.name}}, {{file.content}}

Added frontend/tests/unit/NodeDetailsPanel.spec.ts — the first component-mount test in this package, using @vue/test-utils + createGettext() (real gettext plugin, since useGettext() throws without it) with oc-icon/oc-button/oc-text-input stubbed (host-shell design-system components unavailable in unit tests). Covers LLM, tag/move/notify action nodes, and a trigger node.

Both new tests were written first, confirmed to fail for the right reason (fictional/missing wiring), then made to pass.

Test plan

  • cd backend && go test ./... — all packages pass, including new TestActionOutputIsReferenceableDownstream
  • cd backend && go vet ./... — clean
  • cd frontend && npm run test:unit — 3 files / 9 tests pass
  • cd frontend && npm run check:types — clean
  • cd frontend && npm run lint — clean

Scope note: did not touch node categorization, the "+" button, node positioning, the OK button, or auto-open-on-add behavior — kept strictly to the output-hint concern.

@LukasHirt
LukasHirt requested a review from a team as a code owner July 24, 2026 16:05
@LukasHirt LukasHirt self-assigned this Jul 24, 2026
mzner
mzner previously approved these changes Jul 24, 2026
@LukasHirt
LukasHirt force-pushed the feat/node-output-reference-hint branch from de4c87c to bcc04fc Compare July 24, 2026 20:50
Action nodes (tag/comment/move/copy/rename/notify) already recorded their
result on the NodeResult execution record, but never wrote it into the
shared vars map that {{...}} template rendering reads from — unlike LLM
nodes, whose output is wired in via vars["llm.output"]. That meant an
action's result was, in practice, never referenceable from a downstream
node's template, even though nothing in the code or UI signaled that.

Mirror the llm.output pattern by writing each action's result into vars
under "<actionType>.output" (tag.output, comment.output, move.output,
copy.output, rename.output, notify.output) right where result.Output is
already set. This makes the upcoming frontend output hint truthful
instead of aspirational.

Signed-off-by: Lukas Hirt <info@hirt.cz>
Users configuring a node had no reliable way to learn the exact {{...}}
syntax needed to reference its output from a downstream node — only a
couple of fields carried it as scattered placeholder text.

Add a persistent "Output" section to NodeDetailsPanel.vue, shown for
every node type, that states the verified-correct template token(s):
- llm node: {{llm.output}}
- action nodes: {{tag.output}}, {{comment.output}}, {{move.output}},
  {{copy.output}}, {{rename.output}}, {{notify.output}} — matching the
  vars keys the executor now writes (see the paired backend commit)
- trigger node: {{file.name}} / {{file.content}}, available whenever
  the run targets a specific file

The WorkflowNodeData.outputVariable field was checked and confirmed to
have no effect on runtime behavior (backend/pkg/executor/executor.go
hardcodes the "llm.output" key regardless of it), so the LLM hint
intentionally does not offer a "customized" variant of the token.

Adds a Vitest component-mount test for NodeDetailsPanel.vue (the first
of its kind in this package) covering LLM, action, and trigger nodes.

Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt
LukasHirt force-pushed the feat/node-output-reference-hint branch from bcc04fc to 91931a1 Compare July 27, 2026 14:48

@dj4oC dj4oC left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat(frontend): show node output reference hint in config modal

Stats: +221/-0 across 4 files (backend + frontend)

Overview

Adds a persistent "Output" section to NodeDetailsPanel showing the exact {{...}} token(s) a node's result is referenceable by downstream. To make the hint true rather than aspirational, the backend fix comes first: runAction() in executor.go previously set result.Output on the NodeResult for every action type but never wrote anything into the shared vars map, so {{tag.output}}, {{move.output}}, etc. silently did not work despite looking plausible. This PR closes that gap for all six action types, mirroring the existing llm.output convention, and only then documents the (now-true) tokens in the UI.

I independently verified the two central claims against main:

  • runAction() on main does indeed set result.Output for every case but never touches vars — confirmed by reading the full function.
  • The pre-existing TestRunTriggerLLMAction does verify {{llm.output}} substitution end-to-end (fGraph.taggedWith == "summary:a short summary"), so that half of the "ground truth" investigation checks out too.

Both hold up — this PR's premise is solid, not just asserted.

Code quality & style

  • The backend change is minimal and mechanical (one vars[...] = ... line per case), which is the right size for what's actually a bug fix wearing a "docs" PR's clothes.
  • Keying move/copy dynamically as vars[actionType+".output"] (rather than a shared action.output) is a good call — called out directly in a comment — since it lets a graph with both a copy and a move node address each independently.
  • frontend/src/components/NodeDetailsPanel.vue's inline comment enumerates every vars key the executor actually writes, with a direct instruction that nothing be listed here without a matching backend write. That's a good guardrail against the hint drifting out of sync with reality again.

Suggestions

  • actionOutputHints is typed Record<string, OutputHint> rather than Record<ActionType, OutputHint>. All six current ActionType values are covered today, but the looser string key means TypeScript won't force an update here if a new action type is ever added — it'll silently fall through to ?? null (no hint shown) rather than a compile error. Tightening the type would make this self-enforcing the same way the rest of the PR tries to keep the hint accurate.
  • The trigger-node hint doesn't vary by triggerType. It always shows {{file.name}}/{{file.content}} for any trigger node, including a Schedule Trigger — which, per the executor (and the investigation in the sibling PR #22, currently also open), can never actually populate those vars, since the scheduler always calls Run with an empty resourcePath. The current wording ("e.g. a file-event trigger, or a manual run targeting a file") doesn't claim schedule triggers qualify, so nothing here is technically false — but given how rigorously this PR verifies everything else against ground truth, explicitly branching the message for triggerType === 'schedule' (e.g., "not available — this trigger never supplies a file") would close the one case where the hint could mislead by omission.
  • Minor duplication in the test file: a new mountOutputHintPanel helper is added alongside the pre-existing mountPanel helper in NodeDetailsPanel.spec.ts, with slightly different setup (adds stubs for oc-icon/oc-button/oc-text-input, which the original helper doesn't use). Consider extending the existing helper instead of maintaining two similar-but-different mount configs in the same file.
  • PR-description nit: the description states this is "the first component-mount test in this package" — NodeDetailsPanel.spec.ts already had mount()-based tests using a real createGettext() plugin on main prior to this PR. Doesn't affect the code, just flagging since the PR leans heavily on precise claims elsewhere.

Cross-PR note (FYI, not blocking)

Both this PR and the currently-open PR #22 modify NodeDetailsPanel.vue and NodeDetailsPanel.spec.ts in overlapping regions (new template blocks and new computeds inserted right around the same spot). Not a problem for either PR individually, but whichever merges second will need a straightforward manual conflict resolution.

Test coverage

Good, and appropriately TDD'd per the PR description (new test added first, confirmed to fail for the right reason, then fixed). TestActionOutputIsReferenceableDownstream chains a real tag → comment graph and asserts the rendered output through the fake Comment call — a meaningful end-to-end check, not just a unit check of the vars map. Frontend tests cover LLM, three action types (tag/move/notify), and a trigger node; comment/copy/rename aren't separately asserted in the component test, though they share the same code path as tag/move, so risk is low.

Security / performance

None — additive vars map entries only, no new I/O, no new inputs from untrusted sources.

Summary

Small, well-verified, low-risk PR that fixes a real latent bug (action outputs were never actually referenceable) before documenting the fix. Suggestions above are polish, not blockers.


🤖 Generated with Claude Code

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.

3 participants