Skip to content

tsk-ihf2vw [OPEN] Library UI: storage accounting view#2099

Open
jaylfc wants to merge 2 commits into
devfrom
exec/tsk-ihf2vw
Open

tsk-ihf2vw [OPEN] Library UI: storage accounting view#2099
jaylfc wants to merge 2 commits into
devfrom
exec/tsk-ihf2vw

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Autonomous build of board card tsk-ihf2vw.

Files:
desktop/src/apps/LibraryApp.storage.test.tsx | 190 ++++++++++++++++++++++
desktop/src/apps/LibraryApp.tsx | 230 +++++++++++++++++++++++++--
2 files changed, 406 insertions(+), 14 deletions(-)


Summary by Gitar

  • Library UI:
    • Added storage view to LibraryApp allowing users to toggle between items and storage accounting.
    • Implemented deriveMockStorageData to calculate and sort storage usage by source and individual items.
    • Added visual indicators for storage utilization, including capacity progress bars and warnings when usage hits the 50GB cap.
  • Testing:
    • Added LibraryApp.storage.test.tsx to verify view toggling, data rendering, and storage limit overflow logic.

This will update automatically on new commits.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d4865951-59bd-4121-9676-dfe1773bd82a

📥 Commits

Reviewing files that changed from the base of the PR and between 3878ccb and 8ba4d26.

⛔ Files ignored due to path filters (1)
  • desktop/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • desktop/src/apps/LibraryApp.storage.test.tsx
  • desktop/src/apps/LibraryApp.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exec/tsk-ihf2vw

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@jaylfc

jaylfc commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: Test flaw in storage view test may cause false test failure.

  • desktop/src/apps/LibraryApp.storage.test.tsx:127 - The test for paused-at-cap warning uses 60 items which do not exceed the 50 GB cap, making the test invalid.

Automated first-pass review by the nemotron-super lane. The lead still reviews before merge.

expect(screen.queryByText(/Paused at cap/)).toBeNull();
});

