Skills Management
What This Is
The Skills page is the primary management interface for installed skill packs. It displays all skill packs as expandable cards showing their roles, modules, and workflows. Users can toggle individual roles ON/OFF, expand packs to browse modules and run workflows, install new packs (via Library redirect), uninstall packs, and reload all skill data. User Actions (custom workflows from the /actions directory) appear as a separate section.
The Module Detail View (/skills/{domain}/{module}) is a dedicated sub-page that shows a module's skill documentation status, full workflow list with run buttons, associated roles, and recent execution history.
URL Patterns
| View | URL | Notes |
|---|
| Skills List | /skills | Main list of all installed skill packs + user actions |
| Skill Detail | /skills/{domain} | Same SkillsView component, auto-expands the specified pack |
| Module Detail | /skills/{domain}/{module} | Module-level detail: skill doc, workflows, roles, recent runs |
Legacy redirects: /actions → /skills, /actions/{id} → /skills
Page Structure — Skills List (/skills)
+--[Header: "Skills" + "+ Install" button + Reload icon]--+
| |
| [Skill Pack Card 1] |
| pack-icon + Title + domain·version·counts + badge |
| Chevron (expand/collapse) |
| [Expanded: Roles section (h4)] |
| role-dot + role-name + ON/OFF toggle-btn |
| [Expanded: Modules section (h4)] |
| module-header (button) → module-name + count |
| [Expanded module: module-skill-doc + workflow-list] |
| workflow-item: wf-name + ▶ play button |
| [pack-actions: View Run Logs + Uninstall] |
| |
| [Skill Pack Card 2...] |
| |
| [User Actions Card (.pack-card.user-actions)] |
| ⚡ icon + "User Actions" + action count |
| [Expanded: workflow-list with ▶ + 🗑 per action] |
| |
+----------------------------------------------------------+
Page Structure — Module Detail (/skills/{domain}/{module})
+--[Header: Back to Dashboard + H1 displayName + domain]--+
| |
| [Skill Context section] (only if hasSkillDoc) |
| context-card: "_skill.md" + description |
| |
| [Workflows section] |
| list-card with list-row items: |
| row-id (monospace) + row-title + ▶ Run button |
| |
| [Roles section] (only if hasRoles.length) |
| list-card with list-row items: |
| role filename + ".md" |
| |
| [Recent Runs section] (only if recentRuns > 0) |
| list-card with clickable list-row items: |
| status-icon (✓/✗/—) + workflow_id + time + duration|
| |
+----------------------------------------------------------+
Key Elements — Skills List
Header
| Element | Selector | Notes |
|---|
| Page title | heading "Skills" | H1 in page header |
| Install button | button "+ Install" | .btn.btn-secondary. Navigates to Library page |
| Reload button | button (SVG icon only) | .btn.btn-ghost. Calls /api/reload, refreshes all data |
Skill Pack Card
| Element | Selector | Notes |
|---|
| Pack card | .pack-card | Container for each skill pack |
| Pack header | .pack-header | Clickable div — toggles expansion. Hover changes background |
| Pack icon | .pack-icon | Package emoji (📦) |
| Pack title | h3 inside .pack-info | Pack display name (title field, falls back to name) |
| Pack metadata | .pack-meta | "{domain} · v{version} · {N} modules · {N} workflows · {N} roles". Separator is · (U+00B7 middle dot) |
| Toggle badge | .toggle-badge | ON (green, .on) / OFF (gray, .off) / PARTIAL (amber, .partial). Click toggles ALL roles |
| Chevron | .chevron | SVG arrow, gains .expanded class when open → rotate(180deg) |
Role List (inside expanded pack)
| Element | Selector | Notes |
|---|
| Section heading | h4 "Roles" | Inside .pack-section. Uppercase, small caps style |
| Role item | .role-item | Row container per role |
| Role dot | .role-dot | 8px circle. Green (.enabled) when role.enabled !== false, gray otherwise |
| Role name | .role-name | Text label, flex-1 |
| Role toggle | .toggle-btn | "ON" (green, .on) or "OFF" (gray). Click calls PUT /api/context/roles/{filename} then reloads |
Module List (inside expanded pack)
| Element | Selector | Notes |
|---|
| Section heading | h4 "Modules" | Inside .pack-section |
| Module item | .module-item | Wrapper per module |
| Module header | .module-header | <button> element — clickable, toggles module expansion |
| Module chevron | .chevron inside .module-header | 14px, gains .expanded when module is open |
| Module name | .module-name | Display name, flex-1 |
| Module workflow count | .module-count | "{N} workflow" or "{N} workflows" (pluralized) |
| Skill doc indicator | .module-skill-doc | Only shown when mod.hasSkillDoc is true. Text: "_skill.md: {displayName} — selectors, data model, states" |
Workflow List (inside expanded module)
| Element | Selector | Notes |
|---|
| Workflow item | .workflow-item | Row container. Hover shows surface background |
| Workflow name | .wf-name | Workflow title (wf.title) |
| Play button | button.btn.btn-sm | "▶" text only (no "Play" label). Runs runWorkflow(wf.id, {}) then navigates to Execution view |
Pack Footer Actions
| Element | Selector | Notes |
|---|
| Footer container | .pack-actions | Flex row at bottom of expanded pack body. Has top border |
| View Run Logs | button "View Run Logs" | .btn.btn-ghost.btn-sm. Navigates to run logs view |
| Uninstall | button "Uninstall" | .btn.btn-danger.btn-sm. Triggers browser confirm() dialog |
User Actions Section
| Element | Selector | Notes |
|---|
| User Actions card | .pack-card.user-actions | Separate card, only rendered when userActions.length > 0 |
| Icon | .pack-icon | Lightning bolt emoji (⚡) |
| Title | h3 "User Actions" | Inside .pack-info |
| Metadata | .pack-meta | "Custom workflows from /actions directory · {N} actions" |
| Action play button | button.btn.btn-sm | "▶" — runs the action workflow |
| Action delete button | button.btn.btn-sm.btn-ghost | "🗑" — triggers confirm() dialog then calls deleteAction(action.id) |
| Actions container | .wf-actions | Flex row wrapping play + delete buttons |
Empty State
| Element | Selector | Notes |
|---|
| Empty card | .empty-card | Shown when $skillPacks.length === 0 |
| Message | h3 "No Skill Packs Installed" | Heading inside .empty-card |
| Description | p | "Install a skill pack from the Library to get domain-specific modules, workflows, and roles." |
| Install CTA | button "Go to Library" | .btn.btn-primary. Navigates to /library |
Loading State
| Element | Selector | Notes |
|---|
| Loading text | .loading | Centered. Text is "Loading skills..." (not just "Loading...") |
Key Elements — Module Detail View
Header (Module Detail)
| Element | Selector | Notes |
|---|
| Back button | .back-btn | "← Back to Dashboard" with SVG arrow icon. Calls navigateBack() |
| Module title | .header-title h1 | Module displayName, 24px bold |
| Domain label | .header-domain | Domain string from $viewState.selectedSkillDomain, secondary color |
Skill Context Section
| Element | Selector | Notes |
|---|
| Section | section.section with h2 "Skill Context" | Only rendered when mod.hasSkillDoc is true |
| Context card | .context-card | Flex row, card background, bordered |
| File label | .context-file | "_skill.md" in monospace bold |
| Description | .context-desc | "Selectors, data model, states, gotchas" in secondary color |
Workflows Section
| Element | Selector | Notes |
|---|
| Section | section.section with h2 "Workflows ({N})" | Always rendered when module is found |
| List container | .list-card | Card with border, overflow hidden |
| Workflow row | .list-row | Flex row, bottom-bordered. Last row has no border |
| Workflow ID | .row-id | Monospace, min-width: 120px, muted color |
| Workflow title | .row-title | 14px, primary text color |
| Run button | .run-btn | "▶ Run" text. On hover turns primary color. Calls runWorkflow(wf.id, {}) |
Roles Section
| Element | Selector | Notes |
|---|
| Section | section.section with h2 "Roles" | Only rendered when mod.hasRoles.length > 0 |
| Role row | .list-row | Each role filename displayed as "{role}.md" |
| Role text | .row-title | Role filename string with .md suffix appended |
Recent Runs Section
| Element | Selector | Notes |
|---|
| Section | section.section with h2 "Recent Runs" | Only rendered when recentRuns.length > 0 |
| Run row | .list-row.clickable | Clickable — navigates to Run Log Detail via navigateToRunLogDetail(run.id) |
| Status icon | .status-icon | "✓" (green, .success) / "✗" (red, .failed) / "—" (neutral) |
| Workflow ID | .row-title | Shows run.workflow_id |
| Run meta | .row-meta | Flex row with time-ago and formatted duration |
| Time ago | span inside .row-meta | e.g., "5m ago", "2h ago", "1d ago", "just now" |
| Duration | span inside .row-meta | e.g., "430ms", "12s", "2m 5s" |
Loading States (Module Detail)
| State | Element | Notes |
|---|
| Loading | .loading text "Loading module..." | While fetching module data |
| Not found | .loading text "Module not found" | When module path not found in results |
Data Model
Skill Pack (InstalledPack)
| Field | Type | Notes |
|---|
| name | string | Pack identifier (e.g., "sidebutton-dashboard") |
| domain | string | Domain pattern (e.g., "test-app.venmate.net") |
| title | string | Display name (falls back to name if absent) |
| version | string | Semver (e.g., "2.0.0") |
Skill Pack Detail (SkillPackDetail)
| Field | Type | Notes |
|---|
| roles | RoleContext[] | Array of role objects (see below) |
| workflowCount | number | Total workflow count across all modules |
Role Context (RoleContext)
| Field | Type | Notes |
|---|
| filename | string | Role file basename (used in API path) |
| name | string | Display name |
| match | string | URL/domain match pattern |
| enabled | boolean | true = active. Treated as true when enabled !== false (undefined → enabled) |
| body | string | Role prompt body content |
Skill Module (SkillModule)
| Field | Type | Notes |
|---|
| name | string | Module identifier (used as path segment) |
| path | string | Full path used to match module in fetchSkillModules results |
| displayName | string | Human-readable name shown in UI |
| workflowCount | number | Count of workflows in this module |
| workflows | array | { id: string, title: string }[] — full workflow list |
| hasSkillDoc | boolean | Whether a _skill.md exists for this module |
| hasRoles | string[] | Array of role filenames associated with this module (empty array = no roles) |
User Action (Action)
| Field | Type | Notes |
|---|
| id | string | Action identifier (used as workflow_id when running) |
| title | string | Display name shown in UI |
| description | string | Optional description |
| params | array | Parameter definitions |
Run Log Metadata (RunLogMetadata)
| Field | Type | Notes |
|---|
| id | string | Unique run identifier |
| workflow_id | string | ID of the workflow that ran |
| status | string | "success" / "failed" / other |
| timestamp | string | ISO timestamp string |
| duration_ms | number | Execution duration in milliseconds |
States
Skills List States
| State | Trigger | Visual Indicator |
|---|
| Loading | Initial data fetch | .loading with "Loading skills..." centered |
| Default | Load complete, packs installed | Pack cards collapsed, metadata visible |
| Expanded pack | Click .pack-header | Chevron rotates 180°, roles + modules sections visible |
| Expanded module | Click .module-header button | Module chevron rotates, skill doc indicator + workflow list visible |
| All roles ON | All roles enabled in pack | Green "ON" badge (.toggle-badge.on) |
| All roles OFF | All roles disabled | Gray "OFF" badge (.toggle-badge.off) |
| Partial roles | Mix of enabled/disabled | Amber "PARTIAL" badge (.toggle-badge.partial) |
| No roles | Pack has zero roles | Badge defaults to "OFF" state |
| Empty | $skillPacks.length === 0 | .empty-card with "Go to Library" CTA |
| Running workflow | Click play button | Navigates to Execution view |
Module Detail States
| State | Trigger | Visual Indicator |
|---|
| Loading | Fetching module data | "Loading module..." centered in .loading |
| Module not found | Path not matched in results | "Module not found" centered in .loading |
| Loaded — with skill doc | mod.hasSkillDoc === true | Skill Context section rendered |
| Loaded — no skill doc | mod.hasSkillDoc === false | Skill Context section absent |
| Has roles | mod.hasRoles.length > 0 | Roles section rendered |
| Has recent runs | At least one matching run in last 50 logs | Recent Runs section rendered |
| No recent runs | No matching runs | Recent Runs section absent entirely |
| Running workflow | Click "▶ Run" | Navigates to Execution view |
| Navigating to run | Click run row | Navigates to Run Log Detail view |
Toggle Badge Colors
| State | CSS Class | Background | Text Color | Notes |
|---|
| ON | .toggle-badge.on | var(--color-success-light) | var(--color-success) | All roles enabled |
| OFF | .toggle-badge.off | var(--color-border) | var(--color-text-muted) | All roles disabled or zero roles |
| PARTIAL | .toggle-badge.partial | #fef3c7 (hardcoded) | #d97706 (hardcoded) | Mix of enabled/disabled. NOT using CSS variable |
Common Tasks
- View installed skill packs: Navigate to
/skills. Cards show pack name, domain, version, module/workflow/role counts
- Expand skill pack details: Click
.pack-header → roles and modules sections appear
- Toggle all roles in pack: Click ON/OFF/PARTIAL badge → toggles ALL roles to opposite state (OFF→all on, ON or PARTIAL→all off)
- Toggle individual role: Expand pack → find role → click
.toggle-btn
- Browse module workflows: Expand pack → click
.module-header button → workflow list visible
- Run a workflow from pack: Expand pack → expand module → click "▶" play button → navigates to Execution view
- View module detail: Navigate to
/skills/{domain}/{module} — shows skill doc status, full workflow list, roles, recent runs
- Run a workflow from module detail: Click "▶ Run" button in workflow row
- Navigate to a run log: Click a Recent Runs row → navigates to Run Log Detail
- Install new pack: Click "+ Install" → redirects to Library page
- Uninstall pack: Expand pack → click "Uninstall" in
.pack-actions → confirm browser dialog
- Reload all data: Click reload icon in header → all packs/roles/workflows refreshed from disk
- View run logs: Click "View Run Logs" in
.pack-actions → navigates to run logs view
- Delete user action: Expand User Actions → click "🗑" on action → confirm browser dialog
Tips
- Toggle badge aggregate logic: ON = all roles enabled, OFF = all disabled OR pack has no roles, PARTIAL = mix. One click on the badge toggles all roles in the pack
- Pack detail vs. Skills list:
/skills/{domain} uses the same SkillsView component but auto-expands the named pack — it is NOT the same as ModuleDetailView
- Module detail is a separate view:
/skills/{domain}/{module} renders ModuleDetailView.svelte, not SkillsView
- Recent runs are filtered: Module detail loads last 50 run logs globally, then filters to those whose
workflow_id matches one of the module's workflows. Only the first 10 matches are shown
- Module matching by path:
ModuleDetailView calls fetchSkillModules(domain) and finds the module where m.path === $viewState.selectedModulePath
- User Actions only show when non-empty: The
.pack-card.user-actions section is conditionally rendered; it does not appear at all when there are zero user actions
hasRoles is an array, not a boolean: Unlike hasSkillDoc (boolean), mod.hasRoles is a string[] of role filenames. Condition is .length > 0
- Reload is global: The reload button calls
/api/reload which re-reads all skill files, roles, and workflows from disk
Gotchas
- Uninstall uses browser
confirm(): Native browser dialog — cannot be intercepted by snapshot/click alone. Needs browser dialog event handling or page.on('dialog', ...) in automation
- Delete action also uses
confirm(): Same native dialog pattern as Uninstall — dialog text is Delete "{action.title}"?
- Role
enabled defaults to true: The condition role.enabled !== false means a role with enabled: undefined is treated as enabled. Only explicit false disables it
- Pack toggle logic: OFF → enable all; ON → disable all; PARTIAL → disable all. There is no "enable all" path from PARTIAL
- PARTIAL badge uses hardcoded amber:
background: #fef3c7; color: #d97706 — not var(--color-warning). Cannot be overridden via CSS variable
- Expand state is component-local: Expanding/collapsing packs and modules is stored in component
$state — resets on any navigation away from /skills
- Module detail routing caveat: Navigating directly to
/skills/{domain}/{module} in the browser address bar returns a 404 JSON response from the server. Must navigate via in-app (click or navigate() from within the SPA)
- Module detail back button:
.back-btn calls navigateBack() — goes to whatever the previous view was, not necessarily /skills
- Reload triggers full data refetch: After
reloadAll(), loadData() is called again fetching all packs and their details in parallel. If any pack detail fails, it is silently skipped (logged to console only)
- Play button in SkillsView has no label: The "▶" run button in the pack card workflow list has NO text label. The "▶ Run" label only appears in
ModuleDetailView