Repository navigation
v1.2.0
Fixed
-
The test suite stopped losing races with Windows file handles. Cleaning up a
temporary directory failed about one run in six with "being used by another
process": a background write or a directory watcher had not let go yet. The
retry budget was one second, which is long enough to look like it works and
short enough to keep losing. It is now about five, which is far longer than it
ever needs and far shorter than a flake costs. This is not a change to the app
— it is the difference between a suite you trust and one you re-run. -
The editor can no longer be shrunk to nothing. It could be dragged by a
corner down to a few pixels and left there — a window too small to hold a
title, a note and a status bar is not a smaller version of this app, it is a
broken one. There is now a floor of 520 × 360; the window still grows to
most of the screen and maximises like any ordinary window.The floor comes from the layout rather than from taste: the editor already
collapses to one pane at a time below 760 wide, and that pane stops fitting
much under 400, so 520 leaves the narrow layout genuinely usable rather than
technically reachable.Enforced with
WM_GETMINMAXINFO, the only hook that governs the size the user
can reach by dragging a frame edge. Three things about it are easy to get
wrong and invisible in a diff, so all three are guarded:ptMinTrackSizeis in physical pixels, so it is scaled for the window's
DPI. A literal would be a 520 px floor at 100% and a 347 logical px floor
at 150% — the displays people actually use get the wrong answer, and nothing
looks wrong.ptMinSizeis the wrong field. It also caps programmatic sizing, so
Dart asking for a particular size would be silently ignored.ptMaxPositionandptMaxSizeare left alone. The first governs how far
the window may be dragged off-screen, which the runner already owns; writing
it here would be a second, conflicting answer to the same question.
The widget is deliberately untouched. It is frameless and resized by the drag
loop, which already clamps to 200 × 140 and never reaches the window manager's
track-size path — a minimum set here would do nothing for it while reading
like a second source of truth.Verified on a release build by dragging the corner past the top-left of the
screen: it stops at exactly 520 × 360, each edge clamps independently, and
maximise and restore are unaffected. -
The widget no longer sits on top of the editor. Always-on-top is absolute:
a window carryingWS_EX_TOPMOSTis above every other window, so once the
widget had that flag there was no position in the Z-order meaning "above other
applications but below the editor" — it simply covered the editor, and
nothing in the app could raise the editor above it. The widget now leaves the
topmost band for exactly as long as the editor is the foreground window, and
comes back to it the moment the editor stops being foreground. A yield, not a
downgrade: with the editor closed the widget floats exactly as configured.The obvious half of this fix is wrong, and measuring it on a release build is
the only reason it was not shipped wrong. Taking the widget out of the
topmost band on its own leaves it at the top of the ordinary band — still
above an ordinary editor — so the widget ends up covering the editor and
burying it behind itself, which looks worse than the bug being fixed. The
editor has to be brought forward in the same moment. Measured: demote alone
put the widget at z=2 with the editor at z=5; demote-and-raise put the editor
at z=2 and the widget at z=3.Two related invariants are now guarded in
widget_guard_test, because both
are silent and both are easy to undo by accident: the editor is never created
WS_EX_TOPMOST, andSetAlwaysOnTopstays widget-only so the preference
cannot reach the editor through a back door.
Added
-
The notes list renders Markdown too. Each row's title gets its inline
formatting and its preview is rendered rather than shown as source, so a row
reads the way the note looks instead of the way it was typed. A plain note is
untouched - the asterisks stay exactly where they were typed, because that note
never asked for otherwise.A row is two lines in a 300px column, so it keeps the structure that fits and
drops the rest with the edge faded. It gets blocks, not just spans:
rendering the preview inline-only would have concatenated a task list's items
with no marker at all - "Buy milkPost the thing" - which is less than the note
said. That is the same failure the table flatten had, and it is now pinned by a
test.The row has its own type scale rather than the widget's: a row is 12px
bodySmall, not 13px, soMarkdownTextgrew afontSizeoverride that scales
the whole metric set - ratios kept, pixel values multiplied - rather than
overriding one number and inheriting the wrong gaps. That is what keeps a
Markdown row exactly as tall as a plain one, so the list rhythm does not change
depending on whether a note happens to use Markdown.Two fixes the rows turned up.
MarkdownText.inlineignored themaxLinesit
was given and always clipped at one, so the large widget card's two-line title
silently got one. And the clamp's fade covered a fixed 40% of the box, which at
a two-line budget swallowed most of the second line - a surface that showed one
line and a half was not showing the two it had been given. The fade band is now
added to the requested lines rather than taken out of them, which is what "two
lines" has to mean if a reader is to believe it. -
Markdown, per note. A small switch beside the title turns it on for the
note you are editing, and the body becomes a source field on the left with a
rendered preview on the right - or one at a time when the window is too narrow
for both. Headings, lists including task lists, quotes, code blocks, bold,
italic, strikethrough, links, and tables. The widget renders the same thing,
compressed to fit a card.Per note rather than a global setting, because the two kinds of note sit
side by side in one library and neither should be forced to be the other: a
to-do list and a formatted note is the normal case. Off by default, so an
existing note does not start rendering its asterisks differently the day the
app updated. The flag ismarkdownon the note and is omitted from
notes.jsonwhile it is off, on the same rule ascompletedAt- a profile
that never used the feature stays byte-identical to one written before it
existed. Only a literaltrueturns it on, so a hand-edited"markdown": "yes"cannot produce a note the renderer has never been asked to handle.The stored text is never rewritten. Rendering derives widgets from the
source and writes nothing back, so the plain-text export still exports exactly
what you typed, search still matches the source, and turning the switch off
gives your asterisks and hashes back exactly as they were. A note is still a
title and a body innotes.json, not a document tree.One renderer, a budget per surface.
PROJECT.mdresolved "plain text only,
or Markdown with a preview" in favour of plain text, on the reasoning that "a
Markdown editor would leave the widget still guessing how to render it". That
was right about the problem and wrong about the size of the answer: the widget
does not guess if it is given a renderer with a stated budget. At card density
headings compress to within 5% of body text, code clamps to three lines and
says how many were hidden, and a table becomes its text - because a grid in a
360 px card is a grid of unreadable slivers and its content is the point. Both
surfaces are the same walk of the same tree with a smaller budget, so they
cannot drift apart.Two things it deliberately will not do. A
- [x]draws a box that looks
like the completion circle and is not one: completion is per note here and a
Markdown task list is per line, and two answers to "is this done" is worse
than one that only looks like the other. And a link is styled but cannot be
clicked, becausePROJECT.mdsays this app does not touch the internet at
all - the href stays in the source, so nothing is lost, and the widget has
nowhere to send you.One dependency, and it is the parser.
package:markdown- a conforming
CommonMark implementation.AGENTS.md§0.4 has been amended from "zero
dependencies" to an enumerated list, with the reasoning: the rule was never
"depend on nothing", it was "do not buy a platform capability instead of
owning it", and parsing to a standard is the opposite case - writing a
CommonMark implementation would have been less ownership, not more. The
renderer, both density budgets, the palette styling and every decision about
what is and is not rendered stayed in this repo. The rule is now a guard:
dependency_guard_testfails on a package that is not on the list, and on a
list that has quietly grown into "anything goes".Three defects found by looking at it running rather than by reading it:
**bold**was not actually bold inside a heading. The renderer attached
styles to block elements and left leaf runs to inherit - which meant they
inherited the body style, so emphasis inside anything that changed size
rendered at body size and body weight. Found by a test that resolves style
inheritance while walking spans, which is the only way to ask the question
correctly.- Tables rendered nothing. The rows are one level down, inside
theadand
tbody, and looking only at direct children found no rows at all - which
reads as "the parser does not do tables" rather than as a bug here. - A twenty-paragraph note overflowed the card by 365 pixels.
maxLines
bounds the lines inside oneTextand says nothing about how many blocks a
note has. Clamped by height now, with the content laid out unbounded inside
anOverflowBox, because aConstrainedBoxstill lets theColumnreport
an overflow it is merely clipping. The cut is faded rather than hard-edged:
it cannot be made to land on a line boundary, since block gaps do not sit on
the line grid, and a half-visible line behind a sharp edge reads as a
rendering fault.
And two decisions that only a screenshot could settle. A table flattened for a
card becameSurfaceDensityWidgetcompressed-textContentconcatenates cells
with nothing between them, which is less than the note said, and losing the
grid is not worth losing the separators. And the compact card flattens
headings to body size while the large card keeps the real scale: a card shows
the note's title directly above the body, so a# Headingin the body is a
repeat, and at 1.3x it was eating a third of a two-line card and pushing the
content off the end.