Skip to content

feat(app): open the slash picker anywhere and insert inline skill chips#1156

Merged
Astro-Han merged 24 commits into
devfrom
claude/slash-anywhere-skills
Jun 4, 2026
Merged

feat(app): open the slash picker anywhere and insert inline skill chips#1156
Astro-Han merged 24 commits into
devfrom
claude/slash-anywhere-skills

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

Type / anywhere in the composer to open the command picker, and on selection insert a position-independent inline skill chip (like an @-file/@-agent mention) that activates the skill for the whole turn. Previously the slash picker only opened when the text was exactly /query from the start of an empty composer.

Architecture mirrors the @-agent pipeline end to end:

  • A new structured SkillPart rides along the prompt via promptAsync. The server's resolvePart expands the skill/command template into a synthetic: true, model-visible text part (bubble-suppressed), exactly like AgentPart.
  • The chip stays where it was typed; the user's prose is preserved (it is NOT the leading-command XOR path that suppresses the body).
  • Mid-text triggers restrict the picker to actual skills only (cmd.source === "skill"). Action builtins (/clear, /new, …), custom commands, and MCP entries keep the leading /name args path and only appear at an empty/at-start composer.
  • Inline skill chips carry the new skill glyph (from the chrome icon registry) in both the composer pill and the sent bubble.