it("shows paused-at-cap warning when total exceeds cap", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: This test creates 60 mock items and assumes their total mock bytes will exceed the 50GB cap. But mockItemBytes generates 100MB–4.1GB per item deterministically via a hash of the ID. With 60 items at minimum 100MB each, that's only 6GB — nowhere near 50GB. At maximum it's 246GB. Whether this test passes depends entirely on the hash values of overflow-0 through overflow-59. This is a flaky test waiting to happen.

🩹 The Fix:

Suggested change
it("shows paused-at-cap warning when total exceeds cap", async () => {
const manyItems = Array.from({ length: 60 }, (_, i) => ({
...MOCK_ITEMS[0],
id: `overflow-${i}`,
title: `Overflow Item ${i}`,
source_type: "youtube",
created_at: 1700000000 + i,
updated_at: 1700000000 + i,
// Force large mock bytes by using IDs that hash to max values
// Or better: mock deriveMockStorageData directly in the test
}));

Better approach: Mock the fetch to return items, then spy on deriveMockStorageData or just override the storage calculation in the test. But since it's not exported, the simplest fix is to use IDs that you've verified produce large hash values, or increase the item count significantly (e.g., 600 items guarantees >50GB even at minimum).

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const sources: SourceStorage[] = Array.from(sourceMap.entries())
.map(([source_type, data]) => ({ source_type, bytes: data.bytes, item_count: data.count }))
.sort((a, b) => b.bytes - a.bytes);
return {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: deriveMockStorageData(items) is called directly inside the render body (inside storageViewUI IIFE). This means it recalculates mock bytes for every item on every single render — including when unrelated state changes like search text, filter toggles, or detail view updates. The hash computation loops over every character of every item ID. With hundreds of items, this is wasted CPU cycles on every keystroke.

🩹 The Fix: Wrap it in useMemo:

Suggested change
return {
const storageViewData = useMemo(() => deriveMockStorageData(items), [items]);

Then use storageViewData in the render instead of calling deriveMockStorageData(items) inline.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

/* Helpers */
/* ------------------------------------------------------------------ */

const fmtBytes = (b: number): string => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: fmtBytes divides by 1024 (binary) but labels the units KB, MB, GB (decimal). This is the classic "my hard drive is smaller than advertised" lie. 1024 bytes = 1 KiB, not 1 KB. Either use 1000-based units for KB/MB/GB, or label them KiB/MiB/GiB.

🩹 The Fix:

Suggested change
const fmtBytes = (b: number): string => {
const fmtBytes = (b: number): string => {
if (b < 1024) return `${b} B`;
if (b < 1024 * 1024) return `${(b / 1024).toFixed(1)} KiB`;
if (b < 1024 * 1024 * 1024) return `${(b / (1024 * 1024)).toFixed(1)} MiB`;
return `${(b / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
};

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<p className="text-[10px] uppercase tracking-wider text-shell-text-tertiary mb-2">By source</p>
<table className="w-full text-xs">
<thead>
<tr className="text-shell-text-tertiary border-b border-white/5">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: Table headers (<th>) are missing scope="col". Screen readers won't know these headers apply to the columns below. This is Accessibility 101.

🩹 The Fix:

Suggested change
<tr className="text-shell-text-tertiary border-b border-white/5">
<th className="text-left pb-1.5 font-normal" scope="col">Source</th>

(Add scope="col" to all four <th> elements in this table and the next one.)

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<td className="py-1.5 pr-3 text-shell-text-tertiary tabular-nums">{src.item_count}</td>
<td className="py-1.5 pr-3 text-shell-text-secondary text-right tabular-nums">{fmtBytes(src.bytes)}</td>
<td className="py-1.5">
<div className="h-1.5 w-full rounded-full bg-white/5" role="progressbar" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The outer <div> has role="progressbar" with aria attributes, and the inner <div> also has role="progressbar" with the same aria attributes. That's double the announcements for screen reader users — they'll hear the progress value twice. Pick one element to be the progressbar.

🩹 The Fix: Remove role="progressbar" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100} from the inner <div> (the colored bar). Keep it only on the outer track element.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

/>
</div>
{data.paused_at_cap && (
<div className="flex items-center gap-1.5 text-xs text-red-400 bg-red-500/10 border border-red-500/20 rounded-md px-2 py-1">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The AlertCircle icon is decorative (the text "Paused at cap..." conveys the meaning), but it's missing aria-hidden="true". Screen readers will announce "alert circle" which is noise.

🩹 The Fix:

Suggested change
<div className="flex items-center gap-1.5 text-xs text-red-400 bg-red-500/10 border border-red-500/20 rounded-md px-2 py-1">
<AlertCircle size={12} aria-hidden="true" />

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<tr key={item.id} className="border-t border-white/5">
<td className="py-1.5 pr-3 text-shell-text-secondary truncate max-w-[200px]">{item.title}</td>
<td className="py-1.5 pr-3 text-shell-text-tertiary">{SOURCE_LABELS[item.source_type] ?? item.source_type}</td>
<td className="py-1.5 pr-3 text-shell-text-secondary text-right tabular-nums">{fmtBytes(item.bytes)}</td>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: Same duplicate role="progressbar" issue on the "By item" table progress bars. Outer div has it, inner div has it too.

🩹 The Fix: Remove the duplicate role and aria attributes from the inner colored bar <div>.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

vi.restoreAllMocks();
});

it("renders the view mode toggle and switches to storage view", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The storage view has a branch for items.length === 0 showing "No items to account for", but there's no test for it. Zero-coverage edge case.

🩹 The Fix: Add a test:

Suggested change
it("renders the view mode toggle and switches to storage view", async () => {
it("shows empty state when no items exist", async () => {
const emptyOverrides: Record<string, Promise<Response>> = {
"/api/knowledge/items": Promise.resolve({
ok: true,
status: 200,
headers: new Map([["content-type", "application/json"]]),
json: () => Promise.resolve({ items: [], count: 0 }),
} as Response),
};
vi.stubGlobal("fetch", createFetchMock(emptyOverrides) as unknown as typeof fetch);
render(<LibraryApp windowId="test-win" />);
await waitFor(() => screen.getByRole("radio", { name: "storage" }));
fireEvent.click(screen.getByRole("radio", { name: "storage" }));
await waitFor(() => screen.getByText("No items to account for"));
});

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

items: ItemStorageRow[];
}

const STORAGE_CAP_BYTES = 50 * 1024 * 1024 * 1024;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: STORAGE_CAP_BYTES = 50 * 1024 * 1024 * 1024 — the classic "count the asterisks" game. Use the exponentiation operator for readability: 50 * 1024 ** 3.

🩹 The Fix:

Suggested change
const STORAGE_CAP_BYTES = 50 * 1024 * 1024 * 1024;
const STORAGE_CAP_BYTES = 50 * 1024 ** 3;

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`;
};

const mockItemBytes = (id: string): number => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The hash function uses hash |= 0 to force 32-bit signed integer behavior, but then Math.abs(hash) can return 2147483648 for INT32_MIN (-2147483648), which Math.abs doesn't negate in JS (it stays negative). The % 4000 on a negative number yields a negative remainder in JS. So Math.abs(hash) % 4000 could be negative, making the final bytes calculation wrong. In practice the hash distribution makes this astronomically unlikely, but it's a latent bug.

🩹 The Fix:

Suggested change
const mockItemBytes = (id: string): number => {
const mockItemBytes = (id: string): number => {
let hash = 0;
for (let i = 0; i < id.length; i++) {
hash = ((hash << 5) - hash) + id.charCodeAt(i);
hash |= 0;
}
return ((hash >>> 0) % 4000 + 100) * 1024 * 1024;
};

Using >>> (unsigned right shift) converts to unsigned 32-bit, avoiding the INT32_MIN edge case and negative modulo.

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Code Review Roast 🔥

Verdict: 9 Issues Found | Recommendation: Address before merge

Overview

Severity Count
🚨 critical 0
⚠️ warning 2
💡 suggestion 5
🤏 nitpick 2
Issue Details (click to expand)
File Line Roast
desktop/src/apps/LibraryApp.storage.test.tsx 162 Flaky test: 60 mock items at minimum 100MB each = 6GB, not 50GB+
desktop/src/apps/LibraryApp.tsx 158 deriveMockStorageData recalculates on every render — memoize it
desktop/src/apps/LibraryApp.tsx 126 fmtBytes uses binary math (1024) but decimal labels (KB/MB/GB)
desktop/src/apps/LibraryApp.tsx 1046 Table headers missing scope="col" for accessibility
desktop/src/apps/LibraryApp.tsx 1062 Duplicate role="progressbar" on nested elements — screen readers announce twice
desktop/src/apps/LibraryApp.tsx 1034 Decorative AlertCircle icon missing aria-hidden="true"
desktop/src/apps/LibraryApp.tsx 1090 Duplicate role="progressbar" on "By item" progress bars
desktop/src/apps/LibraryApp.storage.test.tsx 115 No test for empty-items storage view state
desktop/src/apps/LibraryApp.tsx 120 50 * 1024 * 1024 * 1024 — use 50 * 1024 ** 3 for readability

🏆 Best part: The storage accounting UI is genuinely well-designed — progress bars with color thresholds (sky→amber→red), per-source and per-item breakdowns, and the "paused at cap" warning. The mock data derivation is clever for a demo. Oh wait, this part is actually clean. I need to sit down.

💀 Worst part: The flaky test at line 162. It looks like it tests the cap-exceeded case, but with 60 items at a minimum of 100MB each, you only get 6GB — an order of magnitude short of the 50GB cap. Whether it passes depends on hash lottery. This is a time bomb.

📊 Overall: Like a beautifully plated dish with a hair in it — the UI work is solid, but the test reliability and render-performance issues need fixing before merge.

Files Reviewed (2 files)
  • desktop/src/apps/LibraryApp.tsx - 7 issues
  • desktop/src/apps/LibraryApp.storage.test.tsx - 2 issues

Fix these issues in Kilo Cloud

@jaylfc

jaylfc commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-kilo review

VERDICT: Overall solid feature implementation with good test coverage, but has a few correctness concerns and style issues.

  • desktop/src/apps/LibraryApp.tsx:109 - mockItemBytes uses a simple hash that can produce collisions, causing different items to report identical byte sizes. Consider using a more robust hash (e.g., crypto.subtle.digest) or at minimum a better distribution like Math.imul with a larger prime modulus.

  • desktop/src/apps/LibraryApp.tsx:127 - deriveMockStorageData is called during render (inside storageViewUI IIFE at line 1006), recalculating on every render. Should be memoized with useMemo keyed on items to avoid unnecessary work.

  • desktop/src/apps/LibraryApp.tsx:1006 - The IIFE pattern for storageViewUI is unconventional and hurts readability. Extract to a separate component or useMemo hook.

  • desktop/src/apps/LibraryApp.tsx:117 - STORAGE_CAP_BYTES hardcoded at 50GB with no configuration option. Should be injected via props or context for flexibility.

  • desktop/src/apps/LibraryApp.tsx:1037 - Progress bar aria-valuenow receives a float (totalPct.toFixed(1) returns string but aria-valuenow expects number). Pass the numeric value directly.

  • desktop/src/apps/LibraryApp.tsx:1068 - Source share progress bar uses data.total_bytes as denominator but label says "Share" (ambiguous: share of total vs share of cap). Consider clarifying or using cap as denominator consistently.

  • desktop/src/apps/LibraryApp.tsx:1097 - Item "Share of cap" progress bar correctly uses cap_bytes but visual bar maxes at 100% even when item exceeds cap (single item can't exceed 50GB with current mock, but real data could). Clamp pct to 100 for display.

  • desktop/src/apps/LibraryApp.storage.test.tsx:18 - MOCK_ITEMS have metadata: {} but real items may have nested metadata affecting byte estimation. Tests don't verify mockItemBytes behavior with varying IDs.

  • desktop/src/apps/LibraryApp.storage.test.tsx:113 - "shows paused-at-cap warning" test creates 60 items but relies on mockItemBytes distribution to exceed 50GB. Flaky if hash distribution changes. Should mock deriveMockStorageData or override mockItemBytes to guarantee cap exceedance.

  • desktop/package-lock.json - Removing libc fields from optional dependencies (@img/sharp-libvips-*, @tailwindcss/oxide) is harmless (npm ignores unknown fields) but unusual. Verify this wasn't accidental stripping during lockfile regeneration.

  • desktop/src/apps/LibraryApp.tsx:702 - View toggle buttons use Button with role="radio" but Button component may render <button> which doesn't support role="radio" natively. Ensure Button forwards role and aria-checked to underlying element, or use native <input type="radio"> with visual styling.
    VERDICT: Overall solid feature implementation with good test coverage, but has a few correctness concerns and style issues.

  • desktop/src/apps/LibraryApp.tsx:109 - mockItemBytes uses a simple hash that can produce collisions, causing different items to report identical byte sizes. Consider using a more robust hash (e.g., crypto.subtle.digest) or at minimum a better distribution like Math.imul with a larger prime modulus.

  • desktop/src/apps/LibraryApp.tsx:127 - deriveMockStorageData is called during render (inside storageViewUI IIFE at line 1006), recalculating on every render. Should be memoized with useMemo keyed on items to avoid unnecessary work.

  • desktop/src/apps/LibraryApp.tsx:1006 - The IIFE pattern for storageViewUI is unconventional and hurts readability. Extract to a separate component or useMemo hook.

  • desktop/src/apps/LibraryApp.tsx:117 - STORAGE_CAP_BYTES hardcoded at 50GB with no configuration option. Should be injected via props or context for flexibility.

  • desktop/src/apps/LibraryApp.tsx:1037 - Progress bar aria-valuenow receives a float (totalPct.toFixed(1) returns string but aria-valuenow expects number). Pass the numeric value directly.

  • desktop/src/apps/LibraryApp.tsx:1068 - Source share progress bar uses data.total_bytes as denominator but label says "Share" (ambiguous: share of total vs share of cap). Consider clarifying or using cap as denominator consistently.

  • desktop/src/apps/LibraryApp.tsx:1097 - Item "Share of cap" progress bar correctly uses cap_bytes but visual bar maxes at 100% even when item exceeds cap (single item can't exceed 50GB with current mock, but real data could). Clamp pct to 100 for display.

  • desktop/src/apps/LibraryApp.storage.test.tsx:18 - MOCK_ITEMS have metadata: {} but real items may have nested metadata affecting byte estimation. Tests don't verify mockItemBytes behavior with varying IDs.

  • desktop/src/apps/LibraryApp.storage.test.tsx:113 - "shows paused-at-cap warning" test creates 60 items but relies on mockItemBytes distribution to exceed 50GB. Flaky if hash distribution changes. Should mock deriveMockStorageData or override mockItemBytes to guarantee cap exceedance.

  • desktop/package-lock.json - Removing libc fields from optional dependencies (@img/sharp-libvips-*, @tailwindcss/oxide) is harmless (npm ignores unknown fields) but unusual. Verify this wasn't accidental stripping during lockfile regeneration.

  • desktop/src/apps/LibraryApp.tsx:702 - View toggle buttons use Button with role="radio" but Button component may render <button> which doesn't support role="radio" natively. Ensure Button forwards role and aria-checked to underlying element, or use native <input type="radio"> with visual styling.

Automated first-pass review by the nemotron-ultra-kilo lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-orB review

VERDICT: Acceptable with concerns — mock storage accounting needs real implementation; tests have gaps and flaky assumptions

  • desktop/src/apps/LibraryApp.tsx:122mockItemBytes generates fake sizes from ID hashes; UI explicitly labels "(mock)" but users may mistake for real data. Needs real backend integration or clear "simulated" disclaimer.
  • desktop/src/apps/LibraryApp.tsx:102STORAGE_CAP_BYTES hardcoded at 50GB; should be configurable via settings/env.
  • desktop/src/apps/LibraryApp.storage.test.tsx:157 — "paused at cap" test creates 60 items but mockItemBytes output is non-deterministic per ID; test may flakily pass/fail depending on hash values. Should override mockItemBytes or inject controlled sizes.
  • desktop/src/apps/LibraryApp.storage.test.tsx:37 — Tests use deprecated vi.stubGlobal("fetch", ...); modern Vitest prefers vi.spyOn(global, "fetch") or MSW.
  • desktop/src/apps/LibraryApp.storage.test.tsx — No tests verify: byte sorting (desc), progress bar percentages, source/item share calculations, accessibility (ARIA on progressbars/radiogroup), or mobile layout.
  • desktop/src/apps/LibraryApp.tsx:228 — Large inline storageViewUI function component (200+ lines); should be extracted to separate component file for maintainability.
  • desktop/package-lock.json — Removal of libc fields from optional deps (glibc/musl) across multiple platforms; verify this doesn't break native module resolution on Alpine/musl targets.

Automated first-pass review by the nemotron-ultra-orB lane. The lead still reviews before merge.

expect(screen.queryByText(/Paused at cap/)).toBeNull();
});

it("shows paused-at-cap warning when total exceeds cap", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: This test creates 60 items to exceed the 50GB cap, but mockItemBytes returns a minimum of 100MB per item (100 * 1024 * 1024). 60 × 100MB = 6GB — an order of magnitude short of 50GB. Whether this passes depends entirely on hash lottery of the overflow-{i} IDs. This is a flaky time bomb.

🩹 The Fix: Either mock deriveMockStorageData directly to return a controlled total_bytes > cap_bytes, or increase the item count to ~500+, or override mockItemBytes in the test.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const sources: SourceStorage[] = Array.from(sourceMap.entries())
.map(([source_type, data]) => ({ source_type, bytes: data.bytes, item_count: data.count }))
.sort((a, b) => b.bytes - a.bytes);
return {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: deriveMockStorageData(items) is called inline during render (line 1111). Every keystroke, filter change, or parent re-render recalculates mock bytes for all items. With hundreds of items, this is wasted CPU. It's a pure function of items — memoize it.

🩹 The Fix: Wrap with useMemo: const data = useMemo(() => deriveMockStorageData(items), [items]);

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

/* Helpers */
/* ------------------------------------------------------------------ */

const fmtBytes = (b: number): string => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: fmtBytes divides by 1024 (binary) but labels the units KB, MB, GB (decimal). Technically these are KiB, MiB, GiB. The industry ignores this distinction, but pedants will pedant.

🩹 The Fix: Either change labels to KiB/MiB/GiB, or divide by 1000 for true KB/MB/GB. Pick a lane.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<p className="text-[10px] uppercase tracking-wider text-shell-text-tertiary mb-2">By source</p>
<table className="w-full text-xs">
<thead>
<tr className="text-shell-text-tertiary border-b border-white/5">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: Table headers (<th>) in the "By source" section lack scope="col". Screen readers won't associate headers with their columns properly.

🩹 The Fix: Add scope="col" to each <th> in both tables.

Suggested change
<tr className="text-shell-text-tertiary border-b border-white/5">
<th className="text-left pb-1.5 font-normal" scope="col">Source</th>

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<td className="py-1.5 pr-3 text-shell-text-tertiary tabular-nums">{src.item_count}</td>
<td className="py-1.5 pr-3 text-shell-text-secondary text-right tabular-nums">{fmtBytes(src.bytes)}</td>
<td className="py-1.5">
<div className="h-1.5 w-full rounded-full bg-white/5" role="progressbar" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The progress bar has role="progressbar" on both the outer <div> (line 1061) and inner <div> (line 1062). Screen readers will announce the progress twice — once for the track, once for the fill. Pick one.

🩹 The Fix: Remove role="progressbar" and aria attributes from the outer wrapper; keep them on the inner fill element.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

/>
</div>
{data.paused_at_cap && (
<div className="flex items-center gap-1.5 text-xs text-red-400 bg-red-500/10 border border-red-500/20 rounded-md px-2 py-1">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The AlertCircle icon in the "Paused at cap" warning is decorative but lacks aria-hidden="true". Screen readers may announce "Alert circle" which adds noise.

🩹 The Fix: Add aria-hidden="true" to the <AlertCircle /> component.

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<tr key={item.id} className="border-t border-white/5">
<td className="py-1.5 pr-3 text-shell-text-secondary truncate max-w-[200px]">{item.title}</td>
<td className="py-1.5 pr-3 text-shell-text-tertiary">{SOURCE_LABELS[item.source_type] ?? item.source_type}</td>
<td className="py-1.5 pr-3 text-shell-text-secondary text-right tabular-nums">{fmtBytes(item.bytes)}</td>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: Same duplicate role="progressbar" issue on the "By item" table progress bars — outer and inner divs both have it.

🩹 The Fix: Remove from outer wrapper, keep on inner fill element.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

vi.restoreAllMocks();
});

it("renders the view mode toggle and switches to storage view", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: The storageViewUI has a branch for items.length === 0 (line 1000-1009) showing "No items to account for", but no test covers it. Untested code is broken code waiting to happen.

🩹 The Fix: Add a test that renders LibraryApp with empty items and asserts the empty state message appears in storage view.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

items: ItemStorageRow[];
}

const STORAGE_CAP_BYTES = 50 * 1024 * 1024 * 1024;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔥 The Roast: 50 * 1024 * 1024 * 1024 is readable to people who think in powers of two, but 50 * 1024 ** 3 or 50 << 30 is clearer about intent (50 GiB).

🩹 The Fix: Use 50 * 1024 ** 3 for clarity.

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Code Review Roast 🔥

Verdict: 9 Issues Found | Recommendation: Address before merge

Overview

Severity Count
🚨 critical 0
⚠️ warning 2
💡 suggestion 5
🤏 nitpick 2
Issue Details (click to expand)
File Line Roast
desktop/src/apps/LibraryApp.storage.test.tsx 162 Flaky test: 60 mock items at minimum 100MB each = 6GB, not 50GB+
desktop/src/apps/LibraryApp.tsx 158 deriveMockStorageData recalculates on every render — memoize it
desktop/src/apps/LibraryApp.tsx 126 fmtBytes uses binary math but decimal labels (KB/MB/GB)
desktop/src/apps/LibraryApp.tsx 1046 Table headers missing scope="col" for screen readers
desktop/src/apps/LibraryApp.tsx 1062 Duplicate role="progressbar" on nested elements
desktop/src/apps/LibraryApp.tsx 1034 Decorative AlertCircle icon missing aria-hidden="true"
desktop/src/apps/LibraryApp.tsx 1090 Duplicate role="progressbar" on "By item" progress bars
desktop/src/apps/LibraryApp.storage.test.tsx 115 No test for empty-items storage view branch
desktop/src/apps/LibraryApp.tsx 120 50 * 1024 * 1024 * 1024 — use 50 * 1024 ** 3

🏆 Best part: The storage accounting UI is genuinely well-designed — clean tables, progress bars with color thresholds, and the "Paused at cap" warning is a nice UX touch. The mock data approach is pragmatic for a demo view.

💀 Worst part: That flaky test. It looks like it tests the cap warning, but 60 items × 100MB minimum = 6GB. You'd need ~500 items at minimum size to hit 50GB. The test passes only by hash lottery.

📊 Overall: Like a beautifully plated dish with a hair in it — the presentation (UI) is great, but the test reliability needs fixing before this leaves the kitchen.

Files Reviewed (2 files)
  • desktop/src/apps/LibraryApp.tsx - 7 issues
  • desktop/src/apps/LibraryApp.storage.test.tsx - 2 issues

Fix these issues in Kilo Cloud


Reviewed by nemotron-3-ultra-550b-a55b:free · Input: 90.4K · Output: 9.8K · Cached: 483.1K

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant