Skip to content

fix(note): make notes readable by default and collapse very long ones - #151

Open
Luna Qiu (lunaqiu) wants to merge 4 commits into
microsoft:mainfrom
lunaqiu:fix/note-height-prompt-guidance
Open

fix(note): make notes readable by default and collapse very long ones#151
Luna Qiu (lunaqiu) wants to merge 4 commits into
microsoft:mainfrom
lunaqiu:fix/note-height-prompt-guidance

Conversation

@lunaqiu

Copy link
Copy Markdown
Collaborator

Notes were unreadable at both ends: a short one rendered at 56px — one line and a sliver of the next — while a long one grew to thousands of pixels and buried its neighbours. A note pinned to a numeric height was born heightMode: 'fixed' and never measured again, so it stayed that way for life.

Root cause

The auto-height engine was fine. Agents were pinning heights because layout-recipes.md told them to, and any numeric height makes a note fixed at birth — the proposal effect and the prewarm queue both skip pinned nodes, so it is never re-measured.

Changes

Prompt — never pin a note's height; notes are 400 wide; take the median of neighbours, not the maximum. The last rule closes a ratchet: one wide node propagated itself through "match your peers" with nothing to pull the number back.

Two constants, not one. They look alike and do opposite jobs:

  • minIntrinsicHeight: 50 → 91 (renders 96px, three lines) — a floor for a genuinely short note, so it must stay small or it erases the difference between a two-line note and a nine-line one.
  • collapsedIntrinsicHeight: 244 (renders 248px) — a preview window onto a note too long to show, so it must be generous or every long note becomes a stub.

Collapse on create past NOTE_COLLAPSE_CONTENT_THRESHOLD = 550 characters, with a chevron to expand. Only when the caller expressed no height preference: a number pins as always, and an explicit 'auto' means "expand this one".

The threshold is derived, not picked for roundness. Across real notes past ~600 characters, intrinsic height is 1.34x the character count with ~5% spread — tighter than a line count predicts the same heights (~9%). Inverting that ratio, 550 lands a little over 700px, about a screenful. The obvious alternative — judging by measured intrinsicHeight — is worse here: nothing has been measured yet at creation time, and character count turns out to be the more stable proxy in the range where the decision is made.

The chevron is now a button. It was a decorative glyph inside a full-width hit target that sat exactly where selection and resize gestures land, so it fired by accident more often than on purpose. The container is now pointer-events-none and only the small button opts back in.

Notes

  • Collapse is decided at creation only, so no existing node changes height and HEIGHT_LAYOUT_VERSION does not need a bump.
  • Not done deliberately: a collapsed note is indistinguishable from a user-pinned one (both are fixed + 248). The cost is that collapsed notes are not prewarmed, so expanding flickers. Fixing it means adding a field to the node data model and handling existing spaces — out of scope here.

Verification

pnpm typecheck clean · packages/shared 377 passed · note-auto-height e2e 7/7 (two new cases: born-collapsed-and-expands, and the narrow-note floor) · pnpm lint 0 errors.

Replayed against a real space of 14 agent-written notes: the 6 short ones (80–121 chars) all expand, the 8 long ones (638–1121 chars) all collapse, with no inversion across the boundary. Before this change a 762-char note rendered at 1100px while an 843-char one collapsed to 248px — 81 characters apart, 852px of visual difference.

Luna Qiu (lunaqiu) and others added 4 commits September 2, 2026 10:44
A note whose CREATE_NODES entry carries a numeric height is born with
`heightMode: 'fixed'`, and both the mounted ResizeObserver and the
offscreen prewarm queue skip pinned notes — so the node keeps that box
for life and truncates its content as it grows. `layout-recipes.md`
was telling agents to do exactly that for long or multi-section notes,
which made the longest notes the most likely to be stranded.

Tell agents to leave a note's height to Huabu, and settle the same
contradiction in the external-agent guide, which already described the
note as content-driven two lines earlier.

