Show what a tap would do while the pen hovers, and declare 1.0.0 - #62
Merged
Conversation
The palette was one tool button wider than it needed to be. The dual layout sizes its shared colour column from the pen's six swatches, and the highlighter row carried three tool buttons against the pen row's two, so that row set the width of the whole palette and overflowed the picture-in-picture the default top-right placement exists to hide under. The highlighter has four swatches, leaving two swatch widths of that shared column unused. The select button now sits there, docked to the column's right edge, which drops the palette from 328 to 286 logical pixels without moving anything else. A DockPanel rather than overlapping elements, so a palette that ever gains colours squeezes the swatches instead of drawing over the button. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clicking the barrel button over a hovering pen made a short laser trail appear without the pen ever reaching the glass, and the first real tap afterwards drew nothing. The tablet driver synthesises a full stylus down for the button press. It claims everything a real touch claims - not in air, tip switch pressed - so only the pressure gives it away, and nothing is pressing on the tip. That phantom set the contact flag, and the barrel handler then started a trail on the strength of it. The phantom is now rejected in the window and in the ink renderer, which runs on the raw input thread and so never saw the window's guard; left alone it opened a wet stroke that was torn down mid-flight when the tool switched. Two further problems surfaced underneath. The tracker feeding hover positions kept a contact flag of its own, and the phantom's matching up does not arrive until after the next real touch, so every hover in between was dropped. And this tablet reports InAir false while hovering, so three places that read InAir as "hovering" fought the tracker on every sample. Both now defer to the window's contact flag, which is the only copy of that state worth keeping. Finally, WPF raises no stylus down at all for the first landing after a phantom, having never closed the phantom's own down. The laser recovers contact from tip pressure instead, which is the one signal that stayed honest throughout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hovering with the laser showed the same flat red dot every tool uses. Microsoft Whiteboard shows the laser itself - a red core inside a soft halo, trailing a short comet as it moves - which reads as a pointer rather than as a cursor. The trail surface now takes hover samples as well as contact ones and keeps every sample from a 110ms window, so the comet is the distance the pen covered in that time: sweep fast and it stretches, stop and it collapses to the dot. A 170px cap keeps a flick from streaking across the board. Hover state is held apart from contact state on purpose: a lifted pen leaves its stroke open for a moment so a resumed line joins the last one, and that window is measured from the contact head being gone, which a hovering pen must not fill. The head is drawn once for both, so the instrument does not change appearance the instant it touches glass. Its centre is now solid red rather than near white, which is what the reference shows and what survives a projector. This also introduces the trail weight the surface reads. A pen reports very little pressure on a quick tap, thin and faint enough to miss in a recording, and the weights raise that floor without touching what a firm stroke looks like. The numbers live beside the enum in Core so the renderer and anything previewing it cannot drift apart. Preferences follows separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The trail weight is a setting about how something looks, so a dropdown of three words made the reader imagine the result. The three options are drawn instead, side by side, each showing a light tap above a firm stroke: the firm stroke is identical across all three, so what the setting does and does not change is visible without reading anything. The samples are computed from the same floors the trail itself uses rather than being drawn to look about right, so a preview cannot promise something the laser will not deliver. Light stays the default, matching the behaviour before the weights existed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reversing the pen erases on contact, but hovering with it showed nothing at all - the hover dot was suppressed for a reversed pen, so the one pose that most needs to explain itself explained the least. A dot would not help much either, since erasing clears a patch of board rather than a point. Hovering with the eraser now draws a dashed square around exactly what a tap would remove, as Microsoft Whiteboard does. It is sized from the same reach EraseAt uses, so it cannot outline an area the eraser will not clear, and that reach is a fixed number of screen pixels, so the square holds its size at any zoom. The eraser also takes precedence over the laser while hovering: what a tap would do is the question the pointer answers, and a reversed pen erases whatever tool is selected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What 1.0 was waiting on was in the product, not the pipeline: Preferences, .wimport, the Explorer and VS Code previews, the documentation site, and Finger drawing. All of them shipped during 0.9.x and nothing numbered was left behind them, so the open question of what had to be true before the version stopped being 0.x has an answer. VersionPrefix is the only place the desktop version is written; the assembly stamp, the informational version, the installer file names, and the Store identity version all derive from it. decisions.md records the call and no longer carries it as open, and TODO.md's statement of where the project stands moves with it. Delivery is unchanged. A merge publishes a pre-release and promotion stays an approval on that run, which is what let the number describe the product rather than an event in the pipeline. Co-Authored-By: Claude Fable 5 <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.
A hovering pen used to say very little about what would happen if it touched down.
The laser showed the same flat dot as every other tool, and a reversed pen showed
nothing at all. Both now show the tool that would actually act, which is the change
most of this branch is about. Two input bugs surfaced underneath while testing it,
and the version moves to 1.0.0.
The pointer shows what a tap would do
With the laser selected, hovering draws the laser itself — a red core inside a soft
halo — trailing a short comet as it moves. The comet is every sample from a 110 ms
window, so its length is the distance the pen covered in that time: sweep fast and it
stretches, stop and it collapses to the dot. That mirrors Microsoft Whiteboard, which
is where the request came from.
Hover state is kept apart from contact state deliberately. A lifted pen leaves its
stroke open for a moment so a resumed line joins the previous one, and that window is
measured from the contact head being gone — a hovering pen must not fill it.
With the eraser, whether from the toolbar or by reversing the pen, hovering draws a
dashed square around exactly what a tap would clear. Erasing removes a patch of board
rather than a point, so a dot would have said nothing useful about reach. The square
is sized from the same number
EraseAtuses, so it cannot outline an area the eraserwill not clear, and that reach is in screen pixels, so it holds its size at any zoom.
Trail weight is a setting
A pen reports very little pressure on a quick tap — 0.06 is typical — which drew a
1.7 px line at half opacity: thin and faint enough to miss on a projector. Three
weights raise that floor without changing what a firm stroke looks like. Light is the
default and matches the previous behaviour exactly.
The options are drawn rather than named, side by side, each showing a light tap above
a firm stroke. The firm stroke is identical across all three, so what the setting does
and does not change is visible without reading anything. The samples are computed from
the same floors the trail itself uses, so a preview cannot promise something the laser
will not deliver.
Two input bugs
Clicking the pen's barrel button over a hovering pen drew a short laser trail without
the pen ever reaching the glass, and the first real tap afterwards drew nothing.
The tablet driver synthesises a full stylus down for the button press. It claims
everything a real touch claims — not in air, tip switch pressed — so only the pressure
gives it away, because nothing is pressing on the tip. That phantom set the contact
flag, and the barrel handler started a trail on the strength of it. It is now rejected
in the window and in the ink renderer, which runs on the raw input thread and never saw
the window's guard.
Two more problems sat underneath. The tracker feeding hover positions kept a contact
flag of its own, and the phantom's matching up does not arrive until after the next
real touch, so every hover in between was dropped. And this tablet reports
InAirfalse while hovering, so three places reading
InAiras "hovering" fought thattracker on every sample. Both now defer to the window's contact flag, the only copy of
that state worth keeping.
Finally, WPF raises no stylus down at all for the first landing after a phantom, having
never closed the phantom's own down, so the laser recovers contact from tip pressure —
the one signal that stayed honest throughout. This is a workaround for driver behaviour,
not for anything the application does wrong, and there is no way to make WPF issue the
down it skipped.
Toolbar width
The tool palette was one button wider than it needed to be and overflowed the presenter
picture-in-picture that the default top-right placement exists to hide under. The dual
layout sizes its shared colour column from the pen's six swatches, and the highlighter
row carried three tool buttons against the pen row's two, so that row set the width of
the whole palette. The highlighter has four swatches, leaving two swatch widths of that
column unused; the select button now sits there, which brings the palette from 328 to
286 logical pixels.
1.0.0
decisions.mdcarried "whether 1.0.0 is declared, and on what" as an open question.What 1.0 was waiting on was in the product rather than the pipeline — Preferences,
.wimport, the Explorer and VS Code previews, the documentation site, and Fingerdrawing — and all of it shipped during 0.9.x. The question is now decision 20, and
TODO.mdmoves with it.Delivery is unchanged: this merge publishes a pre-release, and promotion stays an
approval on that run.
Verification
Every commit builds independently in a clean worktree with zero warnings, and the Core
smoke tests pass. The smoke tests gained cases for the new setting, matching those
already covering the laser hold mode.
The pen behaviour was tested on hardware by @marcorusso — the tablet-specific bugs above
are not reproducible without it. The laser and eraser rendering was verified by driving
the real surfaces headlessly and rendering the result, rather than by eye.
🤖 Generated with Claude Code