A feature that opts out of your shared lifecycle will reimplement it, badly
TLDR
Our measure feature had its own on/off flag instead of joining the tool system. It did not thereby avoid needing activation, cancellation and persistence. It just implemented all three worse, privately, and nobody noticed because each gap looked like a small missing feature rather than one structural mistake.
The position
Most apps grow a shared lifecycle for their main interaction: a tool system, a command registry, a job runner. Then a feature arrives that does not quite fit, and someone gives it a boolean.
That boolean is not a shortcut past the lifecycle. It is a commitment to reimplement it, one piece at a time, somewhere nobody will look.
What the industry does and why
Opting out is locally rational every time. Conforming means bending your feature or widening the system; a flag is five minutes. It also reviews well: if (this._measuring) is legible, and nobody thinks to ask "does this participate in cancellation" because the flag never raises the question.
There is usually a real objection too. Our tool system cancels any in-progress draw on tool switch, and measure had a dialog to show first.
Why it fails
Because the lifecycle is not optional, it is just unowned.
Measure, as a mode:
- Escape did not cancel it, because Escape is handled centrally for draw tools and measure was not one.
- Tool lock did not apply. Q keeps a tool active for repeat use, and measuring repeatedly is the common case.
- It never handed back to Select, so your next click started another measurement instead of selecting what you just made.
- Its output was not in the document. Measurements lived in a throwaway overlay: they vanished on the next action, never exported, never undid.
- Sizes were divided by the current zoom at creation, so a measurement taken at 4x was microscopic at 1x.
The flag caused a second failure directly. Measure owned its settings dialog and only started once you pressed OK, welding a document setting (scale, set once) to an action (measuring, repeated). You could not measure twice without re-confirming your units. Nobody designs that; it falls out of one entry point serving two concepts.
The better approach
Register it, and solve the one genuine conflict explicitly.
_DRAW_TOOLS: ['pen', 'line', 'rect', 'ellipse', 'polygon', 'text', 'wire', 'measure'],
That line delivered Escape, tool lock, auto-revert, cursor, hint and consistent activation. The real objection was answered with a guard, not an architecture:
if (tool === 'measure') this._measureBegin?.();
else this._measureEnd?.();
Settings moved to the toolbar, where settings live: one button opens units and calibration, another activates the tool.
The output then became a real document element rather than overlay debris, which unlocked something unplanned. Once a measurement is an element whose vertex list is its source of truth, it inherits the editing model too: node handles, drag to reshape, double-click to add one. The same interactions as a wire, because it is the same kind of object, sharing code rather than paralleling it.
That is the argument in one observation. Conforming did not cost a feature, it handed us one, because membership means inheriting the improvements too.
What you give up
Real things. Your feature inherits decisions made for other features, so if the shared system is wrong for you, conforming propagates its wrongness. Widening it touches shared code: a bigger diff and a wider blast radius than a flag.
And some features are not that kind of thing. Splitting measure into a tool plus separate settings worked precisely because half of it did not belong in the tool system either.
When the flag is right
When the thing genuinely has no lifecycle: a modal, a rendering toggle, anything with no notion of starting, being cancelled, or producing output.
The test is one question. Can this be interrupted? If a user can be halfway through it, it has a lifecycle, and you will implement one whether you meant to or not. The only choice is whether you write it once in the shared place or a fifth time in private.