Claude-skill-registry add-section
Create a new HUD section (tab) with lifecycle, persistent state, and registry registration
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/add-section" ~/.claude/skills/majiayu000-claude-skill-registry-add-section && rm -rf "$T"
manifest:
skills/data/add-section/SKILL.mdsource content
Add Section Skill
Usage
/add-section <SectionName>
Step 1: Ask Questions
1. Section Purpose
Brief description (1 sentence): What does this section display/manage?
2. Persistent State
What state needs to persist across sessions? (Must be JSON-serializable: strings, numbers, booleans, arrays, plain objects) Examples: - Selected tab/filter - Sort order - Collapsed/expanded states - User preferences
3. Game Data
Does this section need game data? A) Static data → MGData (plants, items, pets, etc.) B) Real-time state → Globals (myInventory, myGarden, etc.) C) Both D) None
4. Sprites
Does it display game sprites? → MGSprite.toCanvas()
5. Child Components
Does it need existing UI components? → REUSE from src/ui/components/ [ ] ArcadeButton, GeminiIconButton → Buttons [ ] Modal → Dialogs [ ] ProgressBar → Progress [ ] SegmentedControl → Tab-like selection [ ] Tab → Tabs [ ] SoundPicker → Audio [ ] Other: ___
6. Complexity
A) Simple - Single file section B) Complex - Split into parts/ folder
7. Tab Info
- Label (displayed in tab bar): ___________ - Icon (emoji): ___________
Step 2: Create Structure
Simple Section
src/ui/sections/<SectionName>/ ├── index.ts # Public exports ├── section.ts # build() / destroy() lifecycle ├── state.ts # Persistent state (createSectionStore) └── styles.css.ts # Scoped CSS (optional)
Complex Section (with parts)
src/ui/sections/<SectionName>/ ├── index.ts ├── section.ts # Assembles parts ├── state.ts ├── styles.css.ts └── parts/ ├── header.ts # Search, filters ├── content.ts # Main content └── footer.ts # Actions
Read existing sections for templates:
src/ui/sections/*/
Step 3: File Responsibilities
| File | Purpose |
|---|---|
| Export and types only |
| , , |
| with stable ID |
| CSS string with scoped classes |
| Sub-components with own |
Key Patterns
state.ts
import { createSectionStore } from '../core/state'; export interface <Name>State { // JSON-serializable only! selectedTab: string; sortOrder: 'asc' | 'desc'; } const DEFAULT_STATE: <Name>State = { selectedTab: 'all', sortOrder: 'asc', }; // ID must be stable forever (storage key) export const state = createSectionStore<<Name>State>('tab-<name>', { version: 1, // Increment when state shape changes defaults: DEFAULT_STATE, });
section.ts
export interface SectionDefinition { build(container: HTMLElement): void; destroy(): void; } let root: HTMLElement | null = null; const cleanups: (() => void)[] = []; function build(container: HTMLElement): void { if (root) return; // Prevent double-build root = document.createElement('div'); root.className = '<name>-section'; // Inject styles const style = document.createElement('style'); style.textContent = <name>Css; root.appendChild(style); // Subscribe to state const unsub = state.subscribe((s) => { /* update UI */ }); cleanups.push(unsub); // Add child components (reuse existing!) // const button = createArcadeButton({ ... }); // cleanups.push(() => button.destroy()); container.appendChild(root); } function destroy(): void { cleanups.forEach(fn => fn()); cleanups.length = 0; root?.remove(); root = null; } export const <Name>Section: SectionDefinition = { build, destroy };
Step 4: Register
Registry → src/ui/sections/registry.ts
src/ui/sections/registry.tsimport { <Name>Section } from './<SectionName>'; export const sectionRegistry: Record<string, SectionConfig> = { // ... existing 'tab-<name>': { id: 'tab-<name>', label: '<Label>', icon: '<emoji>', section: <Name>Section, }, };
Step 5: Validate
Structure
-
exportsindex.ts
only<Name>Section -
hassection.ts
andbuild()destroy() -
usesstate.ts
with stable IDcreateSectionStore()
State
- All fields JSON-serializable (no functions, DOM, classes)
- Section ID is
format (stable forever)tab-<name> -
incremented when state shape changesversion - Default state is complete (no undefined)
Lifecycle
-
guards against double-build (build()
)if (root) return -
cleans up ALL resourcesdestroy() - Subscriptions unsubscribed in
destroy() - Child components'
calleddestroy()
Styling
- Uses CSS variables (no hardcoded colors)
- Classes scoped (
).section-name__element - Styles injected into
, notrootdocument.head
Game Data (if applicable)
- Static data →
MGData.get() - Real-time → Globals with
+ cleanup.subscribe() - Sprites →
MGSprite.toCanvas()
Components (if applicable)
- Reuses existing components from
src/ui/components/ - Child components tracked in
cleanups[]
Registration
- Added to
src/ui/sections/registry.ts - ID matches state ID (
)tab-<name>
Existing Sections
For reference/templates:
- User preferencesSettings
- Feature configAutoFavoriteSettings
- Journal progressJournalChecker
- Pet managementPets
- Stats trackingTrackers
- Notifications configAlerts
- Developer toolsDev
- Testing sectionTest
References
- Rules:
.claude/rules/ui/sections.md - Existing sections:
src/ui/sections/*/ - Core state:
src/ui/sections/core/state.ts - UI Components:
src/ui/components/ - Split workflow:
.claude/workflows/ui/section/split-section-into-parts.md