Skip to content

Branch picker rows, unread panes, and two Markdown wrapping fixes - #191

Merged
matej21 merged 10 commits into
mainfrom
minor-ui-improvements
Aug 19, 2026
Merged

Branch picker rows, unread panes, and two Markdown wrapping fixes#191
matej21 merged 10 commits into
mainfrom
minor-ui-improvements

Conversation

@matej21

@matej21 matej21 commented Aug 19, 2026

Copy link
Copy Markdown
Member

Ten small, independent fixes and features that accumulated on one branch. Nothing here shares code, so the commits are reviewable one at a time; they are grouped below by the area they touch.

Branch picker

The picker listed bare branch names in git's ref order. Choosing a branch meant remembering which one is current work, whether it is pushed, and whether a worktree already holds it — the last one only surfacing as a failed checkout.

  • The current branch leads the LOCAL section and becomes the default keyboard selection. The filter/order pass is extracted out of recompute_branch_filtered into a pure branch_nav_items so the ordering is unit-testable.
  • Every row carries its state, right-aligned: the worktree holding it, local / gone / ↑N ↓M against the upstream, and the tip age. Sections order by recency. All of it comes from one git for-each-ref pass — git answers ahead/behind for every ref off its own commit-graph (~10ms for ~70 refs here), where a per-branch gix rev-walk does not come close. It rides the existing BranchList wire type as an additive field, so a remote host without it degrades to plain names.
  • A row context menu with the three things that need no checkout: Show History, Compare with Current, Copy Branch Name. The first two route into what already exists — the commit log popover already renders an arbitrary branch's graph, and DiffMode::BranchCompare already backs a three-dot diff — so this is wiring, not a second implementation. Reachable by right-click and, since the picker is keyboard-driven, by the menu key / shift-F10. Compare is disabled on the current branch and navigation steps past it.

Unread panes

The bell indicator only ever came from the shell ringing BEL.

  • A manual mark: "Mark as Unread" in the terminal context menu (flipping to "Mark as Read" when a bell is already lit) and a ToggleUnread action on cmd-u / ctrl-shift-u. The render path clears the bell on every frame the pane is focused, which would undo the mark instantly, so a manual_unread flag holds it — set by hand, skipped by the clear, released when focus leaves.
  • The mark shows where the pane is hidden: tabs and the minimized/detached chips now report it with the sidebar's glyph and colour, outranking the hook, waiting and active states. That is the case where being told to come back matters most. Toggling also refreshes the windows, since the sidebar sits behind a .cached() wrapper a notify from the pane never reaches.

Wrapped-URL detection

Two follow-ups to the phase-2 guard that extends a URL onto the next row when a TUI wraps the line itself.

  • The width guard compared against the wrong thing. "A continuation can never be wider than the row it continues" was given the same +3 slack the surrounding guards had at the time; those lost it later (the layout edge is exact, so slack only lets prose in) and this one was missed. It now compares against the edge itself.
  • The width guards cannot decide the narrow case at all: word-wrapping at width W produces rows no wider than W, so a URL row landing exactly on W is the widest row in the block. Read the break lexically instead — an opening paren starts a bracketed token, so it is a parenthetical rather than the URL's tail; and a wholly numeric last segment (/pull/567, /issues/42) can only continue with more digits or with a delimiter opening the next segment. The numeric rule covers PR and issue links without knowing about any particular forge, and still joins a wrap falling inside the number.

Compact header

Revealing the hide/focus buttons on hover widened the cluster to the right of the base-compare chip (branch +N -M), so the chip slid left every time the pointer entered the corner. The buttons move to the git status row, where the flex spacer absorbs their width. The comfortable two-row layout keeps them in the header row — there the chip already sits on a row of its own.

Markdown wrapping

