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>
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 => { | ||
| 29 | 29 | if (!p.exists) return { tone: 'accent', label: 'Folder missing' } |
| 30 | 30 | return summaries.value[p.id]?.status ?? null |
| 31 | 31 | } |
| 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 | |
| 34 | 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 | 34 | </script> |
| 36 | 35 | |
| @@ -52,7 +51,7 @@ const countOf = (p: ProjectSummary) => !p.exists ? '—' : summaries.value[p.id] | ||
| 52 | 51 | <div class="truncate font-mono text-[11px] text-sk-faint">{{ summaries.home?.root ?? '~' }}</div> |
| 53 | 52 | </div> |
| 54 | 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 | 55 | <span class="truncate text-sk-muted">{{ summaries.home ? (summaries.home.presets.join(', ') || '—') : '' }}</span> |
| 57 | 56 | <span class="font-mono text-xs text-sk-muted">{{ summaries.home?.items ?? '' }}</span> |
| 58 | 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 | 77 | <div class="truncate font-medium">{{ p.name }}</div> |
| 79 | 78 | <div class="truncate font-mono text-[11px] text-sk-faint" :title="p.path">{{ p.path }}</div> |
| 80 | 79 | </div> |
| 81 | - <span class="truncate text-sk-muted">{{ agentsOf(p) }}</span> | |
| 80 | + <span class="text-sk-muted"><SkAgentIcons :agents="agentsOf(p)" /></span> | |
| 82 | 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 | 82 | <span class="font-mono text-xs text-sk-muted">{{ countOf(p) }}</span> |
| 84 | 83 | <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 ?? null | 30 | return summaries.value[p.id]?.status ?? null |
| 31 | } | 31 | } |
| 32 | -const agentsOf = (p: ProjectSummary) => summaries.value[p.id]?.agents | 32 | +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) { | ||
| 151 | 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 | 154 | const patterns = computed(() => { |
| 157 | 155 | const c = scope.config.value |
| 158 | 156 | if (!c) return [] |
| @@ -197,7 +195,6 @@ function confirmPicker() { | ||
| 197 | 195 | <template> |
| 198 | 196 | <SkSection :title="home ? 'Items in Global' : 'Items in this project'"> |
| 199 | 197 | <template #actions> |
| 200 | - <span v-if="hint" class="hidden text-pretty text-sk-muted sm:inline">{{ hint }}</span> | |
| 201 | 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 | 199 | <SkButton v-if="importable.length" :label="`Import all to catalog (${importable.length})`" @click="scope.askImport(importable)" /> |
| 203 | 200 | <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.value | 155 | 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 @@ | ||
| 1 | 1 | <script setup lang="ts"> |
| 2 | 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 | 5 | const scope = useScopeContext() |
| 6 | 6 | const AGENT_LIST: AgentId[] = ['claude', 'codex'] |
| 7 | 7 | |
| 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 ?? []) | |
| 12 | 9 | |
| 13 | 10 | function toggle(a: AgentId, on: boolean | 'indeterminate') { |
| 14 | 11 | scope.edit((c) => { |
| @@ -23,9 +20,10 @@ function toggle(a: AgentId, on: boolean | 'indeterminate') { | ||
| 23 | 20 | type="button" |
| 24 | 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 | 22 | :class="scope.config.value?.targets.length ? 'text-sk-faint' : 'text-sk-accent-text'" |
| 23 | + :title="targets.map(a => AGENT_LABELS[a]).join(', ')" | |
| 26 | 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 | 27 | <UIcon name="i-lucide-chevron-down" class="size-3" /> |
| 30 | 28 | </button> |
| 31 | 29 | <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' | |
| 2 | 2 | import type { Tone } from '../utils/status' |
| 3 | 3 | |
| 4 | 4 | export interface ScopeSummary { |
| 5 | 5 | managed: boolean |
| 6 | - agents: string | |
| 6 | + agents: AgentId[] | |
| 7 | 7 | presets: string[] |
| 8 | 8 | items: number |
| 9 | 9 | status: { tone: Tone, label: string } |
| @@ -35,8 +35,7 @@ export function useScopeSummaries() { | ||
| 35 | 35 | const entries = resolveItems(catalog.presets.value, cfg.presets, cfg.add).entries |
| 36 | 36 | items = entries.filter(e => !matchIn(cfg.exclude[e.type], e.name)).length |
| 37 | 37 | } |
| 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 | |
| 40 | 39 | const status = !cfg ? { tone: 'faint' as Tone, label: 'Not managed' } : summarizePlan(s.plan) |
| 41 | 40 | const pending = cfg ? new Set((s.plan?.actions ?? []).filter(isPending).map(a => `${a.type}/${a.name}`)).size : 0 |
| 42 | 41 | 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: boolean | 5 | managed: boolean |
| 6 | - agents: string | 6 | + agents: AgentId[] |
| 7 | presets: string[] | 7 | presets: string[] |
| 8 | items: number | 8 | 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).entries | 35 | const entries = resolveItems(catalog.presets.value, cfg.presets, cfg.add).entries |
| 36 | items = entries.filter(e => !matchIn(cfg.exclude[e.type], e.name)).length | 36 | items = entries.filter(e => !matchIn(cfg.exclude[e.type], e.name)).length |
| 37 | } | 37 | } |
| 38 | - const targets = cfg?.targets ?? inv.agentsDetected | 38 | + 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 : 0 | 40 | 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 { | ||
| 45 | 45 | if (a.type === 'rule' || a.type === 'agent') return a.files.map(norm).find(f => f.endsWith(`/${a.name}.md`)) ?? null |
| 46 | 46 | 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 | -} | |
| @@ -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`)) ?? null | 45 | if (a.type === 'rule' || a.type === 'agent') return a.files.map(norm).find(f => f.endsWith(`/${a.name}.md`)) ?? null |
| 46 | return null | 46 | 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 @@ | ||
| 12 | 12 | "@fontsource/ibm-plex-mono": "^5.3.0", |
| 13 | 13 | "@fontsource/instrument-sans": "^5.3.0", |
| 14 | 14 | "@iconify-json/lucide": "^1.2.0", |
| 15 | + "@iconify-json/simple-icons": "^1.2.100", | |
| 15 | 16 | "@nuxt/ui": "^4.12.0", |
| 16 | 17 | "@skilly/core": "workspace:*", |
| 17 | 18 | "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' | ||
| 3 | 3 | import { buildRows, matchIn, mergeStatuses } from '../app/utils/scopeRows' |
| 4 | 4 | import { manageInConfig } from '../app/utils/scopeConfig' |
| 5 | 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 | 8 | const config = (over: Partial<ScopeConfig> = {}): ScopeConfig => ({ |
| 9 | 9 | version: 1, targets: ['claude'], presets: ['base'], add: { skill: ['extra'] }, exclude: {}, onDemand: { skills: ['seo-*'] }, |
| @@ -125,7 +125,4 @@ describe('plan view helpers', () => { | ||
| 125 | 125 | expect(s).toEqual({ tone: 'accent', label: '1 drift · 1 update' }) |
| 126 | 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 | }) |
| @@ -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: | ||
| 65 | 65 | '@iconify-json/lucide': |
| 66 | 66 | specifier: ^1.2.0 |
| 67 | 67 | version: 1.2.147 |
| 68 | + '@iconify-json/simple-icons': | |
| 69 | + specifier: ^1.2.100 | |
| 70 | + version: 1.2.100 | |
| 68 | 71 | '@nuxt/ui': |
| 69 | 72 | specifier: ^4.12.0 |
| 70 | 73 | version: 4.12.0(5b14c4ff6cb0908ddbfab51a24207b59) |
| @@ -607,6 +610,9 @@ packages: | ||
| 607 | 610 | '@iconify-json/lucide@1.2.147': |
| 608 | 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 | 616 | '@iconify/collections@1.0.754': |
| 611 | 617 | resolution: {integrity: sha512-5AJQgPgjBFGwsQGyDjySYvkzX2tU4GO+blKb1HExUuiha3rhvUvw5rvQxZ/WBX3YJFl4kqqV3gcjpdidsXhH/w==} |
| 612 | 618 | |
| @@ -4673,6 +4679,10 @@ snapshots: | ||
| 4673 | 4679 | dependencies: |
| 4674 | 4680 | '@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 | 4686 | '@iconify/collections@1.0.754': |
| 4677 | 4687 | dependencies: |
| 4678 | 4688 | '@iconify/types': 2.0.0 |
| @@ -65,6 +65,9 @@ importers: | |||
| 65 | '@iconify-json/lucide': | 65 | '@iconify-json/lucide': |
| 66 | specifier: ^1.2.0 | 66 | specifier: ^1.2.0 |
| 67 | version: 1.2.147 | 67 | 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.0 | 72 | 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.0 | 4680 | '@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.0 | 4688 | '@iconify/types': 2.0.0 |