Knowledge Pack Files
SideButton Dashboard Knowledge Pack Files
Browse the source files that power the SideButton Dashboard MCP server knowledge pack.
sidebutton install sidebutton.local What This Is
The Library page is the skill pack discovery and installation interface. It shows installed packs from external publishers, lets users connect to skill pack registries (publishers), browse available packs from those registries, and install/update packs. It's the entry point for adding new skill packs to SideButton.
URL Patterns
| View | URL | Notes |
|---|---|---|
| Library | /library | Single page β all state is component-local |
Legacy redirect: /workflows β /library, /workflows/{id} β /library
Page Structure
+--[Header: "Library" + Refresh button]--------------------+
| |
| [Publisher Selector Bar] |
| Registry <select> "Publisher:" + Search input |
| |
| === Available Skill Packs === |
| [Pack Card: π¦ icon + title + domainΒ·version + desc] |
| Install / Installed+Manage / Update+Manage buttons |
| |
| [Empty: "No skill packs found..."] (when none match) |
| |
| === INSTALLED (OTHER PUBLISHERS) === |
| [Pack Card: π¦ icon + title + domainΒ·version] |
| "Installed" badge + "Manage" button |
| |
| === PUBLISHERS === |
| [Publisher Row: name + type badge + status] |
| "Remove" button |
| [+ Add Publisher] button |
| [Expanded: URL input + Name input + Add/Cancel] |
| |
| [Empty State: "No Publishers Connected"] (no registries) |
| description + "+ Add Publisher" button |
+-----------------------------------------------------------+
Key Elements
Header
| Element | Selector | Notes |
|---|---|---|
| Page title | h1 / heading "Library" | H1 in header bar |
| Refresh button | .btn.btn-ghost in header | Icon-only SVG button. Calls loadData() β reloads registries + catalog |
Publisher Selector Bar (.publisher-bar)
| Element | Selector | Notes |
|---|---|---|
| Publisher label + dropdown | label:has-text("Publisher:") select | Native <select>. Options show "{name} ({packCount} packs)". Auto-selects first on load |
| Search input | .search-box input[type="text"] | Placeholder: "Search skill packs...". Filters by name, title, description, domain (case-insensitive) |
Catalog Section β Available Packs
| Element | Selector | Notes |
|---|---|---|
| Section heading | h2:has-text("Available Skill Packs") | Uppercase via CSS |
| Pack card | .catalog-card | One per pack in filtered results |
| Pack icon | .catalog-icon | Always π¦ emoji |
| Pack title | .catalog-header h3 | pack.title or falls back to pack.name |
| Pack meta | .catalog-meta | Format: "{domain} Β· v{version}" |
| Pack description | .catalog-desc | Optional β only shown if pack.description is set |
| Install button | button:has-text("Install") / .btn.btn-primary.btn-sm | Shown when pack.installed is false. Disabled while installing |
| Installing state | same button, text "Installing..." | Shown when installingDomain === pack.domain. Button is disabled |
| Installed badge | .installed-badge | Green pill badge, text "Installed". Shown when installed and version matches |
| Update button | button:has-text("Update to v") / .btn.btn-secondary.btn-sm | Shown when pack.installed && pack.installedVersion !== pack.version. Disabled while installing |
| Manage button | button:has-text("Manage") / .btn.btn-ghost.btn-sm | Shown alongside Installed badge or Update button. Navigates to Skills page |
| Empty catalog | .empty-card p | "No skill packs found matching "{query}"." or "No skill packs found in this registry." |
Loading States
| Element | Selector | Notes |
|---|---|---|
| Page loading | .loading:has-text("Loading library...") | Shown while initial loadData() runs. Replaces all content |
| Catalog loading | .loading:has-text("Loading catalog...") | Shown inside catalog area while loadCatalog() runs after registry switch |
Installed (Other Publishers) Section
| Element | Selector | Notes |
|---|---|---|
| Section heading | h2:has-text("Installed (Other Publishers)") | Uppercase via CSS |
| Pack card | .catalog-card | Same card style as catalog. Has icon, title, meta line |
| Installed badge | .installed-badge | Green pill, "Installed" |
| Manage button | button:has-text("Manage") / .btn.btn-ghost.btn-sm | Navigates to Skills page |
Publishers Section
| Element | Selector | Notes |
|---|---|---|
| Section heading | h2:has-text("Publishers") | Uppercase via CSS. Always visible (even when no registries) |
| Publisher row | .publisher-row | One per registry |
| Publisher name | .pub-name | Registry display name |
| Type badge | .pub-type | Registry type string, pill style |
| Status | .pub-status | "Connected" or "Disabled" (driven by reg.enabled) |
| Remove button | button:has-text("Remove") / .btn.btn-ghost.btn-sm | Triggers confirm() dialog before removal |
| Add Publisher button | button:has-text("+ Add Publisher") / .btn.btn-secondary.btn-sm.add-pub-btn | Shown when form is hidden |
| URL input | .add-pub-form input[placeholder="Registry URL or path"] | First input in add form |
| Name input | .add-pub-form input[placeholder="Name (optional)"] | Second input in add form |
| Add button | .add-pub-actions button:has-text("Add") / .btn.btn-primary.btn-sm | Submits new publisher |
| Cancel button | .add-pub-actions button:has-text("Cancel") / .btn.btn-ghost.btn-sm | Collapses add form without saving |
Empty State (No Publishers)
| Element | Selector | Notes |
|---|---|---|
| Heading | .empty-card h3:has-text("No Publishers Connected") | Shown only when registries.length === 0 |
| Description | .empty-card p | "Add a skill pack registry to browse and install skill packs." |
| Add Publisher button | .empty-card button:has-text("+ Add Publisher") / .btn.btn-primary | Sets showAddPublisher = true, scrolls to Publishers section |
Data Model
Registry (Publisher)
| Field | Type | Notes |
|---|---|---|
| name | string | Registry display name. Also used as the identifier for selectedRegistry state |
| url | string | Registry URL |
| type | string | Registry type label |
| enabled | boolean | true β "Connected", false β "Disabled" |
| packCount | number | Optional. Shown in dropdown option as "({packCount} packs)". Defaults to 0 if missing |
Catalog Pack (CatalogPack)
| Field | Type | Notes |
|---|---|---|
| name | string | Pack identifier |
| title | string | Display name (falls back to name in UI) |
| domain | string | Domain pattern. Used as keyed identifier in lists |
| version | string | Semver β current version in registry |
| description | string | Optional. Shown as catalog-desc when present |
| path | string | Path passed to installSkillPack(pack.path) for install/update |
| installed | boolean | Whether this pack is currently installed |
| installedVersion | string | Installed version. Compared to version to determine if update is available |
Installed Pack (InstalledPack)
Used for the "Installed (Other Publishers)" section β packs in $skillPacks store that are not in the current registry catalog.
| Field | Type | Notes |
|---|---|---|
| name | string | Pack identifier |
| title | string | Display name |
| domain | string | Domain pattern |
| version | string | Installed version |
Component State
| State Variable | Type | Initial | Description |
|---|---|---|---|
isLoading | boolean | true | Page-level loading β true during initial loadData() |
registries | Publisher[] | [] | All connected registries from fetchRegistries() |
selectedRegistry | string | '' | Name of the currently selected registry |
catalogPacks | CatalogPack[] | [] | Packs from the selected registry's catalog |
catalogName | string | '' | Display name of the current catalog |
searchQuery | string | '' | Current search filter text |
isLoadingCatalog | boolean | false | True while fetchRegistryCatalog() is running |
showAddPublisher | boolean | false | Whether the add-publisher form is expanded |
newPublisherUrl | string | '' | URL input value in add-publisher form |
newPublisherName | string | '' | Name input value in add-publisher form |
installingDomain | string|null | null | Domain of the pack currently being installed. Only one at a time |
Derived State
| Derived | Source | Notes |
|---|---|---|
filteredPacks | catalogPacks + searchQuery | Case-insensitive filter across name, title, description, domain |
installedPacks | $skillPacks store + catalogPacks | Packs in store whose domain is NOT present in current catalog β shown in "Other Publishers" section |
UI States
| State | Trigger | Visual |
|---|---|---|
| Page loading | Initial mount | .loading "Loading library..." covers content area |
| Catalog loading | Registry selection change | .loading "Loading catalog..." in catalog area |
| Default β publishers exist | Load complete, registries present | Publisher bar + catalog visible |
| No publishers | registries.length === 0 | .empty-card "No Publishers Connected" shown below Publishers section |
| Search filtered | Non-empty searchQuery | Catalog filtered; empty card shown if no matches with query in message |
| Empty registry | Registry has 0 packs | .empty-card "No skill packs found in this registry." |
| Installing | installingDomain set | Install button text β "Installing...", button disabled |
| Update available | pack.installed && installedVersion !== version | btn-secondary "Update to v{version}" replaces "Installed" badge |
| Add publisher form | showAddPublisher = true | URL + Name inputs replace "+ Add Publisher" button |
| Error (toast) | loadCatalog, install, add/remove publisher fail | showToast(message, "error") β transient toast, no persistent banner |
API Calls
| Action | API | Payload / Notes |
|---|---|---|
| Load registries | fetchRegistries() | GET /api/registries |
| Load catalog | fetchRegistryCatalog(registryName) | GET /api/registries/{name}/catalog. Returns { name, packs } |
| Load installed packs | fetchSkillPacks() | GET /api/skills |
| Install / Update | installSkillPack(pack.path) | POST /api/skills/install with pack path. Same endpoint for both install and update |
| Add publisher | addRegistry(url, name?) | POST /api/registries |
| Remove publisher | removeRegistry(name) | Uses registry name as identifier |
Common Tasks
- Browse available packs: Select registry from dropdown β catalog loads β browse pack cards
- Search packs: Type in search input β filters by name/title/description/domain (case-insensitive)
- Install skill pack: Find pack β click
"Install"β button shows"Installing..."β toast on success - Update skill pack: Find pack with
"Update to v{version}"button β click it β same install flow - Manage installed pack: Click
"Manage"β navigates to Skills page - Add publisher: Click
"+ Add Publisher"β enter"Registry URL or path"(+ optional"Name (optional)") β click"Add"β page reloads with new registry - Remove publisher: Click
"Remove"next to publisher β nativeconfirm()dialog β removed on confirm, reinstall on cancel
Gotchas
- Search placeholder is "Search skill packs..." β not "Search packs...". Use this exact string for placeholder assertions.
- Registry dropdown option format includes pack count:
"{name} ({packCount} packs)"βpackCountmay be0if missing. selectedRegistryis a name string, not an object: The<select>value isreg.name. Catalog is fetched by name, not URL.- Install uses
pack.path, notpack.source: The field passed toinstallSkillPack()ispack.path. - Update and Install use the same handler:
handleInstall(pack)handles both. Install button isbtn-primary, Update button isbtn-secondary. installingDomainblocks only matching domain: The disabled check isinstallingDomain === pack.domain. Other packs remain clickable.- No persistent error banner: Errors use
showToast()β transient, no static.errorelement in DOM. The originalerrorstring state documented elsewhere does not exist in this component. - Remove uses browser
confirm(): Native dialog with exact textRemove publisher "{name}"? This will also uninstall its packs.β automation must handle native confirms. - Publishers section always renders: The
<section class="publishers">is always shown (even with 0 registries), with theempty-cardappearing below it whenregistries.length === 0. - "Other Publishers" logic is derived: A pack appears in this section if its
domainis not in the currentcatalogPacks. Switching registries changes which packs appear here. - No explicit
errorstate in component: Unlike prior assumption, there is noerrorstring or error banner. All errors surface via the globalshowToaststore. - Add form URL input placeholder:
"Registry URL or path"β accepts both HTTP URLs and local filesystem paths. - Page reload after add/remove: Both
handleAddPublisherandhandleRemovePublishercallloadData()on success, re-fetching all registries and resetting catalog to first registry. - SPA 404 on direct navigation:
/libraryreturns 404 JSON if accessed directly β must navigate via the SideButton shell.