Repository navigation
feat: mention two level hierarchy - #35
Merged
Merged
Conversation
…medium]
Groundwork for the single-`@` two-level mention menu. No user-visible
change: the four existing sources insert exactly the same text as before.
- `MentionItem` gains `insertText` (commit) and `navigateText` (drill-down).
Both replace the whole span from the trigger to the caret and carry their
own leading marker; without them the primitive still composes
`${trigger}${label}`.
- `navigateText` generalises the directory drill-down into a navigation
contract: rewrite the span, keep the menu open, record no range and no
selected value. The primitive no longer infers navigation from a trailing
`/` — the `@` file menu now passes both props explicitly.
- `MentionKind` widens so a committed range can name its own type.
- Keyboard: Tab/ArrowRight descend into a highlighted navigation item;
Backspace/ArrowLeft pop a `<namespace>:` prefix back to the bare trigger.
Path drill-downs are excluded so Backspace still walks a path one
character at a time.
The one intentional behaviour delta: Tab on a directory candidate now
descends instead of closing the menu.
The `MentionSource` registry skeleton listed for this step is deferred to
the migration that gives it callers, rather than landing a type with none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Model: claude-opus-5
The logic layer the `@` two-level menu will render. No UI change: nothing consumes the registry yet, and the four existing menus are untouched. - `selectMentionMenuView` maps the text between the trigger and the caret to one of three levels: the category list (`@`), an aggregate search across every category (`@foo`), or one scoped category (`@issue:foo`). Keeping `@foo` on aggregate search is deliberate — a strict two-level menu would retire today's one-step `@filename`. - Candidate builders reuse each source's existing ranking and keep each type's prompt form (`@path`, `#123`, `$token`, `/cmd`), so reaching a type through `@` does not change what the agent receives. - `getCandidates` is lazy per category: a bare `@` ranks nothing and a scoped query never ranks the file index. - Issues and PRs rank over their own slice of the shared cache. The shared ranking caps its result set, so ranking the merged list would let a long issue list starve every PR out of the PR category. `getIssuePrSuggestions` and its helpers are exported for reuse; the issue/PR module is otherwise unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
Renders the registry as one menu: a category list at `@`, an aggregate search at `@foo`, and one scoped category at `@issue:foo`. Gated on the new `twoLevelMentions` prop — no caller passes it yet, so the per-type menus remain the default and are untouched. - Category rows carry `navigateText`, so selecting one rewrites the input to `@<ns>:` and stays open instead of committing. Candidate rows carry their own `insertText`, so an issue reached through `@` still writes `#123`. - The second level shows a breadcrumb with a visible Back button. Mobile has no Backspace habit, so the way out cannot be keyboard-only; the click path reuses the primitive's pending-selection channel rather than touching the DOM selection, which would race the controlled value commit. - `/` keeps a direct route through the command category's `directTrigger`: a slash command owns the whole prompt and never nests under a category. - Fuse loading stays inside the open menu and reuses one module-cached import, so mounting a composer still never pulls Fuse in. Storybook coverage and the first-level-to-second-level analytics funnel are still outstanding for this step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
`@` becomes the only menu trigger. The two-level menu is no longer behind a flag, and the four per-trigger menus are deleted. - `/` stays the one exception and still opens commands directly: a slash command must own the whole prompt, so it never nests under a category. - `#` and `$` no longer open a menu, but their hydrators stay. A hand-typed or pasted `#123` / `$token` is still highlighted, and `$token` still expands to its skill path before send — only the menu route is gone. - Deleting the old menus removed every mention analytics event with them, so the funnel is re-established on the new menu with a `category` dimension: `menu_open` -> `category_enter` -> `select`. `category_enter` is reported from the resolved view rather than a row callback, because a navigation item never fires `onMentionSelect` and the keyboard route must count. - `mention-skill-menu-idle-measure.test.tsx` is deleted with its subject. It guarded against measuring the composer while the `$` menu was closed; the new menu performs no measurement at all, so the bug class is structurally gone rather than merely untested. Known regression: the `$` menu's two-pane detail panel (description, scope, version, path) had no equivalent in the new menu and is not carried over. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
Recovers the one real capability the `$` menu lost when it was replaced: its two-pane detail view (description, scope, version, path, symlink target). Modelled as neutral `MentionCandidateDetail` fields on the candidate rather than a per-source component, so a single pane serves every category — issues and sessions get one for free. Desktop only, matching the old menu: the docked mobile strip is too narrow and has no hover to preview with. The pane follows the highlight and falls back to the first candidate so it is populated before the highlight effect lands, as the `$` menu did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
The locale files are flat dotted-key maps because i18next runs with `keySeparator: false`. The keys added for the two-level menu were written as nested blocks, so every one of them resolved to nothing and fell back to its inline default — invisible in English and untranslated everywhere else. Rewrites them as flat keys, preserving the file's existing key order, and adds the missing `detailSymlink` key the detail panel needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
Replaces the coverage lost with `SkillMentionMenu.stories.tsx`: every level and state of the single `@` menu — categories, aggregate search, each scoped category, the detail panel, its mobile list-only form, loading, error, truncated file list, and no-results. Candidates are built through the real registry mappers, so a story breaks if the insert-text or detail mapping drifts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
Adds `@session:` and, with it, the layer the composer was missing. ## The type The composer holds `@session:<title-slug>`, the mention range holds the real `sessionId`, and the token is rewritten on send into `use lody mcp to query session[id: <id>] history`. The split is forced, not stylistic: the composer is a textarea with a character-aligned highlight overlay, so it cannot render a chip whose width differs from its text. Showing a title while sending an id has to go through the token-plus-expansion route `$skill` already uses. Consequences that needed deciding: - `@session:` stays in the committed text, unlike every other namespace prefix, because it is the anchor expansion matches on. That also makes expansion idempotent for free — a re-sent prompt has no anchor left. - Slugs resolve through the live session list first, then a `localStorage` slug -> id map that survives a reload or a rename. It is synchronous on purpose: expansion runs on the send path, and an async store would make that entire path async for a few hundred bytes. - An unresolvable slug is sent verbatim. A stale token the agent can ignore beats a confidently wrong session id. - Duplicate titles disambiguate the *older* session, so the most recent holder of a title keeps the clean slug and re-typing it stays stable. - The detail panel shows the exact text that will be sent, so the one place where display and payload diverge is visible rather than surprising. ## The layer `useMentionPromptExpansion` becomes the single before-send transform and composes the per-type expansions. There are exactly two send paths (chat landing and the session composer), and each previously wired every expandable type by hand — adding one meant editing both. It reads its own data rather than taking it through props, so a send path no longer has to know which data an expansion needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
`currentSessionId` reached the send-path expansion but never the menu, so a session listed itself as a mentionable candidate — referring to history the agent already has. Threads it from the session composer through `ChatComposer` to `CombinedMentionTextarea`. Chat landing passes nothing, which is correct: a new chat has no session to exclude. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
…sk:medium] Arrow keys could not leave the first group of an aggregate search. `useFilterStore` was running without `manualFiltering`, so on top of each category's own ranking the primitive scored the search term against every item's `value` and hid the misses. Under `@mention` a file row survived (its path contains the term) but an issue row did not — its payload is `#3312`. A hidden row renders null, so its collection entry lost its DOM node, the position sort collapsed, and movement stopped at the first group. Categories already rank and slice their candidates, so the primitive must not filter at all. Also trims the menu chrome to what earns its space: the second level keeps only the way back, category rows are just their name, and a session row is just its title. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
Trimming the menu chrome left `MentionCategory.hint` with no renderer — the first-level row and the breadcrumb were its only two readers. Removes the field, its six i18n keys in both locales, and the fixtures that carried it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Model: claude-opus-5
…[risk:medium]
Arrow keys still broke across groups after the filtering fix: entering the
next category showed no highlight, and moving on jumped back to the first.
`MentionItem` was registering `{ current: itemNode }` — a fresh snapshot
object built from state that is null on the first render. The collection keys
its map by that object and sorts entries by document position through
`.current`, so every stale snapshot left a null-node entry. `compareNodePosition`
returns 0 for those, collapsing the sort, and `findIndex` matched the
highlighted item against the wrong row: no highlight, then index 0 on the next
move.
Registering the component's stable ref instead keeps one entry per item with a
`.current` that always tracks the live node.
Also drops the `@session:` side panel: a session row is its title and project,
and the id and the outgoing instruction were noise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Model: claude-opus-5
…tion-two-level-hierarchy # Conflicts: # locales/en.json # locales/zh_CN.json
…ke work [risk:medium] Cleanup pass over the two-level `@` menu. No intended behavior change apart from the two noted below. Reuse and dedupe: - `mention-hydration.ts` owns the hydrate-once effect and range merge that file/session/skill/issue-PR hydrators each had a copy of. - `context.onNavigateBack()` owns the drill-down pop-back once; the menu's Back button and Backspace/ArrowLeft both call it instead of restaging the value-commit/caret-restore transaction separately. - `mention-trigger.ts` owns the `@<ns>:` grammar; the registry parses through it rather than keeping a second regex. - `useSessionMentionItems` is the single owner of the mentionable-session list, which the composer and the send-path expansion both derived. - `getEffectiveLatestMessageAt` replaces a weaker local timestamp parse, and `getMentionViewCandidates` replaces three copies of the view flatten. Wasted work: - Issue/PR slices are partitioned once and shared with their Fuse indexes. - `getCandidates(term, limit)` lets a source stop before mapping results the aggregate level drops; `selectMentionMenuView` still enforces the cap. - The session category is capped like the others. - The Fuse constructor is latched, so closing the menu no longer discards every index and rebuilds it on the next `@`. - The slug cache skips a `setItem` when the serialized map is unchanged, and session hydration reads it only for drafts carrying the anchor. Dead code: six unused analytics helpers, `issue-pr-trigger-refresh.ts`, an empty import, `SkillMentionMenuPlacement`, `replacesWholePrompt` (set and asserted, never read), and three orphaned locale keys. Behavior fixes: the skill detail scope badge goes through i18n instead of rendering the raw enum, and a local session's subtitle no longer reads "local". Registers `lody:session-mention-slugs` in the cache-clear list, and keeps `MentionKind` product-neutral in the shared primitive. Model: claude-opus-5[1m]
Leeeon233
added a commit
that referenced
this pull request
Aug 26, 2026
* fix: restore reliable skill mentions Model: GPT-5 * fix: stabilize mention detail scrollbar Model: GPT-5 * fix: stabilize mention detail height Model: GPT-5 * fix: advertise skill mention trigger Model: GPT-5 --------- Co-authored-by: Leeeon233 <leeeon233@gmail.com>
Leeeon233
added a commit
that referenced
this pull request
Sep 9, 2026
* fix: keep forked Codex sessions subscribed Pin acp-extension-codex to fc91dce (LodyAI/acp-extension-codex#37) so thread/fork children stay subscribed for turn/completed. Pin acp-extension-core 0.1.2 because that adapter already depends on it. This does not take Lody#534 host worktree identity changes. Adapter #35 is already on Codex main and comes along with #37. Closes #543 * docs: record Codex fork subscription pin Agent Note for pinning acp-extension-codex fc91dce and Core 0.1.2 without taking Lody #534 host identity changes. Model: grok-4.6 * fix: pin DSH main with awaited smoke tests Use merged acp-extension-dsh #14 to fix the no-floating-promises CI error without weakening lint checks. Model: gpt-6 --------- Co-authored-by: Leon Zhao <leeeon233@gmail.com>
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.
No description provided.