anentrypoint-design 0.0.369 → 0.0.370

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ // SkillsConfig — skill list + detail panel, ported from pi-web's
2
+ // SkillsConfig.tsx UX (modal, sidebar list grouped/searchable, detail pane
3
+ // with enable/disable toggle) but rebuilt over freddie's real skill contract,
4
+ // not pi-web's installable-package model:
5
+ //
6
+ // { file, name, description, frontmatter, body, platforms? }
7
+ //
8
+ // (see freddie's AGENTS.md — `src/skills/index.js::listSkills/loadSkill`,
9
+ // `skills/<category>/<name>/SKILL.md` with YAML frontmatter). There is no
10
+ // install/update/search-registry flow here: freddie skills are local
11
+ // filesystem discovery only (`skills/`, `~/.freddie/skills/`), so the only
12
+ // host-facing action is enable/disable. `category` (derived from the skill's
13
+ // path — the directory directly under a `skills/` root) replaces pi-web's
14
+ // source/scope grouping (project/global/path), and `platforms` renders as
15
+ // chips in place of pi-web's version/update-check UI.
16
+ //
17
+ // Usage (consumer wires its own state/fetch, this is presentation-only):
18
+ // SkillsConfig({ skills, selected, onSelect, onToggle, onClose })
19
+ //
20
+ // Props:
21
+ // skills : [{ file, name, description, category?, platforms?, enabled, frontmatter?, body? }]
22
+ // category is derived from `file`'s path when not passed explicitly
23
+ // (segment directly under the nearest `skills` directory).
24
+ // enabled drives the toggle and the sidebar status dot.
25
+ // selected : name of the currently-selected skill, or null
26
+ // loading : bool — sidebar shows a loading row instead of the list
27
+ // error : string|null — sidebar shows this instead of the list
28
+ // busyName : name of the skill currently mid-toggle, or null
29
+ // query : current search text (string) — controlled by the consumer
30
+ // onQuery : (text) => void — fired on search input
31
+ // onSelect : (name) => void
32
+ // onToggle : (skill) => void — fired with the full skill row to flip enabled
33
+ // onClose : () => void
34
+ //
35
+ // No decorative glyphs beyond the kit's Icon SVGs — status communicated by a
36
+ // tone dot + text label, never color alone.
37
+
38
+ import * as webjsx from '../../vendor/webjsx/index.js';
39
+ import { Icon } from './shell.js';
40
+ import { SearchInput } from './content.js';
41
+ const h = webjsx.createElement;
42
+
43
+ const CATEGORY_ORDER = ['software-development', 'ops', 'data', 'planning', 'creative'];
44
+
45
+ function deriveCategory(skill) {
46
+ if (skill.category) return skill.category;
47
+ const file = skill.file || '';
48
+ const parts = file.split(/[/\\]/).filter(Boolean);
49
+ const idx = parts.lastIndexOf('skills');
50
+ if (idx >= 0 && parts.length > idx + 1) return parts[idx + 1];
51
+ return 'other';
52
+ }
53
+
54
+ function statusTone(skill) {
55
+ return skill.enabled === false ? 'neutral' : 'add';
56
+ }
57
+
58
+ function statusLabel(skill) {
59
+ return skill.enabled === false ? 'disabled' : 'enabled';
60
+ }
61
+
62
+ function matchesQuery(skill, query) {
63
+ if (!query) return true;
64
+ const q = query.toLowerCase();
65
+ return (skill.name || '').toLowerCase().includes(q) ||
66
+ (skill.description || '').toLowerCase().includes(q);
67
+ }
68
+
69
+ function SkillSidebarRow({ skill, active, busy, onSelect }) {
70
+ return h('button', {
71
+ type: 'button',
72
+ class: 'ds-plugins-row' + (active ? ' active' : ''),
73
+ onclick: () => onSelect(skill.name),
74
+ 'aria-pressed': active ? 'true' : 'false',
75
+ 'aria-label': skill.name + ': ' + statusLabel(skill),
76
+ },
77
+ h('span', { class: 'ds-plugins-dot tone-' + statusTone(skill), 'aria-hidden': 'true' }),
78
+ h('span', { class: 'ds-plugins-row-body' },
79
+ h('span', { class: 'ds-plugins-row-name' }, skill.name),
80
+ skill.description
81
+ ? h('span', { class: 'ds-plugins-row-meta' }, skill.description)
82
+ : null),
83
+ busy ? h('span', { class: 'ds-plugins-row-busy' }, '…') : null);
84
+ }
85
+
86
+ function SkillDetail({ skill, busy, onToggle }) {
87
+ if (!skill) {
88
+ return h('div', { class: 'ds-plugins-empty', role: 'status' },
89
+ h('span', { 'aria-hidden': 'true' }, Icon('circle-dot', { size: 22 })),
90
+ h('span', {}, 'Select a skill'));
91
+ }
92
+ const platforms = Array.isArray(skill.platforms) ? skill.platforms : [];
93
+ return h('div', { class: 'ds-plugins-detail' },
94
+ h('div', { class: 'ds-plugins-detail-head' },
95
+ h('div', { class: 'ds-plugins-detail-title' },
96
+ h('span', { class: 'ds-plugins-dot tone-' + statusTone(skill), 'aria-hidden': 'true' }),
97
+ h('span', { class: 'name' }, skill.name)),
98
+ h('button', {
99
+ type: 'button',
100
+ class: 'ds-plugins-toggle' + (skill.enabled !== false ? ' on' : ''),
101
+ disabled: busy ? true : null,
102
+ onclick: () => onToggle && onToggle(skill),
103
+ 'aria-pressed': skill.enabled !== false ? 'true' : 'false',
104
+ 'aria-label': skill.enabled !== false ? 'Disable skill' : 'Enable skill',
105
+ }, h('span', { class: 'ds-plugins-toggle-knob' }))),
106
+ skill.description
107
+ ? h('div', { class: 'ds-skills-description' }, skill.description)
108
+ : null,
109
+ h('div', { class: 'ds-plugins-fact-grid' },
110
+ h('div', { class: 'ds-plugins-fact-label' }, 'status'),
111
+ h('div', { class: 'ds-plugins-fact-value tone-text-' + statusTone(skill) }, statusLabel(skill)),
112
+ h('div', { class: 'ds-plugins-fact-label' }, 'category'),
113
+ h('div', { class: 'ds-plugins-fact-value' }, deriveCategory(skill)),
114
+ skill.file ? h('div', { class: 'ds-plugins-fact-label' }, 'path') : null,
115
+ skill.file ? h('div', { class: 'ds-plugins-fact-value ds-plugins-mono' }, skill.file) : null),
116
+ h('div', { class: 'ds-plugins-requires' },
117
+ h('div', { class: 'ds-plugins-group-label' }, 'platforms'),
118
+ platforms.length
119
+ ? h('div', { class: 'ds-plugins-requires-list' },
120
+ ...platforms.map((p) => h('span', { key: p, class: 'ds-plugins-chip' }, p)))
121
+ : h('div', { class: 'ds-plugins-requires-empty' }, 'all platforms')),
122
+ skill.body
123
+ ? h('div', { class: 'ds-skills-body-group' },
124
+ h('div', { class: 'ds-plugins-group-label' }, 'body preview'),
125
+ h('pre', { class: 'ds-skills-body-preview' }, skill.body.slice(0, 2000)))
126
+ : null);
127
+ }
128
+
129
+ export function SkillsConfig({
130
+ skills = [],
131
+ selected = null,
132
+ loading = false,
133
+ error = null,
134
+ busyName = null,
135
+ query = '',
136
+ onQuery,
137
+ onSelect,
138
+ onToggle,
139
+ onClose,
140
+ } = {}) {
141
+ const selectedSkill = skills.find((s) => s.name === selected) || null;
142
+ const filtered = skills.filter((s) => matchesQuery(s, query));
143
+
144
+ // Group filtered skills by category, ordering freddie's five bundled
145
+ // categories first, then any others (custom/`~/.freddie/skills/`) alphabetically.
146
+ const byCategory = new Map();
147
+ for (const s of filtered) {
148
+ const cat = deriveCategory(s);
149
+ if (!byCategory.has(cat)) byCategory.set(cat, []);
150
+ byCategory.get(cat).push(s);
151
+ }
152
+ const otherCats = [...byCategory.keys()].filter((c) => !CATEGORY_ORDER.includes(c)).sort();
153
+ const orderedCats = [...CATEGORY_ORDER.filter((c) => byCategory.has(c)), ...otherCats];
154
+
155
+ const sidebarBody = loading
156
+ ? h('div', { class: 'ds-plugins-sidebar-status' }, 'Loading…')
157
+ : error
158
+ ? h('div', { class: 'ds-plugins-sidebar-status ds-plugins-status-error' }, error)
159
+ : filtered.length === 0
160
+ ? h('div', { class: 'ds-plugins-sidebar-status' }, skills.length === 0 ? 'No skills found' : 'No skills match your search')
161
+ : h('div', { class: 'ds-plugins-list', role: 'listbox', 'aria-label': 'skill list' },
162
+ ...orderedCats.map((cat) => h('div', { key: 'grp-' + cat, class: 'ds-skills-group' },
163
+ h('div', { class: 'ds-skills-group-label' }, cat),
164
+ ...byCategory.get(cat).map((s) => SkillSidebarRow({
165
+ key: s.name,
166
+ skill: s,
167
+ active: selected === s.name,
168
+ busy: busyName === s.name,
169
+ onSelect,
170
+ })))));
171
+
172
+ const footerText = filtered.length + ' skill' + (filtered.length === 1 ? '' : 's') +
173
+ (query ? ' (of ' + skills.length + ')' : '');
174
+
175
+ return h('div', { class: 'ds-plugins-overlay', onclick: (e) => { if (e.target === e.currentTarget && onClose) onClose(); } },
176
+ h('div', { class: 'ds-plugins-modal', role: 'dialog', 'aria-label': 'Skills' },
177
+ h('div', { class: 'ds-plugins-header' },
178
+ h('span', { class: 'ds-plugins-title' }, 'Skills'),
179
+ onClose ? h('button', { type: 'button', class: 'ds-plugins-close', onclick: onClose, 'aria-label': 'Close' }, '×') : null),
180
+ h('div', { class: 'ds-skills-search-row' },
181
+ SearchInput({ value: query, placeholder: 'search skills…', onInput: onQuery, label: 'search skills' })),
182
+ h('div', { class: 'ds-plugins-body' },
183
+ h('div', { class: 'ds-plugins-sidebar' }, sidebarBody),
184
+ h('div', { class: 'ds-plugins-main' },
185
+ SkillDetail({ skill: selectedSkill, busy: busyName === (selectedSkill && selectedSkill.name), onToggle }))),
186
+ h('div', { class: 'ds-plugins-footer' },
187
+ h('span', { class: 'ds-plugins-footer-count' }, footerText))));
188
+ }
@@ -5,6 +5,47 @@ import * as webjsx from '../../vendor/webjsx/index.js';
5
5
  import { Icon } from './shell.js';
