Skip to content

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 19:50
· 27 commits to main since this release

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:

    • ptMinTrackSize is 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.
    • ptMinSize is the wrong field. It also caps programmatic sizing, so
      Dart asking for a particular size would be silently ignored.
    • ptMaxPosition and ptMaxSize are 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 carrying WS_EX_TOPMOST is 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, and SetAlwaysOnTop stays 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, so MarkdownText grew a fontSize override 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.inline ignored the maxLines it
    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 is markdown on the note and is omitted from
    notes.json while it is off, on the same rule as completedAt - a profile
    that never used the feature stays byte-identical to one written before it
    existed. Only a literal true turns 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 in notes.json, not a document tree.

    One renderer, a budget per surface. PROJECT.md resolved "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, because PROJECT.md says 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_test fails 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 thead and
      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 one Text and says nothing about how many blocks a
      note has. Clamped by height now, with the content laid out unbounded inside
      an OverflowBox, because a ConstrainedBox still lets the Column report
      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 became SurfaceDensityWidgetcompressed - textContent concatenates 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 # Heading in 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.