Foundational unlock under the Skill GUI push (#220).

Why

Skills are becoming a first-class product surface, but the only way to invoke one was a leading /command typed from the very start of the composer. That is unlike every other reference affordance (@file, @agent), which can appear anywhere. This makes skills feel position-independent and composable with surrounding prose, which is the foundation the Skill GUI builds on.

Related Issue

Relates to #220 (Skill GUI). No dedicated issue for this foundational slice.

Human Review Status

Pending

Review Focus

  • Server seam (packages/opencode/src/session/prompt.ts): the extracted expandCommandTemplate helper and the resolvePart skill branch. The helper carries ONLY template load + $1..$n/$ARGUMENTS/shell interpolation; all agent/model/subtask/hook/event machinery stays in command(). Server-side cmd.source !== "skill" guard rejects non-skill commands delivered as SkillPart, preventing SDK bypass of the full command pipeline.
  • Legacy-endpoint guard (submit.ts, send-followup-draft.ts, session-action-readiness.ts): a skill chip at offset 0 flattens to /name, which previously would route to the legacy session.command endpoint. Both routing branches and the canSubmitPrompt command-ready gate are guarded by "prompt contains a type:skill part". Path D (leading marked TextPart) is untouched — a skill chip carries no command metadata.
  • CJK-correct trigger regex (slash-trigger.ts): fires after start/whitespace/CJK, never inside foo/bar, http://, 2/3, paths. Covered by a unit matrix.
  • Render is net-new, not mirrored: @-agents render as plain text ([Feature] Remove the visible primary-agent mode picker #239), so there was no chip to mirror. HighlightedText gains a skill reference segment keyed off the skill part's source span, rendered inline in the prose branch.

Risk Notes

  • [Feature] Collapse slash command into inline mark inside the prompt input #778 index-0 invariant: the inline chip is a separate structured part type via the generic pill path; the leading marked-TextPart code and its tests are untouched.
  • Restore/undo: renderPartsToEditor reconstructs a structured skill chip from the persisted SkillAttachmentPart (name + source span), so restore rebuilds the interactive pill — it does NOT degrade to plain /name text.
  • Skill template @mentions stay literal: unlike command(), the skill branch does not run resolvePromptParts, so @file/@agent inside a skill template are not resolved. Fine for prose SKILL.md; would matter only if a skill template embeds @path.
  • Server-side source validation: resolvePart checks cmd.source === "skill" — a config command or MCP entry with a name collision gets the chip preserved for bubble rendering but no synthetic template injection, preventing bypass of hooks/events/resolvePromptParts.
  • SDK regen scope: consumer-side packages/sdk/js/src/v2/gen only; the checked-in packages/sdk/openapi.json route-inventory snapshot is intentionally untouched.
  • No platform/packaging/updater/signing surface touched.

How To Verify

opencode test/session/prompt.test.ts: 20 passed (skill resolves to structured chip + synthetic template; unknown skill → chip-only fallback; non-skill command rejected)
app prompt-input unit suite: 431 passed (incl. slash-trigger matrix 19, editor-serialize skill round-trip, build-request-parts SkillPartInput + optimistic, readiness gate bypass)
ui command-icon: 4 passed; app no-mode-picker (agent highlight removal still holds): 4 passed
app typecheck (tsgo -b): pass; ui typecheck: pass
e2e prompt-slash-open "slash mid-sentence … inline skill chip": 1 passed (seeds project skill, types prose+/summarize, picker opens with skill + excludes file.open, click → chip lands)
snap message-skill-inline: rendered + reviewed (leading / mid-sentence / long-prose bubble variants)

Pre-existing, unrelated: the home composer shows slash commands after a bare slash e2e fails in this local environment because real global skills (~/.agents/skills/*) leak into the picker and push file.open down — it fails on dev too and is a test-sandbox issue, not introduced here.

Screenshots or Recordings

Visual check via the added message-skill-inline snap target (bun run snap message-skill-inline, grid under docs/design/preview/screenshots/): the /summarize token renders inline in the user bubble with the skill glyph + brand-accent color, prose unchanged, across leading / mid-sentence / long-prose cases. The composer pill uses the same glyph.

Checklist

  • Type label — this PR carries exactly one of bug, enhancement, task, documentation.
  • Routing labels — this PR carries app, ui, harness (assigned by the labeler bot / confirmed).
  • Priority label — this PR carries P2 (foundational enhancement, not urgent).
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Astro-Han added 15 commits June 3, 2026 23:29
Inline skill chips need a structured, position-independent message part,
mirroring AgentPart: a reference that activates a skill for the whole turn
regardless of where it sits in the text. Add the SkillPart schema (name +
optional source span) and register it in the Part discriminated union.

Resolution (expand the skill template into a synthetic model-visible text
part) and the composer UI land in later commits; this commit is the schema
foundation only.
Pull the template text transform out of SessionPrompt.command() into a
reusable expandCommandTemplate(cmd, args) helper: positional ($1..$n) +
$ARGUMENTS substitution, inline shell interpolation, trim. Behavior is
unchanged for command(); the helper deliberately excludes agent/model/
subtask selection, plugin hooks, and command events so the upcoming
inline-skill resolvePart branch can reuse only the text expansion.

No behavior change. Existing command tests pass.
Add SkillPartInput to PromptInput.parts and a skill branch to resolvePart,
mirroring the agent branch: a skill part resolves to the persisted structured
chip part plus a synthetic, model-visible text part carrying the expanded
template (argless — the surrounding prose is the turn body). This makes skill
invocation position-independent (activation reads the parts array, not a
leading token) without touching the session.command endpoint, command(), or
toModelMessages (synthetic text flows through; the structured part is skipped
like AgentPart). Unknown skill names keep the chip but inject nothing.

Tests: skill part after prose resolves to chip + synthetic template + intact
prose; unknown skill keeps chip, injects no synthetic text.
Regenerated src/v2/gen from the opencode server schema (bun run build):
adds SkillPart to the Part union and SkillPartInput to PromptInput.parts.
Consumer-side only; the checked-in packages/sdk/openapi.json snapshot is
untouched (route-inventory reads paths only).
Introduce SkillAttachmentPart in the prompt content union so an inline skill chip can live anywhere in the composer, mirroring the @-agent part. Thread the new member through clonePart, prompt equality, history cloning, and the composer preview helpers.
Round-trip a contenteditable=false skill pill (data-type=skill, data-name, data-source) through createPill / parseEditorToParts / renderPartsToEditor, accept it in isNormalizedEditor so the editor does not rebuild every keystroke, and count it as a non-editable boundary in the caret-placement predicates.
Replace the start-anchored slash regex with a cursor-relative, CJK-aware trigger so "/" opens the command picker mid-sentence without firing inside paths, URLs, or fractions. When the trigger fires mid-text, restrict the picker to inline-eligible entries (skills + simple custom commands), excluding action builtins and agent/arg-heavy commands which keep the leading path.
Selecting a skill or simple command from the picker now replaces the typed "/query" with a position-independent skill chip via addPart, instead of prepending a leading command mark. Arg/agent-heavy commands and action builtins keep their existing leading-pill / trigger paths.
Extract inline skill chips into SkillPartInput on send (mirroring agents), tagging the "/name" span via source so the bubble can render it. Add a toOptimisticPart skill case before the agent fallthrough so the optimistic bubble shows the chip and does not flicker when the server-persisted SkillPart arrives.
When a prompt or followup draft contains a structured skill part, its flattened text can start with "/name" (chip at offset 0). Guard both legacy session.command routing branches (submit + followup) and the canSubmitPrompt command_ready gate so such prompts flow through promptAsync, where the skill part resolves into synthetic model text. Path D (marked leading TextPart) is unaffected: a skill chip carries no command metadata.
Add a skill reference segment to HighlightedText keyed off the skill part's source span, and pass skill parts alongside inline files in the user-message prose branch (not the commandInvocation XOR branch, which suppresses the body). The "/name" token stays inline and is colored with the brand accent, matching the leading command mark.
Bridge the skill glyph from the chrome icon registry into the command-icon registry so both the composer pill and the sent bubble can resolve it. The composer skill pill now renders glyph + /name label (textContent stays /name for caret math); the bubble skill segment renders the glyph before the brand-accent /name token. The persisted SkillPart drops the source kind, so the glyph is uniform across surfaces. Adds a focused message-skill-inline snap target.
… test

Move SLASH_TRIGGER + matchSlashTrigger into slash-trigger.ts so it can be unit-tested without importing editor-input (which pulls @solidjs/router and throws in the test env). DRYs the two offset computations in editor-input. Adds a regex matrix covering start/space/newline/CJK triggers and path/URL/fraction/word-char negatives.
…ness

editor-serialize: skill pill attributes, '/name' textContent invariant, prose+skill+prose round-trip, position independence in isNormalizedEditor. build-request-parts: SkillPartInput emission + matching optimistic skill part (no agent fallthrough). session-action-readiness: inline skill bypasses the command-hydration gate but still respects submitReady.
Seeds a project-scoped skill (.agents/skills/summarize/SKILL.md), types prose then "/summarize" mid-sentence, asserts the picker opens with the skill and excludes action builtins, clicks it, and verifies a position-independent skill chip lands in the composer.
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds inline "skill" parts: detect /name triggers, render skill pills in the editor, include skill parts in request/optimistic payloads, resolve templates server-side, render skill highlights and icon in messages, and cover behavior with unit, snapshot, and e2e tests.

Changes

Inline Skill Attachment Support

Layer / File(s) Summary
Type system and schemas
packages/app/src/context/prompt.tsx, packages/opencode/src/session/message-v2.ts
SkillAttachmentPart/SkillPart types added and included in ContentPart/Part unions; clone/equality updated for skills.
Slash trigger detection
packages/app/src/components/prompt-input/slash-trigger.ts, packages/app/src/components/prompt-input/slash-trigger.test.ts
Exported SLASH_TRIGGER and matchSlashTrigger to detect /name with Unicode-aware boundaries and tests for positive/negative cases and last-run matching.
Editor serialization and DOM
packages/app/src/components/prompt-input/editor-serialize.ts, packages/app/src/components/prompt-input/editor-serialize.test.ts, packages/app/src/components/prompt-input/editor-dom.ts, packages/app/src/components/prompt-input/command-copy.ts, packages/app/src/components/prompt-input/command-pill.css
createPill renders data-type="skill" pills, parser pushes skill parts with canonical /name content and spans, editor treats skill pills as normalized pills for caret/range/length, copy logic rewrites pills to /<name>, and CSS styling extended for skill pills.
Popover controllers & input insertion
packages/app/src/components/prompt-input/popover-controllers.ts, packages/app/src/components/prompt-input/slash-popover.tsx, packages/app/src/components/prompt-input/editor-input.ts
Add slashMidText and slashOnInput(triggeredMidText?), filter to inline-eligible simple skills mid-text, insert skill parts via addPart with proper token replacement using matchSlashTrigger, and avoid opening picker inside leading command args.
Request building and submission routing
packages/app/src/components/prompt-input/build-request-parts.ts, packages/app/src/components/prompt-input/build-request-parts.test.ts, packages/app/src/components/prompt-input/submit.ts
Map skill attachments to type: "skill" request parts (with source spans), produce optimistic skill parts, insert skills into request order before images, compute hasSkillPart to gate legacy slash-command dispatch, and test optimistic/persisted parity.
Prompt restore, history, preview
packages/app/src/utils/prompt.ts, packages/app/src/components/prompt-input/history.ts, packages/app/src/pages/session/composer/*, packages/app/src/pages/session/use-session-followups.ts, packages/app/src/pages/session/session-action-readiness.ts, packages/app/src/pages/session/session-action-readiness.test.ts
extractPromptFromParts restores persisted skill parts as inline SkillAttachmentParts with spans, clone/equality handle skills, preview/followup render /${name}, and readiness gating bypasses command hydration when hasSkillPart.
Backend resolution and template expansion
packages/opencode/src/session/prompt.ts, packages/opencode/test/session/prompt.test.ts
Adds expandCommandTemplate, resolves MessageV2.SkillPart by expanding command template into synthetic text part for the model while preserving the skill part, and accepts SkillPart in PromptInput validation; tests cover known/unknown skill behavior.
Message UI rendering and icon
packages/ui/src/components/command-icon.tsx, packages/ui/src/components/command-icon.test.ts, packages/ui/src/components/message-part/highlighted-text.tsx, packages/ui/src/components/message-part/user-message.tsx, packages/ui/src/components/message-part.css
Adds SKILL_GLYPH and registry entry, HighlightedText accepts skills spans and renders skill-prefixed CommandIcon with stripped / label, UserMessageDisplay passes inline skills to highlighter, and CSS adds skill highlight style.
End-to-end & snapshots
packages/app/e2e/prompt/prompt-slash-open.spec.ts, packages/app/e2e/snap/message-skill-inline.snap.ts
E2E test seeds a project skill, triggers mid-sentence slash picker, selects skill row, asserts inline skill chip insertion and popover close; snapshot test seeds messages with structured skill parts and captures bubble renders across scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

"A rabbit noses through slashes bright,
mid-sentence chips take gentle flight.
/summarize becomes a friendly pill,
injected, saved, and styled with skill.
🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature: opening the slash picker anywhere and inserting inline skill chips, which is the primary accomplishment of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all required template sections: Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, Screenshots, and a fully completed Checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/slash-anywhere-skills

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Astro-Han Astro-Han added the enhancement New feature or request label Jun 3, 2026
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@Astro-Han Astro-Han added app Application behavior and product flows ui Design system and user interface P2 Medium priority labels Jun 3, 2026
@github-actions github-actions Bot added the harness Model harness, prompts, tool descriptions, and session mechanics label Jun 3, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested priority: P2 (includes user-path files (packages/app/src/components/prompt-input/build-request-parts.test.ts, packages/app/src/components/prompt-input/build-request-parts.ts, packages/app/src/components/prompt-input/command-pill.css, packages/app/src/components/prompt-input/editor-dom.ts, packages/app/src/components/prompt-input/editor-input.ts, packages/app/src/components/prompt-input/editor-serialize.test.ts, packages/app/src/components/prompt-input/editor-serialize.ts, packages/app/src/components/prompt-input/history.ts, packages/app/src/components/prompt-input/popover-controllers.ts, packages/app/src/components/prompt-input/send-followup-draft.ts, packages/app/src/components/prompt-input/slash-popover.tsx, packages/app/src/components/prompt-input/slash-trigger.test.ts, packages/app/src/components/prompt-input/slash-trigger.ts, packages/app/src/components/prompt-input/submit.ts, packages/app/src/context/prompt-equality.ts, packages/app/src/context/prompt.tsx, packages/app/src/pages/session/composer/home-composer-region.tsx, packages/app/src/pages/session/composer/session-composer-region.tsx, packages/app/src/pages/session/session-action-readiness.test.ts, packages/app/src/pages/session/session-action-readiness.ts, packages/app/src/pages/session/use-session-followups.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

Astro-Han added 2 commits June 4, 2026 00:39
extractPromptFromParts now reconstructs a SkillAttachmentPart from a persisted SkillPart's source span, so fork/undo/revert of a message sent with an inline skill chip brings the chip back instead of a literal "/name" that no longer expands (and, at offset 0, would reroute to the legacy command endpoint). Addresses a /codex review P2.
When a leading marked command owns the turn (Path D / session.command, which carries only image parts), a "/" in its args no longer opens the skill picker — an inserted skill chip would be silently dropped on submit. The slash stays literal command-argument text. Addresses a /codex review P2.
@Astro-Han

Copy link
Copy Markdown
Owner Author

Follow-up: the slash picker's icon system (per-function semantic icons, brand accent reserved for the active/in-effect state, drop the source badge) is intentionally out of scope here to keep this PR on the mechanics + the inline chip. Tracked in #1164.

Astro-Han added 3 commits June 4, 2026 22:05
command-icon.tsx imported { icons } from icon.tsx just to read one
skill glyph, pulling the entire ~140KB registry module into the
command-icon → prompt-input bundle chain.  bun's named-export static
analysis chokes on that module on Windows, causing the unit-windows-app
CI job to fail with:

  SyntaxError: Export named 'icons' not found in module 'icon.tsx'

Inline the skill SVG content directly and add a sync assertion in
command-icon.test.ts (ui-only, not in the Windows matrix) that compares
the inlined copy against icon.tsx's source of truth.
The skill glyph (✦) already signals "this is a /command"; showing the
slash as well produces "✦ /screenshot" instead of "✦ screenshot".
Strip the leading "/" when the segment type is "skill" so the chip reads
as glyph + name only.
The skill glyph already signals "this is a slash command", so the
leading "/" in the pill label is redundant visual noise. Strip it from
the label span; pushSkill rebuilds the canonical "/<name>" token from
data-name so flattened text and source offsets are unchanged.

editor-dom already computes logical pill length as 1 + data-name.length
(not textContent), so caret math is unaffected.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/components/prompt-input/editor-input.ts`:
- Around line 286-289: The trigger matching uses beforeCursor =
rawText.substring(0, cursorPosition) which strips non-text chips, but
replaceStart/setRangeEdge operate on editor logical offsets, causing mismatched
coordinates; fix by performing trigger matching in the same coordinate space as
replacement: either derive beforeCursor from the editor's logical text (not
rawText) or map the match indices back to editor offsets before computing
replaceStart; update usages around beforeCursor, atMatch, matchSlashTrigger,
replaceStart and setRangeEdge so the match start index is converted to the
editor's logical offset consistently.

In `@packages/app/src/components/prompt-input/history.ts`:
- Line 46: isPromptEqual currently doesn't handle parts with type "skill", so
distinct skill-chip prompts can be treated as equal and dropped by
prependHistoryEntry; update isPromptEqual to explicitly handle when partA.type
=== "skill" (and partB.type === "skill") by comparing the skill's unique
identifier or stable property (e.g., skill.id or skillKey/name) rather than
falling through to the generic comparison, and ensure it returns false when
types differ; adjust any equality branches so skill parts are correctly
distinguished and prependHistoryEntry won't dedupe different skills.

In `@packages/app/src/pages/session/session-action-readiness.ts`:
- Around line 15-23: canSendFollowupDraft currently doesn't propagate the
hasSkillPart flag so queued follow-ups that begin with an inline skill token can
be incorrectly blocked by the commandsReady gate; update canSendFollowupDraft to
accept/inspect input.hasSkillPart and forward it when calling canSubmitPrompt
(or mirror the same early-return logic: if input.hasSkillPart return true) so
that follow-ups with inline skill parts bypass the command_ready/commandsReady
check like canSubmitPrompt does.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e544f55-6d5f-43cb-847a-1d6db99526db

📥 Commits

Reviewing files that changed from the base of the PR and between c985011 and a40aa69.

⛔ Files ignored due to path filters (2)
  • packages/sdk/js/src/v2/gen/sdk.gen.ts is excluded by !**/gen/**
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (33)
  • packages/app/e2e/prompt/prompt-slash-open.spec.ts
  • packages/app/e2e/snap/message-skill-inline.snap.ts
  • packages/app/src/components/prompt-input/build-request-parts.test.ts
  • packages/app/src/components/prompt-input/build-request-parts.ts
  • packages/app/src/components/prompt-input/command-pill.css
  • packages/app/src/components/prompt-input/editor-dom.ts
  • packages/app/src/components/prompt-input/editor-input.ts
  • packages/app/src/components/prompt-input/editor-serialize.test.ts
  • packages/app/src/components/prompt-input/editor-serialize.ts
  • packages/app/src/components/prompt-input/history.ts
  • packages/app/src/components/prompt-input/popover-controllers.ts
  • packages/app/src/components/prompt-input/send-followup-draft.ts
  • packages/app/src/components/prompt-input/slash-popover.tsx
  • packages/app/src/components/prompt-input/slash-trigger.test.ts
  • packages/app/src/components/prompt-input/slash-trigger.ts
  • packages/app/src/components/prompt-input/submit.ts
  • packages/app/src/context/prompt-equality.ts
  • packages/app/src/context/prompt.tsx
  • packages/app/src/pages/session/composer/home-composer-region.tsx
  • packages/app/src/pages/session/composer/session-composer-region.tsx
  • packages/app/src/pages/session/session-action-readiness.test.ts
  • packages/app/src/pages/session/session-action-readiness.ts
  • packages/app/src/pages/session/use-session-followups.ts
  • packages/app/src/utils/prompt.test.ts
  • packages/app/src/utils/prompt.ts
  • packages/opencode/src/session/message-v2.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/session/prompt.test.ts
  • packages/ui/src/components/command-icon.test.ts
  • packages/ui/src/components/command-icon.tsx
  • packages/ui/src/components/message-part.css
  • packages/ui/src/components/message-part/highlighted-text.tsx
  • packages/ui/src/components/message-part/user-message.tsx

Comment thread packages/app/src/components/prompt-input/editor-input.ts
Comment thread packages/app/src/components/prompt-input/history.ts
Comment thread packages/app/src/pages/session/session-action-readiness.ts
Astro-Han added 3 commits June 4, 2026 23:01
Simple custom commands inserted mid-text were routed as SkillPart,
bypassing the command() pipeline's resolvePromptParts (which resolves
@file/@agent in templates), plugin hooks (command.execute.before), and
Command.Event.Executed. Restrict the inline chip path to source ===
"skill" entries. Custom/MCP commands keep the leading /name path where
the full command pipeline applies.
The skill chip now renders glyph + bare name (no leading "/") in both
the composer pill and the sent bubble. Update comments in
editor-serialize, message-part.css, highlighted-text.tsx, and the
message-skill-inline snap description to match the current behavior.
Three gaps after stripping the leading "/" from the pill label:

1. getNodeLength/getTextLength only checked data-cmd-mark, not
   data-type="skill". Skill chips reported textContent length (bare
   name) while the prompt model used "/name" length, causing cursor
   offsets to drift by one after any skill chip.

2. The copy handler (command-copy.ts) only intercepted data-cmd-mark
   pills. Copying a selection with a skill chip produced bare "name"
   on the clipboard instead of "/name", losing the slash on paste.

3. The e2e assertion in prompt-slash-open.spec.ts still expected
   "/summarize" in the chip's visible text.

Fix all three: extend the pill detection in editor-dom and
command-copy to include [data-type="skill"], update the e2e assertion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/e2e/prompt/prompt-slash-open.spec.ts`:
- Line 117: The assertion uses a substring check (await
expect(chip).toContainText("summarize")) which allows a leading slash; update
the test to assert the chip's exact text equals "summarize" so "/summarize"
fails — locate the test in prompt-slash-open.spec.ts where the variable chip is
asserted and replace the substring matcher with an exact-text matcher (e.g., use
an equality/exact-text assertion on chip's text content or
toHaveText("summarize") if available) to enforce the no-slash contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d55129bd-44de-49f4-9f7e-53a125b6e76e

📥 Commits

Reviewing files that changed from the base of the PR and between a40aa69 and 32796c8.

📒 Files selected for processing (9)
  • packages/app/e2e/prompt/prompt-slash-open.spec.ts
  • packages/app/e2e/snap/message-skill-inline.snap.ts
  • packages/app/src/components/prompt-input/command-copy.ts
  • packages/app/src/components/prompt-input/editor-dom.ts
  • packages/app/src/components/prompt-input/editor-serialize.test.ts
  • packages/app/src/components/prompt-input/editor-serialize.ts
  • packages/app/src/components/prompt-input/popover-controllers.ts
  • packages/ui/src/components/message-part.css
  • packages/ui/src/components/message-part/highlighted-text.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ui/src/components/message-part/highlighted-text.tsx
  • packages/app/e2e/snap/message-skill-inline.snap.ts
  • packages/app/src/components/prompt-input/popover-controllers.ts

Comment thread packages/app/e2e/prompt/prompt-slash-open.spec.ts Outdated
The server-side resolvePart accepted any command name delivered as a
SkillPart, including config-based commands and MCP tools. A crafted SDK
call could bypass the full command pipeline (hooks, resolvePromptParts,
events) by wrapping an arbitrary command name in a SkillPart.

Add a source check: only commands with source === "skill" are expanded
inline. Non-skill entries keep the chip (for bubble rendering) but
inject nothing, matching the unknown-skill fallback.

Test: seed a real SKILL.md via skills.paths instead of a config command,
add an explicit non-skill rejection case. Tighten the e2e assertion to
check the label sub-element with toHaveText rather than the whole chip
with toContainText.
@Astro-Han Astro-Han merged commit caab3eb into dev Jun 4, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant