feat(ui): collapse slash command into inline mark#768
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (15)
📝 WalkthroughWalkthroughThis PR introduces command invocation rendering to display slash-command templates as compact inline chips. It derives command metadata from message parts, preserves metadata through synthetic attachments, updates session title generation, restores prompts to command text, refactors user message rendering to show command chips, and adds comprehensive tests and e2e coverage. ChangesCommand Invocation & Rendering
Sequence DiagramsequenceDiagram
participant User as User Message
participant Derive as deriveCommandInvocation
participant Filter as Suppress Parts
participant Render as Render UI
participant Copy as Copy Action
User->>Derive: message parts
Derive-->>User: CommandInvocation object
User->>Filter: check suppression lists
Filter-->>User: filtered text/files
User->>Render: invocation exists?
alt Command Path
Render->>Render: render command chip
Render->>Render: mark + label + args
else Text Path
Render->>Render: render highlighted text
Render->>Render: with attachments
end
User->>Copy: user clicks copy
alt Using Command
Copy->>Copy: copy invocation.copyText
else Using Text
Copy->>Copy: copy derived text
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes The PR spans multiple layers (core command derivation, metadata utilities, session integration, prompt extraction, and UI rendering) with mixed complexity. The Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Suggested priority: P2 (includes user-path files (packages/app/src/components/dialog-fork.tsx, packages/app/src/utils/prompt.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.
Perf delta summaryComparator: pass
|
There was a problem hiding this comment.
Code Review
This pull request introduces a command invocation system, allowing for structured command representation within messages. It adds utilities to derive command metadata from message parts, updates the UI to render these commands with specific icons and labels, and ensures proper metadata inheritance and display logic. No review comments were provided for this pull request.
- Keep /<name> as a single nowrap token so long args don't fragment the command name across rows.
- Args use overflow-wrap: anywhere so long single-token args still wrap inside the bubble.
- Add e2e snap target (message-command-inline) covering no-args / with-args / long-args; injects commandInvocation metadata via session.prompt({ noReply: true }) to bypass the LLM round-trip.
V8 spec text wrote /<name> as the displayed label, but the visual reference docs/design/scratch/slash-command-final.html shows the icon visually replacing the slash — the bubble renders <icon> <name>, not <icon> /<name>. - displayLabel: name only (used by the bubble inline mark) - copyText / restoreText / forkPreviewText: keep /<name> (plain-text contexts where the slash carries re-trigger / identifier semantics)
- Move color: var(--brand-primary) from the label to the prefix flex container so both icon and label inherit it. Icon was rendering in fg-strong before because color: inherit walked past the label to the bubble text color. - Snap target uses deviceScaleFactor: 2 to match the real mac retina render. At DPR=1 the 16px icon's internal detail (scroll curl, three lines, sparkle tips) collapses into a solid blob; DPR=2 produces 32 real pixels and the figure reads as designed.
The hover action wrapper (`user-message-copy-wrapper`) is a flex row with `min-height: 30px`, not an absolute overlay — when a `/command` is invoked together with a pasted user attachment, the wrapper still occupies a 30px gap that becomes visible on hover, breaking the "inline mark first, attachments second" layout. Swap the render order in command mode so the mark sits directly above its attachments, matching the no-command branch where attachments render first.
ensureTitle previously read the first text part of the user message to decide whether to seed the title prompt with `Command: /<name> <args>`. That works when the template body is also the first part, but resolvePart can prepend synthetic read-text in front of an `@file` reference, and the command stamper writes `commandInvocation` onto the first text part of the *template* rather than the assembled message. The mismatch lets a file-leading or agent-leading template slip past the seed and feed the title model the expanded body instead. Extract the seed logic into `deriveCommandTitleSeed` and scan for the text part that actually carries `metadata.commandInvocation`, so the title prompt stays compact regardless of part ordering. Covered by five unit tests including a synthetic-read-text-prepended scenario.
PR description claimed `packages/app test prompt` covered command-mode restore, but the suite only had attachment and AgentPart cases. Add three cases that lock in the actual contract: - `/<cmd> <args>` restores alongside a markerless user attachment (the template-tagged file is suppressed, the user file is preserved). - Argless invocations restore as `/<cmd> ` with a trailing space so the editor caret lands ready for typing. - A `commandTemplate=true` file with no companion user attachment yields a text-only restore — the template file must not leak through.
Resolved SDK gen conflicts by regenerating from the merged OpenAPI source. SDK now exposes: - externalResult.list (from PR B) - RateLimit, command-inline, draft-isolation surfaces from dev Legacy question/blocker routes stay deleted (PR B's intent preserved). typecheck + bun test pass on app/opencode/core/ui/sdk.
## Summary Collapse `/<command> args` in the prompt input into the same inline pill (icon + brand-colored name) that PR #768 already renders in the sent bubble. Selecting from the slash popover, typing the command followed by Space, or pasting `/<known-name> args` into an empty input all materialise the leading `/<name>` into a `data-cmd-mark` element while the args remain plain text. ## Why Fixes #778. The sent bubble shows the command name as a brand-colored pill, but until now the same text in the prompt input rendered as raw `/<name>`. Two surfaces, two visual treatments for the same concept. This PR aligns the editor side to the send-side model: a single optional `TextPart.command` field carries the metadata, content stays `/<name> <args>` end-to-end, and `metadata.commandInvocation` on the wire is unchanged. ## Related Issue Fixes #778 ## Human Review Status Pending ## Review Focus The marked-TextPart invariants. There is at most one per Prompt, always at index 0, and `content` always equals `/<name> <args>` with a single trailing space after the name. Most of the new code is the helpers (`command-text-part.ts`, `command-prepend.ts`, `command-space-trigger.ts`, `command-paste.ts`, `command-backspace.ts`) that enforce these invariants on the way into the store, plus the editor-side primitives (`editor-dom.ts`, `editor-serialize.ts`) that treat the pill as a unit of logical length `1 + name.length`. The contenteditable round-trip is the most fragile surface and has the largest test coverage. Other things worth double-checking: - ASCII-only case-insensitive matching in `command-text-part.ts:16-45`. Non-ASCII names fall back to exact byte match so caret math stays length-preserving. - Path B trigger uses `inputType === "insertText" && data === " "`, which is naturally false on paste, Backspace, IME commit, and programmatic mutations. No flag state machine. - Backspace ladder in `command-backspace.ts`: caret inside the prefix region of a marked TextPart degrades the pill back to plain text without dropping attachments. ## Risk Notes - Path A (popover-select) and Path B (Space-trigger) preserve image attachments by passing `imageAttachments()` through to the new prompt, so the existing image flow is unchanged. Same for `prompt.context.items()`. - E1 mid-prompt variant: when the popover is opened by the global trigger while the editor already has unrelated content, the marked TextPart is prepended and original parts keep both order and reference identity. No clone of incoming `FileAttachmentPart` / `AgentPart` / `ImagePart`. - Legacy fallback boundary: the existing `text.split(" ")[0] === "/<known-name>"` codepath in `submit.ts` is kept as a safety net; the new path only fires when `part.command` metadata is present. - Deferred follow-ups, none of which affect this PR's user-facing acceptance: - Homepage owner-mirror in Path A and Path C (Codex P2, narrow widening of an existing gap on Path A; new but narrow on Path C). - E2E snap target for the pill rendering. The unit tests at `editor-dom.test.ts` and `editor-serialize.test.ts` cover the DOM contract; a visual diff for the pill itself can be added in a small follow-up. - `dev:desktop` walkthrough of the IME + paste + Backspace paths on macOS/Windows. The unit and integration coverage exercises the same code paths. ## How To Verify ```text bun --cwd packages/app test → 1405 pass, 0 fail bun --cwd packages/app run typecheck → 0 errors gh pr diff (visual) → 17 commits, 1 P0 + 2 P1 from crosscheck addressed manual: open slash popover, select brainstorming → pill renders, caret lands after the trailing space manual: type `/brainstorming ` (with Space) → buffer materialises into pill, popover closes manual: Backspace just after pill → pill collapses back to `/brainstorming ` plain text, attachments survive manual: paste `/brainstorming hello` into empty input → pill + " hello" args ``` The 4 pre-existing icon-button test failures on `dev` (CSS 30x30 vs test expectation 24x24) are unrelated to this PR. ## Screenshots or Recordings (To be attached by reviewer; pill visuals match PR #768 sent-bubble exactly, sharing `--brand-primary` and `--font-weight-body` via `command-pill.css`.) ## Checklist - [ ] **Type label** — this PR carries exactly one of `bug`, `enhancement`, `task`, `documentation`. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this. - [ ] **Routing labels** — this PR carries at least one of `app`, `ui`, `platform`, `harness`, `ci`. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this. - [ ] **Priority label** — this PR carries exactly one of `P0`, `P1`, `P2`, `P3`. The priority-triage bot suggests one on PR open. Confirm or override, then tick this. - [x] 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). - [x] I linked the related issue, or stated in Summary why there is no issue. - [x] I described the review focus and any meaningful risks. - [x] I replaced the example block in How To Verify with the real verification steps and the key result for each. - [x] 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. Leave unticked only if no visible UI or copy changed. - [ ] **(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. Leave unticked only if none of those surfaces was touched. - [x] I reviewed the final diff for unrelated changes and suspicious dependency changes. - [x] I am targeting `dev`, and my PR title and commit messages use Conventional Commits in English. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added visual command pills that display slash commands in the prompt editor with branded styling and icons. * Implemented copy/paste support for slash commands with proper clipboard handling and text rewriting. * Added keyboard interactions for command editing, including backspace handling to remove or modify command content. * Command icons now display with appropriate visual resolution and styling. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/Astro-Han/pawwork/pull/785?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
Replace the thousands-of-words skill methodology that floods a chat bubble after a slash trigger (e.g.
/brainstorming xxx) with a single inline mark: 16 px filled icon (visually replacing the slash) + brand-coloured command name + the user'sargsrendered as regular text. The bubble shell, copy / revert affordances, and user-pasted attachments are preserved. What the model receives is unchanged — only the visible layer changes.Why
Today every
/<cmd>user message pastes the fulltemplate(multi-thousand-word markdown) into the chat feed. Mainstream agent apps (Codex desktop, Claude Code CLI, Cursor's pinned skills) all fold these to a compact representation; PawWork was on the old path.Spec (V8, locked after 7 external review passes):
docs/superpowers/specs/2026-05-19-slash-command-card-design.md.Implementation plan:
docs/superpowers/specs/2026-05-19-slash-command-card-plan.md.Visual reference (light + dark, with / without args):
docs/design/scratch/slash-command-final.html.Related Issue
Related: #762
Human Review Status
Pending
Review Focus
executeCommand(packages/opencode/src/session/prompt.ts) and marker propagation throughresolvePart's two FilePart-deriving sites via the newinheritMetadatahelper.Command: /<name> <args>, even when the template begins with an@fileor agent reference (resolvePart can shuffle parts before the title seed runs).@opencode-ai/ui/lib/command-invocationand the bad-data degradation rule: missing / empty / non-stringsource/icon/argsmust never make the helper return null — only missing / empty / non-stringnamedoes.user-message.tsxrender order: command-mode renders inline mark, then attachments, then the hover meta / action row (the row hasmin-height: 30pxand is not absolutely positioned, so it would otherwise leave a visible gap between the mark and the attachment when revealed on hover).extractPromptFromPartscommand-mode branch preserves user-pasted attachments on Undo / Fork and drops the template-tagged file.Risk Notes
MessageV2.Part/Command.Infoschema changes; metadata lives in the free-formmetadatarecord that already exists onTextPartandFilePart.metadatafall back to the original text rendering — graceful degradation.How To Verify
Screenshots or Recordings
Snap output committed to
docs/design/preview/screenshots/message-command-inline.png— three scenarios in one grid: no-args, with-args, long-args. Matchesdocs/design/scratch/slash-command-final.html1:1 (icon replaces slash, brand-primary on the prefix, args inherit--fg-strong).Checklist
bug,enhancement,task,documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.app,ui,platform,harness,ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.P0,P1,P2,P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.Pending,Approved by @<reviewer>, orNot required: <reason>(default isPending; "not required" is restricted to bot-authored low-risk PRs).dev, and my PR title and commit messages use Conventional Commits in English.Summary by CodeRabbit
New Features
Improvements
Tests