maintaining-dev-playground
Skill11 starsChanged 49 days agoMITView on GitHub
Keeps the Dev Playground current with the application. Use when editing UI components in apps/client/src/ — assesses playground candidacy, checks existing playground coverage, and guides adding or updating showcases. Also use when directly editing files in apps/client/src/dev/, building new widgets or features, or when the user mentions the dev playground, component showcase, or visual testing. Proactively evaluate whether edited components belong in the playground even if the user doesn't ask.
In this repository44 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .claude/CLAUDE.mdCLAUDE.md
- auditing-uiSkill
- browser-testingSkill
- capturing-product-mediaSkill
- ci-local-exportSkill
- ci-pulseSkill
- ci-quarantineSkill
- ci-statusSkill
- creating-pull-requestsSkill
- debugging-systematicallySkill
- debugging-test-failuresSkill
- debugging-typescript-errorsSkill
- deps-weeklySkill
- designing-frontendSkill
- opensrcSkill
- organizing-fsd-architectureSkill
- runtime-watchSkill
- stewarding-ci-pipelineSkill
- syncing-agent-skillsSkill
- verification-before-completionSkill
- vision-drift-checkSkill
- visual-companionSkill
- writing-adrsSkill
- writing-app-copySkill
- writing-blog-postsSkill
- writing-changelogsSkill
- writing-developer-guidesSkill
- writing-for-humansSkill
- writing-to-usersSkill
- adding-config-fieldsSkill
- clarifying-requirementsSkill
- maintaining-dependenciesSkill
- managing-specsSkill
- marketplace-devSkill
- orchestrating-parallel-workSkill
- reading-session-transcriptsSkill
- receiving-code-reviewSkill
- requesting-code-reviewSkill
- styling-with-tailwind-shadcnSkill
- test-driven-developmentSkill
- upgrading-runtime-dependenciesSkill
- working-in-worktreesSkill
- dorkbot-triageSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. About 0 are common, 8 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 29
After editing a UI component, ask yourself three questions:
- Very specific: line 32
Is it already in the playground?: Check the section registries in dev/sections/.
- Distinctive: line 68
Create a new page when a feature has 5+ sections that don't fit naturally into an existing page, OR when the feature is a complex …
- Very specific: line 79
Leave the registry entry where it is, so the existing /dev/<page>#<anchor> keeps working.
- Distinctive: line 98
When showcasing a composed widget (a dialog, panel, or multi-component feature):
- Distinctive: line 147
If a component can't be showcased without duplicating its layout, that's a signal to refactor.
- Distinctive: line 158
When adding a component to the playground:
- Very specific: line 162
Add entries to the appropriate section file in dev/sections/:
- Very specific: line 212
Use ShowcaseLabel to distinguish variants within a section
- Very specific: line 213
Use ShowcaseDemo (with responsive prop when layout is width-sensitive) to wrap each demo
- Distinctive: line 214
Import the REAL component — never recreate its markup
- Very specific: line 217
Imports: barrel when there is one, leaf when there isn't: If the slice's index.ts already exports the symbol, import it from …
- Distinctive: line 221
When the barrel does not export it, import the leaf module directly.
- Very specific: line 229
Import the showcase in the relevant page file (dev/pages/FeaturesPage.tsx):
- Very specific: line 242
Add the Page type union member in dev/playground-registry.ts
- Very specific: line 243
Create dev/sections/new-page-sections.ts exporting NEW_PAGE_SECTIONS
- Very specific: line 245
Add a PageConfig entry in dev/playground-config.ts (group, icon, description, path)
- Very specific: line 246
Create dev/pages/NewPage.tsx using PlaygroundPageLayout
- Very specific: line 247
Add the page component to PAGE_COMPONENTS in dev/playground-pages.ts (plus its import)
- Distinctive: line 254
When reviewing an existing playground showcase, verify:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Maintaining the Dev Playground
The Dev Playground (apps/client/src/dev/, accessible at /dev in development) is a living gallery of every visual component in DorkOS. It exists so that designers and developers can review components in isolation, catch regressions visually, and verify that the design system is coherent.
The playground is only useful if it stays in sync with the actual application. Stale or missing showcases erode trust and let regressions slip through. This skill exists to prevent that drift.
When This Skill Applies
Assess playground impact whenever you edit files in:
apps/client/src/layers/widgets/**/ui/**— widget UI componentsapps/client/src/layers/features/**/ui/**— feature UI componentsapps/client/src/layers/entities/**/ui/**— entity UI componentsapps/client/src/layers/shared/ui/**— shared UI primitivesapps/client/src/dev/**— direct playground edits
After editing a UI component, ask yourself three questions:
- Is this component a good playground candidate? (See candidacy criteria below)
- Is it already in the playground? Check the section registries in
dev/sections/. - If it's already there, is the showcase still accurate? Does it reflect the current props, states, and visual appearance?
Candidacy Criteria
A component belongs in the playground if it meets any of these:
- Visual and reusable — renders UI that appears in more than one place, or could
- Has multiple meaningful states — loading, empty, error, active, disabled, etc.
- Part of the design system — buttons, inputs, cards, badges, overlays
- A composed widget or panel — TasksPanel, MessagingConnections, TunnelDialog, etc.
- Complex enough to regress — more than trivial markup; has conditional rendering, animations, or data-dependent layout
A component does NOT belong if it's:
- Pure logic (hooks, utilities, stores) with no visual output
- A one-line wrapper that adds a className
- A layout container with no meaningful visual states
- A page-level route component (the playground itself IS the page-level view)
Placement Decision
Which page?
Match to the existing page structure. The playground has 5 sidebar groups, each derived from PageConfig.group in dev/playground-config.ts — check there rather than trusting this table, which is a summary and can age:
| Group | Pages | Use for |
| Design System | Design Tokens, Forms, Components, Tables | Shared primitives, design tokens, form elements |
| Generative UI | Widgets | Agent-authored widgets rendered from UI fences |
| Session | Conversation, Entry Actions, Simulator | Chat UI, message rendering, streaming |
| Agents | Identity, Agent & Relay, Home, Inbox & Approvals, Topology, Rooms, Marketplace | Faces and handles, Relay, Mesh, Tasks, approvals & inbox, graph nodes |
| App Shell | Tour Spotlight, Command Palette, Filter Bar, Onboarding, Error States, Feature Promos, Settings, Sidebar Model, Sidebar Boot & Motion, One Bar | App-wide chrome, navigation, onboarding flows |
When to create a new page
Create a new page when a feature has 5+ sections that don't fit naturally into an existing page, OR when the feature is a complex multi-component system that benefits from dedicated space (e.g., a full dialog with multiple sub-views like TunnelDialog).
Soft cap: ~20 sections per page (batch 20 audit finding 20.2, DOR-1766) — where the playground's own distribution already breaks: every page under that settles fine, and the pages that crossed 2.5-3× it (Conversation, the old combined "Subsystems", Components) are the ones a reader gets lost on. When a page's section file needs the 500-line split, split the page along the same seam. The Agent & Relay / Home, Inbox & Approvals split is the worked example: the section array had already been split into features-agent-sections.ts / features-surface-sections.ts for the file-size cap, and the page above it followed the same boundary rather than staying one list. TocSidebar's category sub-headings (§ Grouping within a page) help every oversized page regardless, but they are a mitigation, not a substitute — a page that keeps growing past the cap still wants a split.
Showing one component on two pages
A component can appear on multiple pages if it genuinely belongs in both contexts — a shared primitive on the Components page and again on the feature page that composes it.
Do it by rendering, never by registering twice. The registry forbids duplicate ids and pins id === slugify(title), so one title holds exactly one entry, on one page, at one anchor. Add a second entry and the drift test goes red. Instead:
- Export the showcase component for that one section (split it out of its file's
*Showcaseswrapper if it isn't already), and render it from both pages. - Leave the registry entry where it is, so the existing
/dev/<page>#<anchor>keeps working. - Compose the borrowing page's TOC from both halves in its
PageConfig.sections—[...OWN_SECTIONS, ...borrowed]. That is legal becausePLAYGROUND_REGISTRYis the union of the per-page arrays, not ofPAGE_CONFIGS[].sections.
The accepted cost: a borrowed section still groups under its owning page in Cmd+K. Say so on the borrowing page rather than letting the next person discover it. Worked example: dev/pages/IdentityPage.tsx and IDENTITY_CROSS_LISTED in dev/playground-config.ts.
Grouping within a page
Group components that work together. The category field drives the TOC: TocSidebar renders one sub-heading per distinct category, keyed by name rather than position (batch 20 audit finding 20.3/I2, DOR-1766) — a category's entries don't need to be adjacent in the section array, since every section sharing a name lands under the same heading regardless of where else it appears. Cmd+K still groups by page only (PlaygroundSearch); category has never been a search grouping. Use the feature/subsystem name — "Relay", "Mesh", "Tasks", "Identity" — the same words a reader would look for in the TOC.
The Parity Problem
The goal: render the SAME component in both the playground and the application. Never rebuild a layout or composition in the playground — if the widget changes in the app, it should change in the playground automatically.
Current state
Most showcases render only leaf components (individual cards, empty states, buttons). Full widget panels are usually NOT showcased — only their children are — which means the composed experience (how the parts work together) is invisible in the playground for most of them. TasksPanel and relay's MessagingConnections are the worked examples (DOR-1766): each is showcased with an isolated, pre-seeded QueryClient per the data-injection pattern below. Mesh's composed panels, TopologyPanel and DiscoveryView, are not showcased yet.
The pattern to follow
When showcasing a composed widget (a dialog, panel, or multi-component feature):
1. Separate content from chrome. The dialog wrapper (e.g., TasksDialogWrapper) handles ResponsiveDialog chrome and open/close state. The content component (e.g., TasksPanel) handles the actual UI. The playground should render the content component, not the dialog wrapper.
App renders: Playground renders:
DialogWrapper PlaygroundSection
└─ ResponsiveDialog └─ ShowcaseDemo
└─ ContentPanel ←──────────────── └─ ContentPanel (same component!)2. Support data injection via props. Components that only get data from hooks (TanStack Query, Zustand) can't render with mock data in the playground. Refactor to accept data via props, with the hook as the default:
// BEFORE: tightly coupled to hooks — can't showcase with mock data
function WidgetPanel() {
const { data } = useWidgetData();
return <ItemList items={data} />;
}
// AFTER: accepts props OR falls back to hook
interface WidgetPanelProps {
items?: Item[];
}
function WidgetPanel({ items: itemsProp }: WidgetPanelProps) {
const query = useWidgetData();
const items = itemsProp ?? query.data;
return <ItemList items={items} />;
}This lets the playground pass mock data while the app continues using the hook. No duplication.
2b. Or seed an isolated QueryClient, when the component is TanStack-Query-only and a props refactor isn't warranted. A component whose only obstacle is useQuery — no Zustand, no other outside state — can be shown as-is: wrap it in its own QueryClient (retry: false, staleTime: Infinity so nothing refetches against a server that isn't running) and setQueryData the exact keys it reads, then render the REAL component inside a <QueryClientProvider client={...}>. MarketplaceShowcases.tsx, SidebarShowcases.tsx and (DOR-1766) TasksShowcases.tsx/RelayShowcases.tsx/ConnectionsShowcases.tsx all use this. Export the query key alongside its hook (export const CATALOG_KEY = …) so the showcase can import it rather than re-typing the array by hand. Check every hook the component reaches for a null-vs-undefined landmine first: the playground's ambient transport resolves every unseeded call to null, and a destructured default (const { data = [] } = useX()) only fires on undefined — so a hook whose consumer does data.map(...) with no optional chaining will crash on an unseeded query. Either seed that key too, or confirm the consumer already guards with ?./??/an explicit if (!data).
3. Support controlled state for state machines. Components with internal state machines (like TunnelDialog's landing/setup/connecting/connected/error views) should accept an optional initialState or state prop so the playground can showcase each state independently:
// Playground can now render each state:
<TunnelContent initialView="connecting" />
<TunnelContent initialView="connected" mockData={connectedData} />
<TunnelContent initialView="error" mockData={errorData} />4. Keep mock data alongside showcases. Mock data factories live in dev/mock-factories.ts and dev/mock-samples/ (split by domain — DOR-1766, finding 20.8 — and re-exported from its index.ts). When adding a new showcase that needs mock data, add the factories there rather than inline in the showcase file. This keeps mock data reusable and the showcases focused on layout.
When to refactor
If a component can't be showcased without duplicating its layout, that's a signal to refactor. Common refactors:
- Extract content from dialog/sheet wrapper — make the content renderable standalone
- Add optional prop overrides for hook data — let props take precedence over hooks
- Add initialState prop to state machines — let the playground control which view renders
- Extract sub-views into named components — makes individual states showcaseable
These refactors improve the component's testability and composability beyond just playground support. They're worth doing.
Implementation Checklist
When adding a component to the playground:
1. Add section metadata
Add entries to the appropriate section file in dev/sections/:
// dev/sections/features-agent-sections.ts
{
id: 'tunneldialog', // anchor ID — lowercase, no spaces
title: 'TunnelDialog', // display name
page: 'features', // which page (must match Page type)
category: 'Tunnel', // groups the TOC — same name anywhere in the array shares one heading
keywords: ['tunnel', 'remote', 'ssh', 'connect', 'security'],
}The id must be the slugified version of the title (the PlaygroundSection component auto-generates anchors from its title prop via slugify()).
2. Create the showcase file
Create dev/showcases/TunnelShowcases.tsx:
import { PlaygroundSection } from '../PlaygroundSection';
import { ShowcaseLabel } from '../ShowcaseLabel';
import { ShowcaseDemo } from '../ShowcaseDemo';
import { TunnelContent } from '@/layers/features/settings';
export function TunnelShowcases() {
return (
<>
<PlaygroundSection
title="TunnelDialog"
description="Tunnel connection manager with multi-step state machine."
>
<ShowcaseLabel>Landing</ShowcaseLabel>
<ShowcaseDemo>
<TunnelContent initialView="landing" />
</ShowcaseDemo>
<ShowcaseLabel>Connected</ShowcaseLabel>
<ShowcaseDemo>
<TunnelContent initialView="connected" mockData={connectedMock} />
</ShowcaseDemo>
</PlaygroundSection>
</>
);
}Guidelines:
- One showcase file per feature/subsystem, not per component
- Use
ShowcaseLabelto distinguish variants within a section - Use
ShowcaseDemo(withresponsiveprop when layout is width-sensitive) to wrap each demo - Import the REAL component — never recreate its markup
- Pass mock data via props, using factories from
dev/mock-factories.ts
Imports: barrel when there is one, leaf when there isn't. If the slice's
index.ts already exports the symbol, import it from
@/layers/features/<slice> like every other consumer does — the playground is
the first place a new contributor reads, so it should model the convention.
When the barrel does not export it, import the leaf module directly. That is
allowed here and nowhere else: showing a leaf component in isolation is this
directory's whole job, and widening a feature's public API just to feed a
showcase would be the worse trade. Do not add an export to a barrel only for
the playground.
3. Add to the page component
Import the showcase in the relevant page file (dev/pages/FeaturesPage.tsx):
import { TunnelShowcases } from '../showcases/TunnelShowcases';
// Add inside the PlaygroundPageLayout:
<TunnelShowcases />;4. Create a new page (if needed)
A new page is seven touch points, not six. The seventh is a test file, and missing it turns CI red rather than the browser:
- Add the
Pagetype union member indev/playground-registry.ts - Create
dev/sections/new-page-sections.tsexportingNEW_PAGE_SECTIONS - Export it from
dev/playground-registry.ts— the named re-export and the aliased import spread intoPLAYGROUND_REGISTRY - Add a
PageConfigentry indev/playground-config.ts(group, icon, description, path) - Create
dev/pages/NewPage.tsxusingPlaygroundPageLayout - Add the page component to
PAGE_COMPONENTSindev/playground-pages.ts(plus its import) - Add the array to
dev/__tests__/playground-registry.test.ts— both its import and the hardcoded union in "PLAYGROUND_REGISTRY equals the union of all page-level arrays". That assertion lists every section array by name, so a new one that is not there fails the test even though the page works.
Then run pnpm vitest run apps/client/src/dev/__tests__/playground-registry.test.ts — green there proves union membership, unique ids, id === slugify(title), and that every rendered section is registered and every registered section is rendered.
Quality Checks
When reviewing an existing playground showcase, verify:
- Accuracy — Does the showcase still match the component's current props interface? Are there new props or states not represented?
- Completeness — Are all meaningful states shown? (default, loading, empty, error, disabled, active)
- Parity — Is the showcase rendering the actual component, or a recreation? If it's a recreation, flag it for refactoring.
- Mock data — Is mock data realistic? Would a reviewer understand what this component looks like with real data?
- Grouping — Is this showcase on the right page and in the right category? Has the component's role changed since it was added?
Files to Know
| File | Purpose |
dev/playground-config.ts | Page metadata — add new pages here |
dev/playground-registry.ts | Section type, Page type, full registry |
dev/sections/*.ts | Section entries per page (drives TOC + search) |
dev/showcases/*.tsx | Showcase components (the actual demos) |
dev/pages/*.tsx | Page components that compose showcases |
dev/PlaygroundSection.tsx | Section card wrapper |
dev/ShowcaseDemo.tsx | Demo container with responsive viewport toggle |
dev/ShowcaseLabel.tsx | Label for distinguishing variants |
dev/mock-factories.ts | Mock data factory functions |
dev/mock-samples/ | Sample data constants, split by domain, barrelled from index.ts |
dev/playground-pages.ts | PAGE_COMPONENTS — maps page IDs to their page component |
dev/DevPlayground.tsx | Root component with page routing |
dev/__tests__/playground-registry.test.ts | The drift gate — lists every page-level array by name |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

