╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │389 inner.children.clearAndFree(allocator); │ │390 inner.getFocus().child_id = null; │ │391 │ │392 - // the description as a focusable word-wrapped text box. its hidden │ │393 - // border reserves the space the border occupies when focused, so │ │394 - // focusing doesn't shift layout. │ │392 + // the issue's tags, each linking to the list filtered to that tag. │ │393 { │ │396 - var tb = try wgt.TextBox(ui.Widget).init(allocator, entry.issue.description, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .word });│ │397 - errdefer tb.deinit(allocator); │ │398 - tb.getFocus().focusable = true; │ │399 - try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │400 - } │ │401 - │ │402 - // the issue's tags flow under the description like a paragraph; each │ │403 - // links to the list filtered to that tag. │ │404 - { │ │394 var items: std.ArrayList(ui.TagFlow.Item) = .empty; │ │395 defer items.deinit(allocator); │ │396 var tag_iter = evt.Issue.tagIterator(entry.issue.tags); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │406 } │ │407 } │ │408 │ │420 - // point the pane at its row so focus recovery can land here. │ │409 + // the description as a focusable word-wrapped text box. its hidden │ │410 + // border reserves the space the border occupies when focused, so │ │411 + // focusing doesn't shift layout. │ │412 + { │ │413 + var tb = try wgt.TextBox(ui.Widget).init(allocator, entry.issue.description, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .word });│ │414 + errdefer tb.deinit(allocator); │ │415 + tb.getFocus().focusable = true; │ │416 + try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │417 + } │ │418 + │ │419 + // point the pane at its first row so focus recovery can land here. │ │420 inner.getFocus().child_id = inner.children.keys()[0]; │ │421 │ │422 // reset the scroll to the top for the newly-shown issue: directly on the │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │462 const sc = self.detailScroll(); │ │463 switch (key) { │ │464 .arrow_left => return self.focusList(root_focus), │ │466 - .arrow_up => sc.y -= 1, │ │467 - // scroll through the description; once it can't scroll further│ │468 - // (always, on the web's native scroll), move into the tags. │ │469 - .arrow_down => { │ │465 + // once the scroll can't move further, cross into the tags. │ │466 + .arrow_up => { │ │467 const before = sc.y; │ │471 - sc.y += 1; │ │468 + sc.y -= 1; │ │469 sc.clampToContent(); │ │470 if (sc.y == before) try self.focusTags(root_focus); │ │471 return; │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────╮ │472 }, │ │473 + .arrow_down => sc.y += 1,│ │474 .page_up => sc.y -= 10, │ │475 .page_down => sc.y += 10,│ │476 .home => sc.y = 0, │ ╰──────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │484 sc.clampToContent(); │ │485 } │ │486 │ │489 - // left/right step through the tags (left exits to the list at the first);│ │490 - // up/down move to the first tag of the adjacent row, up escaping to the │ │491 - // description from the top row. │ │487 + // arrow keys move the tag selection; at the flow's edges focus crosses to│ │488 + // the neighboring widgets. │ │489 fn tagsInput(self: *View, key: Key, root_focus: *Focus) !void { │ │490 const tf = self.tagFlow() orelse return; │ │491 const cid = tf.focus.child_id orelse return; │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │495 switch (key) { │ │496 .arrow_left => if (cur > 0) self.focusTag(tf, root_focus, cur - 1) else try self.focusList(root_focus), │ │497 .arrow_right => if (cur + 1 < count) self.focusTag(tf, root_focus, cur + 1), │ │501 - .arrow_up => if (tf.rowStep(cur, false)) |i| self.focusTag(tf, root_focus, i) else try self.focusDescription(root_focus), │ │502 - .arrow_down => if (tf.rowStep(cur, true)) |i| self.focusTag(tf, root_focus, i), │ │498 + .arrow_up => if (tf.rowStep(cur, false)) |i| self.focusTag(tf, root_focus, i), │ │499 + .arrow_down => if (tf.rowStep(cur, true)) |i| self.focusTag(tf, root_focus, i) else try self.focusDescription(root_focus),│ │500 .home => self.focusTag(tf, root_focus, 0), │ │501 .end => self.focusTag(tf, root_focus, count - 1), │ │502 .mouse => |mouse| switch (mouse.action) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────╮ │510 } │ │511 } │ │512 │ │516 - const tags_child_index: usize = 1; │ │513 + const tags_child_index: usize = 0; │ │514 │ │515 fn tagFlow(self: *View) ?*ui.TagFlow {│ │516 const inner = self.detailInner(); │ ╰───────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │520 - if (inner.children.count() <= tags_child_index) return null; │ │517 + if (inner.children.count() == 0) return null; │ │518 return switch (inner.children.values()[tags_child_index].widget) {│ │519 .tag_flow => |*tf| tf, │ │520 else => null, │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │524 fn tagsFocused(self: *View) bool { │ │525 const inner = self.detailInner(); │ │526 const cid = inner.getFocus().child_id orelse return false; │ │530 - return inner.children.getIndex(cid) == tags_child_index; │ │527 + return inner.children.getIndex(cid) == tags_child_index and self.tagFlow() != null;│ │528 } │ │529 │ │530 fn focusTag(self: *View, tf: *ui.TagFlow, root_focus: *Focus, index: usize) void { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │551 │ │552 fn focusDescription(self: *View, root_focus: *Focus) !void { │ │553 const inner = self.detailInner(); │ │557 - if (inner.children.count() == 0) return; │ │558 - root_focus.setFocus(inner.children.keys()[0]); │ │554 + const index: usize = if (self.tagFlow() != null) 1 else 0; │ │555 + if (inner.children.count() <= index) return; │ │556 + root_focus.setFocus(inner.children.keys()[index]); │ │557 } │ │558 │ │559 // enter the detail pane. an empty pane (no issues) can't be entered.│ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