diff --git a/docs/superpowers/plans/2026-07-22-slate-gui-redesign.md b/docs/superpowers/plans/2026-07-22-slate-gui-redesign.md
new file mode 100644
index 0000000..29d3e77
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-22-slate-gui-redesign.md
@@ -0,0 +1,1265 @@
+# Slate GUI Redesign Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Restyle the n-link desktop app with a dark-first "Slate" visual language (semantic CSS-variable theming, light/dark via OS preference, Inter type) while keeping the existing three-pane workflow unchanged.
+
+**Architecture:** Introduce a semantic color-token layer as CSS custom properties mapped into Tailwind, then migrate every hardcoded color utility to a token. Replace Element-Plus's `ElPopover` (its only usage) and duplicated button/progress/icon markup with four small in-repo components (`Popover`, `AppButton`, `ProgressBar`, `Icon`) that carry real logic and get unit tests. The remaining work is per-component restyle verified by build + visual check.
+
+**Tech Stack:** Vue 3 (`
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd desktop && npm test -- ProgressBar`
+Expected: PASS — 4 passed.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add n-link-core/components/ProgressBar.vue n-link-core/components/ProgressBar.spec.ts
+git commit -m "feat(ui): add ProgressBar component"
+```
+
+---
+
+## Task 4: AppButton component
+
+**Files:**
+- Create: `n-link-core/components/AppButton.vue`
+- Test: `n-link-core/components/AppButton.spec.ts`
+
+**Interfaces:**
+- Produces: `AppButton.vue` with props `{ variant?: 'primary' | 'secondary' | 'danger' | 'success'; size?: 'md' | 'sm'; disabled?: boolean }` (defaults `'primary'`, `'md'`, `false`). Renders a `` wrapping the default slot, forwards native `click`, and reflects `disabled`.
+
+- [ ] **Step 1: Write the failing test**
+
+Create `n-link-core/components/AppButton.spec.ts`:
+```ts
+import { describe, it, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import AppButton from './AppButton.vue';
+
+describe('AppButton', () => {
+ it('renders slot content', () => {
+ const wrapper = mount(AppButton, { slots: { default: 'Download' } });
+ expect(wrapper.text()).toBe('Download');
+ });
+
+ it('applies the primary accent style by default', () => {
+ const wrapper = mount(AppButton, { slots: { default: 'Go' } });
+ expect(wrapper.get('button').classes()).toContain('bg-accent');
+ });
+
+ it('applies the danger variant', () => {
+ const wrapper = mount(AppButton, {
+ props: { variant: 'danger' }, slots: { default: 'Delete' },
+ });
+ expect(wrapper.get('button').classes()).toContain('bg-danger');
+ });
+
+ it('emits click when enabled', async () => {
+ const wrapper = mount(AppButton, { slots: { default: 'Go' } });
+ await wrapper.get('button').trigger('click');
+ expect(wrapper.emitted('click')).toHaveLength(1);
+ });
+
+ it('reflects the disabled prop', () => {
+ const wrapper = mount(AppButton, {
+ props: { disabled: true }, slots: { default: 'Go' },
+ });
+ expect(wrapper.get('button').attributes('disabled')).toBeDefined();
+ });
+});
+```
+
+- [ ] **Step 2: Run the test to verify it fails**
+
+Run: `cd desktop && npm test -- AppButton`
+Expected: FAIL — cannot resolve `./AppButton.vue`.
+
+- [ ] **Step 3: Write the component**
+
+Create `n-link-core/components/AppButton.vue`:
+```vue
+
+
+
+
+
+
+
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd desktop && npm test -- AppButton`
+Expected: PASS — 5 passed.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add n-link-core/components/AppButton.vue n-link-core/components/AppButton.spec.ts
+git commit -m "feat(ui): add AppButton component"
+```
+
+---
+
+## Task 5: Popover component
+
+**Files:**
+- Create: `n-link-core/components/Popover.vue`
+- Test: `n-link-core/components/Popover.spec.ts`
+
+**Interfaces:**
+- Produces: `Popover.vue` with a `visible` v-model (`defineModel('visible')`), props `{ width?: number | string; placement?: 'bottom-start' | 'bottom-end' | 'top-start' }` (default `'bottom-start'`), a `#trigger` slot (bound `{ visible }`, click toggles), and a default slot for the panel. The panel renders only when visible, carries `data-testid="panel"`, and closes on outside `mousedown` or `Escape`. Consumers can also set `visible` directly (used by FileData's forms).
+
+- [ ] **Step 1: Write the failing test**
+
+Create `n-link-core/components/Popover.spec.ts`:
+```ts
+import { describe, it, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import Popover from './Popover.vue';
+
+function make() {
+ return mount(Popover, {
+ attachTo: document.body,
+ slots: {
+ trigger: 'open ',
+ default: 'panel body
',
+ },
+ });
+}
+
+describe('Popover', () => {
+ it('hides the panel initially', () => {
+ expect(make().find('[data-testid="panel"]').exists()).toBe(false);
+ });
+
+ it('opens when the trigger is clicked', async () => {
+ const wrapper = make();
+ await wrapper.get('.trg').trigger('click');
+ expect(wrapper.find('[data-testid="panel"]').exists()).toBe(true);
+ expect(wrapper.emitted('update:visible')?.at(-1)).toEqual([true]);
+ });
+
+ it('closes on Escape', async () => {
+ const wrapper = make();
+ await wrapper.get('.trg').trigger('click');
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('[data-testid="panel"]').exists()).toBe(false);
+ });
+
+ it('closes on an outside mousedown', async () => {
+ const wrapper = make();
+ await wrapper.get('.trg').trigger('click');
+ document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
+ await wrapper.vm.$nextTick();
+ expect(wrapper.find('[data-testid="panel"]').exists()).toBe(false);
+ });
+
+ it('applies a width style to the panel', async () => {
+ const wrapper = mount(Popover, {
+ attachTo: document.body,
+ props: { width: 200 },
+ slots: { trigger: 'o ', default: 'b
' },
+ });
+ await wrapper.get('.trg').trigger('click');
+ expect(wrapper.get('[data-testid="panel"]').attributes('style')).toContain('width: 200px');
+ });
+});
+```
+
+- [ ] **Step 2: Run the test to verify it fails**
+
+Run: `cd desktop && npm test -- Popover`
+Expected: FAIL — cannot resolve `./Popover.vue`.
+
+- [ ] **Step 3: Write the component**
+
+Create `n-link-core/components/Popover.vue`:
+```vue
+
+
+
+
+
+```
+
+- [ ] **Step 4: Run the test to verify it passes**
+
+Run: `cd desktop && npm test -- Popover`
+Expected: PASS — 5 passed.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add n-link-core/components/Popover.vue n-link-core/components/Popover.spec.ts
+git commit -m "feat(ui): add Popover component to replace element-plus"
+```
+
+---
+
+## Task 6: Icon component
+
+**Files:**
+- Create: `n-link-core/components/Icon.vue`
+- Test: `n-link-core/components/Icon.spec.ts`
+- Modify: `n-link-core/package.json`
+
+**Interfaces:**
+- Produces: `Icon.vue` with props `{ name: string; size?: number }` (default `20`). Renders the named feather icon inline (via `feather.icons[name].toSvg`), so the SVG's `stroke="currentColor"` inherits the surrounding text color. Throws `Error("unknown feather icon: ")` for an unknown name.
+
+- [ ] **Step 1: Declare feather-icons as a dependency of n-link-core**
+
+In `n-link-core/package.json`, add a `dependencies` block (the file currently has only `peerDependencies` and `devDependencies`):
+```json
+ "dependencies": {
+ "feather-icons": "^4.29.2"
+ },
+```
+Run: `npm install` from repo root.
+
+- [ ] **Step 2: Write the failing test**
+
+Create `n-link-core/components/Icon.spec.ts`:
+```ts
+import { describe, it, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import Icon from './Icon.vue';
+
+describe('Icon', () => {
+ it('renders an inline svg that inherits currentColor', () => {
+ const wrapper = mount(Icon, { props: { name: 'refresh-cw' } });
+ expect(wrapper.html()).toContain(' {
+ const wrapper = mount(Icon, { props: { name: 'chevron-right', size: 32 } });
+ expect(wrapper.html()).toContain('width="32"');
+ expect(wrapper.html()).toContain('height="32"');
+ });
+
+ it('throws for an unknown icon name', () => {
+ expect(() => mount(Icon, { props: { name: 'not-a-real-icon' } }))
+ .toThrow(/unknown feather icon/);
+ });
+});
+```
+
+- [ ] **Step 3: Run the test to verify it fails**
+
+Run: `cd desktop && npm test -- Icon`
+Expected: FAIL — cannot resolve `./Icon.vue`.
+
+- [ ] **Step 4: Write the component**
+
+Create `n-link-core/components/Icon.vue`:
+```vue
+
+
+
+
+
+```
+
+- [ ] **Step 5: Run the test to verify it passes**
+
+Run: `cd desktop && npm test -- Icon`
+Expected: PASS — 3 passed.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add n-link-core/components/Icon.vue n-link-core/components/Icon.spec.ts \
+ n-link-core/package.json package-lock.json
+git commit -m "feat(ui): add inline Icon component for themeable feather icons"
+```
+
+---
+
+## Task 7: Restyle DeviceSelect
+
+**Files:**
+- Modify: `n-link-core/components/DeviceSelect.vue`
+
+**Interfaces:**
+- Consumes: `Popover.vue` (Task 5), `Icon.vue` (Task 6), theme tokens (Task 2).
+
+- [ ] **Step 1: Rewrite the component with Popover + Icon + tokens**
+
+Replace the entire contents of `n-link-core/components/DeviceSelect.vue` with:
+```vue
+
+
+
+
+
+
+
+```
+(The `el-popover`, its `element-plus` imports, the `feather-icons/dist/icons/refresh-cw.svg` import, and the second `