API log: stop the hover flicker, make a selection stick, read prompts as markdown - #106
Conversation
… as markdown Three items from the operator, on the view merged in #98. **1. The flicker loop, diagnosed by the operator and fixed at both ends.** Card appears → page grows → scrollbar appears → layout narrows → plot shifts left → cursor is no longer on the mark → card goes → scrollbar goes → shifts back. - `scrollbar-gutter: stable` on `.settings-main`, so a scrollbar arriving cannot change the layout width. Chosen over always-on `overflow-y: scroll` (which shows a track that is usually pointless) and over reserving the card's height (which cannot work: the card is as tall as the body it shows). Where the property is unsupported — Safari before 18.2 — that platform uses overlay scrollbars, which take no layout space and cannot start the loop. - and the card no longer empties when the pointer leaves a mark. Even without a gutter the loop cannot close if the card never disappears, and it means reading an entry no longer requires keeping the cursor on a 1px arrow. **2. Selection was broken, not undiscoverable.** Brushing calls `setPointerCapture` on the SVG, and a captured pointer retargets the trailing click to the capture element — so the mark's own `onClick` never fired and clicking an entry did nothing. The review round's test dispatched a synthetic click straight at the mark, which is exactly the one path that did work. Selection is now committed from the SVG's `pointerup`, reading what was pressed from the event's target (`data-op`) rather than from mouseenter bookkeeping that capture and re-renders both invalidate. Around it: capture is taken when the drag starts rather than on press (capturing on press sent the mark a `mouseleave`, so the press threw away what it was selecting); hover never overrides a pinned entry; the pinned mark wears a ring so the picture and the card agree; Escape and the ✕ let go. The old swallow-the-next-click flag is gone with the click handler. **3. Prompts render as markdown, with `Rendered`/`Source` beside them** — the file viewer's own control, wording and renderer (`renderMarkdown`, sanitized), and no editing. It keeps the file viewer's 14px reading base but does NOT follow a zoom: #102 gave the file PANE's markdown the pane's text-size control, and Settings has no such control to follow. If one ever arrives, this is where it would hook in. Tests: every one of the above, and they use a real mouse press rather than a synthetic click, since the synthetic path is what hid the bug. The clip note and the JSON block keep their coverage against a 400 KB body. One honest limit on the verification, in the PR body: this container's Chromium has no layout-affecting scrollbars at all (`overflow-y: scroll` reserves 0px in every configuration tried), so the pixel-level flicker cannot be reproduced here. The gutter is asserted through `getComputedStyle`, the loop is broken independently by the card change, and the pixel claim wants one look on the operator's own machine.
lvwerra
left a comment
There was a problem hiding this comment.
Verdict: good to merge — no findings.
Correctness — no finding.
I found a way around the stated browser limitation and checked the layout mechanism with a real classic scrollbar under headed Chromium/Xvfb. The settings scroller reserved a 10 px gutter before overflow. Showing the 400 KB entry grew scrollHeight from 1400 to 4731 px, while the plot stayed at left 116 px / width 958 px. Removing only scrollbar-gutter: stable made clientWidth fall from 1200 to 1190 px when the card appeared and shifted the plot from left 121 to 116 px. Thus the gutter fix independently prevents the layout movement; the sticky card is not masking an unstable layout. The second cut also holds: leaving a mark no longer clears the card.
Selection is genuinely pinned. A real pointer press pins it, the card and mark both show the selection, pointer movement and scrolling do not clear it, and hovering another mark does not replace it. Both the visible close button and Escape release it.
Prompts initially use the same Rendered / Source vocabulary as the file viewer and are read-only. I added probe fixtures beyond the committed test: ordinary non-Markdown text rendered as a single paragraph, and a fenced JavaScript prompt produced one intact pre > code block; Source returned the exact original fence and text. Rendering goes through the existing sanitized Markdown helper. The #102 zoom decision is explicitly accounted for: this Settings view has no shared zoom control to follow, unlike the file pane.
I also mutation-checked what apiLog.test.mjs pins. Removing the stable gutter fails its computed-style assertion; restoring clear-on-leave fails two card-retention checks; replacing pin with preview fails three selection/ring checks; and defaulting to Source fails four Markdown/default-view checks. The committed gutter check pins the mechanism rather than measuring plot coordinates; the headed measurement above supplies the platform/layout proof that the normal container cannot.
Does it solve the operator's problems? — no finding.
The flicker-causing width change is eliminated, selection remains readable while moving and scrolling, and the prompt presentation works as Rendered-by-default with an exact Source escape hatch. At 390 px the card stayed inside the pane (14–366 px) and the Rendered / Source control remained fully visible. I also checked the supplied artifact and the full web typecheck/test suite; both are clean.
Three items from the operator after using the API log on am-dev-2. #98 is merged; this is the follow-up.
Screens: https://lvwerra-agent-artifacts.static.hf.space/api-log-pinning.html
1. The flicker loop
The operator's diagnosis was complete and correct: card appears → page grows → scrollbar appears → layout narrows → plot shifts left → cursor is no longer over the mark → card disappears → scrollbar goes → shifts back.
Cut at both ends, deliberately:
scrollbar-gutter: stableon.settings-main, so a scrollbar arriving cannot change the layout width. Chosen over always-onoverflow-y: scroll(a permanently visible track that is usually pointless) and over reserving the card's height (impossible in general — the card is as tall as the body it is showing). Where the property is unsupported, Safari before 18.2, that platform uses overlay scrollbars, which take no layout space and cannot start the loop.The verification limit, stated plainly
This container's Chromium has no layout-affecting scrollbars at all. I measured before trusting anything: a plain
overflow-y: scrollbox reserves0px, withscrollbar-width: thin, with::-webkit-scrollbar { width: 8px }, and with--disable-features=OverlayScrollbars/OverlayScrollbar. So the operator's symptom cannot be reproduced in it, and a green here would have meant nothing — which is the trap you flagged.What is verified:
getComputedStyle('.settings-main').scrollbarGuttercontainsstable— asserted in the browser test64.50pxcard-hidden →64.50pxcard-shown → moved 0.00pxThe pixel claim still wants one look on a machine with classic scrollbars (Windows/Linux Chrome, or macOS with a mouse attached). Everything else stands on its own.
2. Selection was broken, not undiscoverable
It could not work, and the earlier test could not have caught it. Brushing calls
setPointerCaptureon the SVG; a captured pointer retargets the trailingclickto the capture element, so a mark's ownonClicknever fired. The review round's test dispatched a syntheticclickstraight at the mark — the one path that did work.pointerup, reading what was pressed from the event's target (data-op) rather than from mouseenter bookkeeping that capture and re-renders both invalidate.mouseleave— so the press was throwing away the very thing it was selecting. This was the second half of the same bug.Every test here now drives a real mouse press, because the synthetic path is exactly what hid the bug.
3. Prompts as markdown, with a raw view
Rendered by default with
Rendered/Sourcebeside it — the file viewer's own control, wording and renderer (renderMarkdown, sanitized, same callFilesPanemakes). No editing.On #102 and zoom: no, and here is why. #102 gave the file pane's rendered markdown the pane's text-size control. Settings has no such control — there is nothing to follow. The card keeps the file viewer's 14px reading base so the two look the same at 100%, and if a zoom ever arrives in Settings this is the one line that would hook into it. Say the word if you would rather it borrowed the terminal zoom instead.
Verified
web npm test— 17 suites including 40 checks inapiLog.test.mjs;server npm test— 24 suites; typecheck and production build. Green on top ofmain(3ce91a9).Rendered/Sourcewording, and Source showing the text byte-for-byte.What could regress
scrollbar-gutterreserves a strip on platforms with classic scrollbars even when the settings page fits. That is the trade for a layout that never jumps.data-opbeing on the mark groups. A future mark type without that attribute would be unselectable rather than throwing, so it would fail quietly — the pin tests would catch it for the existing three kinds.