[4/4] feat(settings): add commit message model picker - #1230
[4/4] feat(settings): add commit message model picker#1230Rafael-Silva-Oliveira wants to merge 4 commits into
Conversation
Adds `getCommitContext()`, which gathers the changes a commit message should describe. Part 1 of 4 for AI commit-message generation; nothing consumes it yet. Staged changes are collected first, since that is what a commit will actually contain. When nothing is staged it falls back to the working tree so callers still have something to summarize before staging. The fallback reads `git status --short` rather than a diff because untracked files appear in no diff and would otherwise be invisible. The fallback deliberately runs `git diff` rather than `git diff HEAD`: the index is known to be empty at that point so the output is identical, but `HEAD` does not resolve in a repository without an initial commit, where it would fail. Reuses the existing `checkGitInstalled`, `checkGitRepo`, and `truncateOutput` helpers. `maxBuffer` is raised past Node's 1MB `exec` default, which real diffs routinely exceed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Part 2 of 4 for AI commit-message generation. Adds the model-facing half: a customizable prompt and a service that turns git context into a message. No command or UI wires it up yet. - Registers COMMIT_MESSAGE in `support-prompt.ts` alongside ENHANCE. Because the Prompts settings tab iterates the support-prompt registry, this gives the template an editor and a reset button without any new UI code. The default asks for Conventional Commits and tells the model to account for every changed file; without that instruction models describe the largest file and silently drop the rest. - Adds `commitMessageApiConfigId` so a small, fast profile can be used for this task. Mirrors `enhancementApiConfigId`, including the `listApiConfigMeta` guard before `getProfile()` so a deleted profile falls back to the active configuration rather than throwing. - Adds `generateCommitMessage()`, which resolves the repository from the git extension API, collects context, and writes the cleaned result into the SCM input box. Models wrap answers in fences and quotes despite instruction, so the response is stripped before use. Progress is reported at `ProgressLocation.Window`; `SourceControl` drops the title, and no location renders a cancel button that would work, since nearly every provider ignores `completePrompt`'s abort signal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Part 3 of 4 for AI commit-message generation. Wires the generator to a button
in the Source Control title bar, making the feature reachable for the first
time.
The command receives the clicked `SourceControl`, which lets the generator pick
the right repository in a multi-root workspace. It resolves the provider from
`registerCommands` rather than the visible instance, so the button works while
the Zoo Code sidebar is closed.
The icon is the existing zebra mark, reusing the `panel_light`/`panel_dark` pair
already shipped for the tab icon. That required widening `commandsSchema.icon`
in `packages/build`: it accepted only a codicon string, so a themed
{light, dark} pair would have thrown in `contributesSchema.parse()` during the
nightly manifest build. That path only runs under `vsix:nightly`, so the failure
would not have surfaced in normal development.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Part 4 of 4 for AI commit-message generation. Exposes `commitMessageApiConfigId` in Settings > Providers, so the profile used for commit messages can be chosen without editing configuration by hand. The picker sits directly below the provider options, next to the profile it selects. It binds to `cachedState` and saves with the Save button, following the SettingsView pattern rather than persisting immediately. A sibling label does not name a Radix trigger, so the label and trigger are linked with `aria-labelledby` and the relationship is covered by a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds AI-generated commit messages from Git changes through a Source Control command. The change includes Git context extraction, provider-profile selection, settings persistence, prompt definitions, command registration, tests, and localization. ChangesCommit message generation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
packages/types/src/global-settings.ts (1)
238-238: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAdd an explicit empty-string fallback test.
CommitMessageModelSelectpersists"", andupdateSettingsstores it unchanged. The service tests coverundefinedand an unknown ID, but not""; assert thatgenerateCommitMessageusesapiConfigurationand does not callgetProfilefor"".🤖 Prompt for 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. In `@packages/types/src/global-settings.ts` at line 238, Extend the service tests for generateCommitMessage to cover an empty commitMessageApiConfigId (""). Assert that apiConfiguration is used and getProfile is not called, alongside the existing undefined and unknown-ID cases.Source: Learnings
🤖 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 `@src/activate/__tests__/registerCommands.spec.ts`:
- Line 203: Update the test invocation of
handlers["zoo-code.generateCommitMessage"] to await the returned promise,
ensuring rejected asynchronous handlers fail the test instead of remaining
floating promises.
In `@src/services/commit-message/__tests__/generateCommitMessage.spec.ts`:
- Around line 44-53: Update the makeProvider test-double factory to avoid the
undocumented `as unknown as ClineProvider` assertion by using a properly typed
factory or an explicit, nearby explanation if the structural cast is required.
Preserve the existing mocked getState and providerSettingsManager behavior.
In `@src/services/commit-message/index.ts`:
- Around line 101-104: Handle getProfile() failure in the commit-message
generation flow around commitMessageApiConfigId so a deleted metadata-listed
profile falls back to apiConfiguration instead of propagating to the outer
generation-error handler. Update src/services/commit-message/index.ts lines
101-104 accordingly; in
src/services/commit-message/__tests__/generateCommitMessage.spec.ts lines
102-110, make getProfile() reject and assert generation uses apiConfiguration.
In `@src/utils/__tests__/git.spec.ts`:
- Around line 423-425: Expand the assertion in the diffCommands loop so the
regex rejects shell metacharacters including ;, &, |, backticks, $, <, and > in
addition to the existing quotes and parentheses. Keep the test focused on
validating every command produced by the diff command construction.
In `@webview-ui/src/i18n/locales/ca/settings.json`:
- Around line 360-361: Update the Catalan label and description in the settings
locale entry to use “commit” or “confirmació” for the Git operation instead of
“comissió,” while preserving the existing meaning about generating commit
messages and using the active profile when unselected.
In `@webview-ui/src/i18n/locales/es/prompts.json`:
- Around line 107-110: Update the COMMIT_MESSAGE localization entry to use the
Git term “commit” consistently: change the label to “Mensaje de commit” and
replace the corresponding “mensaje de confirmación” wording in the description,
preserving the rest of the Spanish text.
---
Nitpick comments:
In `@packages/types/src/global-settings.ts`:
- Line 238: Extend the service tests for generateCommitMessage to cover an empty
commitMessageApiConfigId (""). Assert that apiConfiguration is used and
getProfile is not called, alongside the existing undefined and unknown-ID cases.
🪄 Autofix
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 Plus
Run ID: 40e13bfb-c393-483b-88dd-341b862b9685
📒 Files selected for processing (89)
packages/build/src/types.tspackages/types/src/global-settings.tspackages/types/src/vscode-extension-host.tspackages/types/src/vscode.tssrc/activate/__tests__/registerCommands.spec.tssrc/activate/registerCommands.tssrc/core/webview/ClineProvider.tssrc/i18n/locales/ca/common.jsonsrc/i18n/locales/de/common.jsonsrc/i18n/locales/en/common.jsonsrc/i18n/locales/es/common.jsonsrc/i18n/locales/fr/common.jsonsrc/i18n/locales/hi/common.jsonsrc/i18n/locales/id/common.jsonsrc/i18n/locales/it/common.jsonsrc/i18n/locales/ja/common.jsonsrc/i18n/locales/ko/common.jsonsrc/i18n/locales/nl/common.jsonsrc/i18n/locales/pl/common.jsonsrc/i18n/locales/pt-BR/common.jsonsrc/i18n/locales/ru/common.jsonsrc/i18n/locales/tr/common.jsonsrc/i18n/locales/vi/common.jsonsrc/i18n/locales/zh-CN/common.jsonsrc/i18n/locales/zh-TW/common.jsonsrc/package.jsonsrc/package.nls.ca.jsonsrc/package.nls.de.jsonsrc/package.nls.es.jsonsrc/package.nls.fr.jsonsrc/package.nls.hi.jsonsrc/package.nls.id.jsonsrc/package.nls.it.jsonsrc/package.nls.ja.jsonsrc/package.nls.jsonsrc/package.nls.ko.jsonsrc/package.nls.nl.jsonsrc/package.nls.pl.jsonsrc/package.nls.pt-BR.jsonsrc/package.nls.ru.jsonsrc/package.nls.tr.jsonsrc/package.nls.vi.jsonsrc/package.nls.zh-CN.jsonsrc/package.nls.zh-TW.jsonsrc/services/commit-message/__tests__/generateCommitMessage.spec.tssrc/services/commit-message/index.tssrc/shared/support-prompt.tssrc/utils/__tests__/git.spec.tssrc/utils/git.tswebview-ui/src/components/settings/CommitMessageModelSelect.tsxwebview-ui/src/components/settings/SettingsView.tsxwebview-ui/src/components/settings/__tests__/CommitMessageModelSelect.spec.tsxwebview-ui/src/components/settings/__tests__/SettingsView.spec.tsxwebview-ui/src/i18n/locales/ca/prompts.jsonwebview-ui/src/i18n/locales/ca/settings.jsonwebview-ui/src/i18n/locales/de/prompts.jsonwebview-ui/src/i18n/locales/de/settings.jsonwebview-ui/src/i18n/locales/en/prompts.jsonwebview-ui/src/i18n/locales/en/settings.jsonwebview-ui/src/i18n/locales/es/prompts.jsonwebview-ui/src/i18n/locales/es/settings.jsonwebview-ui/src/i18n/locales/fr/prompts.jsonwebview-ui/src/i18n/locales/fr/settings.jsonwebview-ui/src/i18n/locales/hi/prompts.jsonwebview-ui/src/i18n/locales/hi/settings.jsonwebview-ui/src/i18n/locales/id/prompts.jsonwebview-ui/src/i18n/locales/id/settings.jsonwebview-ui/src/i18n/locales/it/prompts.jsonwebview-ui/src/i18n/locales/it/settings.jsonwebview-ui/src/i18n/locales/ja/prompts.jsonwebview-ui/src/i18n/locales/ja/settings.jsonwebview-ui/src/i18n/locales/ko/prompts.jsonwebview-ui/src/i18n/locales/ko/settings.jsonwebview-ui/src/i18n/locales/nl/prompts.jsonwebview-ui/src/i18n/locales/nl/settings.jsonwebview-ui/src/i18n/locales/pl/prompts.jsonwebview-ui/src/i18n/locales/pl/settings.jsonwebview-ui/src/i18n/locales/pt-BR/prompts.jsonwebview-ui/src/i18n/locales/pt-BR/settings.jsonwebview-ui/src/i18n/locales/ru/prompts.jsonwebview-ui/src/i18n/locales/ru/settings.jsonwebview-ui/src/i18n/locales/tr/prompts.jsonwebview-ui/src/i18n/locales/tr/settings.jsonwebview-ui/src/i18n/locales/vi/prompts.jsonwebview-ui/src/i18n/locales/vi/settings.jsonwebview-ui/src/i18n/locales/zh-CN/prompts.jsonwebview-ui/src/i18n/locales/zh-CN/settings.jsonwebview-ui/src/i18n/locales/zh-TW/prompts.jsonwebview-ui/src/i18n/locales/zh-TW/settings.json
| const { generateCommitMessage } = await import("../../services/commit-message") | ||
| const sourceControl = { rootUri: { fsPath: "/repo" } } | ||
|
|
||
| handlers["zoo-code.generateCommitMessage"](sourceControl) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Await the asynchronous command handler.
Line 203 leaves the promise returned by generateCommitMessage unobserved. Await the call so a rejected handler fails the test instead of becoming a floating promise.
Proposed test fix
- handlers["zoo-code.generateCommitMessage"](sourceControl)
+ await handlers["zoo-code.generateCommitMessage"](sourceControl)As per coding guidelines, “Avoid floating promises; use void, await, or .catch() as appropriate.”
📝 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.
| handlers["zoo-code.generateCommitMessage"](sourceControl) | |
| await handlers["zoo-code.generateCommitMessage"](sourceControl) |
🤖 Prompt for 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.
In `@src/activate/__tests__/registerCommands.spec.ts` at line 203, Update the test
invocation of handlers["zoo-code.generateCommitMessage"] to await the returned
promise, ensuring rejected asynchronous handlers fail the test instead of
remaining floating promises.
Source: Coding guidelines
| const makeProvider = (commitMessageApiConfigId?: string) => | ||
| ({ | ||
| getState: vi.fn().mockResolvedValue({ | ||
| apiConfiguration, | ||
| listApiConfigMeta, | ||
| customSupportPrompts: {}, | ||
| commitMessageApiConfigId, | ||
| }), | ||
| providerSettingsManager: { getProfile }, | ||
| }) as unknown as ClineProvider |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Replace the undocumented double assertion.
Line 53 uses as unknown as ClineProvider without a nearby reason. Use a typed test-double factory, or document why the structural cast is necessary.
🤖 Prompt for 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.
In `@src/services/commit-message/__tests__/generateCommitMessage.spec.ts` around
lines 44 - 53, Update the makeProvider test-double factory to avoid the
undocumented `as unknown as ClineProvider` assertion by using a properly typed
factory or an explicit, nearby explanation if the structural cast is required.
Preserve the existing mocked getState and providerSettingsManager behavior.
Source: Coding guidelines
| if (commitMessageApiConfigId && listApiConfigMeta?.find(({ id }) => id === commitMessageApiConfigId)) { | ||
| const { name: _, ...providerSettings } = await provider.providerSettingsManager.getProfile({ | ||
| id: commitMessageApiConfigId, | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Handle profile deletion after state retrieval.
A profile can be deleted after getState() returns metadata. getProfile() then throws, and the outer catch reports generation failure instead of falling back to apiConfiguration.
src/services/commit-message/index.ts#L101-L104: Catch agetProfile()failure and retainapiConfiguration.src/services/commit-message/__tests__/generateCommitMessage.spec.ts#L102-L110: MakegetProfile()reject for a metadata-listed profile. Assert that generation usesapiConfiguration.
📍 Affects 2 files
src/services/commit-message/index.ts#L101-L104(this comment)src/services/commit-message/__tests__/generateCommitMessage.spec.ts#L102-L110
🤖 Prompt for 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.
In `@src/services/commit-message/index.ts` around lines 101 - 104, Handle
getProfile() failure in the commit-message generation flow around
commitMessageApiConfigId so a deleted metadata-listed profile falls back to
apiConfiguration instead of propagating to the outer generation-error handler.
Update src/services/commit-message/index.ts lines 101-104 accordingly; in
src/services/commit-message/__tests__/generateCommitMessage.spec.ts lines
102-110, make getProfile() reject and assert generation uses apiConfiguration.
Sources: Coding guidelines, Learnings
| for (const command of diffCommands) { | ||
| expect(command).not.toMatch(/['"()]/) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Expand the shell-metacharacter assertion.
The current regex permits command separators and shell expansions such as ;, &, |, `, $, <, and >. A future change can reintroduce an unsafe diff argument while this test still passes.
Proposed fix
- expect(command).not.toMatch(/['"()]/)
+ expect(command).not.toMatch(/['"()|&;<>$`\\\r\n]/)📝 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.
| for (const command of diffCommands) { | |
| expect(command).not.toMatch(/['"()]/) | |
| } | |
| for (const command of diffCommands) { | |
| expect(command).not.toMatch(/['"()|&;<>$`\\\r\n]/) | |
| } |
🧰 Tools
🪛 ast-grep (0.45.1)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { ExecException } from "child_process"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🤖 Prompt for 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.
In `@src/utils/__tests__/git.spec.ts` around lines 423 - 425, Expand the assertion
in the diffCommands loop so the regex rejects shell metacharacters including ;,
&, |, backticks, $, <, and > in addition to the existing quotes and parentheses.
Keep the test focused on validating every command produced by the diff command
construction.
Source: Coding guidelines
| "label": "Model per als missatges de comissió", | ||
| "description": "Perfil utilitzat per generar missatges de comissió des del plafó de control de codi font. Normalment n'hi ha prou amb un model petit i ràpid. Deixa-ho sense seleccionar per fer servir el perfil actiu.", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the Catalan translation for Git commits.
comissió means “commission.” Use commit or confirmació for the Git operation. The current text describes commission messages instead of commit messages.
Proposed fix
- "label": "Model per als missatges de comissió",
- "description": "Perfil utilitzat per generar missatges de comissió des del plafó de control de codi font. Normalment n'hi ha prou amb un model petit i ràpid. Deixa-ho sense seleccionar per fer servir el perfil actiu.",
+ "label": "Model per als missatges de commit",
+ "description": "Perfil utilitzat per generar missatges de commit des del plafó de control de codi font. Normalment n'hi ha prou amb un model petit i ràpid. Deixa-ho sense seleccionar per fer servir el perfil actiu.",📝 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.
| "label": "Model per als missatges de comissió", | |
| "description": "Perfil utilitzat per generar missatges de comissió des del plafó de control de codi font. Normalment n'hi ha prou amb un model petit i ràpid. Deixa-ho sense seleccionar per fer servir el perfil actiu.", | |
| "label": "Model per als missatges de commit", | |
| "description": "Perfil utilitzat per generar missatges de commit des del plafó de control de codi font. Normalment n'hi ha prou amb un model petit i ràpid. Deixa-ho sense seleccionar per fer servir el perfil actiu.", |
🤖 Prompt for 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.
In `@webview-ui/src/i18n/locales/ca/settings.json` around lines 360 - 361, Update
the Catalan label and description in the settings locale entry to use “commit”
or “confirmació” for the Git operation instead of “comissió,” while preserving
the existing meaning about generating commit messages and using the active
profile when unselected.
| "COMMIT_MESSAGE": { | ||
| "label": "Mensaje de confirmación", | ||
| "description": "Resume tus cambios en un mensaje de confirmación. Disponible mediante el icono de Zoo Code en el panel de control de código fuente, que escribe el resultado directamente en el campo del mensaje de confirmación." | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the Git term commit in the Spanish text.
"Mensaje de confirmación" means a confirmation message. Use "Mensaje de commit" in the label and description so the setting matches the Git feature.
🤖 Prompt for 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.
In `@webview-ui/src/i18n/locales/es/prompts.json` around lines 107 - 110, Update
the COMMIT_MESSAGE localization entry to use the Git term “commit” consistently:
change the label to “Mensaje de commit” and replace the corresponding “mensaje
de confirmación” wording in the description, preserving the rest of the Spanish
text.
| {t("settings:providers.commitMessageModel.label")} | ||
| </label> | ||
| <Select | ||
| value={commitMessageApiConfigId || USE_CURRENT_CONFIG} |
There was a problem hiding this comment.
What should the picker display when commitMessageApiConfigId no longer exists in listApiConfigMeta? Can it use the fallback value so Radix always has a matching item to display?
| "providers": { | ||
| "providerDocumentation": "Documentació de {{provider}}", | ||
| "commitMessageModel": { | ||
| "label": "Model per als missatges de comissió", |
There was a problem hiding this comment.
Should this use “missatges de commit” instead of “missatges de comissió”? “Comissió” means “commission,” not the Git operation.
| ) | ||
| }) | ||
|
|
||
| it("includes the commit message model in the saved settings", async () => { |
There was a problem hiding this comment.
Would this test be stronger if it selected a different profile before saving? That would cover the complete user-selection → cached-state → updateSettings path rather than only resaving the initial value.
Related GitHub Issue
Closes: #291
Part of: #145 · Stack 4 of 4 · Depends on #1227, #1228, #1229 · Replaces the all-in-one #1218
Description
Adds the Commit Message Model picker to Settings → Providers, so the profile used
for commit messages can be chosen without editing configuration by hand. The setting
itself (
commitMessageApiConfigId) landed in stack 2; this is the control that sets it.Why Providers rather than Prompts. The enhancement equivalent lives in the Prompts
tab, so there is a reasonable argument either way. This sits in Providers because it
selects a provider profile, directly below the profile it relates to — the prompt text
lives in Prompts, the model choice lives with the models. Happy to move it if you would
rather keep the two pickers together.
Binds to
cachedState, saves with the Save button, per the SettingsView pattern inAGENTS.md— not an immediate-persistpostMessage. The value is included in theupdateSettingspayload and round-trips throughgetStateToPostToWebview, so theselection survives a reload rather than visually reverting.
Accessibility. A sibling
<label>does not name a Radix trigger, so the label andtrigger are linked with
aria-labelledbyand the relationship is covered by a testrather than assumed.
Test Procedure
CommitMessageModelSelect.spec.tsxcovers: rendering the profile list alongside thefallback option, the
"-"sentinel mapping to"", showing a saved profile, storing aselected id, and the label-to-trigger accessibility relationship.
SettingsView.spec.tsxgains a test asserting the setting survives thecachedStateround trip into the
updateSettingspayload — the failure mode being a control thatrenders and appears to save but is dropped from the payload.
Translations for all 17 locales are included, not left as English fallbacks.
Manual verification: set the picker to a specific profile → Save → reload the window →
selection persists → generating a message uses the chosen provider.
Local checks:
pnpm lint,pnpm check-types(11/11 packages), fullwebview-uisuite(1679 passed), full
srcsuite (7389 passed, 37 skipped),node scripts/find-missing-translations.js.Pre-Submission Checklist
Visual Snapshots
Check #1218
Documentation Updates
Worth a line in the docs alongside the Source Control button from stack 3, noting that a
small fast profile is usually the right choice here.
Additional Notes
This completes the stack. Of the #145 sub-issues, #289 (progress/cancellation UX) is only
partially addressed — progress and error handling are done, but there is no cancel
button, because no progress location renders one that would work while nearly every
provider ignores
completePrompt's abort signal. #292 (telemetry) is untouched. Bothremain open for follow-up.
Review order: #1227 → #1228 → #1229 → 4. All target
mainbecause GitHub cannot basea cross-fork PR on another fork's branch, so this diff appears cumulative until the
parents merge. The Commits tab shows only this PR's own commit.
Summary by CodeRabbit
New Features
Localization
Tests