Add a comprehensive kitchen-sink Storybook for the design system - #486
Conversation
Adds the mock app shell, in-memory router, fixtures, route mocks and component galleries under src/kitchen-sink/lib, plus a working dark-mode decorator and viewport options in the Storybook preview.
Interactive mock of the whole Studio app (live sidebar nav, in-memory router, menus/sheets/dialogs/toasts) plus per-page-state sub-stories (library, global states, OAuth connect) and galleries covering every design-system primitive, form, overlay and content component. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive "kitchen-sink" mock application and Storybook stories for Director Studio, adding several routes, component galleries, and mock fixtures. The review feedback is highly constructive and focuses on improving code quality and robustness. Key recommendations include centralizing the duplicated delay helper function into a shared fixture file, enhancing the slugify helper to strip non-alphanumeric characters, checking for navigator.clipboard availability to prevent silent failures in non-secure contexts, and updating mock server connection info to ensure the "Logout" action is testable.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| import type { ConnectionInfo } from "@director.run/design/components/playbooks-clients/playbook-section-connect.tsx"; | ||
| import type { PlaybookDetail } from "@director.run/design/components/types.ts"; |
There was a problem hiding this comment.
The delay helper function is currently duplicated across seven different route files. Centralizing it in a shared location like fixtures.ts improves maintainability and keeps the codebase DRY.
import type { ConnectionInfo } from "@director.run/design/components/playbooks-clients/playbook-section-connect.tsx";
import type { PlaybookDetail } from "@director.run/design/components/types.ts";
export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));There was a problem hiding this comment.
Done in 385c1f2 — delay is now a single exported helper in lib/fixtures.ts, imported by all seven route files.
| status: "connected", | ||
| lastConnectedAt: new Date("2026-06-02T14:05:00.000Z"), | ||
| }, | ||
| }, |
There was a problem hiding this comment.
The github server's connection info does not specify isAuthenticated: true. Since the TargetActionsDropdown component conditionally renders the "Logout" option only when isAuthenticated is true, adding this property to the fixture ensures that the "Logout" action is rendered and testable in the Storybook.
connectionInfo: {
status: "connected",
lastConnectedAt: new Date("2026-06-02T14:05:00.000Z"),
isAuthenticated: true,
},There was a problem hiding this comment.
Good catch — added isAuthenticated: true to the github server fixture so the target-detail dropdown renders (and lets you exercise) the Logout action.
| import { kitchenSinkConnectionInfo } from "../fixtures"; | ||
| import type { KitchenSinkNavigate } from "../types"; | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
There was a problem hiding this comment.
Import the centralized delay helper from fixtures.ts instead of redefining it locally.
| import { kitchenSinkConnectionInfo } from "../fixtures"; | |
| import type { KitchenSinkNavigate } from "../types"; | |
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| import { kitchenSinkConnectionInfo, delay } from "../fixtures"; | |
| import type { KitchenSinkNavigate } from "../types"; |
There was a problem hiding this comment.
Done in 385c1f2 — delay is now exported once from lib/fixtures.ts and imported here instead of redefined locally.
| import { kitchenSinkApiKey } from "../fixtures"; | ||
| import type { KitchenSinkPageState } from "../types"; | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
There was a problem hiding this comment.
Import the centralized delay helper from fixtures.ts instead of redefining it locally.
| import { kitchenSinkApiKey } from "../fixtures"; | |
| import type { KitchenSinkPageState } from "../types"; | |
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| import { kitchenSinkApiKey, delay } from "../fixtures"; | |
| import type { KitchenSinkPageState } from "../types"; |
There was a problem hiding this comment.
Done in 385c1f2 — delay is now exported once from lib/fixtures.ts and imported here instead of redefined locally.
| onCopyApiKey={(text) => { | ||
| void navigator.clipboard?.writeText(text); | ||
| toast({ | ||
| title: "Copied", | ||
| description: "The API key was copied to your clipboard.", | ||
| }); | ||
| }} |
There was a problem hiding this comment.
In environments where navigator.clipboard is not available (e.g., non-secure contexts or certain iframe setups in Storybook), calling navigator.clipboard?.writeText will fail silently, but the success toast will still be shown. Adding a check for navigator.clipboard prevents this false positive.
onCopyApiKey={(text) => {
if (!navigator.clipboard) {
toast({
title: "Copy failed",
description: "Clipboard access is not available.",
});
return;
}
void navigator.clipboard.writeText(text);
toast({
title: "Copied",
description: "The API key was copied to your clipboard.",
});
}}
There was a problem hiding this comment.
Fixed — the copy handler now returns early when navigator.clipboard is unavailable, so the success toast only appears on an actual copy.
| import { useState } from "react"; | ||
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
There was a problem hiding this comment.
Import the centralized delay helper from fixtures.ts instead of redefining it locally.
| import { useState } from "react"; | |
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; | |
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| import { useState } from "react"; | |
| import { delay } from "../fixtures"; | |
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; |
There was a problem hiding this comment.
Done in 385c1f2 — delay is now exported once from lib/fixtures.ts and imported here instead of redefined locally.
| import { useState } from "react"; | ||
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
There was a problem hiding this comment.
Import the centralized delay helper from fixtures.ts instead of redefining it locally.
| import { useState } from "react"; | |
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; | |
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| import { useState } from "react"; | |
| import { delay } from "../fixtures"; | |
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; |
There was a problem hiding this comment.
Done in 385c1f2 — delay is now exported once from lib/fixtures.ts and imported here instead of redefined locally.
| const slugify = (value: string) => | ||
| value.trim().toLowerCase().replace(/\s+/g, "-") || "prompt"; |
There was a problem hiding this comment.
The slugify helper only replaces spaces with hyphens, which leaves special characters (like ?, #, /) intact. This can cause issues if the generated slug is used in routing or identifiers. Stripping non-alphanumeric characters makes the slug generation much more robust.
const slugify = (value: string) =>
value
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-") || "prompt";
There was a problem hiding this comment.
Fixed — slugify now lowercases, collapses any run of non-alphanumerics to a single hyphen, trims leading/trailing hyphens, and falls back to prompt.
| import { useState } from "react"; | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
There was a problem hiding this comment.
There was a problem hiding this comment.
Done in 385c1f2 — delay is now exported once from lib/fixtures.ts and imported here instead of redefined locally.
| import { useState } from "react"; | ||
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; | ||
|
|
||
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); |
There was a problem hiding this comment.
Import the centralized delay helper from fixtures.ts instead of redefining it locally.
| import { useState } from "react"; | |
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; | |
| const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | |
| import { useState } from "react"; | |
| import { delay } from "../fixtures"; | |
| import type { KitchenSinkNavigate, KitchenSinkPageState } from "../types"; |
There was a problem hiding this comment.
Done in 385c1f2 — delay is now exported once from lib/fixtures.ts and imported here instead of redefined locally.
- Centralize the delay helper in fixtures.ts (was duplicated across 7 routes) - Give the github fixture isAuthenticated so the target Logout action shows - Guard the settings clipboard copy before showing the success toast - Harden slugify to strip non-alphanumerics - Whitelist hackernews/modelcontextprotocol/summarise/triaging in cspell Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for the review @gemini-code-assist — all points addressed in 385c1f2: centralized the duplicated |
|
Thanks for the update, barnaby. The changes look solid and address the points raised during the review. I have no further comments on this PR. |
Summary
Adds a comprehensive kitchen-sink Storybook to
apps/studiofor examining the whole design system in one place. Dev-tooling only — no product runtime code changes; the real app andpackages/designare untouched.8b8cc6b): a mock app shell with an in-memory router, fixtures, seven route mocks, and four component galleries undersrc/kitchen-sink/lib; plus a working dark-mode toolbar decorator and viewport options in.storybook/preview.tsx(renamed from.ts).09b284b): the flagshipkitchen-sink/appinteractive mock (live sidebar nav, menus, sheets, dialogs, toasts; 10 variants incl. Mobile/Tablet/SidebarLoading/LoadingStates/EmptyStates/ErrorState/Onboarding/DarkMode), page-state sub-stories (library,global-states, OAuthconnect), and galleries for every primitive/form/overlay/content component plus anall-componentspage.Test Coverage
@director.run/studiohas no unit-test suite. Correctness was verified bystorybook build(compiles every story) and live browser QA via Playwright: sidebar navigation, actions dropdown to settings sheet, mobile hamburger nav, and gallery rendering, with zero console errors beyond expected offline icon 404s.Pre-Landing Review
Error, which shadows the constructor — matches the repo's existing login stories), and themockTools()cast is load-bearing (matchesplaybook-detail.stories.tsx).Design Review
Frontend diff, but it showcases the existing design system rather than introducing new UI. No new design patterns; components are used per their real APIs.
Plan Completion
Complete. All planned milestones delivered: preview config, types + fixtures, app shell plus seven routes, ten app variants, three page-state story files, four galleries and an all-components page. lint, typecheck, and build all green.
Notes
@director.run/studiois in the changesetignorelist and is private, so no changeset or version bump applies..darkdesign tokens are defined; three real pages nestLayoutViewinside the root layout's (the mock reproduces this faithfully);not-found-page.tsxbypasses the design system.Test plan
bun run lint— clean (all packages)bun run typecheck— clean (all packages)bun run build(studio) — vite + storybook build succeed🤖 Generated with Claude Code