Two layout bugs in the rendered Markdown view, both from min-width: auto on flex children.

  • Long frontmatter values ran off the card. The value column of an entry is a flex child, so it was pinned to its unwrapped single-line width and a long description: painted straight past the edge of the metadata card. min-width: 0 lets it shrink and wrap inside it.

  • Every inline boundary forced a line break. A paragraph was a wrapping flex row with one item per inline run, so a run long enough to wrap occupied a full-width box and whatever followed it — a code chip, the text after a bold lead-in — started on the next line. This is the limitation Lay out the rendered Markdown as a reading column #185 listed as known and left open.

    Inline elements are now flattened into that row as word tokens: text splits on word boundaries (each token keeping its trailing whitespace), and bold/italic/link hand their emphasis down to the tokens instead of wrapping them in a container. Code stays a single chip, and selection ranges are split per token.

    Measured at 600px against a plain-text baseline of 161px: text interleaved with code chips went from 276px to 161px, a bold lead-in from 184px to 161px. Both shapes are covered by new tests in tests/inline_layout.rs.

Validation

  • cargo test on the touched crates — okena-markdown 15, okena-terminal 182, okena-git 175, okena-views-git 35, okena-views-terminal 24, all passing
  • cargo clippy --workspace --all-targets and cargo fmt --all --check — clean
  • Not done: the Markdown changes were verified by measuring block heights in the test harness, not by looking at a rendered .md in the running app. Somebody should eyeball a document with frontmatter and inline code before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DcL4r46s9HMDmrzQkLzUMo

matej21 and others added 10 commits August 18, 2026 16:48
The compact project header pins the base-compare chip (branch +N -M) to
the right edge of the git status area, with the hide/focus buttons in the
cluster to its right. Revealing those buttons on hover widened the
cluster, so the chip slid left every time the pointer entered the corner.

Hand the buttons to the git status row instead. They render just left of
the chip, where the flex spacer absorbs their width and the chip stays
put. The comfortable two-row layout keeps them in the header row — there
the chip already sits on a row of its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsgnZNRhcTnZdEZj8Tm2hp
The bell indicator so far only came from the shell ringing BEL. Add a
manual toggle so a pane can be flagged to come back to: "Mark as Unread"
in the terminal context menu (flips to "Mark as Read" when a bell is
already lit), and a ToggleUnread action on cmd-u / ctrl-shift-u.

The render path clears the bell on every frame the pane is focused, which
would undo the mark instantly. A `manual_unread` flag holds it: set by
hand, skipped by the clear, released when focus leaves — so the mark
survives while you are looking at the pane, and the next visit clears it
like any other bell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsgnZNRhcTnZdEZj8Tm2hp
Phase 2 extends a URL onto the next row when a TUI wraps the line itself.
6c750fc added the "a continuation can never be wider than the row it
continues" guard, but gave it the same +3 slack the surrounding guards had
at the time.  149e4c3 removed that slack from the others — the layout edge
is exact, so slack only lets prose in — and missed this one.

Claude Code's PR line lands exactly on the boundary: the URL row ends at
column 58, the next row is 61 wide, and 61 > 58 + 3 is false.

  ● Hotovo — contember/webmaster#567
    (feat/browser-and-edge-worker-sentry) → main, samostatně od

Compare against the edge itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RusYtFivQgiGheLoKVcTaK
The bell only ever reached the pane's attention border and the sidebar, so
a terminal in an inactive tab or minimized to the header taskbar carried
its mark invisibly — the one case where being told to come back matters
most, now that the mark can be set by hand.

Tabs and the minimized/detached chips report it with the sidebar's glyph
and color: bell.svg in `border_bell`, outranking the hook, waiting and
active states. Both read `has_bell || has_notification`, the same pair the
pane border shows, since the chip is standing in for that border.

Toggling now also refreshes the windows. The mark shows in four places and
the sidebar sits behind a `.cached()` wrapper that a notify from the pane
never reaches; one keypress is far too rare for the cost to matter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsgnZNRhcTnZdEZj8Tm2hp
The width guards cannot decide the narrow case: word-wrapping at width W
produces rows no wider than W, and Claude Code's PR line lands the URL row
exactly on W, making it the widest row in the block.

  ● Hotovo — contember/webmaster#567
  (feat/browser-and-edge-worker-sentry) → main, samostatně
  od #566, nepřekrývají se).

