On 17 September we wrote about the scenario editor, the screen where an author lays out a branching procedure as a graph and, once it is published, mints a short launch code for it and prints that code as a QR image. That article named one honest gap: the page a printed launch code opens had never been carried across into VARGATES Medical. It was filed as a priority-one defect, and until it landed a printed code would open a 404.

It landed on 19 September. This is what the page does, what had to change before it could exist, the two defects that only appeared once somebody walked it end to end in a browser, and what is still open.

What a launch code is for

A launch code is meant to be scanned by a person standing at a piece of equipment, holding a phone, with no account and no session. The page is not a viewer. It resolves the short code, loads the published version of the procedure, starts a run without anyone signing in, steps through the procedure node by node, opens a confirmation window where a step demands a second person as witness, and completes. The person can type an identifier at the start or skip it; the identifier travels with the run and the completion screen shows it, because a run is kept as the record of a procedure somebody performed.

None of the server side was missing. Every call the page needs already existed in the platform. What was missing was the page itself, which made this a page port rather than a new feature.

The one artefact that cannot be corrected

A web page with a wrong link can be fixed and redeployed. A laminated label cannot. Once a code has been printed and fixed to a machine, the address inside it is permanent, and whatever that address answers is what the person at the machine gets. Until this change, every label the editor could produce would have resolved to a 404.

Before the editor was merged, this was argued to be harmless, because a code can only be minted for a published scenario and none from the moved core was published on production. The review withdrew that argument: Publish and Generate sit on the same toolbar, one click apart. The first author to use both would have had a printable image pointing at a page that did not exist. That is why the defect was raised to priority one rather than scheduled.

Two guards were asking a narrower question than their own rule

The page is public, so it had to be declared public in the same lists that decide which addresses need a session. Two automated checks guard those lists, and both had been accurate only because of how the earlier public pages happened to be shaped: each was declared public from its first address segment down, and the only public pages with form fields were sign-in pages. This page is the first to break both assumptions, and both checks turned out to be wrong in the reassuring direction.

  • The route check looked only at the first segment of an address. Nothing is served at /scenarios on its own; the only page under it is the launch-code page. Only that narrower address is declared public, deliberately, because declaring the whole of /scenarios public would silently publish every page added there later. So the check reported an unclaimed route while the real answer, asked of the real address, was public. The other half was worse: a first segment declared public would have waved through everything beneath it. The check now walks to each directory that actually holds a page.

  • The form-field check held only the sign-in pages to its rule and recorded everything else as a known exception. The first public page with a field in it would simply have joined the exceptions, and a list of accepted exceptions reads exactly like a clean run.

Neither was a missing check. Both existed, both ran, and both would have passed a defect. A check that answers a smaller question than the one its name asks is harder to notice than no check at all.

The ending nobody read

The procedure used in the browser test is an isolation procedure: isolate the supply, have a witness confirm it, decide how the isolation is proved, and reach an ending the author wrote, “Safe to work”, with the sentence “The line is dead and the permit is signed.”

Walked in a browser, that sentence never appeared. The server closes a run at the moment a step reaches an ending, and returns the ending in the same response. The page read the first half, saw the run was closed, discarded the ending and went straight to the completion screen. It then asked the server to close the run a second time, which the server refuses, and swallowed the refusal. So the person who had just performed the procedure saw a receipt and never read the outcome, on a page whose entire output is an instruction somebody follows with their hands. The platform's own scenario player had been fixed for the same defect earlier; this page was brought across from a source that never received that fix.

It was first filed separately and deferred, on the reasoning that the completion screen had nowhere to put an authored ending, so showing it would need a design. Re-reading the file to answer the review showed that reasoning to be wrong. The component already had a branch that renders the ending's message under a “Complete procedure” button. It was not absent, only unreachable. Reading the component took minutes and reversed the decision.

A button that could not press

The second defect was found the same way, and it had to be fixed in the same change, because without the fix the page could not do the one thing it exists for. The first step of the test procedure, isolating the supply, has a single button, “Mark as done”. It could never submit the step: the walk stopped at step one on “Error advancing step”, every time.

The cause was one line. The button handed the browser's click event to a function whose first parameter is the identifier of the branch to take. The event is a non-empty value, so the function took it as a branch identifier, skipped its own lookup of the correct one, and tried to turn the event into the request body. That conversion fails on a click event, because the event refers back to itself, and it failed before any request was sent.

