feat(design): surface phase PR 1 — Lucide icon migration + scale enforcement

Replaces every hand-inlined SVG in the chrome (60 across 15 files)
with Lucide components. Snaps icon sizes to the doc's 16/24 scale —
all icons in this pass land at 16. AppLogo wordmark stays as the
legitimate "custom app icon" exception per the doc; GraphView's <svg>
mount point for D3 stays as well.

Replaces emoji-as-icons in clear button-affordance cases with their
Lucide equivalents: ✕ → X (close/dismiss buttons across 8 files), ✓ →
Check (ProjectView task-advance button), 🎤 → Mic (ChatPanel voice
CTA), 📎 → Paperclip (ChatView context toggle), &uarr; → ArrowUp
(ChatInputBar Send), &times; → X (ChatPanel context-note remove), ☀/☾
→ Sun/Moon (AppHeader theme toggle).

Inline emoji punctuation in textual confirmation copy ("Saved ✓",
"Created ✓", `done: "✓"` status maps, `'✓' : '📄'` interpolations)
is left for surface-phase voice/tone touchups — replacing requires
structural template changes and is best done per-component.

Stroke weight stays at Lucide's default 2px; tightening to the doc's
1.5/1 is deferred per the surface-phase spec (option ii: drop-in +
scale enforcement).

Adds lucide-vue-next ^0.469.0 to frontend/package.json. Docker
rebuild handles the install.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 19:56:01 -04:00
parent 54710b35e9
commit 93a3beb5d6
20 changed files with 164 additions and 131 deletions
+31 -20
View File
@@ -7,6 +7,20 @@ import { useChatStore } from "@/stores/chat";
import GraphView from "@/views/GraphView.vue";
import ChatPanel from "@/components/ChatPanel.vue";
import ChatInputBar from "@/components/ChatInputBar.vue";
import {
FileText,
CheckSquare,
User,
MapPin,
List,
Search,
Share2,
ChevronLeft,
ChevronRight,
ChevronUp,
ChevronDown,
X,
} from "lucide-vue-next";
const router = useRouter();
const chatStore = useChatStore();
@@ -426,23 +440,23 @@ onUnmounted(() => {
</button>
<div v-if="newNoteMenuOpen" class="new-note-menu">
<button @click="createNew('note')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
<FileText :size="16" />
Note
</button>
<button @click="createNew('task')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
<CheckSquare :size="16" />
Task
</button>
<button @click="createNew('person')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<User :size="16" />
Person
</button>
<button @click="createNew('place')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<MapPin :size="16" />
Place
</button>
<button @click="createNew('list')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
<List :size="16" />
List
</button>
</div>
@@ -493,7 +507,7 @@ onUnmounted(() => {
<!-- Toolbar -->
<div class="knowledge-toolbar">
<div class="search-wrap">
<svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<Search class="search-icon" :size="16" />
<input
v-model="searchQuery"
@input="onSearchInput"
@@ -509,10 +523,7 @@ onUnmounted(() => {
<option value="type">By type</option>
</select>
<button class="btn-graph" :class="{ active: graphOpen }" @click="toggleGraph" title="Toggle graph view">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/>
<path d="m8.59 13.51 6.83 3.98M15.41 6.51l-6.82 3.98"/>
</svg>
<Share2 :size="16" />
Graph
</button>
</div>
@@ -630,12 +641,12 @@ onUnmounted(() => {
@click="toggleGraphExpand"
:title="graphExpanded ? 'Narrow panel' : 'Expand panel'"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline v-if="graphExpanded" points="15 18 9 12 15 6"/>
<polyline v-else points="9 18 15 12 9 6"/>
</svg>
<ChevronLeft v-if="graphExpanded" :size="16" />
<ChevronRight v-else :size="16" />
</button>
<button class="btn-icon-sm" @click="toggleGraph" title="Close graph">
<X :size="16" />
</button>
<button class="btn-icon-sm" @click="toggleGraph" title="Close graph"></button>
</div>
</div>
<div class="graph-embed">
@@ -656,12 +667,12 @@ onUnmounted(() => {
@click="chatCollapsed = !chatCollapsed"
:title="chatCollapsed ? 'Expand' : 'Collapse'"
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline v-if="chatCollapsed" points="18 15 12 9 6 15"/>
<polyline v-else points="6 9 12 15 18 9"/>
</svg>
<ChevronUp v-if="chatCollapsed" :size="16" />
<ChevronDown v-else :size="16" />
</button>
<button class="btn-icon-sm" @click="closeChat" title="Close chat">
<X :size="16" />
</button>
<button class="btn-icon-sm" @click="closeChat" title="Close chat"></button>
</div>
</div>