kin: KIN-054 Исправить race condition в loadMode() при инициализации ProjectView
This commit is contained in:
parent
ae21e48b65
commit
756f9e65ab
5 changed files with 47 additions and 10 deletions
|
|
@ -47,9 +47,13 @@ def _build_claude_env() -> dict:
|
||||||
if bin_dir.is_dir():
|
if bin_dir.is_dir():
|
||||||
extra.append(str(bin_dir))
|
extra.append(str(bin_dir))
|
||||||
|
|
||||||
seen = set(existing)
|
seen: set[str] = set()
|
||||||
new_dirs = [d for d in extra if d and d not in seen]
|
deduped: list[str] = []
|
||||||
env["PATH"] = ":".join(new_dirs + existing)
|
for d in extra + existing:
|
||||||
|
if d and d not in seen:
|
||||||
|
seen.add(d)
|
||||||
|
deduped.append(d)
|
||||||
|
env["PATH"] = ":".join(deduped)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1173,8 +1173,14 @@ class TestClaudePath:
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_build_claude_env_no_duplicate_paths(self):
|
def test_build_claude_env_no_duplicate_paths(self):
|
||||||
"""_build_claude_env не должен дублировать уже существующие пути."""
|
"""_build_claude_env не должен дублировать уже существующие пути.
|
||||||
env = _build_claude_env()
|
|
||||||
|
Мокируем PATH на фиксированное значение, чтобы тест не зависел от
|
||||||
|
реального окружения (решение #48).
|
||||||
|
"""
|
||||||
|
fixed_path = "/usr/bin:/bin"
|
||||||
|
with patch.dict("os.environ", {"PATH": fixed_path}, clear=False):
|
||||||
|
env = _build_claude_env()
|
||||||
path_dirs = env["PATH"].split(":")
|
path_dirs = env["PATH"].split(":")
|
||||||
seen = set()
|
seen = set()
|
||||||
for d in path_dirs:
|
for d in path_dirs:
|
||||||
|
|
|
||||||
14
web/api.py
14
web/api.py
|
|
@ -136,19 +136,27 @@ class ProjectCreate(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ProjectPatch(BaseModel):
|
class ProjectPatch(BaseModel):
|
||||||
execution_mode: str
|
execution_mode: str | None = None
|
||||||
|
autocommit_enabled: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
@app.patch("/api/projects/{project_id}")
|
@app.patch("/api/projects/{project_id}")
|
||||||
def patch_project(project_id: str, body: ProjectPatch):
|
def patch_project(project_id: str, body: ProjectPatch):
|
||||||
if body.execution_mode not in VALID_EXECUTION_MODES:
|
if body.execution_mode is None and body.autocommit_enabled is None:
|
||||||
|
raise HTTPException(400, "Nothing to update. Provide execution_mode or autocommit_enabled.")
|
||||||
|
if body.execution_mode is not None and body.execution_mode not in VALID_EXECUTION_MODES:
|
||||||
raise HTTPException(400, f"Invalid execution_mode '{body.execution_mode}'. Must be one of: {', '.join(VALID_EXECUTION_MODES)}")
|
raise HTTPException(400, f"Invalid execution_mode '{body.execution_mode}'. Must be one of: {', '.join(VALID_EXECUTION_MODES)}")
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
p = models.get_project(conn, project_id)
|
p = models.get_project(conn, project_id)
|
||||||
if not p:
|
if not p:
|
||||||
conn.close()
|
conn.close()
|
||||||
raise HTTPException(404, f"Project '{project_id}' not found")
|
raise HTTPException(404, f"Project '{project_id}' not found")
|
||||||
models.update_project(conn, project_id, execution_mode=body.execution_mode)
|
fields = {}
|
||||||
|
if body.execution_mode is not None:
|
||||||
|
fields["execution_mode"] = body.execution_mode
|
||||||
|
if body.autocommit_enabled is not None:
|
||||||
|
fields["autocommit_enabled"] = int(body.autocommit_enabled)
|
||||||
|
models.update_project(conn, project_id, **fields)
|
||||||
p = models.get_project(conn, project_id)
|
p = models.get_project(conn, project_id)
|
||||||
conn.close()
|
conn.close()
|
||||||
return p
|
return p
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export interface Project {
|
||||||
priority: number
|
priority: number
|
||||||
tech_stack: string[] | null
|
tech_stack: string[] | null
|
||||||
execution_mode: string | null
|
execution_mode: string | null
|
||||||
|
autocommit_enabled: number | null
|
||||||
created_at: string
|
created_at: string
|
||||||
total_tasks: number
|
total_tasks: number
|
||||||
done_tasks: number
|
done_tasks: number
|
||||||
|
|
@ -169,7 +170,7 @@ export const api = {
|
||||||
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
|
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
|
||||||
patchTask: (id: string, data: { status?: string; execution_mode?: string }) =>
|
patchTask: (id: string, data: { status?: string; execution_mode?: string }) =>
|
||||||
patch<Task>(`/tasks/${id}`, data),
|
patch<Task>(`/tasks/${id}`, data),
|
||||||
patchProject: (id: string, data: { execution_mode: string }) =>
|
patchProject: (id: string, data: { execution_mode?: string; autocommit_enabled?: boolean }) =>
|
||||||
patch<Project>(`/projects/${id}`, data),
|
patch<Project>(`/projects/${id}`, data),
|
||||||
deleteDecision: (projectId: string, decisionId: number) =>
|
deleteDecision: (projectId: string, decisionId: number) =>
|
||||||
del<{ deleted: number }>(`/projects/${projectId}/decisions/${decisionId}`),
|
del<{ deleted: number }>(`/projects/${projectId}/decisions/${decisionId}`),
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,24 @@ async function toggleMode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Autocommit toggle
|
||||||
|
const autocommit = ref(false)
|
||||||
|
|
||||||
|
function loadAutocommit() {
|
||||||
|
autocommit.value = !!(project.value?.autocommit_enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleAutocommit() {
|
||||||
|
autocommit.value = !autocommit.value
|
||||||
|
try {
|
||||||
|
await api.patchProject(props.id, { autocommit_enabled: autocommit.value })
|
||||||
|
if (project.value) project.value = { ...project.value, autocommit_enabled: autocommit.value ? 1 : 0 }
|
||||||
|
} catch (e: any) {
|
||||||
|
error.value = e.message
|
||||||
|
autocommit.value = !autocommit.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Audit
|
// Audit
|
||||||
const auditLoading = ref(false)
|
const auditLoading = ref(false)
|
||||||
const auditResult = ref<AuditResult | null>(null)
|
const auditResult = ref<AuditResult | null>(null)
|
||||||
|
|
@ -124,7 +142,7 @@ watch(selectedStatuses, (val) => {
|
||||||
router.replace({ query: { ...route.query, status: val.length ? val.join(',') : undefined } })
|
router.replace({ query: { ...route.query, status: val.length ? val.join(',') : undefined } })
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
onMounted(() => { load(); loadMode() })
|
onMounted(async () => { await load(); loadMode(); loadAutocommit() })
|
||||||
|
|
||||||
const filteredTasks = computed(() => {
|
const filteredTasks = computed(() => {
|
||||||
if (!project.value) return []
|
if (!project.value) return []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue