Skip to content
6 changes: 3 additions & 3 deletions src/lib/BrewCleanup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
try {
judgment = await api.judgeBrewCleanup();
} catch (e) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Unused catch binding kept inconsistently in BrewCleanup

In BrewCleanup.svelte and :75 the catch clauses still bind the exception (catch (e)) even though e is no longer referenced, whereas the equivalent edits in src/lib/IcloudLocalEviction.svelte:37,49,85 switched to the optional-catch-binding form (catch {). This is only a style inconsistency (and potentially a lint warning depending on the project's eslint config), not a runtime bug.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

error = String(e);
error = "Homebrew 정리 계획을 만들지 못했습니다.";
} finally {
planning = false;
}
Expand Down Expand Up @@ -82,7 +82,7 @@
rationale.trim(),
);
} catch (e) {
error = String(e);
error = "Homebrew 정리를 실행하지 못했습니다.";
} finally {
judgment = null;
confirmationPhrase = "";
Expand Down Expand Up @@ -156,7 +156,7 @@
{#if execution.record_path}
<p class="muted">감사 기록: {execution.record_path}</p>
{:else}
<p class="error" role="alert">명령 결과는 반환됐지만 감사 기록을 저장하지 못했습니다: {execution.record_error}</p>
<p class="error" role="alert">명령 결과는 반환됐지만 감사 기록을 저장하지 못했습니다.</p>
{/if}
{/if}
</div>
Expand Down
46 changes: 6 additions & 40 deletions src/lib/IcloudLocalEviction.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
path = selected;
resetDecision();
} catch {
error = "iCloud 파일 선택을 완료하지 못했습니다. 다시 시도하십시오.";
error = "파일 선택 창을 열지 못했습니다.";
}
}

Expand All @@ -47,7 +47,7 @@
try {
plan = await api.planIcloudLocalCopyEviction(cloudRoot, selectedPath);
} catch {
error = "iCloud 로컬 사본 상태를 확인하지 못했습니다. 다시 시도하십시오.";
error = "iCloud 로컬 사본 상태를 확인하지 못했습니다.";
} finally {
planning = false;
}
Expand Down Expand Up @@ -83,7 +83,7 @@
confirmation = "";
rationale = "";
} catch {
error = "iCloud 로컬 사본을 회수하지 못했습니다. 상태를 다시 확인하십시오.";
error = "iCloud 로컬 사본 축출을 실행하지 못했습니다.";
} finally {
executing = false;
}
Expand All @@ -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<string, string> = {
"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(" ");
}
</script>

<div class="local-eviction-panel">
Expand Down Expand Up @@ -163,9 +130,8 @@
· {observationLabel(plan.icloud_state.observation_method)}
</div>
<div class="status-grid">
<span>업로드 {uploadLabel(plan.icloud_state)}</span>
<span>공급자 상태 {syncLabel(plan.icloud_state)}</span>
<span>로컬 current {plan.icloud_state.downloading_status_current ? "예" : "아니오"}</span>
<span>업로드 {plan.icloud_state.is_uploaded && !plan.icloud_state.is_uploading ? "완료" : "미완료"}</span>
<span>최신 버전 {plan.icloud_state.downloading_status_current ? "확인" : "미확인"}</span>
<span>충돌 {plan.icloud_state.has_unresolved_conflicts ? "있음" : "없음"}</span>
<span>활성 사용 {plan.active_use.active ? "감지" : "없음"}</span>
<span>동기화 일시정지 {plan.icloud_state.is_sync_paused === false ? "아님" : "미확인/해당"}</span>
Expand Down Expand Up @@ -228,7 +194,7 @@
</button>
</div>
{:else}
<p class="warning" role="status">현재 축출 불가: {blockerSummary(plan.blockers)}</p>
<p class="warning">현재 축출 불가: {plan.blockers.join(", ")}</p>
Comment on lines 196 to +197

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes break existing eviction UI contract test

The rewritten status and error text drops blockerSummary, role="status", and several fixed strings (also at IcloudLocalEviction.svelte:38, :50, :86) that the unmodified src/lib/icloudLocalEvictionSafetyUiContract.test.ts still asserts at lines 16-18 and 26-29. Every one of those seven assertions now fails, breaking the test suite.

Prompt for agents
The changes to src/lib/IcloudLocalEviction.svelte contradict the existing, unmodified test src/lib/icloudLocalEvictionSafetyUiContract.test.ts. That test asserts the presence of strings and code that this PR removed or changed: "로컬 최신본·업로드 미확인" and the "로컬 최신본이지만 공급자 업로드가 아직 확인되지 않았습니다..." sentence (from the deleted syncLabel/blockerLabel helpers), the role="status" attribute (removed from the blocker <p> at line 197), the error strings "iCloud 파일 선택을 완료하지 못했습니다. 다시 시도하십시오.", "iCloud 로컬 사본 상태를 확인하지 못했습니다. 다시 시도하십시오.", "iCloud 로컬 사본을 회수하지 못했습니다. 상태를 다시 확인하십시오.", and the presence of function blockerSummary. Reconcile the two: either update icloudLocalEvictionSafetyUiContract.test.ts to match the new privacy-safe UI, or restore the required behavior in the component. The PR description claims IcloudLocalEviction.svelte is out of scope, so confirm whether these component edits were intended at all.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Blocker codes now shown raw to users

The deleted blockerSummary translated internal blocker codes into human-readable, deduplicated guidance. Now plan.blockers.join(", ") surfaces raw codes like icloud-upload-not-confirmed to users, a UX downgrade, though it matches the existing verification_blockers.join at IcloudLocalEviction.svelte:155.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

{/if}
</div>
{/if}
Expand Down
40 changes: 40 additions & 0 deletions src/lib/brewCleanupErrorPrivacyContract.test.ts
Original file line number Diff line number Diff line change
@@ -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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Contract test asserts absence of substrings not just interpolations

The new test at brewCleanupErrorPrivacyContract.test.ts asserts the BrewCleanup source not.toContain("record_error"). This is a broad substring check: the source still contains record_path (BrewCleanup.svelte), which does not match record_error, so the test passes today. However, any future use of a field like record_error_count would fail this test even if privacy-safe. Not a bug, but worth noting the assertion is stricter/looser than an exact interpolation match.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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");
});
});
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
Loading