بنقرة واحدة
add-card
Create a new card part within a section with factory/class pattern, Card wrapper, and proper cleanup
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Create a new card part within a section with factory/class pattern, Card wrapper, and proper cleanup
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Add a new Jotai atom to the state system with type definitions, registry, and Store API access
Create a reusable UI component with factory pattern, theme compatibility, and proper cleanup
Scaffold a new toggleable feature with full structure, storage, API exposure, and bootstrap registration
Create a reactive global variable that derives from atoms with subscription support
Create a game UI injection that modifies existing game elements with proper cleanup
Create a new core infrastructure module with standard API, lazy init, and proper structure
| name | add-card |
| description | Create a new card part within a section with factory/class pattern, Card wrapper, and proper cleanup |
| aliases | ["new-card","create-card"] |
/add-card <SectionName>/<CardName>
Example: /add-card Pets/InventoryCard or /add-card Alerts/WeatherCard
A Card is a self-contained UI part within a Section. It:
Card component (expandable/collapsible container)src/ui/sections/<SectionName>/parts/<cardName>/| Aspect | Card (Part) | Component |
|---|---|---|
| Location | sections/<Section>/parts/ | components/ |
| Scope | Section-specific | Reusable across sections |
| State | Often section-specific state | Props-driven, minimal internal state |
| Complexity | Higher (orchestrates components) | Lower (single purpose) |
Which section will contain this card?
→ Must exist in src/ui/sections/
Brief description (1 sentence):
What does this card display/manage?
A) Factory pattern (recommended for simpler cards)
- createXxxCard(options): XxxCardHandle
- Better for cards with minimal internal state
- Example: ShopsCard, PublicCard
B) Class pattern (for complex cards)
- class XxxCardPart { build(), destroy(), render() }
- Better for cards with complex state, multiple modes, drag handlers
- Example: TeamCard, TrackerCard, AbilityLogsCard
[ ] Expandable (collapsible header with chevron)
[ ] Filterable (search bar, selects)
[ ] Scrollable list (virtual scrolling if >50 items)
[ ] Modes toggle (SegmentedControl for overview/manage/etc.)
[ ] Refresh capability (manual refresh button)
A) Globals (reactive) → subscribe + cleanup
B) MGData (static) → MGData.get('plants')
C) Feature state → feature.getState()
D) API fetch → async loading
E) Section state → state.ts
F) Combination of above
Check existing components before creating new:
Layout:
[ ] Card (always used - the wrapper)
Inputs/Controls:
[ ] SearchBar - text filtering
[ ] Select - dropdown filter
[ ] SegmentedControl - mode toggle
[ ] Checkbox - multi-select
[ ] Button - actions
Display:
[ ] Table - tabular data with sorting
[ ] Badge - status indicators
[ ] ProgressBar - progress display
[ ] TeamListItem - team rows
Utility:
[ ] Modal - popups
[ ] SoundPicker - audio selection
Does it display game sprites? → MGSprite.toCanvas()
src/ui/sections/<SectionName>/parts/<cardName>/
├── <CardName>.ts # Main card logic (class or factory)
├── <cardName>.css.ts # Scoped styles (optional)
├── index.ts # Barrel exports
└── <cardName>Data.ts # Data processing helpers (optional, for complex data)
└── <cardName>Table.ts # Table configuration (optional, if using Table)
Read existing cards for templates:
src/ui/sections/Alerts/parts/shop/shopsCard.tssrc/ui/sections/Room/parts/public.tssrc/ui/sections/Pets/parts/ability/AbilityLogsCard.tssrc/ui/sections/Pets/parts/team/TeamCard.ts<CardName>.ts/**
* <CardName> Card Part
* <Brief description>
*/
import { Card } from "../../../../components/Card/Card";
import { element } from "../../../../styles/helpers";
// Import needed components
// import { SearchBar } from "../../../../components/SearchBar/SearchBar";
// import { Select } from "../../../../components/Select/Select";
// Import data sources
// import { getMyInventory } from "../../../../../globals/variables/myInventory";
// import { MGData } from "../../../../../modules";
/* ─────────────────────────── Types ─────────────────────────── */
export interface <CardName>Options {
defaultExpanded?: boolean;
onExpandChange?: (expanded: boolean) => void;
// Add card-specific options
}
export interface <CardName>Handle {
root: HTMLElement;
refresh?(): void;
destroy(): void;
}
/* ─────────────────────────── Factory ─────────────────────────── */
export function create<CardName>(options: <CardName>Options = {}): <CardName>Handle {
const { defaultExpanded = true, onExpandChange } = options;
// Internal state
let root: HTMLElement | null = null;
const cleanups: (() => void)[] = [];
// Component references (for cleanup)
// let searchHandle: SearchBarHandle | null = null;
/**
* Build the card UI
*/
function buildCard(): HTMLElement {
const content = element("div", {
style: "display: flex; flex-direction: column; gap: 12px;",
}) as HTMLDivElement;
// Add filters, lists, content...
// const searchBar = SearchBar({ ... });
// content.appendChild(searchBar.root);
// cleanups.push(() => searchBar.destroy?.());
root = Card(
{
title: "<Card Title>",
subtitle: "<Card subtitle description>",
expandable: true,
defaultExpanded,
padding: "md",
onExpandChange,
},
content
);
return root;
}
/**
* Refresh data (optional)
*/
function refresh(): void {
// Reload data, update UI
}
/**
* Cleanup all resources
*/
function destroy(): void {
cleanups.forEach(fn => fn());
cleanups.length = 0;
root = null;
}
return {
root: buildCard(),
refresh,
destroy,
};
}
<CardName>.ts/**
* <CardName> Card Part
* <Brief description>
*
* Per .claude/rules/ui/sections.md
*/
import { Card } from "../../../../components/Card/Card";
import { element } from "../../../../styles/helpers";
// Import needed components
// import { SegmentedControl, SegmentedControlHandle } from "../../../../components/SegmentedControl/SegmentedControl";
// Import data sources
// import { Globals } from "../../../../../globals";
// import { MGData } from "../../../../../modules";
/* ─────────────────────────── Types ─────────────────────────── */
export interface <CardName>PartOptions {
// Card-specific options
onSomeEvent?: () => void;
}
/* ─────────────────────────── Class ─────────────────────────── */
export class <CardName>Part {
private card: HTMLDivElement | null = null;
private content: HTMLDivElement | null = null;
private options: <CardName>PartOptions;
private cleanups: (() => void)[] = [];
// Component references
// private modeControl: SegmentedControlHandle | null = null;
// Internal state
// private mode: "overview" | "manage" = "overview";
constructor(options: <CardName>PartOptions = {}) {
this.options = options;
}
/* ───────────────────── Public API ───────────────────── */
build(): HTMLDivElement {
if (this.card) return this.card;
return this.createCard();
}
destroy(): void {
this.cleanups.forEach(fn => fn());
this.cleanups.length = 0;
// Destroy child components
// this.modeControl?.destroy();
// this.modeControl = null;
this.card = null;
this.content = null;
}
render(): void {
if (!this.card) return;
this.renderContent();
}
/* ───────────────────── Card Setup ───────────────────── */
private createCard(): HTMLDivElement {
const wrapper = element("div", {
className: "<card-name>-wrapper",
});
this.content = element("div", {
className: "<card-name>__content",
});
wrapper.appendChild(this.content);
this.card = Card(
{
title: "<Card Title>",
subtitle: "<Card subtitle description>",
expandable: true,
defaultExpanded: true,
},
wrapper
);
return this.card;
}
/* ───────────────────── Rendering ───────────────────── */
private renderContent(): void {
if (!this.content) return;
this.content.replaceChildren();
// Build UI based on state...
}
/* ───────────────────── Event Handlers ───────────────────── */
// private handleSomeAction(): void { ... }
}
index.ts (Barrel exports)/**
* <CardName> Card Parts - Barrel exports
*/
// Factory pattern:
export { create<CardName> } from "./<CardName>";
export type { <CardName>Options, <CardName>Handle } from "./<CardName>";
// OR Class pattern:
export { <CardName>Part } from "./<CardName>";
export type { <CardName>PartOptions } from "./<CardName>";
// Optional CSS export
export { <cardName>CardCss } from "./<cardName>.css";
<cardName>.css.ts (Optional)/**
* <CardName> Card styles
*/
export const <cardName>CardCss = /* css */`
/* Scoped to card */
.<card-name>-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
}
.<card-name>__content {
/* Content styles */
}
.<card-name>__list {
max-height: 400px;
overflow-y: auto;
}
.<card-name>__empty {
padding: 24px;
text-align: center;
color: color-mix(in oklab, var(--fg) 60%, #9ca3af);
font-size: 14px;
}
`;
parts/index.ts// <CardName> parts
export { create<CardName> } from "./<cardName>/<CardName>";
export type { <CardName>Options, <CardName>Handle } from "./<cardName>/<CardName>";
// OR for class pattern:
export { <CardName>Part } from "./<cardName>/<CardName>";
export type { <CardName>PartOptions } from "./<cardName>/<CardName>";
section.tsimport { create<CardName> } from "./parts";
// OR
import { <CardName>Part } from "./parts";
// In build():
// Factory pattern:
const card = create<CardName>({
defaultExpanded: true,
onExpandChange: (expanded) => { ... },
});
container.appendChild(card.root);
cleanups.push(() => card.destroy());
// OR Class pattern:
const cardPart = new <CardName>Part({ ... });
container.appendChild(cardPart.build());
cardPart.render();
cleanups.push(() => cardPart.destroy());
<CardName>.ts (PascalCase)<cardName>.css.ts (camelCase)index.ts exports card and typesparts/<cardName>/ folder{ root, destroy, refresh? } OR class has build(), destroy(), render()Card component from components/Card/Cardexpandable: true if collapsibledefaultExpanded optiondestroy()destroy() calledcleanups[] array used for trackingsrc/ui/components/cleanups[]Alerts/parts/shop/shopsCard.ts - Table with filtersAlerts/parts/weather/weatherCard.ts - Weather alertsRoom/parts/public.ts - Rooms list with API fetchPets/parts/ability/AbilityLogsCard.ts - Virtual scrolling listPets/parts/team/TeamCard.ts - CRUD with drag-dropPets/parts/teamDetails/TeamDetailsCard.ts - Expansion panelsTrackers/parts/TrackerCard.ts - Team list with expansionconst filters = element("div", {
className: "<card>-filters",
style: "display: flex; gap: 8px; margin-bottom: 12px;",
});
const select = Select({
options: [...],
onChange: (value) => applyFilters(),
});
const search = SearchBar({
placeholder: "Search...",
onSearch: (value) => applyFilters(),
});
filters.append(select.root, search.root);
const list = element("div", {
className: "<card>__list",
style: "max-height: 400px; overflow-y: auto;",
});
if (items.length === 0) {
const empty = element("div", {
className: "<card>__empty",
style: "padding: 24px; text-align: center; color: color-mix(in oklab, var(--fg) 60%, #9ca3af);",
}, "No items yet");
content.appendChild(empty);
return;
}
const modeControl = SegmentedControl({
segments: [
{ id: "simple", label: "Simple" },
{ id: "detailed", label: "Detailed" },
],
selected: "simple",
onChange: (id) => {
mode = id;
renderContent();
},
});
const unsub = getMyInventory().subscribe((inventory) => {
items = inventory.items;
renderList();
});
cleanups.push(unsub);
.claude/rules/ui/sections.mdsrc/ui/components/Card/Card.tssrc/ui/sections/*/parts/*/src/ui/components/src/globals/variables/.claude/workflows/ui/section/add-section.md