6
6
  const h = webjsx.createElement;
7
7
 
8
+ // ---------------------------------------------------------------------------
9
+ // playCompletionCue — short two-tone audio cue (e.g. "agent turn finished"),
10
+ // ported from pi-web's useAudio. This file otherwise covers voice-call UI
11
+ // (PTT/VAD/webcam/queue), not simple one-shot notification tones, so this is
12
+ // the missing piece rather than a duplicate. A single AudioContext is reused
13
+ // (module-scoped) so a browser autoplay-suspended context can be resumed
14
+ // instead of leaking a fresh context per call.
15
+ // ---------------------------------------------------------------------------
16
+ let _cueCtx = null;
17
+ function getCueCtx() {
18
+ if (_cueCtx && _cueCtx.state !== 'closed') return _cueCtx;
19
+ try { _cueCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch { return null; }
20
+ return _cueCtx;
21
+ }
22
+
23
+ export function playCompletionCue() {
24
+ const ctx = getCueCtx();
25
+ if (!ctx) return;
26
+ const play = () => {
27
+ try {
28
+ const now = ctx.currentTime;
29
+ [523.25, 659.25].forEach((freq, i) => {
30
+ const osc = ctx.createOscillator();
31
+ const gain = ctx.createGain();
32
+ osc.connect(gain);
33
+ gain.connect(ctx.destination);
34
+ osc.type = 'sine';
35
+ osc.frequency.value = freq;
36
+ const t = now + i * 0.18;
37
+ gain.gain.setValueAtTime(0, t);
38
+ gain.gain.linearRampToValueAtTime(0.18, t + 0.02);
39
+ gain.gain.exponentialRampToValueAtTime(0.001, t + 0.45);
40
+ osc.start(t);
41
+ osc.stop(t + 0.45);
42
+ });
43
+ } catch { /* swallow: AudioContext/oscillator unsupported, cue is best-effort */ }
44
+ };
45
+ if (ctx.state === 'suspended') { ctx.resume().then(play).catch(() => {}); return; }
46
+ play();
47
+ }
48
+
8
49
  function fmtDur(s) {
9
50
  s = Math.max(0, Math.round(Number(s) || 0));
10
51
  if (s < 60) return s + 's';
package/src/components.js CHANGED
@@ -27,6 +27,8 @@ export {
27
27
 
28
28
  export { AgentChat, MESSAGE_CAP } from './components/agent-chat.js';
29
29
 
30
+ export { ChatMinimap, CHAT_MINIMAP_WIDTH } from './components/chat-minimap.js';
31
+
30
32
  export {
31
33
  ConversationList, SessionCard, SessionDashboard, SessionMeta, fmtDuration, fmtTime, fmtAgo, AgentListSkeleton
32
34
  } from './components/sessions.js';
@@ -39,6 +41,12 @@ export { GitStatusPanel, GitDiffView } from './components/git-status.js';
39
41
 
40
42
  export { WorktreeSwitcher } from './components/worktree-switcher.js';
41
43
 
44
+ export { PluginsConfig } from './components/plugins-config.js';
45
+
46
+ export { SkillsConfig } from './components/skills-config.js';
47
+
48
+ export { ModelsConfig } from './components/models-config.js';
49
+
42
50
  export {
43
51
  DEFAULT_PHASES, PhaseWalk, TreeNode, BarRow, RateCell,
44
52
  StatTile, StatsGrid, SubGrid, SessionRow, DevRow, LiveLogEntry, LiveLog
@@ -66,7 +74,8 @@ export {
66
74
  } from './components/community.js';
67
75
 
68
76
  export {
69
- PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue, VoiceControls
77
+ PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue, VoiceControls,
78
+ playCompletionCue
70
79
  } from './components/voice.js';
71
80
 
72
81
  export { ThemeToggle } from './components/theme-toggle.js';
@@ -78,7 +87,8 @@ export {
78
87
  export {
79
88
  useDraggable, useDropTarget, useNumberScrub, usePointerDrag, Reorderable,
80
89
  useKeyboardShortcut, formatShortcut, ShortcutHint, ShortcutList,
81
- useKeyboardShortcutHelp, ShortcutHelpDialog
90
+ useKeyboardShortcutHelp, ShortcutHelpDialog,
91
+ isMobileNow, onMobileChange
82
92
  } from './components/interaction-primitives.js';
83
93
 
84
94
  export {
@@ -152,6 +152,31 @@
152
152
  .chat-bubble.chat-md th { background: color-mix(in oklab, var(--fg) 5%, transparent); font-weight: 600; }
153
153
  .chat-bubble.chat-md hr { border: 0; border-top: var(--bw-hair) solid var(--rule); margin: 12px 0; }
154
154
 
155
+ /* Mermaid diagram blocks — rendered SVG swapped in over the fenced source by
156
+ src/mermaid.js's renderMermaidBlocksUnder(); the source .pre stays in the
157
+ DOM (display:none) behind a source/diagram toggle button. */
158
+ .ds-mermaid-block {
159
+ position: relative; margin: var(--space-2) 0;
160
+ border: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
161
+ border-radius: var(--r-1); background: var(--bg); padding: 12px 16px;
162
+ }
163
+ .ds-mermaid-diagram { overflow-x: auto; display: flex; justify-content: center; }
164
+ .ds-mermaid-diagram svg { max-width: 100%; height: auto; }
165
+ .ds-mermaid-toggle {
166
+ position: absolute; top: 6px; right: 6px;
167
+ font: inherit; font-size: var(--fs-xs); line-height: 1;
168
+ background: color-mix(in oklab, var(--fg) 8%, transparent);
169
+ color: var(--fg); border: none; border-radius: var(--r-1);
170
+ padding: 4px 8px; cursor: pointer;
171
+ }
172
+ .ds-mermaid-toggle:hover { background: color-mix(in oklab, var(--fg) 14%, transparent); }
173
+
174
+ /* Inline/display math — KaTeX HTML swapped into text nodes by
175
+ src/math.js's renderMathBlocksUnder(). Display math centers on its own line;
176
+ inline math sits within running text. */
177
+ .ds-math-display { display: block; margin: var(--space-2) 0; text-align: center; overflow-x: auto; }
178
+ .ds-math-inline { display: inline-block; }
179
+
155
180
  .chat-msg .chat-bubble.chat-code,
156
181
  .chat-msg.you .chat-bubble.chat-code,
157
182
  .chat-bubble.chat-code {
@@ -659,6 +659,24 @@
659
659
  }
660
660
  .ds-preview-text { white-space: pre-wrap; word-break: break-word; }
661
661
  .ds-preview-code code, .ds-preview-text code { font: inherit; color: inherit; background: none; }
662
+ /* wrap-lines toggle (ported from pi-web's DisplayMode word-wrap control) */
663
+ .ds-preview-code.is-wrapped { white-space: pre-wrap; overflow-wrap: anywhere; }
664
+ .ds-preview-wrap-toggle.active { color: var(--paper); background: color-mix(in oklab, var(--paper) 18%, transparent); }
665
+ /* source/preview mode switch (ported from pi-web's markdown/HTML DisplayMode
666
+ tabs) — the host supplies already-rendered, already-sanitized HTML via
667
+ `previewHtml`; this component only toggles visibility. */
668
+ .ds-preview-mode-switch { display: inline-flex; gap: var(--space-1); margin-right: var(--space-1); }
669
+ .ds-preview-mode-btn {
670
+ font-family: var(--ff-mono); font-size: var(--fs-micro);
671
+ padding: var(--space-1) var(--space-2); border-radius: var(--r-1);
672
+ color: var(--paper-3); background: none; border: none; cursor: pointer;
673
+ }
674
+ .ds-preview-mode-btn.active { color: var(--paper); background: color-mix(in oklab, var(--paper) 18%, transparent); }
675
+ .ds-preview-html {
676
+ flex: 1; min-height: 0; overflow: auto;
677
+ padding: var(--space-3) var(--space-4);
678
+ background: var(--bg-0); border-radius: var(--r-2);
679
+ }
662
680
  .ds-preview-truncated {
663
681
  margin-top: var(--space-2); padding-top: var(--space-2);
664
682
  border-top: var(--bw-hair) solid color-mix(in oklab, var(--paper) 20%, transparent);
@@ -0,0 +1,93 @@
1
+ /* ============================================================
2
+ ModelsConfig — provider/model availability panel.
3
+ ============================================================ */
4
+ .ds-mc { display: flex; flex-direction: column; gap: var(--space-3); }
5
+
6
+ .ds-mc-loading, .ds-mc-empty {
7
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
8
+ gap: var(--space-2); padding: var(--space-6) var(--space-3);
9
+ color: var(--fg-3); text-align: center;
10
+ }
11
+ .ds-mc-empty-hint { font-size: var(--fs-xs); color: var(--fg-3); }
12
+
13
+ /* Summary bar: totals + last-checked + refresh/rebuild actions. */
14
+ .ds-mc-summary {
15
+ display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
16
+ flex-wrap: wrap;
17
+ }
18
+ .ds-mc-summary-facts { display: flex; gap: var(--space-3); font-size: var(--fs-sm); color: var(--fg-2); }
19
+ .ds-mc-summary-ts { color: var(--fg-3); }
20
+ .ds-mc-summary-actions { display: flex; align-items: center; gap: var(--space-2); }
21
+ .ds-mc-rebuild-error { font-size: var(--fs-xs); color: var(--flame); }
22
+
23
+ /* Two-pane body: provider tree (left) + model detail (right). */
24
+ .ds-mc-body {
25
+ display: grid; grid-template-columns: minmax(220px, 300px) 1fr; gap: var(--space-3);
26
+ align-items: start;
27
+ }
28
+ @media (max-width: 720px) {
29
+ .ds-mc-body { grid-template-columns: 1fr; }
30
+ }
31
+
32
+ .ds-mc-tree { display: flex; flex-direction: column; gap: 2px; }
33
+ .ds-mc-tree-group + .ds-mc-tree-group { margin-top: 2px; }
34
+
35
+ .ds-mc-key-chip {
36
+ font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 700;
37
+ padding: 1px 6px; border-radius: var(--r-1);
38
+ }
39
+ .ds-mc-key-chip.tone-ok { color: var(--green-2); background: var(--green-tint); }
40
+ .ds-mc-key-chip.tone-fail { color: var(--fg-3); background: var(--bg-3); }
41
+
42
+ .ds-mc-sampler {
43
+ display: inline-block; margin: 2px 0 4px 12px;
44
+ font-size: var(--fs-micro); padding: 1px 6px; border-radius: var(--r-1);
45
+ }
46
+ .ds-mc-sampler.tone-ok { color: var(--green-2); background: var(--green-tint); }
47
+ .ds-mc-sampler.tone-fail { color: var(--ink); background: var(--sun); }
48
+
49
+ /* Detail pane */
50
+ .ds-mc-detail-empty { padding: var(--space-3); color: var(--fg-3); font-size: var(--fs-sm); }
51
+ .ds-mc-detail-head {
52
+ display: flex; align-items: center; gap: var(--space-2);
53
+ margin-bottom: var(--space-2);
54
+ }
55
+ .ds-mc-detail-title { font-family: var(--ff-mono); font-size: var(--fs-base); font-weight: 700; }
56
+
57
+ .ds-mc-discovery-error {
58
+ display: flex; align-items: center; gap: var(--space-1-75);
59
+ padding: var(--space-1-75) var(--space-2);
60
+ margin-bottom: var(--space-2);
61
+ color: var(--flame); background: color-mix(in oklab, var(--flame) 12%, transparent);
62
+ border-radius: var(--r-1); font-size: var(--fs-xs);
63
+ }
64
+
65
+ .ds-mc-model-list { display: flex; flex-direction: column; gap: var(--space-2); }
66
+ .ds-mc-model-row {
67
+ padding: var(--space-2); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-2);
68
+ }
69
+ .ds-mc-model-row.active { border-color: var(--accent); background: var(--accent-tint); }
70
+ .ds-mc-model-head {
71
+ display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
72
+ margin-bottom: var(--space-1-75);
73
+ }
74
+ .ds-mc-model-id { font-family: var(--ff-mono); font-size: var(--fs-sm); font-weight: 600; }
75
+ .ds-mc-model-via { font-size: var(--fs-micro); color: var(--fg-3); }
76
+ .ds-mc-usable-chip {
77
+ font-size: var(--fs-micro); font-weight: 700; padding: 1px 6px; border-radius: var(--r-1);
78
+ margin-left: auto;
79
+ }
80
+ .ds-mc-usable-chip.tone-ok { color: var(--green-2); background: var(--green-tint); }
81
+ .ds-mc-usable-chip.tone-fail { color: var(--fg-3); background: var(--bg-3); }
82
+
83
+ /* 7-mode availability grid — one chip per mode, wraps on narrow panes. */
84
+ .ds-mc-mode-grid { display: flex; flex-wrap: wrap; gap: 4px; }
85
+ .ds-mc-chip {
86
+ font-family: var(--ff-mono); font-size: var(--fs-micro); font-weight: 600;
87
+ padding: 2px 7px; border-radius: var(--r-1);
88
+ border: var(--bw-hair) solid transparent;
89
+ }
90
+ .ds-mc-chip.tone-ok { color: var(--green-2); background: var(--green-tint); }
91
+ .ds-mc-chip.tone-fail { color: var(--on-color); background: var(--flame); }
92
+ .ds-mc-chip.tone-skip { color: var(--fg-3); background: var(--bg-3); }
93
+ .ds-mc-chip.tone-unknown { color: var(--fg-3); background: none; border-color: var(--rule); opacity: 0.6; }
@@ -0,0 +1,139 @@
1
+ /* ============================================================
2
+ PluginsConfig — plugin/extension list + detail modal.
3
+ ============================================================ */
4
+
5
+ .ds-plugins-overlay {
6
+ position: fixed; inset: 0; z-index: var(--z-modal, 1000);
7
+ background: color-mix(in oklab, var(--ink) 35%, transparent);
8
+ display: flex; align-items: center; justify-content: center;
9
+ padding: var(--space-3);
10
+ }
11
+ .ds-plugins-modal {
12
+ width: min(860px, 100%); height: min(76vh, 720px);
13
+ background: var(--bg); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-3);
14
+ display: flex; flex-direction: column; overflow: hidden;
15
+ box-shadow: var(--shadow-3, 0 8px 32px rgba(0,0,0,0.18));
16
+ }
17
+ .ds-plugins-header {
18
+ display: flex; align-items: center; justify-content: space-between;
19
+ padding: var(--space-3) var(--space-4);
20
+ border-bottom: var(--bw-hair) solid var(--rule); flex: 0 0 auto;
21
+ }
22
+ .ds-plugins-title { font-size: var(--fs-md); font-weight: 700; color: var(--fg); }
23
+ .ds-plugins-close {
24
+ background: none; border: none; color: var(--fg-3); cursor: pointer;
25
+ font-size: 20px; line-height: 1; padding: 2px 6px; border-radius: var(--r-1);
26
+ }
27
+ .ds-plugins-close:hover { background: var(--bg-2); color: var(--fg); }
28
+ .ds-plugins-close:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
29
+
30
+ .ds-plugins-body { flex: 1 1 auto; display: flex; overflow: hidden; }
31
+
32
+ .ds-plugins-sidebar {
33
+ width: 245px; flex: 0 0 auto; overflow-y: auto;
34
+ border-right: var(--bw-hair) solid var(--rule); background: var(--bg-2);
35
+ padding: var(--space-2);
36
+ }
37
+ .ds-plugins-sidebar-status {
38
+ padding: var(--space-3) var(--space-2); font-size: var(--fs-sm); color: var(--fg-3);
39
+ }
40
+ .ds-plugins-sidebar-status.ds-plugins-status-error { color: var(--flame); }
41
+ .ds-plugins-list { display: flex; flex-direction: column; gap: 2px; }
42
+
43
+ .ds-plugins-row {
44
+ display: flex; align-items: center; gap: 8px; width: 100%;
45
+ padding: var(--space-2); border-radius: var(--r-2);
46
+ background: none; border: var(--bw-hair) solid transparent; text-align: left; cursor: pointer;
47
+ color: var(--fg); font: inherit;
48
+ transition: background var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease);
49
+ }
50
+ .ds-plugins-row:hover { background: var(--bg-3, var(--bg)); }
51
+ .ds-plugins-row.active { background: var(--accent-tint); border-color: var(--accent); }
52
+ .ds-plugins-row:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
53
+
54
+ .ds-plugins-row-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
55
+ .ds-plugins-row-name {
56
+ font-family: var(--ff-mono); font-size: var(--fs-sm); color: var(--fg);
57
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
58
+ }
59
+ .ds-plugins-row-meta {
60
+ font-size: var(--fs-micro); color: var(--fg-3);
61
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
62
+ }
63
+ .ds-plugins-row-busy { flex: 0 0 auto; font-size: var(--fs-micro); color: var(--fg-3); }
64
+
65
+ .ds-plugins-dot {
66
+ flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
67
+ }
68
+ .ds-plugins-dot.tone-add { background: var(--green-2); }
69
+ .ds-plugins-dot.tone-delete { background: var(--flame); }
70
+ .ds-plugins-dot.tone-neutral { background: var(--fg-3); }
71
+
72
+ .ds-plugins-main { flex: 1 1 auto; overflow-y: auto; padding: var(--space-4); }
73
+
74
+ .ds-plugins-empty {
75
+ height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;
76
+ gap: var(--space-2); color: var(--fg-3);
77
+ }
78
+
79
+ .ds-plugins-detail { display: flex; flex-direction: column; gap: var(--space-4); max-width: 640px; }
80
+ .ds-plugins-detail-head {
81
+ display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap;
82
+ }
83
+ .ds-plugins-detail-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
84
+ .ds-plugins-detail-title .name {
85
+ font-family: var(--ff-mono); font-size: var(--fs-md); font-weight: 600; color: var(--fg);
86
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
87
+ }
88
+ .ds-plugins-version { font-size: var(--fs-micro); color: var(--fg-3); }
89
+
90
+ .ds-plugins-toggle {
91
+ flex: 0 0 auto; width: 40px; height: 22px; border-radius: 11px; border: none; padding: 0;
92
+ background: var(--rule); position: relative; cursor: pointer;
93
+ transition: background var(--dur-snap) var(--ease);
94
+ }
95
+ .ds-plugins-toggle.on { background: var(--accent); }
96
+ .ds-plugins-toggle[disabled] { cursor: wait; opacity: 0.65; }
97
+ .ds-plugins-toggle-knob {
98
+ position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
99
+ background: var(--bg); box-shadow: var(--shadow-1, 0 1px 4px rgba(0,0,0,0.22));
100
+ transition: left var(--dur-snap) var(--ease);
101
+ }
102
+ .ds-plugins-toggle.on .ds-plugins-toggle-knob { left: 21px; }
103
+
104
+ .ds-plugins-fact-grid {
105
+ display: grid; grid-template-columns: minmax(96px, 130px) minmax(0, 1fr);
106
+ gap: 9px var(--space-3); font-size: var(--fs-sm);
107
+ }
108
+ .ds-plugins-fact-label { color: var(--fg-3); }
109
+ .ds-plugins-fact-value { color: var(--fg-2); }
110
+ .ds-plugins-fact-value.ds-plugins-mono { font-family: var(--ff-mono); overflow-wrap: anywhere; }
111
+ .ds-plugins-fact-value.tone-text-add { color: var(--green-2); }
112
+ .ds-plugins-fact-value.tone-text-delete { color: var(--flame); }
113
+ .ds-plugins-fact-value.tone-text-neutral { color: var(--fg-3); }
114
+
115
+ .ds-plugins-group-label {
116
+ font-size: var(--fs-micro); font-weight: 600; color: var(--fg-3);
117
+ text-transform: uppercase; letter-spacing: 0.02em; margin-bottom: 6px;
118
+ }
119
+ .ds-plugins-requires-list { display: flex; flex-wrap: wrap; gap: 6px; }
120
+ .ds-plugins-chip {
121
+ font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--fg-2);
122
+ border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1); padding: 2px 7px;
123
+ }
124
+ .ds-plugins-requires-empty { font-size: var(--fs-sm); color: var(--fg-3); }
125
+
126
+ .ds-plugins-detail-actions { display: flex; gap: var(--space-2); }
127
+ .ds-plugins-btn {
128
+ padding: 6px 12px; background: none; border: var(--bw-hair) solid var(--rule); border-radius: var(--r-2);
129
+ color: var(--fg-2); cursor: pointer; font-size: var(--fs-sm);
130
+ }
131
+ .ds-plugins-btn:hover { background: var(--bg-2); }
132
+ .ds-plugins-btn[disabled] { cursor: not-allowed; opacity: 0.5; }
133
+
134
+ .ds-plugins-footer {
135
+ display: flex; align-items: center; justify-content: flex-end;
136
+ padding: var(--space-2) var(--space-4);
137
+ border-top: var(--bw-hair) solid var(--rule); flex: 0 0 auto;
138
+ }
139
+ .ds-plugins-footer-count { font-size: var(--fs-micro); color: var(--fg-3); }
@@ -0,0 +1,27 @@
1
+ /* ============================================================
2
+ SkillsConfig — skill list + detail panel. Shares chrome with
3
+ PluginsConfig (.ds-plugins-overlay/-modal/-header/-body/-sidebar/-list/
4
+ -row/-dot/-main/-detail/-toggle/-fact-grid/-chip/-footer); the classes
5
+ below are the skills-specific additions only.
6
+ ============================================================ */
7
+
8
+ .ds-skills-search-row {
9
+ padding: var(--space-2) var(--space-4);
10
+ border-bottom: var(--bw-hair) solid var(--rule); flex: 0 0 auto;
11
+ }
12
+
13
+ .ds-skills-group { margin-bottom: var(--space-2); }
14
+ .ds-skills-group-label {
15
+ padding: var(--space-2) var(--space-2) 3px;
16
+ font-size: var(--fs-micro); font-weight: 600; color: var(--fg-3);
17
+ text-transform: uppercase; letter-spacing: 0.06em;
18
+ }
19
+
20
+ .ds-skills-description { font-size: var(--fs-sm); color: var(--fg-2); line-height: 1.5; }
21
+
22
+ .ds-skills-body-group { display: flex; flex-direction: column; }
23
+ .ds-skills-body-preview {
24
+ font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--fg-2);
25
+ background: var(--bg-2); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-2);
26
+ padding: var(--space-3); overflow: auto; max-height: 260px; white-space: pre-wrap; word-break: break-word;
27
+ }