## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Inbox is the primary triage surface; it supports both mouse hover and `j`/`k` keyboard navigation over a flat, expandable list of rows > - Hover and keyboard selection are meant to be a single shared "cursor": hovering a row and then pressing `j`/`k` should continue from the hovered row, not jump elsewhere > - A prior hover-perf rewrite moved the hovered row into a numeric `hoveredIndexRef` and nulled it whenever the list reshaped; because the inbox polls constantly, any refresh between hovering and pressing a key dropped the hovered index > - With the hovered index dropped, the next keypress fell back to selection index `0`, stranding the cursor at the top of the list instead of continuing from the hovered row > - This pull request tracks the hovered row's stable identity (a nav key) alongside the numeric index and re-anchors it by key across reshapes, mirroring the existing keyboard-selection reconciliation > - The benefit is that mouse hover and keyboard navigation stay in sync exactly as intended, even while the inbox is polling ## Linked Issues or Issue Description **Bug report** - **What happened:** In the Inbox, hovering a row with the mouse and then pressing `j`/`k` (or another keyboard shortcut) does not continue from the hovered row. If the list refreshes (which happens on the inbox's constant polling) in the moment between hovering and pressing a key, keyboard selection snaps back to the top row instead. - **Expected behavior:** The keyboard cursor should be in sync with the hovered row — pressing `j`/`k` after hovering should move relative to the row the mouse is over. - **Steps to reproduce:** 1. Open the Inbox with several rows. 2. Hover the mouse over a row partway down the list. 3. Wait for (or trigger) a background poll/refresh of the list. 4. Press `j` or `k`. 5. Observe selection jumps to the top of the list instead of continuing from the hovered row. - **Root cause:** The `[flatNavItems]` effect nulled `hoveredIndexRef` on every list reshape. Since hover also clears the keyboard selection band to `-1`, the fallback selection index resolved to `0`. ## What Changed - Hoisted `navEntryKey` to module scope so it can compute a stable, index-independent identity for a nav row from both the hover handler and the reshape effect. - Added `hoveredNavKeyRef` to track the hovered row's stable key alongside the existing numeric `hoveredIndexRef`, set whenever the pointer selects a row. - On list reshape, re-anchor the hovered index by key (find the row with the same key) instead of unconditionally dropping it; only drop the hover when the row is actually gone. This mirrors the existing `selectedIndex` key-based reconciliation. - Added a unit test that hovers a row, reshapes the list via a simulated poll, presses `j`, and asserts selection continues from the hovered row. ## Verification - `pnpm --filter ./ui exec vitest run src/pages/Inbox.test.tsx` → 15/15 passing, including the new hover→`j`/`k` sync test and the existing keyboard-nav tests. - The new test explicitly covers the reshape-during-hover path that the prior test suite had deferred to live/e2e verification. ## Risks - Low risk. The change is confined to the Inbox's in-memory hover/selection bookkeeping (two refs and one effect); it adds no new renders (hover still paints via CSS `:hover`) and touches no data fetching, routing, or persistence. Behavior is unchanged when the list does not reshape; when it does, the hover now follows the same row instead of being dropped. ## Model Used - Claude Opus 4.8 (Anthropic), model id `claude-opus-4-8`, used with extended thinking and tool use (file edits, running the UI test suite locally). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [ ] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| public | ||
| src | ||
| storybook | ||
| README.md | ||
| components.json | ||
| index.html | ||
| package.json | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vite.qa.config.mjs | ||
| vitest.config.ts | ||
| vitest.setup.ts | ||
README.md
@paperclipai/ui
Published static assets for the Paperclip board UI.
What gets published
The npm package contains the production build under dist/. It does not ship the UI source tree or workspace-only dependencies.
Storybook
Storybook config, stories, and fixtures live under ui/storybook/.
pnpm --filter @paperclipai/ui storybook
pnpm --filter @paperclipai/ui build-storybook
Typical use
Install the package, then serve or copy the built files from node_modules/@paperclipai/ui/dist.