Branch picker rows, unread panes, and two Markdown wrapping fixes - #191
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
recompute_branch_filteredinto a purebranch_nav_itemsso the ordering is unit-testable.local/gone/↑N ↓Magainst the upstream, and the tip age. Sections order by recency. All of it comes from onegit for-each-refpass — 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 existingBranchListwire type as an additive field, so a remote host without it degrades to plain names.DiffMode::BranchComparealready 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.
ToggleUnreadaction 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 amanual_unreadflag holds it — set by hand, skipped by the clear, released when focus leaves..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.
+3slack 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./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: autoon 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: 0lets 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 teston the touched crates —okena-markdown15,okena-terminal182,okena-git175,okena-views-git35,okena-views-terminal24, all passingcargo clippy --workspace --all-targetsandcargo fmt --all --check— clean.mdin 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