feat(web): rank chat mentions by recency (#3099)

This commit is contained in:
Carlos Florêncio
2026-08-28 22:47:27 +00:00
committed by GitHub
parent a9af759d42
commit d1dbb6b8ea
12 changed files with 1150 additions and 28 deletions
+141
View File
@@ -0,0 +1,141 @@
---
created: 2026-08-27
status: done
requirements:
- REQ-UI-COMPOSER-MENTION-RECENCY-001
system_design:
- ../../specs/ui/system-design/composer-mention-recency.md
legacy_specs: []
---
# Implementation Plan: Rank Chat Mentions by Recency
## Overview
Add one browser-local MRU list for task, saved-prompt, and file selections in
chat `@` menus. Apply the MRU rank after candidate lookup and before menu
rendering. Record selection through the shared TipTap command path.
One vertical work order owns the storage helper, rank integration, selection
wiring, unit tests, and desktop and phone E2E proof. These parts share the same
identity and ordering contract, so a split creates no independent result.
## Scope
### In scope
- Persist 50 unique chat mention identities in newest-first order.
- Rank recent task, saved-prompt, and returned file candidates before unselected
candidates.
- Use text relevance and stable source order after recency.
- Preserve the Plan action's baseline position.
- Record pointer, touch, Enter, and Tab selections through one command path.
- Prove desktop reload persistence and phone touch parity.
### Out of scope
- Backend persistence or sync.
- Candidate lookup changes or larger file-search results.
- Recency for task creation, agent launch, `#`, or `/` menus.
- New copy, settings, recent groups, frequency scores, or time decay.
## Technical approach
### Storage and rank
- Add `apps/web/lib/chat-mention-recency.ts` and its unit test.
- Use the versioned key `kandev.chatMentionRecency.v1`.
- Normalize unknown browser data before each read and write.
- Key task and prompt entries by stable ID. Key file entries by workspace and
path.
- Keep one 50-entry newest-first list. Move a repeated selection to the front.
- Export a pure rank helper that preserves current behavior when no history
exists.
### Composer integration
- Pass `workspaceId` into `useMentionItems` in
`apps/web/components/task/chat/tiptap-input.tsx`.
- Replace the local text-only `filterItems` path with the shared rank helper.
- Extend `MentionSuggestionCallbacks` with one selection callback.
- Invoke that callback from the menu command closure after TipTap inserts the
selected mention.
- Keep `MentionMenu`, `PopupMenu`, selected-index reset, and entity-reference
and slash-command plugins unchanged.
### Responsive contract
- Reuse the current popup on desktop and phone.
- Keep the same listbox, row size, focus behavior, touch behavior, and scroll
owner.
- Do not add a mobile branch. Shared ranking produces the same order on both
viewports.
## Tests
| Acceptance criterion | Evidence |
| --- | --- |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.1` | Pure rank unit test and desktop E2E |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.2` | Unit test with a recent weaker text match |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.3` | Unit tests for baseline relevance and stable ties |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.4` | Selection-callback unit test and desktop/phone E2E |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.5` | Pure rank unit tests with empty and filtered queries |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.6` | Storage recovery unit tests and reload E2E |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.7` | Identity and workspace-scope unit tests |
| `AC-UI-COMPOSER-MENTION-RECENCY-001.8` | Shared component tests and desktop/phone E2E |
Use Red-Green-Refactor. Run the new unit and E2E assertions before production
changes and record their expected failures in the work-order results.
## E2E tests
- Add `apps/web/e2e/tests/chat/mention-recency.spec.ts` for desktop.
- Seed two task candidates whose shared query gives different text scores.
- Select the weaker text match through a unique query.
- Reopen with the shared query and assert that selection recency wins.
- Reload the page and assert that the same candidate remains first.
- Add `apps/web/e2e/tests/chat/mobile-mention-recency.spec.ts` for
`mobile-chrome`.
- Repeat the rank proof with saved prompts and touch selection in the current
mobile popup.
- Remove disposable prompts and tasks through existing fixture cleanup or
explicit `finally` blocks.
## Work orders
- [x] [Task 01: Rank chat mention suggestions](task-01-rank-chat-mention-suggestions.md)
## Verification results
- Red-Green-Refactor passed. The new selection-callback test failed before the
callback wiring existed, and the new helper tests failed before the helper
module existed.
- Focused web unit tests passed: 2 files, 25 tests, including the filtered-query
Plan-position regression found during PR fixup.
- Managed production-build desktop and `mobile-chrome` E2E tests passed: 1 test
each. Desktop coverage includes same-device reload persistence.
- Web typecheck and targeted web lint passed with no errors or warnings.
- Dependency installation passed with `pnpm install --frozen-lockfile`.
## Risks
- File recency can only reorder the current 20 file-search results. The helper
must not reintroduce stale stored paths as live candidates.
- The keyboard path calls the menu command directly. Recording only the React
row click would miss Enter and Tab.
- A comparator that reads mutable storage during pair comparisons can become
inconsistent. Read and normalize the MRU list once per ranking operation.
- Plan can move if it participates in the MRU comparator. Capture and restore
its baseline index instead.
- File paths can collide across workspaces. Include the workspace in file
identities.
## Public documentation
None. The feature changes no command, configuration key, API, install flow, or
public workflow.
## Decisions
No ADR is required. This local UI feature and its system design contain the
selected ranking and persistence rules.
@@ -0,0 +1,126 @@
---
id: "01-rank-chat-mention-suggestions"
title: "Rank chat mention suggestions"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-UI-COMPOSER-MENTION-RECENCY-001
acceptance_criteria:
- AC-UI-COMPOSER-MENTION-RECENCY-001.1
- AC-UI-COMPOSER-MENTION-RECENCY-001.2
- AC-UI-COMPOSER-MENTION-RECENCY-001.3
- AC-UI-COMPOSER-MENTION-RECENCY-001.4
- AC-UI-COMPOSER-MENTION-RECENCY-001.5
- AC-UI-COMPOSER-MENTION-RECENCY-001.6
- AC-UI-COMPOSER-MENTION-RECENCY-001.7
- AC-UI-COMPOSER-MENTION-RECENCY-001.8
system_design:
- ../../specs/ui/system-design/composer-mention-recency.md
---
# Task 01: Rank Chat Mention Suggestions
## Summary
Add the bounded browser-local MRU helper and connect it to the shared TipTap
chat mention path. Prove the same recency-first order on desktop and phone.
## In scope
- Add normalized storage, identity, update, and rank helpers.
- Rank task, prompt, and returned file candidates by MRU position before text
relevance.
- Preserve the Plan action's baseline position.
- Record pointer, touch, Enter, and Tab selections once.
- Add focused unit, desktop E2E, and phone E2E tests.
## Out of scope
- Backend, API, database, boot-payload, or WebSocket changes.
- New candidate sources or file-search limits.
- Other composer types and suggestion triggers.
- New copy or visual treatment.
## Acceptance
- With no valid history, the helper returns the current filtered order and Plan
position.
- After selection, that candidate ranks before stronger text matches and stays
first after a same-device reload.
- Desktop and phone use the same rank and preserve current insertion, focus,
keyboard, touch, and popup behavior.
## Verification
```bash
cd apps && pnpm install --frozen-lockfile
cd apps/web && pnpm test -- \
lib/chat-mention-recency.test.ts \
components/task/chat/tiptap-suggestion.test.ts
cd apps/web && pnpm e2e:run tests/chat/mention-recency.spec.ts
cd apps/web && pnpm e2e:run --project mobile-chrome \
tests/chat/mobile-mention-recency.spec.ts
cd apps/web && pnpm run typecheck
cd apps && pnpm --filter @kandev/web lint
```
## Files likely touched
- `apps/web/lib/chat-mention-recency.ts`
- `apps/web/lib/chat-mention-recency.test.ts`
- `apps/web/components/task/chat/tiptap-input.tsx`
- `apps/web/components/task/chat/tiptap-suggestion.tsx`
- `apps/web/components/task/chat/tiptap-suggestion.test.ts`
- `apps/web/e2e/tests/chat/mention-recency.spec.ts`
- `apps/web/e2e/tests/chat/mobile-mention-recency.spec.ts`
## Dependencies
None.
## Risks
- The async file source can return after task and prompt candidates. Rank the
completed candidate set once and preserve deterministic ties.
- A storage error must not interrupt mention insertion.
- A missing workspace must not give file paths global recency.
- The test must prove recency over a stronger match, not only a source-order
tie.
## Parallelism
`sequential`
## Inputs
- `docs/specs/ui/requirements/composer-mention-recency.md`
- `docs/specs/ui/system-design/composer-mention-recency.md`
- `apps/web/components/task/chat/tiptap-input.tsx`
- `apps/web/components/task/chat/tiptap-suggestion.tsx`
- `apps/web/lib/recent-tasks.ts` as the nearest browser-storage pattern
- Existing composer mention unit and mobile popup E2E tests
## Results
Implemented device-local MRU ranking and shared TipTap selection recording for
task, saved-prompt, and file mentions. Plan remains at its baseline position,
invalid storage is ignored, and the same ranking path serves desktop and phone.
Red-Green-Refactor evidence:
- The selection-callback test failed before the shared callback was wired.
- The helper tests failed before the new helper module was created.
- Both suites passed after implementation.
- PR fixup added a regression test for Plan's filtered baseline position. It
failed before the fix and passed after the rank helper captured that index.
Verification:
- `cd apps && pnpm install --frozen-lockfile` passed.
- `cd apps/web && pnpm test -- lib/chat-mention-recency.test.ts components/task/chat/tiptap-suggestion.test.ts` passed (2 files, 25 tests).
- `cd apps/web && pnpm e2e:run tests/chat/mention-recency.spec.ts` passed (1 test).
- `cd apps/web && pnpm e2e:run --project mobile-chrome tests/chat/mobile-mention-recency.spec.ts` passed (1 test).
- `cd apps/web && pnpm run typecheck` passed.
- `cd apps && pnpm --filter @kandev/web lint` passed.
+5 -3
View File
@@ -11,7 +11,7 @@ owners:
## Purpose
The UI system owns web presentation behavior for desktop and mobile surfaces.
The UI system owns web presentation.
## Ownership
@@ -19,8 +19,8 @@ This system owns navigation, settings, boards, task/review surfaces,
walkthroughs, chat controls, visual feedback, and responsive interaction
contracts without backend-state ownership.
Controls that configure or display provider/task state remain owned by that
system. The UI system owns only independent, reusable presentation contracts.
Controls for provider/task state remain owned by that system.
The UI system owns reusable contracts.
## Exclusions
@@ -55,6 +55,7 @@ system. The UI system owns only independent, reusable presentation contracts.
- [Command-panel Sidebar Task Reveal](requirements/command-panel-sidebar-task-reveal.md)
- [Compact Workflow Step Navigation](requirements/compact-workflow-step-navigation.md)
- [Comment Markdown Rendering](requirements/comment-markdown.md)
- [Mention recency](requirements/composer-mention-recency.md)
- [Composer Suggestion Overlays](requirements/composer-suggestion-overlays.md)
- [Context Compaction Count](requirements/context-compaction-count.md)
- [Context Window Reset Freshness](requirements/context-window-reset-freshness.md)
@@ -151,6 +152,7 @@ system. The UI system owns only independent, reusable presentation contracts.
- [Agent Todo List Panel](system-design/agent-todo-list-panel.md)
- [App Status Bar](system-design/app-status-bar.md)
- [Changes File Row Containment](system-design/changes-file-row-containment.md)
- [Mention recency](system-design/composer-mention-recency.md)
- [Composer Suggestion Overlays](system-design/composer-suggestion-overlays.md)
- [Compact Workflow Step Navigation](system-design/compact-workflow-step-navigation.md)
- [Per-workflow column visibility on the kanban board System Design Part 1](system-design/board-step-visibility-filter-01.md)
@@ -0,0 +1,75 @@
---
status: active
system: ui
created: 2026-08-27
owners:
- web
---
# Composer Mention Recency Requirements
## Overview
Chat composers show tasks, saved prompts, files, and the Plan action in one
`@` suggestion menu. The current menu ranks matches by text only. Users who
mention the same items repeatedly must find them again for each message.
The UI system owns this behavior because the selection history is local
presentation state. The task, settings, and workspace-file systems continue to
own the available candidates.
## Terminology
- **Mention candidate:** A task, saved prompt, or file that the current chat
composer can show in its `@` suggestion menu.
- **Recent selection:** A mention candidate that the user selected from a chat
composer on the current device.
- **Baseline rank:** The existing text-match score and stable source order.
## Requirements
### REQ-UI-COMPOSER-MENTION-RECENCY-001: Rank chat mentions by recent selection
**Intent:** Make repeated mentions faster without changing candidate sources or
the menu interaction.
**User story:** As a user, I want recent task, prompt, and file selections first,
so that I can reuse them with less navigation.
#### Acceptance criteria
- **AC-UI-COMPOSER-MENTION-RECENCY-001.1:** When the `@` menu contains recent
and unselected mention candidates, the menu shall show recent candidates
before unselected candidates.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.2:** When two recent candidates are
available, the menu shall show the most recently selected candidate first.
Text-match quality shall rank candidates only after selection recency.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.3:** When two unselected candidates have
different text-match quality, the menu shall preserve the existing text
ranking. Equal candidates shall preserve the existing stable source order.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.4:** When the user selects a task, prompt,
or file by pointer, touch, Enter, or Tab, it shall become the newest recent
selection. Selecting it again shall move it to the front.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.5:** When recency changes other results,
the Plan action shall keep the position that the baseline rank assigned for
the current query. The Plan action shall not enter selection history.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.6:** When the user reloads Kandev on the
same device, the bounded selection history shall continue to rank chat
suggestions. A missing, malformed, or unavailable history shall restore the
baseline rank without blocking selection.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.7:** Task and saved-prompt identities
shall use their stable IDs. File identities shall include the workspace, so
equal paths in different workspaces do not share recency.
- **AC-UI-COMPOSER-MENTION-RECENCY-001.8:** Task chat, Quick Chat, and
passthrough chat shall use the same ranking on desktop and phone. The current
popup, keyboard navigation, focus, insertion, and touch behavior shall not
change.
## Out of scope
- Changing task, saved-prompt, or workspace-file search sources and limits.
- Showing a recent file that the current file search did not return.
- Applying recency to task creation, agent launch, `#` references, or `/`
commands.
- Server storage, user sync, device sync, frequency scoring, or time decay.
- Adding a recent label, group, setting, clear action, or other menu copy.
@@ -0,0 +1,139 @@
---
status: current
system: ui
created: 2026-08-27
requirements:
- REQ-UI-COMPOSER-MENTION-RECENCY-001
---
# Composer Mention Recency System Design
## Purpose and boundaries
This design adds device-local selection recency to the chat composer `@` menu.
It does not change the candidate sources or their search limits.
The chat composer receives tasks from the Kanban store, prompts from settings,
and files from `workspace.files.search`. The UI system ranks these candidates
after each source returns them.
## Requirement mapping
| Requirement | Design section |
| --- | --- |
| `REQ-UI-COMPOSER-MENTION-RECENCY-001` | [Ranking](#ranking), [Selection flow](#selection-flow), [Persistence](#persistence), [Responsive behavior](#responsive-behavior) |
## Components and responsibilities
- A new `apps/web/lib/chat-mention-recency.ts` module owns storage parsing,
bounded MRU updates, candidate identity, and deterministic ranking.
- `useMentionItems` in `apps/web/components/task/chat/tiptap-input.tsx` keeps
candidate loading in its current order. It applies the shared rank after task,
Plan, prompt, and file candidates are available.
- `createMentionSuggestion` in
`apps/web/components/task/chat/tiptap-suggestion.tsx` records a selection in
the same command path for pointer, touch, Enter, and Tab.
- `MentionMenu` and `PopupMenu` keep their current rendering, focus, geometry,
accessibility, and selected-index behavior.
## Data and contracts
The browser key is `kandev.chatMentionRecency.v1`. Its value is a JSON array in
newest-first order. The module limits the normalized array to 50 unique entries.
Each entry has this shape:
```ts
type ChatMentionRecentEntry = {
kind: "task" | "prompt" | "file";
id: string;
workspaceId?: string;
};
```
Task and prompt entries use their stable IDs. File entries require the active
workspace ID and use the returned path as `id`. The module rejects unknown
kinds, empty IDs, and file entries without a workspace ID. It removes duplicate
identities and keeps the newest valid entry.
The stored data contains no task title or prompt content. File entries contain
the path that is necessary to match a later file result.
## Ranking
The rank function accepts the candidates, query, workspace ID, and normalized
MRU entries. It uses this sequence:
1. Apply the existing text filter and score to create the baseline rank.
2. Record the Plan action's index in that baseline rank, then remove the action.
3. Put known MRU candidates before candidates that are not in the MRU list.
4. For known candidates, use the newest-first MRU index.
5. Use the existing text score after recency.
6. Use the baseline index as the final stable tie-breaker.
7. Insert the Plan action at its recorded baseline index.
If the MRU list is empty, this process returns the exact baseline order. A
candidate outside the current source results does not enter the menu.
## Selection flow
1. The user enters an `@` query in a chat composer.
2. `useMentionItems` collects current tasks, Plan, saved prompts, and file search
results.
3. The rank module reads and normalizes the current device history.
4. The composer shows the ranked candidates and selects index zero.
5. The user selects one candidate with pointer, touch, Enter, or Tab.
6. The TipTap command inserts the mention by using the existing behavior.
7. The selection callback moves the candidate identity to the MRU front.
The callback does not record Plan. It records each successful command path once.
The next query or menu open reads the updated list.
## Failure and recovery
- If browser storage is unavailable, reads return an empty history and writes do
nothing. Mention insertion continues.
- If JSON parsing or entry validation fails, the module ignores invalid data and
ranks with the remaining valid entries.
- If a stored candidate no longer exists, it has no rank effect. New selections
remove stale entries through the fixed limit.
- If the workspace ID is unavailable, file candidates use baseline rank and file
selections do not enter history. Tasks and prompts continue to use recency.
## Persistence
The browser owns the MRU list in `localStorage`. No backend API, database
migration, Zustand slice, boot payload, or WebSocket event changes.
MRU order defines recency. The design does not use wall-clock timestamps,
frequency counters, or decay. Clearing browser data resets the ranking.
## Responsive behavior
Desktop and phone use the same `TipTapInput`, rank function, and selection
callback. The current contextual popup remains the presentation on both
viewports.
This change does not add a surface, scroll owner, touch control, or breakpoint.
`PopupMenu` remains the nearest shipped mobile exemplar. Existing viewport
containment, 44-pixel rows, touch selection, and focus behavior remain valid.
A desktop E2E proves selection, primary recency rank, and reload persistence. A
phone E2E proves the same rank after touch selection in the existing popup.
## Security
The history remains inside the Kandev browser origin. The module stores only
candidate identities and file paths. It does not store prompt content, task
titles, queries, or message text.
## Observability
No production telemetry is added. Unit tests cover storage recovery, scope,
bounded updates, ranking priority, stable ties, and Plan position. Browser tests
cover the user-visible desktop and phone flows.
## Related decisions
None. The requirement and this design preserve the local ranking choice. The
feature does not create a cross-system architecture boundary.