The type checker could not object, and this is a property of the language rather than an oversight: a function whose parameters are all optional may stand wherever a function taking none is expected, and a function may always ignore arguments it does not declare. Each step of the chain was legal, and the mismatch vanished at the first one.

Every channel read like a page nobody had clicked
Where one would lookWhat it showed
The server logClean. No request ever arrived.
The browser's network panelEmpty. No request ever left.
The consoleNo error. The failure was caught inside the page's own error handling, before the part that logs failed requests.
The screen“Error advancing step”, a generic sentence by design, naming nothing.

What settled it was a probe installed in a live browser that records every request the page attempts. It recorded none. The fix is one line; the class is now its own open issue, described below.

The test data refused to be wrong

The browser test needs a published procedure and a real launch code in the local test database. The fixture is published through the same function the product uses to publish, rather than written into the database by hand. On the first attempt that function refused it: the fixture described its ending in a form the product does not read. A hand-written fixture would have agreed with itself, disagreed with the product, and gone green over the disagreement. Publishing it through the product turned that into a loud failure at setup time.

A green report that was red

Before merging, the change went through the full gate against a local stack of database, backend and frontend in containers, with a real browser. The final figures in the pull request were 160 of 162 end-to-end tests passed, one flaky test that passed on retry and is named rather than folded into the count, and one declared skip; 2,149 frontend unit tests passed; and 21,915 backend tests passed with none failed.

An earlier revision of the same pull request reported the end-to-end run green when it was not. The browser suite prints its pass count first and a separate check on declared skips afterwards. The test count recorded for that check had not been updated when two new tests were added, so it refused the run and the command exited with failure. The report was written from the pass count. It was corrected in the pull request itself, the count was fixed, and the testing guide now describes how the miss reads, because the next person will see the same order of lines. The gate's verdict is its exit code, not the number above it.

What we checked on production

After the merge, production was confirmed to be serving the merged commit. The team opened the locale-prefixed address the editor prints, with the test fixture's code, in a browser against production: the page answered 200, the player mounted, called the production backend, received a not-found for that code, and said so in Russian. That is correct, because the fixture's code exists only in the local test database. Before the merge the same address never reached the backend at all.

That check was nearly reported wrong first. A plain download of the page's HTML contains both the page's own “not found” wording and the framework's “This page could not be found”, and so does the site's home page, because the whole translation dictionary is shipped inside every page. Searching the HTML answers whether a sentence is in the dictionary, not whether it is on the screen. For a page drawn in the browser, only the rendered page is evidence.

For this article we repeated the check with an invalid code. On 22 September, the launch-code address with a made-up code rendered “Invalid or expired QR code”, and the browser's request to the production backend for that code came back 404. We did not run a real procedure on production, and this article makes no claim about any launch code in use.

What is still open

  • The class behind the dead button has no guard yet. It is filed as its own issue, and it asks for a count first: how many places in the moved code wire a handler this way, measured before any check is written, so that the check's starting point is a measurement rather than a guess. The count has not been taken. Until it is, we do not know whether this was the only instance.

  • The script that brings pages across can delete work it did not write. Measuring the change turned up a larger problem: rebuilding the moved tree from scratch deletes 46 files that exist in no upstream commit, including the scenario editor itself, and reports success. That issue is open and needs a decision about what the script is for, not a patch.

  • The editor's silent write failures are still open. The previous article named a defect in which several of the scenario editor's write paths can fail without telling the author, and automatic layout can save half a reordering. That issue is still open.

  • The two scenario players still do not share their ending logic. The issue about the lost ending asked for a check that compares the platform's player and this one, since both answer the same question about a completed step. The merged change fixed this page and walks the ending in a browser test; it does not contain that comparison.

If you are evaluating a supplier

Ask what their acceptance test does after the page renders. This page built, type-checked, rendered, resolved its code and started a run, and still could not advance a single step, because nothing had pressed the button. Ask how a green result is established: by reading a number, or by reading whether the gate passed. And ask what they print. Anything that leaves the building on paper deserves a test that follows it all the way to the last screen, because that is the one address nobody gets to correct.

VARGATES Medical is the platform this learning core is moving into. The scenario editor and the launch-code page described here are part of that move. The previous step is in Three defects only a browser could see.