Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .claude/skills/walkthrough/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: walkthrough
description: Guidelines and standard format for generating task completion walkthroughs upon finishing a task or user request.
---

# Walkthrough Skill

Generate a structured, clean, and organized **Walkthrough** summary when completing a user task or execution.

AionUi parses the walkthrough into an interactive **WalkthroughCard** with quick copy, section badges, collapsibles, and syntax-highlighted markdown.

## When to Use

Deliver a Walkthrough at the end of a non-trivial execution, such as:

- Implementing a new feature
- Refactoring or restructuring code
- Fixing complex bugs
- Setting up integrations, models, or configurations

## Format Specification

Wrap the walkthrough in `[WALKTHROUGH]...[/WALKTHROUGH]` tags:

```markdown
[WALKTHROUGH]

# Walkthrough: <Title of the Task or Feature>

<Brief overview / summary paragraph explaining what was achieved.>

## 1. Delivered / What Was Delivered (O que foi Entregue)

- Summary table or bullet points of files modified/created and components delivered.

## 2. How It Works (Como Funciona)

- Technical explanation, architecture, data flow, or mermaid diagram.

## 3. How to Use & Verify (Como Usar / Como Testar)

- Step-by-step instructions or commands for the user to run, test, and verify the changes.

## 4. Things to Watch Out For & Tips (O que Prestar Atenção & Dicas)

- Important caveats, warnings, edge cases, configuration requirements, or follow-ups.
[/WALKTHROUGH]
```

## Section Types Recognized by AionUi

The parser automatically categorizes headings into semantic sections with custom theme icons:

| Section Type | Common Headings (English, Portuguese, Chinese) | Icon |
| ---------------- | --------------------------------------------------------------------------------------------------------- | ----------- |
| **`delivered`** | "Delivered", "What was delivered", "Changes Made", "O que foi Entregue", "Alterações", "交付内容" | Green check |
| **`howItWorks`** | "How It Works", "Architecture", "Como Funciona", "Funcionamento", "Arquitetura", "实现原理" | Blue brain |
| **`usage`** | "How to Use", "Usage", "How to Test", "Testing", "Verification", "Como Usar", "Como Testar", "使用与验证" | Orange play |
| **`notes`** | "Notes", "Things to Watch Out For", "Caveats", "Tips", "O que Prestar Atenção", "Dicas", "注意事项" | Red warning |
| **`custom`** | Any other custom section title (e.g. "Next Steps", "PR Status") | Neutral doc |
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,6 @@ When opening a PR, fill in the PR body using [.github/pull_request_template.md](
| **i18n** | Internationalization workflow and standards | Adding or changing user-facing text, modifying `locales/` or `packages/desktop/src/common/config/i18n` |
| **testing** | Testing workflow and quality standards | Writing tests, changing runtime behavior, fixing bugs, or claiming behavior is verified |
| **bump-version** | Version bump workflow: update package.json, checks, branch, PR, tag release | Bumping version, `/bump-version` |
| **walkthrough** | Task completion walkthrough standards and format | Finishing a task or execution, delivering walkthroughs |

> Skills are located in `.claude/skills/` and contain project conventions that apply to **all** agents and contributors.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ import { stripSkillSuggest, hasSkillSuggest } from '@renderer/utils/chat/skillSu
import { isForkEnabled } from '@/common/chat/forkConversation';
import { useForkConversation } from '@/renderer/hooks/chat/useForkConversation';
import ForkBranchIcon from '@renderer/components/base/ForkBranchIcon';
import WalkthroughCard from './WalkthroughCard';
import {
parseWalkthrough,
stripWalkthrough,
hasWalkthrough,
cleanWalkthroughForClipboard,
} from './WalkthroughCard/walkthroughParser';

/**
* Format a timestamp for message display.
Expand Down Expand Up @@ -117,6 +124,9 @@ const MessageText: React.FC<{
if (hasSkillSuggest(content)) {
content = stripSkillSuggest(content);
}
if (hasWalkthrough(content)) {
content = stripWalkthrough(content);
}
return content;
}
return content;
Expand All @@ -125,6 +135,13 @@ const MessageText: React.FC<{
const { t } = useTranslation();
const [showCopyAlert, setShowCopyAlert] = useState(false);
const isUserMessage = message.position === 'right';

const walkthrough = useMemo(() => {
if (isUserMessage || !message.content.content || typeof message.content.content !== 'string') {
return null;
}
return parseWalkthrough(message.content.content);
}, [isUserMessage, message.content.content]);
// Delivered-but-not-yet-consumed marker for messages sent mid-turn to a
// supporting backend (claude/codex). The message already reached the
// server (it's rendered); this only answers "has the agent picked it up
Expand Down Expand Up @@ -184,7 +201,11 @@ const MessageText: React.FC<{
const fileList = files.length ? `Files:\n${files.map((path) => `- ${path}`).join('\n')}\n\n` : '';
// An AI turn split by tool calls / thinking stores several text messages;
// the row sits on the last one but must copy the whole reply.
const textToCopy = turnTexts?.length ? buildTurnClipboardText(turnTexts) : fileList + baseText;
const copyContent =
typeof message.content.content === 'string' && hasWalkthrough(message.content.content)
? cleanWalkthroughForClipboard(message.content.content)
: baseText;
const textToCopy = turnTexts?.length ? buildTurnClipboardText(turnTexts) : fileList + copyContent;
copyText(textToCopy)
.then(() => {
setShowCopyAlert(true);
Expand Down Expand Up @@ -306,45 +327,48 @@ const MessageText: React.FC<{
)}
</div>
)}
<div
className={classNames('min-w-0 [&>p:first-child]:mt-0px [&>p:last-child]:mb-0px', {
'bg-aou-2 p-6px md:p-8px': isUserMessage || cronMeta,
'bg-3 p-6px md:p-8px': isTeammateMessage,
'w-full': !(isUserMessage || cronMeta || isTeammateMessage),
})}
style={{
...(isUserMessage || cronMeta
? { borderRadius: '8px 0 8px 8px', color: 'var(--text-primary)' }
: isTeammateMessage
? {
borderRadius: '0 8px 8px 8px',
...(teammateColor ? { borderLeft: `3px solid ${teammateColor}` } : {}),
}
: undefined),
}}
>
{/* JSON 内容使用折叠组件 Use CollapsibleContent for JSON content */}
{shouldRenderPlainText ? (
<div className='whitespace-pre-wrap [overflow-wrap:anywhere]' data-testid='message-text-content'>
{renderedText}
</div>
) : json ? (
<CollapsibleContent maxHeight={200} defaultCollapsed={true}>
{Boolean(renderedText && renderedText.trim()) && (
<div
className={classNames('min-w-0 [&>p:first-child]:mt-0px [&>p:last-child]:mb-0px', {
'bg-aou-2 p-6px md:p-8px': isUserMessage || cronMeta,
'bg-3 p-6px md:p-8px': isTeammateMessage,
'w-full': !(isUserMessage || cronMeta || isTeammateMessage),
})}
style={{
...(isUserMessage || cronMeta
? { borderRadius: '8px 0 8px 8px', color: 'var(--text-primary)' }
: isTeammateMessage
? {
borderRadius: '0 8px 8px 8px',
...(teammateColor ? { borderLeft: `3px solid ${teammateColor}` } : {}),
}
: undefined),
}}
>
{/* JSON 内容使用折叠组件 Use CollapsibleContent for JSON content */}
{shouldRenderPlainText ? (
<div className='whitespace-pre-wrap [overflow-wrap:anywhere]' data-testid='message-text-content'>
{renderedText}
</div>
) : json ? (
<CollapsibleContent maxHeight={200} defaultCollapsed={true}>
<div data-testid='message-text-content'>
<MarkdownView
codeStyle={CODE_STYLE}
onLocalFileLink={handleLocalFileLink}
>{`\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``}</MarkdownView>
</div>
</CollapsibleContent>
) : (
<div data-testid='message-text-content'>
<MarkdownView
codeStyle={CODE_STYLE}
onLocalFileLink={handleLocalFileLink}
>{`\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``}</MarkdownView>
<MarkdownView codeStyle={CODE_STYLE} onLocalFileLink={handleLocalFileLink}>
{data}
</MarkdownView>
</div>
</CollapsibleContent>
) : (
<div data-testid='message-text-content'>
<MarkdownView codeStyle={CODE_STYLE} onLocalFileLink={handleLocalFileLink}>
{data}
</MarkdownView>
</div>
)}
</div>
)}
</div>
)}
{walkthrough && <WalkthroughCard walkthrough={walkthrough} onLocalFileLink={handleLocalFileLink} />}
{isPendingDelivery && (
<div className='text-12px text-t-secondary mt-4px select-none' data-testid='message-status-badge'>
{t('messages.delivery.pending', { defaultValue: 'Unread' })}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* @license
* Copyright 2026 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/

.card {
width: 100%;
margin-top: 10px;
margin-bottom: 8px;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--color-primary-6) 28%, var(--color-border-2));
border-radius: 12px;
background: var(--color-bg-2);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
}

.card:hover {
border-color: color-mix(in srgb, var(--color-primary-6) 45%, var(--color-border-2));
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
}

.header {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid transparent;
background: var(--color-fill-1);
cursor: pointer;
user-select: none;
transition: background-color 0.16s ease;
}

.headerExpanded {
border-bottom-color: var(--color-border-2);
}

.headerLeft {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 8px;
}

.iconBox {
display: flex;
width: 26px;
height: 26px;
flex: 0 0 auto;
align-items: center;
justify-content: center;
border-radius: 7px;
color: var(--color-primary-6);
background: var(--color-primary-light-1);
}

.title {
display: block;
min-width: 0;
flex: 1;
overflow: hidden;
color: var(--color-text-1);
font-size: 13px;
font-weight: 600;
line-height: 1.4;
text-overflow: ellipsis;
white-space: nowrap;
}

.badge {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
padding: 1px 7px;
border-radius: 999px;
color: var(--color-primary-6);
background: var(--color-primary-light-1);
font-size: 11px;
font-weight: 600;
line-height: 1.4;
}

.headerRight {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: 6px;
margin-inline-start: 12px;
}

.counterBadge {
color: var(--color-text-3);
font-size: 12px;
}

.body {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px 14px;
}

.summaryBox {
padding: 8px 12px;
border: 1px solid var(--color-border-2);
border-radius: 8px;
color: var(--color-text-2);
background: var(--color-fill-1);
font-size: 13px;
line-height: 1.5;
}

.section {
overflow: hidden;
border: 1px solid var(--color-border-2);
border-radius: 8px;
background: var(--color-bg-1);
transition: border-color 0.16s ease;
}

.section:hover {
border-color: color-mix(in srgb, var(--color-primary-6) 30%, var(--color-border-2));
}

.sectionHeader {
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: var(--color-fill-1);
cursor: pointer;
user-select: none;
transition: background-color 0.16s ease;
}

.sectionHeader:hover {
background: var(--color-fill-2);
}

.sectionHeaderLeft {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 8px;
}

.sectionTitle {
color: var(--color-text-1);
font-size: 12px;
font-weight: 600;
overflow-wrap: anywhere;
}

.sectionContent {
padding: 10px 12px;
font-size: 13px;
line-height: 1.6;
}

.actionButton:global(.arco-btn) {
width: 26px;
height: 26px;
padding: 0;
border-radius: 6px;
color: var(--color-text-2);
}

.actionButton:global(.arco-btn):hover {
color: var(--color-text-1);
background: var(--color-fill-2);
}
Loading
Loading