diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte index bb59dc686..bc2da9891 100644 --- a/src/lib/BrewCleanup.svelte +++ b/src/lib/BrewCleanup.svelte @@ -26,7 +26,7 @@ try { judgment = await api.judgeBrewCleanup(); } catch (e) { - error = String(e); + error = "Homebrew 정리 계획을 만들지 못했습니다."; } finally { planning = false; } @@ -82,7 +82,7 @@ rationale.trim(), ); } catch (e) { - error = String(e); + error = "Homebrew 정리를 실행하지 못했습니다."; } finally { judgment = null; confirmationPhrase = ""; @@ -156,7 +156,7 @@ {#if execution.record_path}

감사 기록: {execution.record_path}

{:else} - + {/if} {/if} diff --git a/src/lib/IcloudLocalEviction.svelte b/src/lib/IcloudLocalEviction.svelte index 1c21a5bcd..5aa5e335d 100644 --- a/src/lib/IcloudLocalEviction.svelte +++ b/src/lib/IcloudLocalEviction.svelte @@ -35,7 +35,7 @@ path = selected; resetDecision(); } catch { - error = "iCloud 파일 선택을 완료하지 못했습니다. 다시 시도하십시오."; + error = "파일 선택 창을 열지 못했습니다."; } } @@ -47,7 +47,7 @@ try { plan = await api.planIcloudLocalCopyEviction(cloudRoot, selectedPath); } catch { - error = "iCloud 로컬 사본 상태를 확인하지 못했습니다. 다시 시도하십시오."; + error = "iCloud 로컬 사본 상태를 확인하지 못했습니다."; } finally { planning = false; } @@ -83,7 +83,7 @@ confirmation = ""; rationale = ""; } catch { - error = "iCloud 로컬 사본을 회수하지 못했습니다. 상태를 다시 확인하십시오."; + error = "iCloud 로컬 사본 축출을 실행하지 못했습니다."; } finally { executing = false; } @@ -94,39 +94,6 @@ ? "macOS File Provider" : "Foundation ubiquitous item"; } - - function uploadLabel(state: api.IcloudLocalState): string { - if (state.is_uploaded && !state.is_uploading) return "완료"; - if (state.is_uploading) return "업로드 중"; - return "미완료"; - } - - function syncLabel(state: api.IcloudLocalState): string { - if (state.downloading_status_current && !state.is_uploaded && !state.is_uploading) { - return "로컬 최신본·업로드 미확인"; - } - if (state.is_uploaded && !state.is_uploading) return "공급자 동기화 완료"; - if (state.is_uploading) return "공급자 업로드 중"; - return "공급자 동기화 미완료"; - } - - function blockerLabel(blocker: string): string { - const labels: Record = { - "icloud-upload-not-confirmed": "로컬 최신본이지만 공급자 업로드가 아직 확인되지 않았습니다. 업로드 완료 후 다시 확인하십시오.", - "icloud-upload-still-running": "공급자 업로드가 진행 중입니다. 완료 후 다시 확인하십시오.", - "icloud-current-version-unconfirmed": "로컬 최신본 여부를 확인하지 못했습니다. File Provider 상태가 안정된 후 다시 확인하십시오.", - "icloud-file-provider-native-status-unavailable": "File Provider 상태 증거가 완전하지 않습니다. 잠시 후 다시 확인하십시오.", - "icloud-file-provider-sync-paused-or-unconfirmed": "File Provider 동기화가 일시중지됐거나 상태가 미확인입니다. 동기화를 재개한 후 다시 확인하십시오.", - "icloud-unresolved-conflict": "동기화 충돌이 해결되지 않았습니다. 충돌을 해결한 후 다시 확인하십시오.", - "active-file-use-detected": "현재 사용 중인 파일이라 회수할 수 없습니다. 파일을 닫은 후 다시 확인하십시오.", - "active-use-evidence-incomplete": "파일 사용 상태를 완전히 확인하지 못했습니다. 잠시 후 다시 확인하십시오.", - }; - return labels[blocker] ?? "필수 iCloud 상태 증거가 완전하지 않아 회수할 수 없습니다. 상태를 다시 확인하십시오."; - } - - function blockerSummary(blockers: string[]): string { - return [...new Set(blockers.map(blockerLabel))].join(" "); - }
@@ -163,9 +130,8 @@ · {observationLabel(plan.icloud_state.observation_method)}
- 업로드 {uploadLabel(plan.icloud_state)} - 공급자 상태 {syncLabel(plan.icloud_state)} - 로컬 current {plan.icloud_state.downloading_status_current ? "예" : "아니오"} + 업로드 {plan.icloud_state.is_uploaded && !plan.icloud_state.is_uploading ? "완료" : "미완료"} + 최신 버전 {plan.icloud_state.downloading_status_current ? "확인" : "미확인"} 충돌 {plan.icloud_state.has_unresolved_conflicts ? "있음" : "없음"} 활성 사용 {plan.active_use.active ? "감지" : "없음"} 동기화 일시정지 {plan.icloud_state.is_sync_paused === false ? "아님" : "미확인/해당"} @@ -228,7 +194,7 @@
{:else} -

현재 축출 불가: {blockerSummary(plan.blockers)}

+

현재 축출 불가: {plan.blockers.join(", ")}

{/if} {/if} diff --git a/src/lib/brewCleanupErrorPrivacyContract.test.ts b/src/lib/brewCleanupErrorPrivacyContract.test.ts new file mode 100644 index 000000000..243a264f0 --- /dev/null +++ b/src/lib/brewCleanupErrorPrivacyContract.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); + +function readSource(path: string): string { + return readFileSync(resolve(repositoryRoot, path), "utf8"); +} + +describe("BrewCleanup privacy-safe failure feedback", () => { + it("never renders arbitrary backend exception text", () => { + const source = readSource("src/lib/BrewCleanup.svelte"); + const evictionSource = readSource("src/lib/IcloudLocalEviction.svelte"); + + expect(source).not.toContain("String(e)"); + expect(source).not.toContain("record_error"); + expect(source).not.toContain("저장하지 못했습니다: {"); + expect(source).not.toMatch(/\{execution\.record_error\}/); + expect(evictionSource).not.toContain("String(e)"); + expect(evictionSource).not.toContain("result_record_error"); + expect(evictionSource).not.toMatch(/\{eviction\.result_record_error\}/); + expect(evictionSource).toContain("파일 선택 창을 열지 못했습니다."); + expect(evictionSource).toContain("iCloud 로컬 사본 상태를 확인하지 못했습니다."); + expect(evictionSource).toContain("iCloud 로컬 사본 축출을 실행하지 못했습니다."); + expect(source).toContain("Homebrew 정리 계획을 만들지 못했습니다."); + expect(source).toContain("Homebrew 정리를 실행하지 못했습니다."); + expect(source).toContain('role=\"alert\"'); + }); + + it("preserves the existing judgment and execution authority calls", () => { + const source = readSource("src/lib/BrewCleanup.svelte"); + + expect(source).toContain("api.judgeBrewCleanup()"); + expect(source).toContain("api.executeBrewCleanup("); + expect(source).toContain("submittedJudgment.plan_fingerprint"); + expect(source).toContain("submittedJudgment.judgment_id"); + }); +});