Repository navigation
bug(llc/frontend): Automation first-run entry drops its destination, and org containers kill canvas panning (post-merge #13939) #13996
Description
Post-merge review of #13939 (PR #13959, merged 2026-08-11 07:15). Both defects are live on
Dev_new_gui and both break behaviour the feature itself advertises. Independently verified.
H1 — the first-run path to Automation silently drops its destination
views/llc/AutomationCompanyRedirectView.vue:40sends
{ name: 'llc-company-select', query: { redirect: route.fullPath } }wherefullPathis
/automationor/automation/canvas.views/llc/CompanySelectorView.vue:52-53honours the redirect only when it
startsWith('/llc/').
/automation/canvas does not, so it is discarded. A first-run user clicks Automation in the main
nav, is sent to the company selector, picks a company, and lands on the company backlog — never
reaching automation. The forwarder's own docblock (:8-9, "carrying the destination so the selector
can return there") is false.
The existing test (__tests__/AutomationCompanyRedirect.test.ts:70-77) asserts only that replace
was called with that query object. It never exercises the selector, so it is green while the
behaviour it names is broken.
Fix: widen the selector allowlist to accept /automation too, keeping the leading-slash and
no-// checks so it stays closed to open redirects. Test must drive the selector, not the forwarder.
H2 — org containers blanket the canvas and kill the pan gesture the UI tells you to use
components/workflow/WorkflowCanvas.vue:429-430—startPanfires only on middle-click or
shiftKey, and it is bound on.canvas-area(:68).:85— every node div carries@mousedown.stop="startDrag(node, $event)", so a press on a node
never reachesstartPan.- An
org-groupcontainer is sized to its entire subtree (928×482 for a 6-person forest), so it
covers essentially the whole drawing area.
Result: shift-drag over a container calls startDrag(container) → emits node-moved →
OrgChart.vue:135-136 deliberately ignores containers → nothing happens. Panning works only in
the 60px gutters. Meanwhile llc.orgChart.canvasHint ships in all 11 locales telling the user to
shift-drag.
Neither drag test can see it: both call vm.$emit('node-moved', …) directly rather than performing a
gesture.
Fix: bail out of startDrag when e.shiftKey || e.button === 1 so pan wins, or set
pointer-events: none on .org-group with its header re-enabled. Test with a real gesture.
M1 — user drags are discarded by pause/resume, and the comment says the opposite
views/llc/OrgChart.vue:49-56. buildOrgCanvasGraph reads name/title/status/adapter_type/is_human
off deeply-reactive tree nodes, so the watchEffect subscribes to all of them. toggleAgentPause
writes node.status at :99 → the whole array rebuilds → every dragged position resets.
Pause/resume is the primary canvas-mode action, so drags survive almost nothing.
Two facts on the same lines: the comment claims "the canvas mutates node.position in place" — it
does not, it emits node-moved (WorkflowCanvas.vue:456). And :100 replaces selectedNode with a
detached shallow copy, so a second toggle mutates the copy and the canvas status dot goes stale
(pre-existing; canvas mode is what makes it visible).
Fix: drive the layout from an explicit shallow source (e.g. watch the root id list) and merge
status changes into existing nodes rather than rebuilding.
Cheap same-file wins to take in the same pass
- M2 —
WorkflowCanvas.vue:236nodeIconsisRecord<string, IconName>and has noloopkey,
while'loop'is inWorkflowNode['type']andnodeLabelsdoes have it.<Icon :name="undefined">
now dev-warns "Missing required prop: name" on every loop node — the previous<i :class>form
failed silently, so fixing the icons introduced a warning. Type the map by node type so the missing
entry is a compile error. - M3 —
nodeStyle(:274-283) honoursdata.width/data.heightfor every node type, and
WorkflowNode['data']is arbitrary. Latent today, but it is unnamespaced coupling on a component
two features depend on. Gate onnode.type === 'org-group'. - M4 —
AutomationCompanyRedirectView.vue:41builds a bare path, droppingroute.queryand
route.hash.useEntityAnchors.ts:76,78emits{ query: { workflow: id } }. One-line fix.
Acceptance criteria
- First-run Automation entry reaches automation, proven by a test that drives the selector.
- Shift-drag pans anywhere on the canvas, proven by a real gesture test, not an emit.
- A drag survives a pause/resume.
- Each fix mutation-proved: break it, confirm red, restore, confirm green.
Activity
mrveiss commentedon Aug 11, 2026
Fix opened as #14003 (branch
issue-13996). All four in-scope defects (H1, H2, M1, plus M2/M3/M4) are addressed; #13994, #13993 and #13995 are deliberately untouched.Each of H1, H2 and M1 is mutation-proved in the PR body — the fix is broken, the new test is confirmed red, the fix is restored and the test is confirmed green. Notably the two pre-existing drag tests and the RTL pan test stay green under the H2 and M1 mutations, which is exactly why these defects shipped: they emit
node-movedand start their gesture in the canvas gutter.fix(llc/frontend): drop the hard-coded English fallback on the loop n…
mrveiss commentedon Aug 11, 2026
Found-but-not-fixed while working this issue, filed rather than dropped:
useEntityAnchorsresolvesworkflowandtaskanchors to?workflow=/?task=, and no view reads either. M4 makes the query survive the forward; nothing consumes it at the other end.CANVAS_NODE_WIDTHalready exists. Latent today (sized nodes only appear in the read-only org canvas), live as soon as a sized node is editable.Both are out of scope for #14003, which is a post-merge defect fix.
fix(llc): reject dot segments so traversal cannot escape the redirect…
fix(llc): keep the automation destination, unblock canvas panning, su…
github-actions commentedon Aug 11, 2026
PR #14003 (merged to
Dev_new_gui) references this issue with a close keyword.If this issue is fully resolved, close it manually. If work remains, no action is needed.
mrveiss commentedon Aug 11, 2026
Delivered in PR #14003, squash-merged into
Dev_new_guias49aa53b2c. Closing manually —Closes #NNNdoes not fire onDev_new_gui.Verified on the merged base, not assumed:
H1 — the Automation first-run entry. The forwarder sent
?redirect=/automation/...while theselector honoured only
startsWith('/llc/'), so the destination was discarded and a first-run userlanded on the backlog. Fixed by extracting
safeRedirectTarget()as one validator every?redirect=consumer shares, rather than widening one comparison.
H2 — canvas panning.
startPanfires only on shift/middle-click on.canvas-area, but every nodecarried
@mousedown.stop, and anorg-groupcontainer is sized to its whole subtree — so itblanketed the canvas and shift-drag did nothing, while the hint string telling users to shift-drag
shipped in all 11 locales. The original brief suggested bailing out inside
startDrag; that would nothave worked, because
.stophalts propagation before the handler's verdict matters. The binding wasreplaced instead.
M1 — drags surviving pause/resume, plus M2–M4 (icon map typing,
nodeStylegated toorg-group,query/hash preserved through the forward).
Added during review — traversal.
/automation/../../evilpassed the prefix check, and although apath-absolute string can never change the origin (proved across 34 payloads),
history.pushStatehands the raw string to the browser, which collapses the dot segments. An attacker-set
?redirect=could therefore steer an authenticated user to any in-app route. Rejected dot segments including the
percent-encoded spellings; mutation-proved — disabling the guard reddens exactly the four traversal
cases.
Mutation evidence. Three proofs from the author, all independently reproduced by review, plus a
fourth the reviewer wrote themselves to confirm the pan fix cannot silently kill node dragging.
Notably, under two of the mutations the pre-existing tests stayed green — they emit
node-movedinstead of gesturing, which is precisely why the defects shipped.
Merge note. This branch conflicted with #13945 in
WorkflowCanvas.vueexactly as review predicted—
nodeFlagagainst thenodeStylerewrite. Resolved keeping both; they are complementary. Finalstate: 229 tests pass, lint and
vue-tscclean, 16 CI checks green.Follow-ups filed rather than folded in: #14079 (a shift-pan starting on a node opens that node's
drawer), #14080 (
/login?redirect=written by two 401 handlers and read by nothing), #14103 (the RTLpan assertion is order-dependent).