A hard wrap breaks a token mid-way — it never starts a new one, so read the
break lexically instead:

  - an opening paren starts a bracketed token, so it is a parenthetical
    after the URL, not the URL's tail;
  - a wholly numeric last segment (`/pull/567`, `/issues/42`) can only
    continue with more digits, or with a delimiter opening the next segment.

The numeric rule covers GitHub PR and issue links without knowing about
GitHub, and still joins a wrap that falls inside the number (`…/pull/56` +
`7`), which an explicit list of complete URL shapes would have had to
special-case anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RusYtFivQgiGheLoKVcTaK
The picker listed locals in git's ref order, so the branch you are on
could sit anywhere in the list. Lead the LOCAL section with it — it is
the row users scan for, and it becomes the default keyboard selection.

Extracts the filter/order pass out of `recompute_branch_filtered` into a
pure `branch_nav_items` so the ordering is unit-testable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsgnZNRhcTnZdEZj8Tm2hp
The picker listed bare names in git's ref order, so choosing a branch
meant remembering which one is current work, whether it is pushed, and
whether a worktree already holds it — the last one only surfacing as a
failed checkout.

Collect per-branch metadata in one `git for-each-ref` pass: tip commit
time, upstream tracking counts, and the worktree holding each branch.
One subprocess beats a per-branch gix rev-walk by a wide margin — git
answers ahead/behind for every ref off its own commit-graph (~10ms for
~70 refs here) — and it rides the existing `BranchList` wire type as an
additive field, so a remote host without it degrades to plain names.

Each row now carries, right-aligned: the holding worktree, `local` /
`gone` / `↑N ↓M` against the upstream, and the tip age. Sections order
by recency, with the current branch still leading LOCAL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsgnZNRhcTnZdEZj8Tm2hp
Picking a branch was all-or-nothing: click it and you are on it. Looking
at what a branch contains first meant checking it out, or leaving for the
commit log popover and finding the branch again there.

Add a row context menu with the three things that need no checkout:
Show History, Compare with Current, Copy Branch Name. The first two route
into what already exists — the commit log popover already renders an
arbitrary branch's graph, and `DiffMode::BranchCompare` already backs a
three-dot diff — so this is wiring, not a second implementation. History
seeds the popover's own branch dropdown from the picker's loaded list, so
it costs one commit-graph fetch and no more.

Reachable by right-click and, since the picker is keyboard-driven, by the
menu key / shift-F10 on the selected row, with arrows and Enter inside the
menu. Compare is disabled on the current branch — it would diff nothing —
and navigation steps past it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsgnZNRhcTnZdEZj8Tm2hp
The value column of a frontmatter entry is a flex child, so `min-width:
auto` pinned it to its unwrapped single-line width: a long `description:`
painted straight past the edge of the metadata card. `min-width: 0` lets
it shrink to the card and wrap inside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcL4r46s9HMDmrzQkLzUMo
…h run

A paragraph was a wrapping flex row with one item per inline run, so a run
long enough to wrap occupied a full-width box and whatever followed it — a
code chip, the text after a bold lead-in — started on the next line.

Inline elements are now flattened into that row as word tokens: text
splits on word boundaries (each token keeping its trailing whitespace),
and bold/italic/link hand their emphasis down to the tokens instead of
wrapping them in a container. Code stays a single chip.

Measured at 600px: text with code chips went from 276px to the 161px
plain-text baseline, a bold lead-in from 184px to 161px. Both shapes are
covered by tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcL4r46s9HMDmrzQkLzUMo
@matej21
matej21 merged commit 7813ced into main Aug 19, 2026
10 checks passed
@matej21
matej21 deleted the minor-ui-improvements branch August 19, 2026 12:57
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