Also repair `note-auto-height.spec.ts`'s mounted-editing case: it
assumed the preview workspace opens itself on creation, which stopped
being true with the unified preview workspace. Drive the shipped path
(select, Expand, edit, Escape) instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two of the three note-sizing complaints had the same shape: the box a
note settles into was chosen for an empty note, not a written one.

P1 - readable minimum. `note.minIntrinsicHeight` was 50, which renders
as a 56px card: one line of text and a sliver of the next. Raise it to
244 (248px at the reference width). This is the only knob that reaches
the rendered height; `nodeSizes.ts` never does, because
`getNodeCreationStyle` drops the nominal height for auto-by-default
types. Its stale 56 is corrected to match so the two stop disagreeing.

P2 - collapse on create. A note past 800 characters is a document, not a
note: auto height grows it to thousands of pixels and buries its
neighbours at the moment it holds the most information. Such a note is
now born pinned to the collapsed height, which is deliberately the same
number a short note settles at - one definition of 'small', not two.
Author preference wins: a numeric height pins as before, and an explicit
`height: 'auto'` means 'expand this one', so only a caller who
expressed no preference gets the heuristic. The rule lives in
`policy.ts` so the headless engine and the web client cannot disagree
and produce a geometry write on every load.

The bottom chevron becomes a control again, so collapsing is not a trap.
It was made inert for a good reason - a full-width hit target sitting
where selection and resize gestures land - so the container stays
`pointer-events-none` and only a 24x24 button opts back in, with
`nodrag nopan`. It also appears on a selected, already-expanded long
note, which truncation alone would never reveal.

`HEIGHT_LAYOUT_VERSION` is bumped because the rendered height of
unchanged content changed; without it every existing workspace keeps its
old heights.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Measured in a real browser at the reference width: one paragraph line is
41px intrinsic and each further line adds 25. That makes the 244 floor
shipped in 6f57601f render every note of nine lines or fewer at an
identical 248px - it does not make short notes readable, it erases the
difference between them.

The two numbers were folded into one on the theory that a collapsed long
note and a short note should be the same size. The measurement shows why
that was wrong: they answer opposite questions. A floor asks how short a
genuinely short note may be, and must stay small so a note under the
collapse threshold can size itself to its content. A collapsed height is
a preview window onto a note too long to show, and must be generous
enough to read.

So: `minIntrinsicHeight` 244 -> 91 (three lines, renders 96px, up from
the 56px that showed one line and a sliver), and a new
`collapsedIntrinsicHeight` 244 keeps the collapsed preview at 248px.
`collapsedLayoutHeight` reads the latter, still routed through
`intrinsicToLayoutHeight` so it scales with node width and can never
land below the floor.

Also close the width ratchet. Nothing in Huabu ever chooses 460 - agents
invent it, because the guidance blessed 'setting only width is fine to
match nearby Notes' while giving no canonical anchor. One wide node then
becomes the width the next agent matches, and nothing pulls it back. The
prompt now states a note is 400px wide, says to omit width unless
inspected peers agree otherwise, takes the median rather than the
maximum, and names length as the height's business.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The birth-collapse threshold was 800 characters, which inverts to about
1080px of rendered height. A note therefore had to outgrow an entire
screen before it collapsed, and everything between 600 and 1080px
rendered at full length next to a 248px collapsed neighbour — the same
wall of notes looked arbitrary depending on which side of the line each
one fell.

Measured against real agent-written notes, intrinsic height tracks the
character count at 1.34x with ~5% spread past ~600 characters (tighter
than a line count predicts the same heights, ~9%), so the unit is fine
and only the number was wrong. Inverting that ratio from the height we
actually want to trigger on gives 550, a little over 700px.

Replaying the 14 notes from a real test Space: the six short ones stay
expanded and the eight long ones now all collapse, with nothing left
straddling the boundary.

Creation-time only, so stored measurements stay valid and
HEIGHT_LAYOUT_VERSION does not move.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant