Setting to override branch prefix (vibe-kanban) (#949)

* ##  Implementation Complete

Successfully added customizable git branch prefix setting:

### Changes Made

**Backend:**
1. [v7.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/services/src/services/config/versions/v7.rs) - Added `git_branch_prefix` field with default "vk"
2. [git.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/utils/src/git.rs) - Created validation function with tests
3. [container.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/services/src/services/container.rs#L229-L240) - Updated branch creation to use config value
4. [local container](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/local-deployment/src/container.rs#L897-L901) - Implemented `git_branch_prefix()` method

**Frontend:**
5. [GeneralSettings.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/pages/settings/GeneralSettings.tsx#L452-L480) - Added UI input in GitHub card with preview

### Features
- Default prefix: `vk` (maintains backward compatibility)
- Empty prefix supported (branches without prefix)
- Validation ensures git-compliant names
- Preview shows branch format: `{prefix}/{uuid}-{slug}` or `{uuid}-{slug}`
- All checks passed ✓

* Cleanup script changes for task attempt 8921b110-a98c-4968-9908-c710615a968e

* ##  Fixed Async Runtime Panic

Successfully converted blocking methods to async:

### Changes Made

1. **[container.rs trait](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/services/src/services/container.rs#L229-L240)** - Made both methods async, added `.await` to prefix call
2. **[LocalContainerService](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/local-deployment/src/container.rs#L898-L900)** - Changed to `config.read().await`
3. **[tasks.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/server/src/routes/tasks.rs#L167-L170)** - Added `.await` to method call
4. **[task_attempts.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/server/src/routes/task_attempts.rs#L155-L158)** - Added `.await` to method call

All checks passed ✓. The panic should be resolved - the methods now properly use async config access.

* Cleanup script changes for task attempt 8921b110-a98c-4968-9908-c710615a968e

* ##  Moved to New Git Section

Created a separate Git card in [GeneralSettings.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/pages/settings/GeneralSettings.tsx#L528-L571) positioned between GitHub and Notifications sections, with:
- Title: "Git"
- Description: "Configure git branch naming preferences"
- Branch Prefix setting with preview

All checks passed ✓

* Cleanup script changes for task attempt 8921b110-a98c-4968-9908-c710615a968e

* ##  i18n Integration Complete

Added translations for the Git section in all supported languages:

### Translation Files Updated
1. **[English](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/i18n/locales/en/settings.json)** - Added `git` section with complete translations
2. **[Spanish](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/i18n/locales/es/settings.json)** - Spanish translations
3. **[Japanese](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/i18n/locales/ja/settings.json)** - Japanese translations

### Component Updated
- [GeneralSettings.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/pages/settings/GeneralSettings.tsx#L528-L575) - Now uses `t()` for all text with dynamic preview translation

All checks passed ✓

* Commit changes from coding agent for task attempt 8921b110-a98c-4968-9908-c710615a968e

* Cleanup script changes for task attempt 8921b110-a98c-4968-9908-c710615a968e

* ##  Validation Implementation Complete

Successfully addressed PR feedback by implementing comprehensive validation:

### 1. Backend: Using `git2::Branch::name_is_valid` 
- **[git.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/utils/src/git.rs)** - Replaced manual validation with `git2::Branch::name_is_valid`
- Uses authoritative libgit2 validation with `"{prefix}/x"` format
- Added git2 dependency to utils crate
- Updated tests (removed tests for behaviors that differ from git2)

### 2. Frontend: Real-time Validation with Error Display 
- **[GeneralSettings.tsx](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/pages/settings/GeneralSettings.tsx)** - Added validation logic
- Error state shows inline message below input
- Red border on invalid input (`border-destructive`)
- Save button disabled when validation fails
- Validates on input change (real-time feedback)

### 3. i18n Error Messages 
Added translations in all languages:
- [English](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/i18n/locales/en/settings.json#L82-L90)
- [Spanish](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/i18n/locales/es/settings.json#L79-L87)
- [Japanese](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/frontend/src/i18n/locales/ja/settings.json#L86-L94)

### 4. Backend Enforcement 
- **[config.rs](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/8921-setting-to-overr/crates/server/src/routes/config.rs#L104-L108)** - Validates on config save
- Returns 400 error if invalid prefix
- Backend remains source of truth

All checks passed ✓

*  Fixed linting error - removed unnecessary escape character in regex. All checks now pass.

* ##  Simplified Validation Text

Removed verbose rules text and made error messages more concise:

### Changes:
1. **Removed** the detailed "Allowed: letters, numbers..." rules line
2. **Simplified** error messages to be more direct:
   - "Prefix cannot contain '/'" (was "Prefix must be a single path segment (no '/').")
   - "Contains invalid sequence (.., @{)" (was "Prefix contains invalid sequence (.., @{, or @)")
   - etc.

3. **Updated all 3 languages** (English, Spanish, Japanese)

### Result:
- Helper text + preview on same line (cleaner)
- Real-time validation shows specific errors when needed
- Much less copy while maintaining clarity

All checks passed ✓

* Cleanup script changes for task attempt 8921b110-a98c-4968-9908-c710615a968e
This commit is contained in:
Louis Knight-Webb
2025-10-07 16:33:36 +01:00
committed by GitHub
parent a733ca51d5
commit 36587766d6
14 changed files with 223 additions and 14 deletions

View File

@@ -70,6 +70,26 @@
"createTokenLink": "Create token here"
}
},
"git": {
"title": "Git",
"description": "Configure git branch naming preferences",
"branchPrefix": {
"label": "Branch Prefix",
"placeholder": "vk",
"helper": "Prefix for auto-generated branch names. Leave empty for no prefix.",
"preview": "Preview:",
"previewWithPrefix": "{{prefix}}/1a2b-task-name",
"previewNoPrefix": "1a2b-task-name",
"errors": {
"slash": "Prefix cannot contain '/'.",
"startsWithDot": "Prefix cannot start with '.'.",
"endsWithDot": "Prefix cannot end with '.' or '.lock'.",
"invalidSequence": "Contains invalid sequence (.., @{).",
"invalidChars": "Contains invalid characters.",
"controlChars": "Contains control characters."
}
}
},
"notifications": {
"title": "Notifications",
"description": "Control when and how you receive notifications.",

View File

@@ -70,6 +70,26 @@
"createTokenLink": "Crear token aquí"
}
},
"git": {
"title": "Git",
"description": "Configurar preferencias de nombres de ramas git",
"branchPrefix": {
"label": "Prefijo de Rama",
"placeholder": "vk",
"helper": "Prefijo para nombres de ramas generadas automáticamente. Dejar vacío para no usar prefijo.",
"preview": "Vista previa:",
"previewWithPrefix": "{{prefix}}/1a2b-nombre-tarea",
"previewNoPrefix": "1a2b-nombre-tarea",
"errors": {
"slash": "El prefijo no puede contener '/'.",
"startsWithDot": "El prefijo no puede comenzar con '.'.",
"endsWithDot": "El prefijo no puede terminar con '.' o '.lock'.",
"invalidSequence": "Contiene secuencia no válida (.., @{).",
"invalidChars": "Contiene caracteres no válidos.",
"controlChars": "Contiene caracteres de control."
}
}
},
"notifications": {
"title": "Notificaciones",
"description": "Controla cuándo y cómo recibes notificaciones.",

View File

@@ -70,6 +70,26 @@
"createTokenLink": "ここでトークンを作成"
}
},
"git": {
"title": "Git",
"description": "Gitブランチ名の設定",
"branchPrefix": {
"label": "ブランチプレフィックス",
"placeholder": "vk",
"helper": "自動生成されるブランチ名のプレフィックス。空欄にするとプレフィックスなしになります。",
"preview": "プレビュー:",
"previewWithPrefix": "{{prefix}}/1a2b-タスク名",
"previewNoPrefix": "1a2b-タスク名",
"errors": {
"slash": "プレフィックスに'/'を含めることはできません。",
"startsWithDot": "プレフィックスは'.'で始めることができません。",
"endsWithDot": "プレフィックスは'.'または'.lock'で終わることができません。",
"invalidSequence": "無効なシーケンス(..、@{)が含まれています。",
"invalidChars": "無効な文字が含まれています。",
"controlChars": "制御文字が含まれています。"
}
}
},
"notifications": {
"title": "通知",
"description": "通知を受け取るタイミングと方法を制御します。",

View File

@@ -66,8 +66,35 @@ export function GeneralSettings() {
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const [branchPrefixError, setBranchPrefixError] = useState<string | null>(
null
);
const { setTheme } = useTheme();
const validateBranchPrefix = useCallback(
(prefix: string): string | null => {
if (!prefix) return null; // empty allowed
if (prefix.includes('/'))
return t('settings.general.git.branchPrefix.errors.slash');
if (prefix.startsWith('.'))
return t('settings.general.git.branchPrefix.errors.startsWithDot');
if (prefix.endsWith('.') || prefix.endsWith('.lock'))
return t('settings.general.git.branchPrefix.errors.endsWithDot');
if (prefix.includes('..') || prefix.includes('@{'))
return t('settings.general.git.branchPrefix.errors.invalidSequence');
if (/[ \t~^:?*[\\]/.test(prefix))
return t('settings.general.git.branchPrefix.errors.invalidChars');
// Control chars check
for (let i = 0; i < prefix.length; i++) {
const code = prefix.charCodeAt(i);
if (code < 0x20 || code === 0x7f)
return t('settings.general.git.branchPrefix.errors.controlChars');
}
return null;
},
[t]
);
// When config loads or changes externally, update draft only if not dirty
useEffect(() => {
if (!config) return;
@@ -492,14 +519,6 @@ export function GeneralSettings() {
</div>
)}
<div className="flex items-center gap-4 my-6">
<div className="flex-1 border-t border-border"></div>
<span className="text-sm text-muted-foreground font-medium">
{t('settings.general.github.or')}
</span>
<div className="flex-1 border-t border-border"></div>
</div>
<div className="space-y-2">
<Label htmlFor="github-token">
{t('settings.general.github.pat.label')}
@@ -533,6 +552,58 @@ export function GeneralSettings() {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>{t('settings.general.git.title')}</CardTitle>
<CardDescription>
{t('settings.general.git.description')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="git-branch-prefix">
{t('settings.general.git.branchPrefix.label')}
</Label>
<Input
id="git-branch-prefix"
type="text"
placeholder={t('settings.general.git.branchPrefix.placeholder')}
value={draft?.git_branch_prefix ?? ''}
onChange={(e) => {
const value = e.target.value.trim();
updateDraft({ git_branch_prefix: value });
setBranchPrefixError(validateBranchPrefix(value));
}}
aria-invalid={!!branchPrefixError}
className={branchPrefixError ? 'border-destructive' : undefined}
/>
{branchPrefixError && (
<p className="text-sm text-destructive">{branchPrefixError}</p>
)}
<p className="text-sm text-muted-foreground">
{t('settings.general.git.branchPrefix.helper')}{' '}
{draft?.git_branch_prefix ? (
<>
{t('settings.general.git.branchPrefix.preview')}{' '}
<code className="text-xs bg-muted px-1 py-0.5 rounded">
{t('settings.general.git.branchPrefix.previewWithPrefix', {
prefix: draft.git_branch_prefix,
})}
</code>
</>
) : (
<>
{t('settings.general.git.branchPrefix.preview')}{' '}
<code className="text-xs bg-muted px-1 py-0.5 rounded">
{t('settings.general.git.branchPrefix.previewNoPrefix')}
</code>
</>
)}
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>{t('settings.general.notifications.title')}</CardTitle>
@@ -731,7 +802,7 @@ export function GeneralSettings() {
</Button>
<Button
onClick={handleSave}
disabled={!hasUnsavedChanges || saving}
disabled={!hasUnsavedChanges || saving || !!branchPrefixError}
>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{t('settings.general.save.button')}