mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-13 03:15:34 +02:00
* feat(issues): add header thread navigator to issue detail The right-edge rail is a good position indicator and a bad finder: a tick carries no text, so locating a thread costs one hover per candidate, and once threads outgrow the rail the ticks compress to their 5px floor and stop being countable. Add a header entry point that lists the threads instead — 8-10 titles readable at once, plus search, resolution/@me filters, and day grouping, so 24 threads and 240 threads cost the same to navigate. Hover previews the list without taking focus; a press pins it, focuses search, and enables arrow-key navigation. Pure hover would have been wrong for a surface you must scroll, type into, and arrow through — all three need the pointer free to leave the button. The two navigators stay one coordinate system rather than two competing lists: both derive the thread set and "you are here" from one memo and one shared useVisibleThreadIds, and hovering a row lights that thread's tick on the rail. Adds Mod+Shift+O (openThreadNav), mirroring "go to symbol in file". MUL-5755 Co-authored-by: multica-agent <github@multica.ai> * feat(issues): tint search matches in the thread navigator Filtering told the reader which threads matched but not why: a row that matched on its excerpt looked identical to one that matched on its title, so they had to re-find the term they had just typed. Tint the matches in both fields using the same --find-match token as the in-page find bar. Caught by driving the real app rather than the unit tests — the mock had this and the implementation had quietly dropped it. MUL-5755 Co-authored-by: multica-agent <github@multica.ai> * fix(issues): drop the viewport marker and use real keycaps in the thread panel Two things the panel got wrong, both visible only once it had real data. The "you are here" marker came from the same viewport set the rail uses to darken its ticks, but "on screen" is a set, not a point: a tall viewport holds several threads at once, so the marker landed on two or three rows and read as a broken multi-select. Absolute position is the rail's job, where a column of ticks can show a span honestly; the panel's job is finding. Removing it also removes the panel's scroll-container dependency, so useVisibleThreadIds goes back to being the rail's private hook. The footer hints were hand-rolled kbd elements with arrows typed into the translation strings, so they matched nothing else in the product. Use the shared ShortcutKeycaps, which draws the same keycaps as the sidebar, command palette, and confirm dialogs, with the platform's own glyphs. The trigger also gains a tooltip like its neighbours, carrying the Mod+Shift+O keycaps — until now the shortcut had nowhere to be discovered. MUL-5755 Co-authored-by: multica-agent <github@multica.ai> * feat(issues): accept the command bar's Ctrl nav aliases in the thread panel Ctrl+N/J move down and Ctrl+P/K move up, matching the command bar — cmdk enables `vimBindings` by default and switches on exactly those four chords, so the palette has always accepted them and the thread navigator was the odd one out. The policy already existed as pickerNavigationDirection, added for the editor's mention and slash pickers for this same reason (MUL-5495). It lived in suggestion-popup.tsx, which imports Tiptap and floating-ui, so reaching it from a header popover would have dragged the editor's dependency graph along. Moved both it and isPickerAcceptKey — pure KeyboardEvent predicates either way — to common/picker-keys.ts, with their tests, and repointed the three consumers. preventDefault matters for the letter aliases here specifically: focus sits in the panel's search field, where Ctrl+K/N/P are readline editing commands that would otherwise mangle the query while moving the cursor. Tab is deliberately not an accept key in this panel, unlike the editor pickers, because Tab has to stay focus navigation for the filter pills. MUL-5755 Co-authored-by: multica-agent <github@multica.ai> * fix(issues): four keyboard and data correctness bugs in the thread panel From review on #6426. All four reproduce; three were mine to begin with. IME: handleKeyDown acted on every Enter, so a CJK user committing an IME candidate in the search field jumped to the active row and closed the panel mid-word. Guard with isImeComposing, the same check Cmd+F already uses. Child controls: the handler sits on the popup so navigation works anywhere inside it, which also caught Enter bubbling from the filter pills and the rows — and preventDefault on a button's keydown cancels the click the browser was about to synthesize. Enter on "Resolved" jumped away instead of filtering. Handle Enter only when the search field is the target; buttons keep native activation. The existing keyboard tests fired at the popup root, which no real interaction targets, so they could not have caught this; they now fire at the search field. Legacy mentions: mentionsUser matched only mention://member/<id>, and its comment claimed the old [@ id=... label=...] shortcodes were normalised before storage. They are not — preprocessMarkdown migrates them on READ, and the timeline hands us raw content, so "@me" silently dropped threads whose only mention of the reader used the old format. Run the same normaliser first; it returns its input untouched when there is no shortcode. Timestamps: formatClock used "less than 48 hours ago" while threadDayGroup used calendar days. The two disagree for most of the day, so an entry from 23:41 the day before yesterday grouped under "Earlier" but rendered as a bare "11:41 PM" with nothing saying which day. Take the group as an argument — one boundary cannot drift from itself. MUL-5755 Co-authored-by: multica-agent <github@multica.ai> * fix(issues): one keyboard cursor in the thread panel, and a live shortcut hint From the second review pass on #6426. Focus and highlight could drift. Gating Enter to the search field last round left the arrows firing for the whole popup, so Tab to a row, ArrowDown, Enter moved the highlight to one thread and jumped to another — DOM focus and the highlight were two pieces of state with nothing holding them together. Rather than gate the arrows too and leave the rows as unreachable-but-focusable tab stops, make the relationship structural: rows are role=option with tabIndex=-1, the list is a listbox, the search field is a combobox that names the current row through aria-activedescendant. Focus never leaves the search field for the list, so there is only one cursor to disagree with. Tab still reaches the filter pills, which keep native Enter activation. This also fixes rows having outline-none with no focus-visible replacement: they are no longer keyboard-focusable at all. The shortcut hint used getShortcut(), a getState() snapshot with no subscription, under a comment claiming it would pick up a rebind. It would not: rebinding openThreadNav in Settings left the tooltip showing the old key while the keydown handler already used the new one. useShortcut subscribes, and is what chat-fab already uses. MUL-5755 Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai>