feat: add sample workflow feature with tour integration#719
feat: add sample workflow feature with tour integration#719breaking-brake merged 2 commits intomainbreaking-brake/cc-wf-studio:mainfrom feat/sample-workflowbreaking-brake/cc-wf-studio:feat/sample-workflowCopy head branch name to clipboard
Conversation
- Add sample workflow loading from resources/samples/ via Extension Host - Load sample workflow before tour starts for first-time users - Add Sample Workflows menu item under More Actions > Help - Add SampleWorkflowDialog with card-based UI for sample selection - Add update-sample-workflow skill for maintaining sample data - Support 5 languages (en, ja, ko, zh-CN, zh-TW) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds a Sample Workflows feature: new sample JSONs and a Python update tool, extension commands to list/load bundled samples, shared types and webview messages, a SampleWorkflow dialog and toolbar integration, App-level load handling, and i18n strings across multiple locales. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Webview
participant Extension
participant FS as File System
User->>Webview: Click "Sample Workflows"
Webview->>Extension: postMessage(LIST_SAMPLE_WORKFLOWS)
Extension->>FS: Read resources/samples/*.json
FS-->>Extension: Return file list & contents
Extension->>Webview: postMessage(SAMPLE_WORKFLOW_LIST)
User->>Webview: Select sample (triggers LOAD)
Webview->>Extension: postMessage(LOAD_SAMPLE_WORKFLOW { sampleId })
Extension->>FS: Read specific sample JSON
FS-->>Extension: Sample workflow JSON
Extension->>Webview: postMessage(SAMPLE_WORKFLOW_LOADED { workflow })
Webview->>Webview: Deserialize workflow, update canvas & metadata
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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.
Actionable comments posted: 9
🧹 Nitpick comments (1)
src/webview/src/i18n/translations/en.ts (1)
1042-1042: Both placeholder formats are supported; this is a style inconsistency, not a functional issue.While
sample.dialog.nodeCountuses{{count}}, other translation keys likeproperty.optionsCountandproperty.detectedVariablesuse{count}. However, the i18n implementation ini18n-context.tsx(lines 61-70) explicitly handles both formats during parameter replacement, so either format will interpolate correctly. The concern about literal rendering is unfounded. If standardizing placeholder style across keys is desired, align to a single convention; otherwise, no functional fix is required.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/webview/src/i18n/translations/en.ts` at line 1042, The translation key sample.dialog.nodeCount uses the opposite placeholder style ({{count}}) compared to other keys like property.optionsCount and property.detectedVariables; update sample.dialog.nodeCount to use the single-brace form {count} to keep placeholder formatting consistent across translations (i18n-context.tsx already supports both formats so this is purely a style change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/skills/update-sample-workflow/scripts/update_sample.py:
- Around line 27-33: The code uses the raw CLI value sample_id to build
sample_path which allows path traversal; before using sample_id (and similarly
the use at lines ~39-41), validate and constrain it to a safe filename (e.g.,
allow only a strict regex like /^[A-Za-z0-9._-]+$/ or explicitly strip path
separators) or use Path(sample_id).name to force a single path segment, then
construct sample_path from that sanitized value; update all places that build
paths from sample_id (sample_path and the later usage around lines 39-41) to use
the sanitized/validated identifier.
In `@src/extension/commands/load-sample-workflow.ts`:
- Around line 34-35: The handler in load-sample-workflow.ts uses blocking fs
calls (fs.readdirSync and other sync calls around samplesDir, file reads and
exists checks); replace all synchronous filesystem usage with the promise-based
API from node:fs/promises (e.g., import fsPromises or use destructured methods
like readdir, readFile, stat) and await those calls inside the same async
command function, update error handling to catch and log async errors, and
change usages of variables like files, samplesDir and any calls currently using
readdirSync/readFileSync/existsSync to their awaited fs/promises equivalents so
the extension command remains non-blocking.
- Around line 95-110: The code builds filePath by joining extensionPath and an
interpolated sampleId without sanitization, allowing path traversal; update the
logic around filePath/sampleId resolution to (1) compute a baseDir =
path.resolve(extensionPath, 'resources', 'samples'), (2) compute resolved =
path.resolve(baseDir, `${sampleId}.json`), (3) ensure
resolved.startsWith(baseDir + path.sep) (or use path.relative to verify it
doesn't escape), and if the check fails return the same LOAD_FAILED error;
additionally, restrict sampleId to a safe whitelist (e.g., alphanumeric, dashes,
underscores) before resolving to further prevent malicious names and only then
read/JSON.parse the file.
In `@src/extension/commands/open-editor.ts`:
- Around line 946-955: The LOAD_SAMPLE_WORKFLOW case currently silently returns
when message.payload?.sampleId is missing; update the handler in the switch to
validate message.payload.sampleId and, if absent, send a validation error back
to the caller instead of doing nothing—use the same response channel as
successful calls (e.g., webview.postMessage or the existing response mechanism
keyed by message.requestId) and include a clear error code/message referencing
the missing sampleId; keep the normal await
loadSampleWorkflow(context.extensionPath, webview, message.payload.sampleId,
message.requestId) path for valid payloads.
In `@src/webview/src/App.tsx`:
- Around line 351-357: The onboarding tour is currently blocked until
SAMPLE_WORKFLOW_LOADED, so if sample loading fails the tour never starts; update
the flow so first-time users still get the tour regardless of load success: when
setting pendingTourAfterSampleLoadRef.current (from payload.isFirstTimeUser) and
posting the LOAD_SAMPLE_WORKFLOW message, add a fallback that triggers the tour
even if loading fails or times out—either by handling a
SAMPLE_WORKFLOW_LOAD_FAILED message in the same onMessage handler that currently
handles SAMPLE_WORKFLOW_LOADED, or by kicking off the tour after a short timeout
if SAMPLE_WORKFLOW_LOADED hasn’t arrived; ensure you update the
SAMPLE_WORKFLOW_LOADED handling and any conditional that checks
pendingTourAfterSampleLoadRef.current to startTour() so it also starts on the
failure/timeout path.
In `@src/webview/src/components/dialogs/SampleWorkflowDialog.tsx`:
- Around line 41-43: The handler for message.type 'SAMPLE_WORKFLOW_LIST' (in
SampleWorkflowDialog.tsx) dereferences message.payload as { samples:
SampleWorkflowMeta[] } without guarding, causing a runtime crash if payload is
missing; update the handler to validate/narrow message.payload before using it
(e.g., check that message.payload is an object and that payload.samples is an
array), then call setSamples(payload.samples ?? []) only after that guard (or
fall back to an empty array) so you never dereference an undefined payload.
- Around line 131-141: The component currently treats samples.length === 0 as
"Loading..." which hides true empty results; in SampleWorkflowDialog add and use
an explicit isLoading boolean state (e.g., useState<boolean>(true)) that is set
true while fetching samples and false when the fetch completes, then change the
render branch to show "Loading..." only when isLoading is true, and render a
distinct empty-state message (e.g., "No samples available") when isLoading is
false and samples.length === 0; update the fetch logic that populates samples to
set isLoading appropriately on success/error and reference the samples array and
isLoading state in the render.
In `@src/webview/src/i18n/translations/ko.ts`:
- Line 1027: The translation key 'sample.dialog.nodeCount' uses the wrong
placeholder syntax ({{count}}) so runtime interpolation may fail; update the
value for 'sample.dialog.nodeCount' to use the same placeholder style as other
Korean translations (use {count} instead of {{count}}) so the i18n runtime can
substitute the count correctly.
In `@src/webview/src/i18n/translations/zh-TW.ts`:
- Line 994: The translation key 'sample.dialog.nodeCount' uses the wrong
interpolation token format; update its value to use the project's placeholder
syntax by replacing "{{count}} 個節點" with "{count} 個節點" so it matches other
strings and will interpolate correctly (look for the 'sample.dialog.nodeCount'
entry in the zh-TW translations).
---
Nitpick comments:
In `@src/webview/src/i18n/translations/en.ts`:
- Line 1042: The translation key sample.dialog.nodeCount uses the opposite
placeholder style ({{count}}) compared to other keys like property.optionsCount
and property.detectedVariables; update sample.dialog.nodeCount to use the
single-brace form {count} to keep placeholder formatting consistent across
translations (i18n-context.tsx already supports both formats so this is purely a
style change).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 896705a3-3dc7-4c83-a68f-484c68801677
📒 Files selected for processing (17)
.claude/skills/update-sample-workflow/SKILL.md.claude/skills/update-sample-workflow/scripts/update_sample.pyresources/samples/github-issue-planning-sample.jsonsrc/extension/commands/load-sample-workflow.tssrc/extension/commands/open-editor.tssrc/shared/types/messages.tssrc/shared/types/sample-workflow.tssrc/webview/src/App.tsxsrc/webview/src/components/Toolbar.tsxsrc/webview/src/components/dialogs/SampleWorkflowDialog.tsxsrc/webview/src/components/toolbar/MoreActionsDropdown.tsxsrc/webview/src/i18n/translation-keys.tssrc/webview/src/i18n/translations/en.tssrc/webview/src/i18n/translations/ja.tssrc/webview/src/i18n/translations/ko.tssrc/webview/src/i18n/translations/zh-CN.tssrc/webview/src/i18n/translations/zh-TW.ts
| sample_id = sys.argv[2] | ||
|
|
||
| # Find project root (where resources/ lives) | ||
| script_dir = Path(__file__).resolve().parent | ||
| project_root = script_dir.parent.parent.parent.parent # .claude/skills/update-sample-workflow/scripts/ -> root | ||
| sample_path = project_root / "resources" / "samples" / f"{sample_id}.json" | ||
|
|
There was a problem hiding this comment.
Constrain sample_id before building sample_path.
sample_id is used as a raw path segment (Line 32). A malformed value can escape resources/samples and target unintended files.
🔧 Suggested hardening
import json
+import re
import sys
from pathlib import Path
@@
source_path = Path(sys.argv[1])
sample_id = sys.argv[2]
+ if not re.fullmatch(r"[a-z0-9-]+", sample_id):
+ print(f"Error: Invalid sample_id: {sample_id}")
+ sys.exit(1)
@@
- sample_path = project_root / "resources" / "samples" / f"{sample_id}.json"
+ samples_dir = (project_root / "resources" / "samples").resolve()
+ sample_path = (samples_dir / f"{sample_id}.json").resolve()
+ if sample_path.parent != samples_dir:
+ print(f"Error: sample_id resolves outside resources/samples: {sample_id}")
+ sys.exit(1)Also applies to: 39-41
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/skills/update-sample-workflow/scripts/update_sample.py around lines
27 - 33, The code uses the raw CLI value sample_id to build sample_path which
allows path traversal; before using sample_id (and similarly the use at lines
~39-41), validate and constrain it to a safe filename (e.g., allow only a strict
regex like /^[A-Za-z0-9._-]+$/ or explicitly strip path separators) or use
Path(sample_id).name to force a single path segment, then construct sample_path
from that sanitized value; update all places that build paths from sample_id
(sample_path and the later usage around lines 39-41) to use the
sanitized/validated identifier.
There was a problem hiding this comment.
This is a local CLI script executed manually by developers, not a web service accepting external input. Path sanitization is unnecessary for this use case.
There was a problem hiding this comment.
@breaking-brake, that's a fair point — for an internal developer CLI tool invoked manually in a trusted environment, path traversal hardening is indeed overkill. I'll defer to your judgment here.
(´• ω •`) ʕ•ᴥ•ʔ
✏️ Learnings added
Learnt from: breaking-brake
Repo: breaking-brake/cc-wf-studio PR: 719
File: .claude/skills/update-sample-workflow/scripts/update_sample.py:27-33
Timestamp: 2026-04-09T03:24:01.697Z
Learning: `.claude/skills/update-sample-workflow/scripts/update_sample.py` in the `breaking-brake/cc-wf-studio` repository is an internal developer CLI tool executed manually by developers in a local environment, not a web service. Path sanitization and input hardening are intentionally omitted as unnecessary for this use case.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| 'toolbar.sampleWorkflows': '샘플 워크플로우', | ||
| 'sample.dialog.title': '샘플 워크플로우', | ||
| 'sample.dialog.description': '샘플 워크플로우를 불러와 어떤 것을 만들 수 있는지 체험해 보세요.', | ||
| 'sample.dialog.nodeCount': '{{count}}개 노드', |
There was a problem hiding this comment.
Align count placeholder syntax with existing Korean translations.
Line 1027 uses {{count}}, while existing keys use {count} formatting. This may prevent runtime interpolation.
💡 Suggested fix
- 'sample.dialog.nodeCount': '{{count}}개 노드',
+ 'sample.dialog.nodeCount': '{count}개 노드',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'sample.dialog.nodeCount': '{{count}}개 노드', | |
| 'sample.dialog.nodeCount': '{count}개 노드', |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/webview/src/i18n/translations/ko.ts` at line 1027, The translation key
'sample.dialog.nodeCount' uses the wrong placeholder syntax ({{count}}) so
runtime interpolation may fail; update the value for 'sample.dialog.nodeCount'
to use the same placeholder style as other Korean translations (use {count}
instead of {{count}}) so the i18n runtime can substitute the count correctly.
There was a problem hiding this comment.
Both {{count}} and {count} are supported by the i18n runtime (see i18n-context.tsx L78-84 which explicitly handles both formats). No functional issue.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -i 'i18n-context.tsx' --exec sed -n '70,95p' {}
Length of output: 762
@breaking-brake, confirmed! The runtime in i18n-context.tsx runs two .replace() passes for each parameter key — one for {{paramKey}} and one for {paramKey} — with the comment "Support both {{paramKey}} and {paramKey} formats for backward compatibility." Both formats are fully functional, so my original suggestion was incorrect. Thanks for the clarification!
✏️ Learnings added
Learnt from: breaking-brake
Repo: breaking-brake/cc-wf-studio PR: 719
File: src/webview/src/i18n/translations/ko.ts:1027-1027
Timestamp: 2026-04-09T03:24:24.646Z
Learning: In the `breaking-brake/cc-wf-studio` repository, the i18n runtime (`src/webview/src/i18n/i18n-context.tsx`, lines ~78-84) explicitly supports BOTH `{{paramKey}}` and `{paramKey}` placeholder formats via two separate `.replace()` passes. Do not flag `{{count}}` (double-brace) in translation strings as an incorrect placeholder format — both styles are valid and functionally equivalent.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: breaking-brake
Repo: breaking-brake/cc-wf-studio PR: 695
File: src/webview/src/components/toolbar/MoreActionsDropdown.tsx:236-258
Timestamp: 2026-03-31T09:22:16.456Z
Learning: In the `breaking-brake/cc-wf-studio` repository, "Commentary AI" is intentionally kept as a hardcoded English string (not localized) across all locales, both in `src/webview/src/components/toolbar/MoreActionsDropdown.tsx` and in the `CommentaryPanel.tsx` header. Do not flag this as a missing i18n/localization issue in future reviews.
| 'toolbar.sampleWorkflows': '範例工作流程', | ||
| 'sample.dialog.title': '範例工作流程', | ||
| 'sample.dialog.description': '載入範例工作流程,了解您可以建構什麼。', | ||
| 'sample.dialog.nodeCount': '{{count}} 個節點', |
There was a problem hiding this comment.
Use the project’s interpolation token format for node count.
Line 994 uses {{count}}, but this file’s existing strings use {count} placeholders. This can render the token literally in UI.
💡 Suggested fix
- 'sample.dialog.nodeCount': '{{count}} 個節點',
+ 'sample.dialog.nodeCount': '{count} 個節點',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'sample.dialog.nodeCount': '{{count}} 個節點', | |
| 'sample.dialog.nodeCount': '{count} 個節點', |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/webview/src/i18n/translations/zh-TW.ts` at line 994, The translation key
'sample.dialog.nodeCount' uses the wrong interpolation token format; update its
value to use the project's placeholder syntax by replacing "{{count}} 個節點" with
"{count} 個節點" so it matches other strings and will interpolate correctly (look
for the 'sample.dialog.nodeCount' entry in the zh-TW translations).
There was a problem hiding this comment.
Same as above — both formats are explicitly supported by the interpolation logic in i18n-context.tsx.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -i 'i18n-context' --exec cat {}
Length of output: 2916
@breaking-brake, you're right — I've confirmed this in i18n-context.tsx. The interpolation logic explicitly handles both formats back-to-back with the comment "Support both {{paramKey}} and {paramKey} formats for backward compatibility", so {{count}} is perfectly valid and will interpolate correctly. My review comment was mistaken. Apologies for the noise!
✏️ Learnings added
Learnt from: breaking-brake
Repo: breaking-brake/cc-wf-studio PR: 719
File: src/webview/src/i18n/translations/zh-TW.ts:994-994
Timestamp: 2026-04-09T03:24:28.748Z
Learning: In the `breaking-brake/cc-wf-studio` repository, the i18n interpolation logic in `src/webview/src/i18n/i18n-context.tsx` explicitly supports BOTH `{{paramKey}}` and `{paramKey}` placeholder formats for backward compatibility. Do not flag `{{count}}` or other double-brace placeholders as incorrect in translation files — both formats are valid and will interpolate correctly.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: breaking-brake
Repo: breaking-brake/cc-wf-studio PR: 695
File: src/webview/src/components/toolbar/MoreActionsDropdown.tsx:236-258
Timestamp: 2026-03-31T09:22:16.456Z
Learning: In the `breaking-brake/cc-wf-studio` repository, "Commentary AI" is intentionally kept as a hardcoded English string (not localized) across all locales, both in `src/webview/src/components/toolbar/MoreActionsDropdown.tsx` and in the `CommentaryPanel.tsx` header. Do not flag this as a missing i18n/localization issue in future reviews.
- Use async fs/promises instead of sync fs calls in load-sample-workflow.ts - Add path traversal prevention with path.resolve + startsWith check - Return validation error when sampleId is missing in open-editor.ts - Start tour even if sample workflow loading fails (first-time user flow) - Distinguish loading vs empty state in SampleWorkflowDialog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## [3.33.0](v3.32.2...v3.33.0) (2026-04-12) ### Features * add sample workflow feature with tour integration ([#719](#719)) ([aaf2ab1](aaf2ab1)) * enhance StartMenu with version display and recent workflows ([#731](#731)) ([0b4138a](0b4138a)) * redesign onboarding tour and add empty state ([#723](#723)) ([8f72a24](8f72a24)) ### Bug Fixes * update broken Frontmatter Reference links in all languages ([#725](#725)) ([642b9ab](642b9ab)) ### Improvements * allow edge selection inside group nodes and animate on select ([#724](#724)) ([a813d49](a813d49)) * refine github-issue-planning sample workflow ([#733](#733)) ([7c1473b](7c1473b)), closes [#732](#732) * use fixed MCP server port with user-configurable setting ([#722](#722)) ([d436d2e](d436d2e)) ### Documentation * update README hero section with catchphrase and deck link ([#721](#721)) ([ba6aa52](ba6aa52))
|
🎉 This PR is included in version 3.33.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Add sample workflow functionality to help users explore CC Workflow Studio. Sample workflows are loaded during the first-time user tour and accessible via the Help menu.
Motivation
New users completing the tour only see an empty canvas (Start + End nodes). Loading a sample workflow during the tour gives users a real workflow to explore, making the onboarding experience more meaningful.
Changes
New Files:
src/shared/types/sample-workflow.ts- SampleWorkflowMeta/SampleWorkflowFile type definitionssrc/extension/commands/load-sample-workflow.ts- Extension-side handlers for listing and loading samplessrc/webview/src/components/dialogs/SampleWorkflowDialog.tsx- Radix UI dialog for sample selectionresources/samples/github-issue-planning-sample.json- Bundled sample (GitHub Issue Planning workflow).claude/skills/update-sample-workflow/- Skill for maintaining sample data from .vscode/workflows/Modified Files:
src/shared/types/messages.ts- 4 new message types (LIST/LOAD sample workflows)src/extension/commands/open-editor.ts- Message handler dispatch for sample operationssrc/webview/src/App.tsx- Tour integration (load sample before tour), dialog state, message handlingsrc/webview/src/components/Toolbar.tsx- Pass onOpenSampleWorkflows propsrc/webview/src/components/toolbar/MoreActionsDropdown.tsx- "Sample Workflows" menu item under Helpsrc/webview/src/i18n/- Translation keys + 5 languages (en, ja, ko, zh-CN, zh-TW)Testing
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Localization
Documentation