Skip to content

fix(flow-editor): stop Save from silently doing nothing - #608

Merged
rubenvdlinde merged 1 commit into
feat/vue-3from
fix/flow-editor-save-silence
Aug 6, 2026
Merged

fix(flow-editor): stop Save from silently doing nothing#608
rubenvdlinde merged 1 commit into
feat/vue-3from
fix/flow-editor-save-silence

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Closes #607.

Three defects combined so that pressing Save could do nothing at all, with no way for the user to find out.

1. The editor was interactive before it was initialised

emptyFlow() has name: '', and only open('new') supplies the default — but open() ran at the tail of load(), behind await GET /api/flows, a flow list that a blank flow does not need. Meanwhile the sidebar was already rendered and Save already enabled, because nodeCatalog had been filled by the index page and the store is a singleton that survives the route change.

Saving in that window posts name: "", which FlowController::create() answers 400 "A flow needs a name." — 9 of 10 attempts against a fresh instance.

A blank flow needs nothing from the server, so it is now opened first.

2. Re-opening threw the user's work away

The late open('new') reset this.flow, so a step placed during the window vanished from the canvas, and a save landing after it stored nodes: []. Silent data loss — and in openregister's e2e it also made the suite pass while asserting nothing, because the flow it saved was empty and so could not fail on its contents.

The blank flow is no longer re-opened at the tail.

id === 'new' only, never id === null

save() calls load({ app }) to refresh the list, and a null id has always meant "reload the list, leave the open flow alone". Treating it as blank would reset the flow immediately after storing it and discard the id the server had just returned. A test pins that.

3. A refused action was invisible

store.error was set on every rejected save and run and rendered nowhere, so a refusal looked exactly like success: the button flickered and nothing else happened. No server log line either — a 400 JSONResponse is not an exception.

There is now an error card, preferring the API's own sentence over axios's: "A flow needs a name." says what to do; "Request failed with status code 400" does not.

Save is additionally disabled while the flow has no name, so the state the API refuses is one the UI stops offering.

Tests, checked in both directions

test without its fix
names a blank flow before the network fails
keeps a step placed while the list is loading fails
shows the server's own reason when a save is rejected fails
falls back to the transport message fails
disables Save while the flow has no name fails
leaves the open flow alone on a null-id reload passes either way
shows nothing while no action has failed passes either way

The last two pin the guards rather than the fixes, which is why they are worth keeping.

Full suite: 6032 tests, 510 suites, green.

Not addressed here

The issue's fourth point — load() refetching both catalogues on every call, ~6 redundant requests per save — is a performance change with a different blast radius and is left for its own PR.

Closes #607. Three defects combined so that pressing Save could do nothing at
all, with no way for the user to find out.

INITIALISE BEFORE THE NETWORK. `emptyFlow()` has `name: ''`, and only
`open('new')` supplies the default — but `open()` ran at the TAIL of `load()`,
behind `await GET /api/flows`, a flow LIST a blank flow does not need.
Meanwhile the sidebar was already rendered and Save already enabled, because
`nodeCatalog` had been filled by the index page and this store is a singleton
that survives the route change. Saving in that window posts `name: ""`, which
`FlowController::create()` answers 400 "A flow needs a name." — 9 of 10
attempts against a fresh instance. A blank flow needs nothing from the server,
so it is now opened first, and NOT re-opened afterwards.

Re-opening was the second defect: the late `open('new')` reset `this.flow`, so
a step placed during the window vanished from the canvas, and a save landing
after it stored `nodes: []`. That is silent data loss, and in openregister's
e2e it also made the suite pass while asserting nothing — the flow it saved
was empty, so it could not fail on its contents.

`id === 'new'` only, never `id === null`. `save()` calls `load({ app })` to
refresh the list, and a null id has always meant "reload the list, leave the
open flow alone"; treating it as blank would reset the flow immediately after
storing it and discard the id the server had just returned. A test pins that.

RENDER THE FAILURE. `store.error` was set on every rejected save and run and
displayed nowhere, so a refusal looked exactly like success: the button
flickered and nothing else happened. No server log line either — a 400
JSONResponse is not an exception. There is now an error card, preferring the
API's own sentence over axios's, because "A flow needs a name." says what to
do and "Request failed with status code 400" does not.

Save is also disabled while the flow has no name, so the state the API refuses
is one the UI stops offering.

Every test was checked against the unfixed code: the two race tests and the
three visibility tests fail without their fix, and the two that pin the
guards — a null-id reload, and an error card absent when nothing failed —
pass either way, which is what makes them worth keeping. Full suite: 6032
tests, 510 suites, green.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/nextcloud-vue @ 9031483

Check PHP Vue Security License Tests
lint ⏭️
phpcs ⏭️
phpmd ⏭️
psalm ⏭️
phpstan ⏭️
phpmetrics ⏭️
eslint
stylelint
build
test
check-build
check-docs
check-jsdoc
check-integration-parity
check-peers
check-docs-fresh
check-a11y
composer
npm ✅ 531/531
PHPUnit ⏭️
Newman ⏭️
Playwright ⏭️
Hydra gates ⏭️

Quality workflow — 2026-08-06 11:15 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit cc8b111 into feat/vue-3 Aug 6, 2026
27 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/flow-editor-save-silence branch August 6, 2026 11:16
rubenvdlinde added a commit to ConductionNL/openregister that referenced this pull request Aug 6, 2026
Carries ConductionNL/nextcloud-vue#608, which closes #607: the flow editor no
longer lets a user press Save before the store has a flow to save.

The window was real and wide. `emptyFlow()` has `name: ''`, only `open('new')`
supplies the default, and `open()` ran behind `await GET /api/flows` — a list
a blank flow does not need — while the sidebar was already rendered and Save
already enabled. Saving in that window posted `name: ""` and the API answered
400 "A flow needs a name." A refused save rendered nothing at all, so the user
saw the button flicker and no more. The same late `open('new')` also reset the
flow, wiping a step already placed on the canvas.

Verified against this bundle, in the window itself: clicking Save 120ms after
opening a blank flow — where a 400 was previously reproducible 9 times in 10 —
returns 201 with `name: "New flow"` and the route advances to the new uuid.

`vue` is pinned back to ^3.5.18 by hand again. `npm install` rewrites it to
^3.5.0 to match the new package's own range, which contradicts the `overrides`
entry, and CI's npm 10.8.2 refuses that with EOVERRIDE while local npm 11
accepts it silently. `npx npm@10.8.2 ci --dry-run` is clean with the range
restored. Worth automating; for now it is a hand check on every bump of this
package.
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