Skip to content

UI Library Bugs

github-actions[bot] edited this page Oct 18, 2026 · 1 revision

The bugs we fixed in the UI library (ui/): what you saw, why it happened, and the fix and why we chose it. The library itself is described in UI-Library.md.

Bugs we fixed

  • Select's arrow touched the right border. WebKit draws a native select's arrow against the border and ignores the padding. Select now hides the native arrow (appearance: none) and draws a chevron inside the padding, using the select tokens for its inset and room, so every platform gets the same numbers. The app's own select.input copy of this fix went away with the migration: every select in the app is the library's.

  • Dragging rows in a Tree did nothing in the app. Tree and TreeRow used HTML5 drag and drop (draggable, dragover, drop). In the browser showcase this worked, but Tauri's window answers every native drag itself, so it can report files dragged in from Finder, and HTML5 drop events never reach the page. The app's Files panel, editor tabs and Interactive Rebase all drag with pointer events for this reason. Tree now does too: a press past 4 px starts a drag, a ghost follows the pointer ("Move cart.ts", "Copy 3 items"), Option copies, a closed folder under the pointer opens after 600 ms, the list scrolls near its edges and Escape cancels. Where a drop may land is pure logic in ui/web/src/lib/logic/treeDrag.ts with tests; the gesture is treeDragger.svelte.ts, whose window listeners live only while a press or a drag does. We chose pointer events over asking Tauri to stop reporting native drags because Finder drops into the Files panel and the terminal need them.

  • A responsive bar could throw "ResizeObserver loop completed with undelivered notifications". A responsive Toolbar or Stack measures its width with a ResizeObserver and hides parts when it gets narrow. Hiding a Badge or a group can change the bar's height, and the change landed while the browser was still delivering the same resize, so it could not deliver the new one in that frame and reported the error (the theme check caught it on the Stack story's repository rows). BarSize in ui/web/src/lib/logic/barSize.svelte.ts now applies the measured width on the next animation frame. We chose one frame of delay over watching only the width, because the browser reports the loop for any skipped size change, whatever the callback does with it.

  • Dragging the Log's split down made the commit list smaller. SplitPane gave its handle the side bottom in a column, which tells ResizeHandle the panel sits below the handle, so dragging down shrank the size. In a column that size belongs to the pane above. ResizeHandle now knows a fourth side, top, which grows downwards, and SplitPane picks the side from where its sized pane is (sized="first" or "second"). We added a side instead of flipping the sign in SplitPane, so the arrow keys follow the same rule as the pointer; resize.test.ts covers it.

  • A SplitHandle could not be grabbed over a diff. The editors covered the handle and took its pointer events. Its stacking token was written as --split-handle-layer: 5px (and --split-grid-splitter-layer: 20px), and a z-index with a unit is ignored. The token build (componentValue in ui/design-tokens/build/css.ts) now writes parts named layer or ...Layer as plain numbers; tokens.test.ts covers it. We fixed the generator rather than the one value, so every later layer token is right too.

  • PatchView could show a removed line as a file header. patchLineKind read every line alone, so a removed line whose text starts with -- (a SQL or Lua comment) became --- ... and looked like a patch's file header. patchLines in ui/web/src/lib/logic/patchLines.ts now counts file headers only between a file's diff --git line and its first hunk, the rule the app's own copy used; that copy is gone and patchLines.test.ts keeps its case. We kept the line-by-line helper for patches without hunks and added the state on top, because the state is what git's format defines.

  • Editor tabs lost their close button. Trimming EditorTab.svelte to 300 lines cut the body of the rule that shows the x on the active, hovered, pinned and single tab, so the selector list ran into the next rule. The rule is back; a check now compares every library component's CSS rules with the last commit when files are trimmed.

  • A picker's count wrapped under its name. A ghost Button put its words in a plain inline span, so an Icon (a block svg) after a count started a new line ("main 1" then the arrow). The label is now a row (display: inline-flex), so Texts and Icons stay on one line and each Text truncates itself.

  • Table cells in the rich Markdown editor grew tall. Its rules moved from the app's scoped style into the library's markdown-rich.css, and lost the scoping class that made them beat markdown.css's .md-body :is(p, ...) margins. Every rule there now names .md-body.md-rich, one class more, which restores the old order without !important.

  • Git Console rows could not be reached with the keyboard. A List tells its Rows that it owns the keys, so a Row there is not a button. That is right for a listbox or a tree, but a plain list (role="list") has no keys of its own. The List context now says whether the list owns the keys, and in a plain list a Row with onclick stays a button.

Home

Using Nibtree

Developers

How features work

Clone this wiki locally