v0.17.0 — the QA engine, plus mid-drag docking with shake-to-disconnect - #22
Merged
Conversation
Validation becomes a rule engine an engineer can work through, instead of two
lists split by an implicit flag.
Severity is now a real thing
The old model encoded it as the ABSENCE of a field — `severity?: 'suggestion'`,
where undefined meant error — so there was no middle and no way to say how much
a finding mattered. Rules now declare critical / warning / information and a
discipline (tagging, topology, process, instrumentation, data), and the report
groups by severity first because that is how an engineer triages: what blocks
issue, then what to look at.
Accepting a finding, with a reason
Not every finding is a mistake. Accepting one records why, and keeps it visible
in its own section rather than hiding it — a hidden acceptance rots, and the
reason someone accepted it is exactly what the next reviewer needs. Because a
finding is keyed by rule + ENGINEERING identity and never by node id, the
acceptance survives deleting and redrawing the symbol.
Fixes are data, not callbacks
A rule returns a FixSpec, so a repair can be named, previewed with its blast
radius, and replayed in a test before anything happens. applyFix reports whether
it actually landed instead of failing silently, and describeFix gives one shared
description so a preview and the action cannot drift. (Requested by another
session building on this; it was the right call regardless, and converting the
closures is what exposed the duplicate-key bug below.)
One index per document
Every rule reads a single prepared walk of the drawing. runChecks alone used to
build four maps, and the advisor rebuilt a neighbour list per node per rule.
Three bugs this found
- Equipment tags were reported as ISA errors. invalid-letters ran the ISA-5.1
INSTRUMENT letter tables over equipment, so P-101 on a pump — a completely
standard tag — was flagged. Found by running the new rules over the project's
own bundled templates. The false positive existed in the old engine too.
- The same finding was emitted twice when two symbols wore one tag, so accepting
one would silently accept both. Findings are now one per rule and entity.
- qaFor's cache never hit, because `doc.qa?.ignored ?? {}` allocated a fresh
object every call, so all 21 rules re-ran on every render.
Calibrated against real drawings, not intuition
Running the rule set over the five bundled examples first reported criticals on
three of them and 11-13 identical warnings. That teaches people to ignore the
report, so: no-relief and no-fail-position are warnings rather than blockers by
default (both are real, both are legitimately unstated on early drawings; a
company standard promotes them in v0.18), and required-field-empty only fires on
a record someone has STARTED — an object nobody has begun specifying is not yet
an omission. All five bundled drawings now report zero criticals.
Retires validate/checks.ts, validate/suggest.ts, validate/issues.ts and the two
panels that read them; their coverage moved to the rule tests. locateCell — the
primitive behind every jump in the app — moves to canvas/locate.ts, where it
belongs. Removes the dead Finding type.
Note on a shared working tree: this commit was made from a checkout in which
another session was concurrently building the mid-drag docking work. Its own
files are committed separately, but a few of its lines ride along here in files
both changes touch — the .pid-dock-cut styles in app.css, dockNode returning the
new edge id in store.ts, one README bullet, and its own [Unreleased] CHANGELOG
section (left under its own heading, deliberately not folded into 0.17.0).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Docking used to wait for the mouse button: you dropped a symbol on a connection point, let go, then picked it back up to pull it into place. The line is now drawn the moment the two points meet, with the button still down, and the pipe stretches as the drag carries on. - Symbols stand off 24px from the point they dock onto, along the way that port faces. Landing the points on top of each other read as nothing having happened: the symbols butted together and hid the line behind themselves. - A magnet only joins ports that FACE each other. Two ports pointing the same way stood a symbol on the wrong side of the nozzle it had just connected to, with its inlet pointing away. - Shake the symbol mid-drag and the line that drag made is cut, with a red flash where it landed. The symbol stays in hand, won't snap back onto the point just rejected, and letting go doesn't sneak the connection back on. - The whole gesture is ONE undo step, catch and carry together. Fixes a pointer-event ordering bug found while testing: 'pointerup' on window lands BEFORE the compatibility 'mouseup' JointJS listens on, so the global teardown was closing the drag's undo group early and letting a release re-connect a shaken-off pair. Authored by a concurrent session working in the same checkout; committed here so it ships with v0.17.0 rather than sitting uncommitted. Its edits to files this branch also touches (app.css, store.ts, README, CHANGELOG) landed in the preceding commit — see the note there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits from a shared working tree, kept separate because they are unrelated changes by different sessions.
efcccef— v0.17.0, the QA engineValidation becomes a rule engine instead of two lists split by an implicit flag.
severity?: 'suggestion', where undefined meant error), so there was no middle. 21 rules now declare critical / warning / information and a discipline, and the report groups by severity because that is how an engineer triages.FixSpec, so a repair can be named, previewed with its blast radius, and replayed in a test.applyFixreports whether it landed instead of failing silently.runChecksalone used to build four maps; the advisor rebuilt a neighbour list per node per rule.Three bugs it found
P-101on a pump was reported as an invalid ISA tag.invalid-lettersran the ISA-5.1 instrument letter tables over equipment. Found by running the new rules against this project's own bundled templates — the false positive existed in the old engine too.qaFor's cache never hit —doc.qa?.ignored ?? {}allocated a fresh object per call, re-running all 21 rules on every render.Calibrated against real drawings
Run over the five bundled examples, the rule set initially reported criticals on three of them and 11–13 identical warnings. That teaches people to ignore the report. So
no-reliefandno-fail-positionare warnings rather than blockers by default (a company standard promotes them in v0.18), andrequired-field-emptyonly fires on a record someone has started. All five bundled drawings now report zero criticals.Retires
validate/checks.ts,suggest.ts,issues.tsand the two panels that read them; coverage moved to rule tests.locateCellmoves tocanvas/locate.ts.659ff7d— mid-drag docking with shake-to-disconnectAuthored by a concurrent session in the same checkout. Docking no longer waits for the mouse button: the line is drawn the moment two connection points meet and stretches as the drag continues. Symbols stand off 24px along the way the port faces (coinciding points read as nothing having happened); magnets only join ports that face each other; shaking the symbol mid-drag cuts the line that drag made. One undo step. Also fixes a pointer-event ordering bug where
pointerupon window lands before JointJS's compatibilitymouseup.Verification
npm run build— cleannpm test— 746 passingnpx playwright test --workers=1— 68 passingRun serially on purpose: the e2e suite flaps against the shared dev server when multiple sessions are editing, because HMR reloads kill tests mid-run.
tsc -binitially reported clean because the check was piped throughhead, which masked its exit code; an isolated worktree at the commit surfaced two realTS6133errors, since fixed.🤖 Generated with Claude Code