Files
kandev/apps/web/components/github/my-github/action-presets.ts
T
João Salavisa ccc62d9324 feat(github): configurable quick-action presets and PR branch checkout (#689)
* feat(github): configurable quick-action presets and PR branch checkout

- Add workspace-scoped action presets (icon, label, hint, prompt template) for PRs and Issues, editable from GitHub settings with reset-to-defaults.
- When starting a task from a PR, check out the PR head branch directly in the worktree instead of branching off main.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): extract action-preset service to keep service.go under line limit

Splits the preset CRUD methods out of the oversized service.go and applies prettier formatting to three frontend files flagged by CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): address review feedback on action presets

- Prevent infinite refetch loop when fetching presets fails by tracking attempted workspaces in a ref instead of re-entering the effect on every loading-state change.
- Drop unused savePR/saveIssue helpers from the hook; only save() is consumed.
- Reorder the init spread in updateGitHubActionPresets/resetGitHubActionPresets so callers can't accidentally overwrite method/body.
- Tighten the GitHubActionPreset.icon type with string & {} so known icon keys keep autocomplete.
- Sharpen the eye-icon test to assert the exact IconEye mapping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): address second round of review feedback

- Seed preset editor drafts from current store state via lazy initializers so a warm mount shows the user's saved customisations instead of silently reverting to defaults when Save is clicked.
- Guard Reset button with its own in-flight state to prevent double-firing.
- Stop freezing resolved defaults into the stored row on a partial update; persist only what the store has for the untouched kind so future default improvements still reach those workspaces.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(github): tighten quick-actions editor UI

Replace the stacked PR/Issue editors and multi-line preset cards with a tabbed layout and single-line rows. Each row shows icon · label · hint inline; an "Edit prompt" toggle reveals the template textarea only when needed. Drops the redundant section headings, up/down reorder buttons, and verbose template helper label (hint moved into the textarea placeholder).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(github): widen icon select trigger so the icon is visible

The icon select trigger had a fixed 40px width which the SelectTrigger's built-in chevron, padding, and icon gap all competed for, clipping the icon out of view. Drop the width override so the trigger sizes to its content.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 14:10:59 +01:00

127 lines
4.0 KiB
TypeScript

import {
IconEye,
IconMessageDots,
IconTool,
IconCode,
IconSearch,
IconBug,
IconSparkles,
IconChecks,
} from "@tabler/icons-react";
import type { Icon } from "@tabler/icons-react";
import type {
GitHubActionPreset,
GitHubActionPresetIcon,
GitHubActionPresets,
} from "@/lib/types/github";
import type { TaskPreset } from "./quick-task-launcher";
export const PRESET_ICON_CHOICES: { key: GitHubActionPresetIcon; icon: Icon; label: string }[] = [
{ key: "eye", icon: IconEye, label: "Eye" },
{ key: "message", icon: IconMessageDots, label: "Message" },
{ key: "tool", icon: IconTool, label: "Tool" },
{ key: "code", icon: IconCode, label: "Code" },
{ key: "search", icon: IconSearch, label: "Search" },
{ key: "bug", icon: IconBug, label: "Bug" },
{ key: "sparkle", icon: IconSparkles, label: "Sparkle" },
{ key: "check", icon: IconChecks, label: "Check" },
];
const ICON_BY_KEY: Record<string, Icon> = Object.fromEntries(
PRESET_ICON_CHOICES.map((choice) => [choice.key, choice.icon]),
);
export function iconForPresetKey(key: string | undefined): Icon {
if (!key) return IconSparkles;
return ICON_BY_KEY[key] ?? IconSparkles;
}
// Interpolate `{url}` and `{title}` placeholders in a prompt template.
// Unknown placeholders are left untouched so the user sees what's broken.
export function interpolatePromptTemplate(
template: string,
opts: { url: string; title: string },
): string {
return template.replace(/\{(url|title)\}/g, (_match, key) => {
if (key === "url") return opts.url;
if (key === "title") return opts.title;
return `{${key}}`;
});
}
export function toTaskPreset(stored: GitHubActionPreset): TaskPreset {
return {
id: stored.id,
label: stored.label,
hint: stored.hint,
icon: iconForPresetKey(stored.icon),
prompt: (opts) => interpolatePromptTemplate(stored.prompt_template, opts),
};
}
export const DEFAULT_PR_PRESETS: GitHubActionPreset[] = [
{
id: "review",
label: "Review",
hint: "Read the diff, flag issues",
icon: "eye",
prompt_template:
"Review the pull request at {url}. Provide feedback on code quality, correctness, and suggest improvements.",
},
{
id: "address_feedback",
label: "Address feedback",
hint: "Apply review comments",
icon: "message",
prompt_template:
"Address the review feedback on the pull request at {url}. Make the requested changes and push them.",
},
{
id: "fix_ci",
label: "Fix CI",
hint: "Diagnose failing checks",
icon: "tool",
prompt_template:
"Investigate and fix the CI failures on the pull request at {url}. Run the failing checks locally, diagnose, and push fixes.",
},
];
export const DEFAULT_ISSUE_PRESETS: GitHubActionPreset[] = [
{
id: "implement",
label: "Implement",
hint: "Build and open a PR",
icon: "code",
prompt_template:
'Implement the changes described in the GitHub issue at {url} (title: "{title}"). Open a pull request when complete.',
},
{
id: "investigate",
label: "Investigate",
hint: "Find the root cause",
icon: "search",
prompt_template:
'Investigate the GitHub issue at {url} (title: "{title}"). Identify root cause and summarize findings.',
},
{
id: "reproduce",
label: "Reproduce",
hint: "Document repro steps",
icon: "bug",
prompt_template:
'Reproduce the bug described in the GitHub issue at {url} (title: "{title}"). Document the reproduction steps.',
},
];
// Resolve stored presets into runtime TaskPreset[]. Falls back to defaults while
// presets load so the /github page doesn't flicker empty dropdowns on first mount.
export function resolvePRPresets(stored: GitHubActionPresets | null): TaskPreset[] {
const source = stored?.pr?.length ? stored.pr : DEFAULT_PR_PRESETS;
return source.map(toTaskPreset);
}
export function resolveIssuePresets(stored: GitHubActionPresets | null): TaskPreset[] {
const source = stored?.issue?.length ? stored.issue : DEFAULT_ISSUE_PRESETS;
return source.map(toTaskPreset);
}