Skip to content

Commit 93548fa

Browse files
committed
Switch from Lucide components to UnoCSS pure-CSS icons
- Replace `@lucide/svelte` (JS components) with UnoCSS Icons preset + `@iconify-json/lucide` (CSS-only, zero runtime) - Icons rendered via CSS `mask-image` with `currentColor` — recolor dynamically via CSS vars - Sizing via scoped CSS classes with explicit `px`, not `em` - Add `uno.config.ts`, UnoCSS Vite plugin, `virtual:uno.css` import in root layout - Update 3 components: `ErrorPane`, `FullList`, `SelectionInfo` - Add thorough icon usage docs in `style-guide.md` § Icons (finding, sizing, coloring, checklist) - Fix stale Tailwind references in `AGENTS.md`, `app.css`, and CLAUDE.md files - Update `knip.json` and `check-css-unused/allowlist.go` for UnoCSS-generated classes
1 parent 1c67861 commit 93548fa

16 files changed

Lines changed: 780 additions & 37 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ Core structure:
6666
- `analytics-dashboard/` - Private metrics dashboard (SvelteKit + CF Pages)
6767
- `desktop/` - The Tauri desktop app
6868
- `test/e2e-linux/` - WebDriverIO + tauri-driver tests (Docker, tests real Tauri app)
69-
- `src/` - Svelte frontend. Uses SvelteKit with static adapter. TypeScript strict mode. Tailwind v4.
69+
- `src/` - Svelte frontend. Uses SvelteKit with static adapter. TypeScript strict mode. Custom CSS with design
70+
tokens.
7071
- `lib/` - Components
7172
- `routes/` - Routes
7273
- `src-tauri/` - Latest Rust, Tauri 2, serde, notify, tokio
@@ -151,6 +152,9 @@ resilience, and common pitfalls.
151152
`withGlobalTauri: true` in dev mode is a security risk.
152153
- ❌ When testing the Tauri app, DO NOT USE THE BROWSER. Use the MCP servers.
153154
- ❌ Don't ignore linter warnings — fix them or justify with a comment.
155+
- **Icons**: We use UnoCSS with the Icons preset (`@iconify-json/lucide`). Icons are pure CSS classes like
156+
`i-lucide:triangle-alert` — no JS imports. See `docs/style-guide.md` § Icons for usage, sizing, coloring, and how to
157+
find new icons. When adding a new icon, also add it to `scripts/check-css-unused/allowlist.go`.
154158
- Always use CSS variables defined in `apps/desktop/src/app.css`. Stylelint catches undefined/hallucinated variables.
155159
- Never use raw `px` values for `font-size`, `border-radius`, `font-family`, or `z-index` >= 10. Use
156160
`var(--font-size-*)`, `var(--radius-*)`, `var(--font-*)`, and `var(--z-*)` tokens. Stylelint enforces this.

apps/desktop/knip.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"webdriverio",
1616
"@crabnebula/tauri-driver",
1717
"@crabnebula/test-runner-backend",
18-
"axe-core"
18+
"axe-core",
19+
"@iconify-json/lucide"
1920
],
2021
"ignoreExportsUsedInFile": true,
2122
"project": ["src/**/*.{ts,svelte}"],

apps/desktop/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"@crabnebula/tauri-plugin-drag": "^2.1.0",
4040
"@leeoniya/ufuzzy": "^1.0.19",
4141
"@logtape/logtape": "^2.0.4",
42-
"@lucide/svelte": "^0.577.0",
4342
"@tauri-apps/api": "^2.10.1",
4443
"@tauri-apps/plugin-dialog": "^2.7.0",
4544
"@tauri-apps/plugin-fs": "^2.5.0",
@@ -54,6 +53,7 @@
5453
"@crabnebula/tauri-driver": "^2.0.9",
5554
"@crabnebula/test-runner-backend": "^0.2.6",
5655
"@eslint/js": "^10.0.1",
56+
"@iconify-json/lucide": "1.2.96",
5757
"@playwright/test": "^1.58.2",
5858
"@srsholmes/tauri-playwright": "^0.2.1",
5959
"@sveltejs/adapter-static": "^3.0.10",
@@ -62,6 +62,7 @@
6262
"@tauri-apps/cli": "^2.10.1",
6363
"@testing-library/svelte": "^5.3.1",
6464
"@types/node": "^25.5.0",
65+
"@unocss/preset-icons": "66.6.6",
6566
"@vitest/coverage-v8": "^4.1.0",
6667
"@wdio/cli": "^9.27.0",
6768
"@wdio/globals": "^9.27.0",
@@ -90,6 +91,7 @@
9091
"tsx": "^4.21.0",
9192
"typescript": "~5.9.3",
9293
"typescript-eslint": "^8.57.1",
94+
"unocss": "66.6.6",
9395
"vite": "^8.0.2",
9496
"vitest": "^4.1.0",
9597
"webdriverio": "^9.27.0"

apps/desktop/src/app.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* See /.interface-design/system.md for the design system specification.
33
*
44
* These variables serve as the single source of truth for all colors, spacing,
5-
* and typography. They can be used in both scoped CSS and Tailwind classes.
5+
* and typography. They are used in scoped CSS throughout the app.
66
*
77
* Future: Can be modified at runtime for user theme customization. */
88

apps/desktop/src/lib/file-explorer/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ and actionable error experience.
164164
Receives a `FriendlyError` struct from Rust (all content is pre-baked on the backend, the frontend doesn't do any error
165165
classification or OS-specific logic):
166166

167-
- **Title**: large text, always in accent color. Lucide icon signals severity: ⚠ `TriangleAlert` in warning color for
168-
transient, ⊘ `CircleAlert` in error color for serious, no icon for needs-action
167+
- **Title**: large text, always in accent color. UnoCSS/Lucide icon signals severity: ⚠ `i-lucide:triangle-alert` in
168+
warning color for transient, ⊘ `i-lucide:circle-alert` in error color for serious, no icon for needs-action
169169
- **Folder path**: shown in secondary text so the user knows exactly which folder is affected
170170
- **Explanation**: rendered as markdown via `snarkdown` — plain-language description of what happened
171171
- **Suggestion**: rendered as markdown — actionable steps, often provider-specific (for example, "Open **MacDroid** and

apps/desktop/src/lib/file-explorer/pane/ErrorPane.svelte

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import { isMacOS } from '$lib/shortcuts/key-capture'
66
import Button from '$lib/ui/Button.svelte'
77
import { renderErrorMarkdown } from './error-pane-utils'
8-
import { TriangleAlert, CircleAlert } from '@lucide/svelte'
98
109
interface Props {
1110
friendly: FriendlyError
@@ -66,13 +65,9 @@
6665
<div class="content">
6766
<h2 class="title">
6867
{#if friendly.category === 'serious'}
69-
<span class="title-icon icon-error">
70-
<CircleAlert size={20} strokeWidth={2} />
71-
</span>
68+
<span class="title-icon icon-error i-lucide:circle-alert" style="width: 20px; height: 20px;"></span>
7269
{:else if friendly.category === 'transient'}
73-
<span class="title-icon icon-warning">
74-
<TriangleAlert size={20} strokeWidth={2} />
75-
</span>
70+
<span class="title-icon icon-warning i-lucide:triangle-alert" style="width: 20px; height: 20px;"></span>
7671
{/if}
7772
{friendly.title}
7873
</h2>

apps/desktop/src/lib/file-explorer/selection/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ In `selection-summary` mode, directory recursive sizes are included in the size
4747
index). The `hasOnlyDirs` branch shows size triads when `totalSize > 0`; when sizes are unavailable (indexing off), it
4848
falls back to showing only dir count and percentage.
4949

50-
Stale indicator (Lucide `Hourglass` icon in accent color) appears in `selection-summary` when `isScanning()` is true and
50+
Stale indicator (UnoCSS/Lucide `i-lucide:hourglass` icon in accent color) appears in `selection-summary` when `isScanning()` is true and
5151
directories are selected, because dir sizes may be incomplete during scanning.
5252

5353
Filename truncation in `file-info` mode uses a ResizeObserver + throwaway `<span>` measurement for middle truncation

apps/desktop/src/lib/file-explorer/selection/SelectionInfo.svelte

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
} from '../views/full-list-utils'
2121
import { isScanning } from '$lib/indexing/index-state.svelte'
2222
import { tooltip } from '$lib/tooltip/tooltip'
23-
import { Hourglass } from '@lucide/svelte'
2423
import type { VolumeSpaceInfo } from '$lib/tauri-commands'
2524
import { formatDiskSpaceStatus } from '../disk-space-utils'
2625
@@ -311,7 +310,7 @@
311310
selected{/if}.
312311
{#if showSelectionStale}
313312
<span class="stale-indicator" use:tooltip={'Updating index — size may change.'}
314-
><Hourglass size={12} color="var(--color-accent)" /></span
313+
><span class="i-lucide:hourglass stale-icon"></span></span
315314
>
316315
{/if}
317316
{:else if hasFiles}
@@ -325,7 +324,7 @@
325324
{pluralize(totalDirs, 'dir', 'dirs')}{/if}.
326325
{#if showSelectionStale}
327326
<span class="stale-indicator" use:tooltip={'Updating index — size may change.'}
328-
><Hourglass size={12} color="var(--color-accent)" /></span
327+
><span class="i-lucide:hourglass stale-icon"></span></span
329328
>
330329
{/if}
331330
{/if}
@@ -390,4 +389,11 @@
390389
vertical-align: middle;
391390
cursor: help;
392391
}
392+
393+
.stale-icon {
394+
/* stylelint-disable-next-line declaration-property-value-disallowed-list -- small icon indicator, not body text */
395+
color: var(--color-accent);
396+
width: 12px;
397+
height: 12px;
398+
}
393399
</style>

apps/desktop/src/lib/file-explorer/views/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ without DOM performance issues.
1515
dual-size display helpers: `getDisplaySize()` (picks logical/physical/smart), `hasSizeMismatch()`,
1616
`buildFileSizeTooltip()`, `buildDirSizeTooltip()`, `buildSelectionSizeTooltip()`
1717
- **FullList.svelte** – Reads `listing.sizeDisplay` (via `getSizeDisplayMode()`) and `listing.sizeMismatchWarning` (via
18-
`getSizeMismatchWarning()`) settings. Uses Lucide `CircleAlert` for size mismatch warnings and `Hourglass` for stale
19-
index indicators
18+
`getSizeMismatchWarning()`) settings. Uses UnoCSS/Lucide `i-lucide:circle-alert` for size mismatch warnings and
19+
`i-lucide:hourglass` for stale index indicators
2020
- **dir-size-display.test.ts** – Tests for `getDirSizeDisplayState` / `buildDirSizeTooltip` (functions in
2121
`full-list-utils.ts`)
2222
- **view-modes.test.ts** – Integration tests for hidden-file filtering and directory listing structure (uses

apps/desktop/src/lib/file-explorer/views/FullList.svelte

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
} from '$lib/settings/reactive-settings.svelte'
4040
import { iconCacheCleared } from '$lib/icon-cache'
4141
import { tooltip } from '$lib/tooltip/tooltip'
42-
import { Hourglass, CircleAlert } from '@lucide/svelte'
4342
import type { RenameState } from '../rename/rename-state.svelte'
4443
4544
interface Props {
@@ -507,7 +506,7 @@
507506
{/each}
508507
{#if indexing}
509508
<span class="size-stale" use:tooltip={'Updating index — size may change.'}
510-
><Hourglass size={12} color="var(--color-accent)" /></span
509+
><span class="i-lucide:hourglass icon-indicator"></span></span
511510
>
512511
{/if}
513512
{#if showSizeMismatchWarning && hasSizeMismatch(file.recursiveSize, file.recursivePhysicalSize)}
@@ -531,7 +530,7 @@
531530
dirTooltipHtml,
532531
}}
533532
>
534-
<CircleAlert size={12} color="var(--color-accent)" />
533+
<span class="i-lucide:circle-alert icon-indicator"></span>
535534
</span>
536535
{/if}
537536
{:else if indexing}
@@ -649,6 +648,13 @@
649648
color: var(--color-text-secondary);
650649
}
651650
651+
.icon-indicator {
652+
/* stylelint-disable-next-line declaration-property-value-disallowed-list -- small icon indicator, not body text */
653+
color: var(--color-accent);
654+
width: 12px;
655+
height: 12px;
656+
}
657+
652658
.size-stale {
653659
display: inline-flex;
654660
align-items: center;

0 commit comments

Comments
 (0)