hlassiege/panopliepublic Fork
⑂ 56db8f6
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/panoplie.git
git clone ssh://git@rickub.com/hlassiege/panoplie.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

feat(ui): show agent logos instead of names and drop the redundant "Written to" hint

- New SkAgentIcons (Claude / OpenAI logos from @iconify-json/simple-icons, name as tooltip and aria-label), used in the project header "Writes for" and the Projects table Agents column.
- Remove the "Written to .claude/, .agents/ and .codex/ · committed with the project" hint above the items list: the target folders are already listed in the "Writes for" popover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hlassiege committed 2026-10-23T16:54:05+02:00 Browse files
56db8f6 parent: ea72e5b
modified packages/ui/app/components/projects/ProjectsTable.vue +3 -4
@@ -29,8 +29,7 @@ const rowStatus = (p: ProjectSummary): { tone: Tone, label: string } | null => {
2929 if (!p.exists) return { tone: 'accent', label: 'Folder missing' }
3030 return summaries.value[p.id]?.status ?? null
3131 }
32-const agentsOf = (p: ProjectSummary) => summaries.value[p.id]?.agents
33- ?? (p.agentsDetected.length ? p.agentsDetected.map(a => AGENT_LABELS[a]).join(', ') : '—')
32+const agentsOf = (p: ProjectSummary) => summaries.value[p.id]?.agents ?? p.agentsDetected
3433 const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]?.items ?? Object.entries(p.counts).filter(([t]) => t !== 'instructions').reduce((n, [, c]) => n + c, 0)
3534 </script>
3635
@@ -52,7 +51,7 @@ const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]
5251 <div class="truncate font-mono text-[11px] text-sk-faint">{{ summaries.home?.root ?? '~' }}</div>
5352 </div>
5453 </div>
55- <span class="truncate text-sk-muted">{{ summaries.home?.agents ?? '' }}</span>
54+ <span class="text-sk-muted"><SkAgentIcons v-if="summaries.home" :agents="summaries.home.agents" /></span>
5655 <span class="truncate text-sk-muted">{{ summaries.home ? (summaries.home.presets.join(', ') || '—') : '' }}</span>
5756 <span class="font-mono text-xs text-sk-muted">{{ summaries.home?.items ?? '' }}</span>
5857 <SkStatus v-if="summaries.home" :tone="summaries.home.status.tone" :label="summaries.home.status.label" />
@@ -78,7 +77,7 @@ const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]
7877 <div class="truncate font-medium">{{ p.name }}</div>
7978 <div class="truncate font-mono text-[11px] text-sk-faint" :title="p.path">{{ p.path }}</div>
8079 </div>
81- <span class="truncate text-sk-muted">{{ agentsOf(p) }}</span>
80+ <span class="text-sk-muted"><SkAgentIcons :agents="agentsOf(p)" /></span>
8281 <span class="truncate text-sk-muted" :title="summaries[p.id]?.presets.join(', ')">{{ p.exists && summaries[p.id] ? (summaries[p.id]!.presets.join(', ') || '—') : '' }}</span>
8382 <span class="font-mono text-xs text-sk-muted">{{ countOf(p) }}</span>
8483 <template v-if="rowStatus(p)"><SkStatus :tone="rowStatus(p)!.tone" :label="rowStatus(p)!.label" /></template>
@@ -29,8 +29,7 @@ const rowStatus = (p: ProjectSummary): { tone: Tone, label: string } | null => {
29 if (!p.exists) return { tone: 'accent', label: 'Folder missing' }29 if (!p.exists) return { tone: 'accent', label: 'Folder missing' }
30 return summaries.value[p.id]?.status ?? null30 return summaries.value[p.id]?.status ?? null
31 }31 }
32-const agentsOf = (p: ProjectSummary) => summaries.value[p.id]?.agents32+const agentsOf = (p: ProjectSummary) => summaries.value[p.id]?.agents ?? p.agentsDetected
33- ?? (p.agentsDetected.length ? p.agentsDetected.map(a => AGENT_LABELS[a]).join(', ') : '—')
34 const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]?.items ?? Object.entries(p.counts).filter(([t]) => t !== 'instructions').reduce((n, [, c]) => n + c, 0)33 const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]?.items ?? Object.entries(p.counts).filter(([t]) => t !== 'instructions').reduce((n, [, c]) => n + c, 0)
35 </script>34 </script>
36 35
@@ -52,7 +51,7 @@ const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]
52 <div class="truncate font-mono text-[11px] text-sk-faint">{{ summaries.home?.root ?? '~' }}</div>51 <div class="truncate font-mono text-[11px] text-sk-faint">{{ summaries.home?.root ?? '~' }}</div>
53 </div>52 </div>
54 </div>53 </div>
55- <span class="truncate text-sk-muted">{{ summaries.home?.agents ?? '' }}</span>54+ <span class="text-sk-muted"><SkAgentIcons v-if="summaries.home" :agents="summaries.home.agents" /></span>
56 <span class="truncate text-sk-muted">{{ summaries.home ? (summaries.home.presets.join(', ') || '—') : '' }}</span>55 <span class="truncate text-sk-muted">{{ summaries.home ? (summaries.home.presets.join(', ') || '—') : '' }}</span>
57 <span class="font-mono text-xs text-sk-muted">{{ summaries.home?.items ?? '' }}</span>56 <span class="font-mono text-xs text-sk-muted">{{ summaries.home?.items ?? '' }}</span>
58 <SkStatus v-if="summaries.home" :tone="summaries.home.status.tone" :label="summaries.home.status.label" />57 <SkStatus v-if="summaries.home" :tone="summaries.home.status.tone" :label="summaries.home.status.label" />
@@ -78,7 +77,7 @@ const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id]
78 <div class="truncate font-medium">{{ p.name }}</div>77 <div class="truncate font-medium">{{ p.name }}</div>
79 <div class="truncate font-mono text-[11px] text-sk-faint" :title="p.path">{{ p.path }}</div>78 <div class="truncate font-mono text-[11px] text-sk-faint" :title="p.path">{{ p.path }}</div>
80 </div>79 </div>
81- <span class="truncate text-sk-muted">{{ agentsOf(p) }}</span>80+ <span class="text-sk-muted"><SkAgentIcons :agents="agentsOf(p)" /></span>
82 <span class="truncate text-sk-muted" :title="summaries[p.id]?.presets.join(', ')">{{ p.exists && summaries[p.id] ? (summaries[p.id]!.presets.join(', ') || '—') : '' }}</span>81 <span class="truncate text-sk-muted" :title="summaries[p.id]?.presets.join(', ')">{{ p.exists && summaries[p.id] ? (summaries[p.id]!.presets.join(', ') || '—') : '' }}</span>
83 <span class="font-mono text-xs text-sk-muted">{{ countOf(p) }}</span>82 <span class="font-mono text-xs text-sk-muted">{{ countOf(p) }}</span>
84 <template v-if="rowStatus(p)"><SkStatus :tone="rowStatus(p)!.tone" :label="rowStatus(p)!.label" /></template>83 <template v-if="rowStatus(p)"><SkStatus :tone="rowStatus(p)!.tone" :label="rowStatus(p)!.label" /></template>
modified packages/ui/app/components/scope/ScopeItemsSection.vue +0 -3
@@ -151,8 +151,6 @@ function onManage(r: ScopeRow) {
151151 scope.manage(manageRefsOf(r))
152152 }
153153
154-const hint = computed(() => (scope.config.value ? writtenToHint(scope.config.value.targets, home) : undefined))
155-
156154 const patterns = computed(() => {
157155 const c = scope.config.value
158156 if (!c) return []
@@ -197,7 +195,6 @@ function confirmPicker() {
197195 <template>
198196 <SkSection :title="home ? 'Items in Global' : 'Items in this project'">
199197 <template #actions>
200- <span v-if="hint" class="hidden text-pretty text-sk-muted sm:inline">{{ hint }}</span>
201198 <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let skilly manage them here" @click="scope.manage(manageable)" />
202199 <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" />
203200 <SkButton v-if="managed" label="Add items" :disabled="!pickable.length" @click="openPicker" />
@@ -151,8 +151,6 @@ function onManage(r: ScopeRow) {
151 scope.manage(manageRefsOf(r))151 scope.manage(manageRefsOf(r))
152 }152 }
153 153
154-const hint = computed(() => (scope.config.value ? writtenToHint(scope.config.value.targets, home) : undefined))
155-
156 const patterns = computed(() => {154 const patterns = computed(() => {
157 const c = scope.config.value155 const c = scope.config.value
158 if (!c) return []156 if (!c) return []
@@ -197,7 +195,6 @@ function confirmPicker() {
197 <template>195 <template>
198 <SkSection :title="home ? 'Items in Global' : 'Items in this project'">196 <SkSection :title="home ? 'Items in Global' : 'Items in this project'">
199 <template #actions>197 <template #actions>
200- <span v-if="hint" class="hidden text-pretty text-sk-muted sm:inline">{{ hint }}</span>
201 <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let skilly manage them here" @click="scope.manage(manageable)" />198 <SkButton v-if="manageable.length" :label="`Manage all in catalog (${manageable.length})`" title="These items are identical to the catalog version: let skilly manage them here" @click="scope.manage(manageable)" />
202 <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" />199 <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" />
203 <SkButton v-if="managed" label="Add items" :disabled="!pickable.length" @click="openPicker" />200 <SkButton v-if="managed" label="Add items" :disabled="!pickable.length" @click="openPicker" />
modified packages/ui/app/components/scope/ScopeTargets.vue +4 -6
@@ -1,14 +1,11 @@
11 <script setup lang="ts">
22 import type { AgentId } from '@skilly/core'
33
4-/** "Writes for: Claude Code, Codex" with a popover to change the target agents (ScopeConfig.targets). */
4+/** "Writes for" + agent logos with a popover to change the target agents (ScopeConfig.targets). */
55 const scope = useScopeContext()
66 const AGENT_LIST: AgentId[] = ['claude', 'codex']
77
8-const label = computed(() => {
9- const t = scope.config.value?.targets ?? []
10- return t.length ? t.map(a => AGENT_LABELS[a]).join(', ') : 'no agent'
11-})
8+const targets = computed(() => scope.config.value?.targets ?? [])
129
1310 function toggle(a: AgentId, on: boolean | 'indeterminate') {
1411 scope.edit((c) => {
@@ -23,9 +20,10 @@ function toggle(a: AgentId, on: boolean | 'indeterminate') {
2320 type="button"
2421 class="inline-flex cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent p-0 font-mono text-xs underline decoration-dotted underline-offset-4 hover:text-sk-text focus-visible:outline-2 focus-visible:outline-sk-accent"
2522 :class="scope.config.value?.targets.length ? 'text-sk-faint' : 'text-sk-accent-text'"
23+ :title="targets.map(a => AGENT_LABELS[a]).join(', ')"
2624 aria-label="Change the agents this project writes for"
2725 >
28- Writes for {{ label }}
26+ Writes for <SkAgentIcons :agents="targets" empty="no agent" />
2927 <UIcon name="i-lucide-chevron-down" class="size-3" />
3028 </button>
3129 <template #content>
@@ -1,14 +1,11 @@
1 <script setup lang="ts">1 <script setup lang="ts">
2 import type { AgentId } from '@skilly/core'2 import type { AgentId } from '@skilly/core'
3 3
4-/** "Writes for: Claude Code, Codex" with a popover to change the target agents (ScopeConfig.targets). */4+/** "Writes for" + agent logos with a popover to change the target agents (ScopeConfig.targets). */
5 const scope = useScopeContext()5 const scope = useScopeContext()
6 const AGENT_LIST: AgentId[] = ['claude', 'codex']6 const AGENT_LIST: AgentId[] = ['claude', 'codex']
7 7
8-const label = computed(() => {8+const targets = computed(() => scope.config.value?.targets ?? [])
9- const t = scope.config.value?.targets ?? []
10- return t.length ? t.map(a => AGENT_LABELS[a]).join(', ') : 'no agent'
11-})
12 9
13 function toggle(a: AgentId, on: boolean | 'indeterminate') {10 function toggle(a: AgentId, on: boolean | 'indeterminate') {
14 scope.edit((c) => {11 scope.edit((c) => {
@@ -23,9 +20,10 @@ function toggle(a: AgentId, on: boolean | 'indeterminate') {
23 type="button"20 type="button"
24 class="inline-flex cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent p-0 font-mono text-xs underline decoration-dotted underline-offset-4 hover:text-sk-text focus-visible:outline-2 focus-visible:outline-sk-accent"21 class="inline-flex cursor-pointer items-center gap-1 rounded-md border-0 bg-transparent p-0 font-mono text-xs underline decoration-dotted underline-offset-4 hover:text-sk-text focus-visible:outline-2 focus-visible:outline-sk-accent"
25 :class="scope.config.value?.targets.length ? 'text-sk-faint' : 'text-sk-accent-text'"22 :class="scope.config.value?.targets.length ? 'text-sk-faint' : 'text-sk-accent-text'"
23+ :title="targets.map(a => AGENT_LABELS[a]).join(', ')"
26 aria-label="Change the agents this project writes for"24 aria-label="Change the agents this project writes for"
27 >25 >
28- Writes for {{ label }}26+ Writes for <SkAgentIcons :agents="targets" empty="no agent" />
29 <UIcon name="i-lucide-chevron-down" class="size-3" />27 <UIcon name="i-lucide-chevron-down" class="size-3" />
30 </button>28 </button>
31 <template #content>29 <template #content>
added packages/ui/app/components/sk/SkAgentIcons.vue +23 -0
new file mode 100644
@@ -0,0 +1,23 @@
1+<script setup lang="ts">
2+import type { AgentId } from '@skilly/core'
3+
4+/** Agent logos (Claude Code, Codex) for at-a-glance lists; the name stays available as tooltip and accessible label. */
5+withDefaults(defineProps<{ agents: AgentId[], empty?: string }>(), { empty: '—' })
6+
7+const AGENT_ICONS: Record<AgentId, string> = { claude: 'i-simple-icons-claude', codex: 'i-simple-icons-openai' }
8+</script>
9+
10+<template>
11+ <span v-if="agents.length" class="inline-flex items-center gap-1.5">
12+ <UIcon
13+ v-for="a in agents"
14+ :key="a"
15+ :name="AGENT_ICONS[a]"
16+ class="size-3.5 shrink-0 text-sk-text"
17+ :title="AGENT_LABELS[a]"
18+ role="img"
19+ :aria-label="AGENT_LABELS[a]"
20+ />
21+ </span>
22+ <span v-else>{{ empty }}</span>
23+</template>
new file mode 100644
@@ -0,0 +1,23 @@
1+<script setup lang="ts">
2+import type { AgentId } from '@skilly/core'
3+
4+/** Agent logos (Claude Code, Codex) for at-a-glance lists; the name stays available as tooltip and accessible label. */
5+withDefaults(defineProps<{ agents: AgentId[], empty?: string }>(), { empty: '—' })
6+
7+const AGENT_ICONS: Record<AgentId, string> = { claude: 'i-simple-icons-claude', codex: 'i-simple-icons-openai' }
8+</script>
9+
10+<template>
11+ <span v-if="agents.length" class="inline-flex items-center gap-1.5">
12+ <UIcon
13+ v-for="a in agents"
14+ :key="a"
15+ :name="AGENT_ICONS[a]"
16+ class="size-3.5 shrink-0 text-sk-text"
17+ :title="AGENT_LABELS[a]"
18+ role="img"
19+ :aria-label="AGENT_LABELS[a]"
20+ />
21+ </span>
22+ <span v-else>{{ empty }}</span>
23+</template>
modified packages/ui/app/composables/useScopeSummaries.ts +3 -4
@@ -1,9 +1,9 @@
1-import type { ScopeState } from '@skilly/core'
1+import type { AgentId, ScopeState } from '@skilly/core'
22 import type { Tone } from '../utils/status'
33
44 export interface ScopeSummary {
55 managed: boolean
6- agents: string
6+ agents: AgentId[]
77 presets: string[]
88 items: number
99 status: { tone: Tone, label: string }
@@ -35,8 +35,7 @@ export function useScopeSummaries() {
3535 const entries = resolveItems(catalog.presets.value, cfg.presets, cfg.add).entries
3636 items = entries.filter(e => !matchIn(cfg.exclude[e.type], e.name)).length
3737 }
38- const targets = cfg?.targets ?? inv.agentsDetected
39- const agents = targets.length ? targets.map(a => AGENT_LABELS[a]).join(', ') : '—'
38+ const agents = cfg?.targets ?? inv.agentsDetected
4039 const status = !cfg ? { tone: 'faint' as Tone, label: 'Not managed' } : summarizePlan(s.plan)
4140 const pending = cfg ? new Set((s.plan?.actions ?? []).filter(isPending).map(a => `${a.type}/${a.name}`)).size : 0
4241 return { managed: !!cfg, agents, presets: cfg?.presets ?? [], items, status, root: inv.root, pending, missing: s.plan?.missing.length ?? 0 }
@@ -1,9 +1,9 @@
1-import type { ScopeState } from '@skilly/core'1+import type { AgentId, ScopeState } from '@skilly/core'
2 import type { Tone } from '../utils/status'2 import type { Tone } from '../utils/status'
3 3
4 export interface ScopeSummary {4 export interface ScopeSummary {
5 managed: boolean5 managed: boolean
6- agents: string6+ agents: AgentId[]
7 presets: string[]7 presets: string[]
8 items: number8 items: number
9 status: { tone: Tone, label: string }9 status: { tone: Tone, label: string }
@@ -35,8 +35,7 @@ export function useScopeSummaries() {
35 const entries = resolveItems(catalog.presets.value, cfg.presets, cfg.add).entries35 const entries = resolveItems(catalog.presets.value, cfg.presets, cfg.add).entries
36 items = entries.filter(e => !matchIn(cfg.exclude[e.type], e.name)).length36 items = entries.filter(e => !matchIn(cfg.exclude[e.type], e.name)).length
37 }37 }
38- const targets = cfg?.targets ?? inv.agentsDetected38+ const agents = cfg?.targets ?? inv.agentsDetected
39- const agents = targets.length ? targets.map(a => AGENT_LABELS[a]).join(', ') : '—'
40 const status = !cfg ? { tone: 'faint' as Tone, label: 'Not managed' } : summarizePlan(s.plan)39 const status = !cfg ? { tone: 'faint' as Tone, label: 'Not managed' } : summarizePlan(s.plan)
41 const pending = cfg ? new Set((s.plan?.actions ?? []).filter(isPending).map(a => `${a.type}/${a.name}`)).size : 040 const pending = cfg ? new Set((s.plan?.actions ?? []).filter(isPending).map(a => `${a.type}/${a.name}`)).size : 0
42 return { managed: !!cfg, agents, presets: cfg?.presets ?? [], items, status, root: inv.root, pending, missing: s.plan?.missing.length ?? 0 }41 return { managed: !!cfg, agents, presets: cfg?.presets ?? [], items, status, root: inv.root, pending, missing: s.plan?.missing.length ?? 0 }
modified packages/ui/app/utils/planView.ts +0 -10
@@ -45,13 +45,3 @@ export function comparableFile(a: PlanAction): string | null {
4545 if (a.type === 'rule' || a.type === 'agent') return a.files.map(norm).find(f => f.endsWith(`/${a.name}.md`)) ?? null
4646 return null
4747 }
48-
49-/** Where a scope writes, for the hint "Written to .claude/ and .agents/". */
50-export function writtenToHint(targets: string[], home: boolean): string {
51- const dirs: string[] = []
52- if (targets.includes('claude')) dirs.push(home ? '~/.claude/' : '.claude/')
53- if (targets.includes('codex')) dirs.push(...(home ? ['~/.agents/', '~/.codex/'] : ['.agents/', '.codex/']))
54- if (!dirs.length) return 'No target agent: nothing is written'
55- const list = dirs.length > 1 ? `${dirs.slice(0, -1).join(', ')} and ${dirs[dirs.length - 1]}` : dirs[0]
56- return home ? `Written to ${list}` : `Written to ${list} · committed with the project`
57-}
@@ -45,13 +45,3 @@ export function comparableFile(a: PlanAction): string | null {
45 if (a.type === 'rule' || a.type === 'agent') return a.files.map(norm).find(f => f.endsWith(`/${a.name}.md`)) ?? null45 if (a.type === 'rule' || a.type === 'agent') return a.files.map(norm).find(f => f.endsWith(`/${a.name}.md`)) ?? null
46 return null46 return null
47 }47 }
48-
49-/** Where a scope writes, for the hint "Written to .claude/ and .agents/". */
50-export function writtenToHint(targets: string[], home: boolean): string {
51- const dirs: string[] = []
52- if (targets.includes('claude')) dirs.push(home ? '~/.claude/' : '.claude/')
53- if (targets.includes('codex')) dirs.push(...(home ? ['~/.agents/', '~/.codex/'] : ['.agents/', '.codex/']))
54- if (!dirs.length) return 'No target agent: nothing is written'
55- const list = dirs.length > 1 ? `${dirs.slice(0, -1).join(', ')} and ${dirs[dirs.length - 1]}` : dirs[0]
56- return home ? `Written to ${list}` : `Written to ${list} · committed with the project`
57-}
modified packages/ui/package.json +1 -0
@@ -12,6 +12,7 @@
1212 "@fontsource/ibm-plex-mono": "^5.3.0",
1313 "@fontsource/instrument-sans": "^5.3.0",
1414 "@iconify-json/lucide": "^1.2.0",
15+ "@iconify-json/simple-icons": "^1.2.100",
1516 "@nuxt/ui": "^4.12.0",
1617 "@skilly/core": "workspace:*",
1718 "marked": "^18.1.1",
@@ -12,6 +12,7 @@
12 "@fontsource/ibm-plex-mono": "^5.3.0",12 "@fontsource/ibm-plex-mono": "^5.3.0",
13 "@fontsource/instrument-sans": "^5.3.0",13 "@fontsource/instrument-sans": "^5.3.0",
14 "@iconify-json/lucide": "^1.2.0",14 "@iconify-json/lucide": "^1.2.0",
15+ "@iconify-json/simple-icons": "^1.2.100",
15 "@nuxt/ui": "^4.12.0",16 "@nuxt/ui": "^4.12.0",
16 "@skilly/core": "workspace:*",17 "@skilly/core": "workspace:*",
17 "marked": "^18.1.1",18 "marked": "^18.1.1",
modified packages/ui/test/scope-views.test.ts +1 -4
@@ -3,7 +3,7 @@ import type { InventoryItem, ScopeConfig, SyncPlan } from '@skilly/core'
33 import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows'
44 import { manageInConfig } from '../app/utils/scopeConfig'
55 import { projectSpecific, splitManagedBlock, homeDirOf } from '../app/utils/inventoryView'
6-import { summarizePlan, writtenToHint } from '../app/utils/planView'
6+import { summarizePlan } from '../app/utils/planView'
77
88 const config = (over: Partial<ScopeConfig> = {}): ScopeConfig => ({
99 version: 1, targets: ['claude'], presets: ['base'], add: { skill: ['extra'] }, exclude: {}, onDemand: { skills: ['seo-*'] },
@@ -125,7 +125,4 @@ describe('plan view helpers', () => {
125125 expect(s).toEqual({ tone: 'accent', label: '1 drift · 1 update' })
126126 expect(summarizePlan(null).label).toBe('Up to date')
127127 })
128- it('describes where a scope writes', () => {
129- expect(writtenToHint(['claude', 'codex'], false)).toBe('Written to .claude/, .agents/ and .codex/ · committed with the project')
130- })
131128 })
@@ -3,7 +3,7 @@ import type { InventoryItem, ScopeConfig, SyncPlan } from '@skilly/core'
3 import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows'3 import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows'
4 import { manageInConfig } from '../app/utils/scopeConfig'4 import { manageInConfig } from '../app/utils/scopeConfig'
5 import { projectSpecific, splitManagedBlock, homeDirOf } from '../app/utils/inventoryView'5 import { projectSpecific, splitManagedBlock, homeDirOf } from '../app/utils/inventoryView'
6-import { summarizePlan, writtenToHint } from '../app/utils/planView'6+import { summarizePlan } from '../app/utils/planView'
7 7
8 const config = (over: Partial<ScopeConfig> = {}): ScopeConfig => ({8 const config = (over: Partial<ScopeConfig> = {}): ScopeConfig => ({
9 version: 1, targets: ['claude'], presets: ['base'], add: { skill: ['extra'] }, exclude: {}, onDemand: { skills: ['seo-*'] },9 version: 1, targets: ['claude'], presets: ['base'], add: { skill: ['extra'] }, exclude: {}, onDemand: { skills: ['seo-*'] },
@@ -125,7 +125,4 @@ describe('plan view helpers', () => {
125 expect(s).toEqual({ tone: 'accent', label: '1 drift · 1 update' })125 expect(s).toEqual({ tone: 'accent', label: '1 drift · 1 update' })
126 expect(summarizePlan(null).label).toBe('Up to date')126 expect(summarizePlan(null).label).toBe('Up to date')
127 })127 })
128- it('describes where a scope writes', () => {
129- expect(writtenToHint(['claude', 'codex'], false)).toBe('Written to .claude/, .agents/ and .codex/ · committed with the project')
130- })
131 })128 })
modified pnpm-lock.yaml +10 -0
@@ -65,6 +65,9 @@ importers:
6565 '@iconify-json/lucide':
6666 specifier: ^1.2.0
6767 version: 1.2.147
68+ '@iconify-json/simple-icons':
69+ specifier: ^1.2.100
70+ version: 1.2.100
6871 '@nuxt/ui':
6972 specifier: ^4.12.0
7073 version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59)
@@ -607,6 +610,9 @@ packages:
607610 '@iconify-json/lucide@1.2.147':
608611 resolution: {integrity: sha512-7Qqa/joSBeUxihWsQJx5nKcqMWtV3YrH90Jldxcx4J9ciTLavRUlpexQwRTcx9dcb7CJpISfjZTj42zD/30sqA==}
609612
613+ '@iconify-json/simple-icons@1.2.100':
614+ resolution: {integrity: sha512-536pN2RCEQyy5bYwzayrs4e9egr8cmtVcbdSHLaQl/ULUsihzn1eZ0+Hhn50zgz9cbEotiynKsKQSEjFJ7qKmw==}
615+
610616 '@iconify/collections@1.0.754':
611617 resolution: {integrity: sha512-5AJQgPgjBFGwsQGyDjySYvkzX2tU4GO+blKb1HExUuiha3rhvUvw5rvQxZ/WBX3YJFl4kqqV3gcjpdidsXhH/w==}
612618
@@ -4673,6 +4679,10 @@ snapshots:
46734679 dependencies:
46744680 '@iconify/types': 2.0.0
46754681
4682+ '@iconify-json/simple-icons@1.2.100':
4683+ dependencies:
4684+ '@iconify/types': 2.0.0
4685+
46764686 '@iconify/collections@1.0.754':
46774687 dependencies:
46784688 '@iconify/types': 2.0.0
@@ -65,6 +65,9 @@ importers:
65 '@iconify-json/lucide':65 '@iconify-json/lucide':
66 specifier: ^1.2.066 specifier: ^1.2.0
67 version: 1.2.14767 version: 1.2.147
68+ '@iconify-json/simple-icons':
69+ specifier: ^1.2.100
70+ version: 1.2.100
68 '@nuxt/ui':71 '@nuxt/ui':
69 specifier: ^4.12.072 specifier: ^4.12.0
70 version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59)73 version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59)
@@ -607,6 +610,9 @@ packages:
607 '@iconify-json/lucide@1.2.147':610 '@iconify-json/lucide@1.2.147':
608 resolution: {integrity: sha512-7Qqa/joSBeUxihWsQJx5nKcqMWtV3YrH90Jldxcx4J9ciTLavRUlpexQwRTcx9dcb7CJpISfjZTj42zD/30sqA==}611 resolution: {integrity: sha512-7Qqa/joSBeUxihWsQJx5nKcqMWtV3YrH90Jldxcx4J9ciTLavRUlpexQwRTcx9dcb7CJpISfjZTj42zD/30sqA==}
609 612
613+ '@iconify-json/simple-icons@1.2.100':
614+ resolution: {integrity: sha512-536pN2RCEQyy5bYwzayrs4e9egr8cmtVcbdSHLaQl/ULUsihzn1eZ0+Hhn50zgz9cbEotiynKsKQSEjFJ7qKmw==}
615+
610 '@iconify/collections@1.0.754':616 '@iconify/collections@1.0.754':
611 resolution: {integrity: sha512-5AJQgPgjBFGwsQGyDjySYvkzX2tU4GO+blKb1HExUuiha3rhvUvw5rvQxZ/WBX3YJFl4kqqV3gcjpdidsXhH/w==}617 resolution: {integrity: sha512-5AJQgPgjBFGwsQGyDjySYvkzX2tU4GO+blKb1HExUuiha3rhvUvw5rvQxZ/WBX3YJFl4kqqV3gcjpdidsXhH/w==}
612 618
@@ -4673,6 +4679,10 @@ snapshots:
4673 dependencies:4679 dependencies:
4674 '@iconify/types': 2.0.04680 '@iconify/types': 2.0.0
4675 4681
4682+ '@iconify-json/simple-icons@1.2.100':
4683+ dependencies:
4684+ '@iconify/types': 2.0.0
4685+
4676 '@iconify/collections@1.0.754':4686 '@iconify/collections@1.0.754':
4677 dependencies:4687 dependencies:
4678 '@iconify/types': 2.0.04688 '@iconify/types': 2.0.0