feat(dashboard): wire skills autocomplete configuration to session cards
Connect the skills enumeration API to session card input fields for slash command autocomplete: App.js: - Add skillsConfig state for Claude and Codex skill configs - Fetch skills for both agent types on mount using Promise.all - Pass agent-appropriate autocompleteConfig to each SessionCard SessionCard.js: - Accept autocompleteConfig prop and forward to SimpleInput - Move context usage display from header to footer status bar for better information hierarchy (activity indicator + context together) SimpleInput.js: - Fix autocomplete dropdown padding (py-2 -> py-1.5) - Fix font inheritance (add font-mono to skill name) - Fix description tooltip whitespace handling (add font-sans, whitespace-normal) SpawnModal.js: - Add SPAWN_TIMEOUT_MS (2x default) to handle pending spawn registry wait time plus session file confirmation polling AgentActivityIndicator.js: - Minor styling refinement for status display Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -92,7 +92,7 @@ export function AgentActivityIndicator({ session }) {
|
||||
const tokenDisplay = formatTokens(turnTokens);
|
||||
|
||||
return html`
|
||||
<div class="flex items-center gap-2 px-3 py-2 border-b border-selection/50 bg-bg/60 font-mono text-label">
|
||||
<div class="flex items-center gap-2 font-mono text-label">
|
||||
${isActive && html`
|
||||
<span class="activity-spinner"></span>
|
||||
`}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { html, useState, useEffect, useCallback, useMemo, useRef } from '../lib/preact.js';
|
||||
import { API_STATE, API_STREAM, API_DISMISS, API_DISMISS_DEAD, API_RESPOND, API_CONVERSATION, API_HEALTH, POLL_MS, fetchWithTimeout } from '../utils/api.js';
|
||||
import { API_STATE, API_STREAM, API_DISMISS, API_DISMISS_DEAD, API_RESPOND, API_CONVERSATION, API_HEALTH, POLL_MS, fetchWithTimeout, fetchSkills } from '../utils/api.js';
|
||||
import { groupSessionsByProject } from '../utils/status.js';
|
||||
import { Sidebar } from './Sidebar.js';
|
||||
import { SessionCard } from './SessionCard.js';
|
||||
@@ -23,6 +23,7 @@ export function App() {
|
||||
const [zellijAvailable, setZellijAvailable] = useState(true);
|
||||
const [newlySpawnedIds, setNewlySpawnedIds] = useState(new Set());
|
||||
const pendingSpawnIdsRef = useRef(new Set());
|
||||
const [skillsConfig, setSkillsConfig] = useState({ claude: null, codex: null });
|
||||
|
||||
// Background conversation refresh with error tracking
|
||||
const refreshConversationSilent = useCallback(async (sessionId, projectDir, agent = 'claude') => {
|
||||
@@ -353,6 +354,18 @@ export function App() {
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// Fetch skills for autocomplete on mount
|
||||
useEffect(() => {
|
||||
const loadSkills = async () => {
|
||||
const [claude, codex] = await Promise.all([
|
||||
fetchSkills('claude'),
|
||||
fetchSkills('codex')
|
||||
]);
|
||||
setSkillsConfig({ claude, codex });
|
||||
};
|
||||
loadSkills();
|
||||
}, []);
|
||||
|
||||
// Group sessions by project
|
||||
const projectGroups = groupSessionsByProject(sessions);
|
||||
|
||||
@@ -524,6 +537,7 @@ export function App() {
|
||||
onRespond=${respondToSession}
|
||||
onDismiss=${dismissSession}
|
||||
isNewlySpawned=${newlySpawnedIds.has(session.session_id)}
|
||||
autocompleteConfig=${skillsConfig[session.agent === 'codex' ? 'codex' : 'claude']}
|
||||
/>
|
||||
`)}
|
||||
</div>
|
||||
@@ -577,6 +591,7 @@ export function App() {
|
||||
onFetchConversation=${fetchConversation}
|
||||
onRespond=${respondToSession}
|
||||
onDismiss=${dismissSession}
|
||||
autocompleteConfig=${skillsConfig[session.agent === 'codex' ? 'codex' : 'claude']}
|
||||
/>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
@@ -116,13 +116,6 @@ export function SessionCard({ session, onClick, conversation, onFetchConversatio
|
||||
</span>
|
||||
`}
|
||||
</div>
|
||||
${contextUsage && html`
|
||||
<div class="mt-2 inline-flex max-w-full items-center gap-2 rounded-lg border border-selection/80 bg-bg/45 px-2.5 py-1.5 font-mono text-label text-dim" title=${contextUsage.title}>
|
||||
<span class="text-bright">${contextUsage.headline}</span>
|
||||
<span class="truncate">${contextUsage.detail}</span>
|
||||
${contextUsage.trail && html`<span class="text-dim/80">${contextUsage.trail}</span>`}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 shrink-0 pt-0.5">
|
||||
<span class="font-mono text-xs tabular-nums text-dim">${formatDuration(session.started_at)}</span>
|
||||
@@ -146,9 +139,20 @@ export function SessionCard({ session, onClick, conversation, onFetchConversatio
|
||||
<${ChatMessages} messages=${conversation || []} status=${session.status} limit=${enlarged ? null : 20} />
|
||||
</div>
|
||||
|
||||
<!-- Card Footer (Activity + Input/Questions) -->
|
||||
<!-- Card Footer (Status + Input/Questions) -->
|
||||
<div class="shrink-0 border-t border-selection/70 bg-bg/55">
|
||||
<!-- Session Status Area -->
|
||||
<div class="flex items-center justify-between gap-3 px-4 py-2 border-b border-selection/50 bg-bg/60">
|
||||
<${AgentActivityIndicator} session=${session} />
|
||||
${contextUsage && html`
|
||||
<div class="flex items-center gap-2 rounded-lg border border-selection/80 bg-bg/45 px-2.5 py-1.5 font-mono text-label text-dim" title=${contextUsage.title}>
|
||||
<span class="text-bright">${contextUsage.headline}</span>
|
||||
<span class="truncate">${contextUsage.detail}</span>
|
||||
${contextUsage.trail && html`<span class="text-dim/80">${contextUsage.trail}</span>`}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<!-- Actions Area -->
|
||||
<div class="p-4">
|
||||
${hasQuestions ? html`
|
||||
<${QuestionBlock}
|
||||
|
||||
@@ -195,7 +195,7 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig =
|
||||
` : filteredSkills.map((skill, i) => html`
|
||||
<div
|
||||
key=${skill.name}
|
||||
class="px-3 py-2 cursor-pointer text-sm transition-colors ${
|
||||
class="group relative px-3 py-1.5 cursor-pointer text-sm font-mono transition-colors ${
|
||||
i === selectedIndex
|
||||
? 'bg-selection/50 text-bright'
|
||||
: 'text-fg hover:bg-selection/25'
|
||||
@@ -203,10 +203,12 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig =
|
||||
onClick=${() => insertSkill(skill)}
|
||||
onMouseEnter=${() => setSelectedIndex(i)}
|
||||
>
|
||||
<div class="font-medium font-mono text-bright">
|
||||
${autocompleteConfig.trigger}${skill.name}
|
||||
${i === selectedIndex && skill.description && html`
|
||||
<div class="absolute left-full top-0 ml-2 w-64 px-2.5 py-1.5 rounded-md border border-selection/75 bg-surface shadow-lg text-micro text-dim font-sans whitespace-normal z-50">
|
||||
${skill.description}
|
||||
</div>
|
||||
<div class="text-micro text-dim truncate">${skill.description}</div>
|
||||
`}
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { html, useState, useEffect, useCallback, useRef } from '../lib/preact.js';
|
||||
import { API_PROJECTS, API_SPAWN, fetchWithTimeout } from '../utils/api.js';
|
||||
import { API_PROJECTS, API_SPAWN, fetchWithTimeout, API_TIMEOUT_MS } from '../utils/api.js';
|
||||
|
||||
// Spawn needs longer timeout: pending spawn registry requires discovery cycle to run,
|
||||
// plus server polls for session file confirmation
|
||||
const SPAWN_TIMEOUT_MS = API_TIMEOUT_MS * 2;
|
||||
|
||||
export function SpawnModal({ isOpen, onClose, onSpawn, currentProject }) {
|
||||
const [projects, setProjects] = useState([]);
|
||||
@@ -95,7 +99,7 @@ export function SpawnModal({ isOpen, onClose, onSpawn, currentProject }) {
|
||||
'Authorization': `Bearer ${window.AMC_AUTH_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({ project, agent_type: agentType }),
|
||||
});
|
||||
}, SPAWN_TIMEOUT_MS);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.ok) {
|
||||
|
||||
Reference in New Issue
Block a user