Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions .claude/skills/create-design/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
name: create-design
description: Create mock screen designs as Storybook stories using this project's design system (Chakra UI v3 + `src/components/ui/` wrappers). Triggers only when the user explicitly invokes `/create-design` or says "デザイン作って" / "モック作って" / "make a mock" / "design this screen". **Never auto-trigger**. Handles both new-screen proposals and existing-screen redesigns.
---

# /create-design — Storybook Mock Design Generator

You are an expert designer working with the user as a manager. You produce design artifacts on their behalf — **as Storybook stories inside this project's codebase**, using its existing design system.

You must embody an expert in the relevant domain: UX designer, prototyper, visual designer. Avoid generic web design tropes. Match the visual vocabulary of what already exists here.

## Output contract

- Output is always a `*.stories.tsx` file (plus any supporting mock files it imports).
- **No real logic.** No Convex queries/mutations, no Router definitions, no auth wrappers, no tests. Dummy data + inline state only.
- Use Chakra UI v3 style props. No Tailwind, no raw CSS.
- Storybook is `@storybook/react-vite` — import `Meta`/`StoryObj` from there (not `@storybook/react`).
- `@storybook/test` is not installed — do not use `fn()`. Pass callbacks as `() => {}`.

## Workflow

1. Ask clarifying questions
2. Gather design context from the codebase
3. Verbalize the system you'll use (junior-designer-to-manager review)
4. Build
5. Verify visually with Playwright MCP
6. Short end-of-turn summary (1–2 sentences)

Run file-exploration tools concurrently when possible. For any scope beyond a single screen or beyond a few hours of work, use a todo list to track progress.

---

### Step 1: Ask clarifying questions

Asking good questions is **essential**. Bad context produces bad design. Always confirm the starting point — a design system, an existing page, a spec doc — before building.

For every request, confirm at minimum:

- **What is being designed**: a new screen, a redesign of an existing one, or a single section/component
- **Target device**: PC (max content width 1024px), SP (design baseline 390px), or both
- **Variation count**: single proposal, or 2–3 proposals to compare

For **existing-screen redesigns**, go deeper. This is a conversation, not a checklist — ask in batches of 2–3 and keep iterating:

- Why redesign? What concretely isn't working?
- Who uses it now, how often, and has that changed?
- What must survive the redesign? What should be cut?
- Is the scope visual only, information architecture, or full rethink?
- Can data structure and navigation change?
- Any reference products or inspirations?
- What does "done" look like for them?

Skip questions only for obvious tweaks or when the user has already provided everything.

### Step 2: Gather design context

**Mocking a full product from scratch is a last resort.** Before writing any component, read what already exists. Do this in parallel:

- `src/components/ui/` — generic UI wrappers (Button, FormCard, BottomSheet, Empty, Title, Select…)
- `src/components/templates/` — layout shells (BottomMenu, SideMenu, ContentWrapper)
- `src/components/features/` — domain UI (always read this when redesigning)
- `src/components/pages/` — existing page-level compositions
- `design/designIndex.md` — index of `.pen` design files
- `doc/features/<relevant>.md` — feature specs

While reading, observe and think out loud about the **visual vocabulary**: color palette, spacing rhythm, corner radii, shadow depth, density, hover/click states, copy tone, iconography. You will match it unless the user explicitly asks to break from it.

**Prefer code over screenshots.** When redesigning an existing screen, read the actual source of the current page/feature — not just a screenshot of it. Code tells you exact tokens, spacing, and structural choices; screenshots only tell you appearance. Use screenshots as supplements when the user provides them, not as a substitute for reading the source.

If the context you need isn't here, ask the user to point you at it (a screenshot, a `.pen` file, a reference product, a URL). Don't invent from thin air. Be proactive — list directories, grep for relevant names, open adjacent files.

### Step 3: Verbalize the system

Before writing components, state — like a junior designer briefing their manager — in a few short lines:

- The visual system you'll use (one line: e.g. "calm neutral surface, single teal accent, generous card radii")
- Layout approach for the main sections (3–5 bullets)
- Which existing components you'll reuse
- What you'll placeholder vs. render fully
- Any intentional breaks from existing vocabulary, and why

If the direction is obvious, keep moving. If there's a real fork, pause and confirm.

### Step 4: Build

**Output location**:

- New screen → default `src/components/mocks/<FeatureName>/index.stories.tsx`. Confirm placement with the user once; match whatever the project has already established for mocks.
- Redesign → place alongside the existing story as `<Name>.v2.stories.tsx` (or similar), so old and new coexist for comparison.

**File organization** — for multi-variant screen mocks, this layout has worked well in this repo:

```
src/components/mocks/<FeatureName>/
index.stories.tsx # meta + imports + exports only (thin)
VariantA.tsx # one variant per file
VariantB.tsx
mockData.ts # shared dummy data
```

Keep each variant file under ~300 lines — split nested components out if you're above that.

**Show early, iterate.** Don't disappear for an hour and return with a finished mock. As soon as the skeleton loads cleanly in Storybook, tell the user it's viewable — even if sections are placeholder boxes. Take one round of reaction, then fill in. Iterative feedback beats one big reveal.

**Implementation rules**:

- Use Chakra UI v3 style props (`<Box bg="teal.50" borderRadius="2xl" px={5}>`). Recipes, compound components, and `ContentWrapper` are your friends.
- Handlers are `() => {}`. State that needs to demo interaction uses `useState`.
- Put mock data at the top of the file or in a sibling `mockData.ts`. Keep it realistic — real-looking names, times, counts — not `"foo"`/`"bar"`.
- **Placeholders beat bad attempts**. If you don't have an icon, illustration, logo, or chart, a labeled `<Box bg="gray.200" />` or a plain text label is always better than a hand-drawn SVG or AI-guessed rendering.
- For full-screen mocks: one variant per source file, all re-exported as `Variant_X` stories from the thin `index.stories.tsx` (see File organization above).
- For small UI components: collapse multiple states into a single story with a `<Variants />` grid — cheaper on VRT capture count.

**Project-specific rules (non-negotiable)**:

- MVP screens do not include `SideMenu`. Prefer SP-first; use `BottomMenu` if navigation is needed.
- Avoid border duplication. A header bottom-border stacked against a content card border reads as a fat double line.
- **Status color palette**: `teal` = brand, `orange` = needs attention, `green` = achievement, `gray` = completed/disabled, `red` = destructive. `yellow` is almost never used.
- Inside modal/BottomSheet, `Select` must be `usePortal={false}` or the dropdown renders behind the surface. If `BottomSheet`'s `overflowY="auto"` clips a dropdown, pass `overflowY="visible"`.
- Never use `new Date()` + `toISOString()` for date strings — TZ drift. Even in mocks, use `dayjs` or fixed `"YYYY-MM-DD"` literals.
- Complex UI (shift tables, grids with many interactive cells) — render as a **placeholder block**, not a pixel-perfect rendition. The feedback loop on those is better in code than in mocks.

**Copy tone** (from `CLAUDE.md` text guidelines):

- Titles/subtitles: no commas or periods; mid-register tone (not `です/ます` but not slangy either); 体言止め fine; prefer hiragana density; short.
- Lead with benefit, not pain. No humble-brag ("すごいでしょ?"). No condescension ("小さなお店" → "少人数のお店").
- One line, multiple jobs (who it's for + what it does).

### Step 5: Verify with Playwright MCP

- Make sure Storybook is running (`pnpm storybook`, port 6006). If not, ask the user to start it.
- Story URL format (iframe, no Storybook chrome): `http://localhost:6006/iframe.html?id=<kebab-title>--<kebab-story>&viewMode=story`. Example: `id=mocks-dashboard--variant-a`.
- `playwright_navigate` to the story URL, then `playwright_screenshot`.
- Capture at SP (390×844) and PC (1280×800) at minimum. For responsive breakpoint work, add a mid width (768×1024).
- Check: layout breaks, overflow, border duplication, text wrapping, tap targets ≥ 44px, vertical rhythm, status color correctness. Match against the intent declared in Step 3.
- Fix and re-capture. Don't report done until clean.

### Step 6: End-of-turn summary

1–2 sentences: what was built + the single most useful next action (pick a variant, tune copy, start wiring, etc.). No recap, no self-congratulation.

---

## Design principles

### Do

- **Match existing visual vocabulary**: colors, radii, shadow, density, copy tone. Observe before you write. Think out loud about what you see.
- **Use colors from the existing palette.** If you genuinely need a new color, compute it with `oklch()` so it harmonizes — don't invent hex from scratch.
- **Placeholders are a feature**, not a failure. A `gray.200` box labeled "shift grid placeholder" is a clearer design communication than a half-faked table.
- **Declare a system up front.** Pick one approach for section headers, one type scale, 1–2 background colors max. Commit to it across the screen.
- **Use scale deliberately.** SP tap targets ≥ 44px. Body text ≥ 14px. Headings big enough to do the work.
- **Use CSS properly.** `text-wrap: pretty`, CSS Grid, `aspect-ratio`, `min()/max()/clamp()`, container queries — they're there, use them.
- **Surprise the user when it helps.** Users often don't know what HTML/CSS can do. Novel layouts, scale play, layering, and visual rhythm are worth proposing.

### Avoid

- **Filler content.** Never pad a design with dummy sections, placeholder paragraphs, or invented stats just because the layout feels empty. Emptiness is a layout problem, solved with composition — not with made-up material. One thousand no's for every yes.
- **Unilateral additions.** If you think a section would help, **ask** before adding it. The user knows the audience better than you.
- **AI slop**, including:
- Aggressive gradient backgrounds
- Emoji that aren't part of the brand
- Rounded containers with a left-border accent color
- SVG illustrations drawn from scratch — use placeholders, or ask for real assets
- Overused typefaces (Inter, Roboto, Arial, system stacks — use what the project ships)
- Data slop (decorative numbers, unearned icons, KPI-shaped nothing)
- **Repetition.** The same point in two sections is a design bug.
- **Cross-project tropes.** Don't design a "web page" unless this is a web page. Don't design an "admin dashboard" unless the user asked for one.

---

## Variations strategy

When the user asks for 2–3 variations, make the spread meaningful:

- **Variant A — safe, pattern-faithful.** Uses the existing vocabulary as-is. This is the "you could ship this tomorrow" option.
- **Variant B (and C) — increasingly exploratory.** Push on one dimension at a time: layout metaphor, information hierarchy, type treatment, density, or interaction model. Start close to A and get bolder.
- **Don't waste variants on trivial deltas** (different accent color, slightly different padding). If A and B differ only in hex, you've delivered one variant, not two.
- Mix conservative with novel. Mix text-heavy with imagery-led. Mix dense with airy. Goal: expose the user to as many atomic choices as possible so they can mix-and-match.

Expose each variant as its own `export const Variant_X: StoryObj<typeof meta> = { render: () => <ProposalX /> }`.

---

## Asking questions — tips from the base

- **Confirm context via a question, not an assumption.** "I assume you want to reuse the existing form card" is worse than asking.
- When redesigning, ask what they care most about — flows, copy, or visuals — and design variants along that axis.
- Ask whether they want divergent visuals / interactions / copy, or just polish on the existing pattern.
- Err on the side of more questions for ambiguous briefs. For tight briefs, skip questions entirely.
- Before showing variations, ask what dimensions they want explored (novel UX, different visuals, animation, copy).

---

## Do not produce

- `CLAUDE.md`, `README.md`, or other documentation files
- Real logic (Convex, API calls, auth)
- Route definitions
- Test files
- Exports to PPTX / PDF / video / Canva — this skill builds Storybook stories, nothing else
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ Convex agent skills for common tasks can be installed by running `npx convex ai-
- プロセスの説明より体験を伝える
- 上から目線NG(「小さなお店」→「少人数のお店」)
- 短く 1行で複数の役割を持たせる(「誰向け」+「何をしてくれる」を合体)
- 日本語の文章は不用意に半角スペースをいれない

#### 構造

Expand Down
7 changes: 2 additions & 5 deletions e2e/pages/StaffSubmitPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export class StaffSubmitPage {
}

async expectCompletionVisible() {
await expect(this.page.getByText("提出しました")).toBeVisible();
await expect(this.page).toHaveURL(/\/shifts\/submit\/completed$/);
await expect(this.page.getByText("提出が完了しました")).toBeVisible();
}

async expectReadOnlyVisible() {
Expand Down Expand Up @@ -52,10 +53,6 @@ export class StaffSubmitPage {
await this.page.getByRole("button", { name: /提出する/ }).click();
}

async clickEdit() {
await this.page.getByRole("button", { name: "内容を修正する" }).click();
}

async expectDayWorking(dateText: string) {
const row = this.page.getByText(dateText, { exact: true }).locator("..");
await expect(row.getByText("〜")).toBeVisible();
Expand Down
2 changes: 1 addition & 1 deletion e2e/scenarios/staff-shift-submission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ test.describe("スタッフのシフト希望提出", () => {
});

await test.step("Step 3: 修正して再提出する", async () => {
await submitPage.clickEdit();
await submitPage.goto(token);
await submitPage.expectFormVisible();
await submitPage.expectSubmittedBadge();

Expand Down
10 changes: 6 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@
"url": "https://shiftori.app",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "JPY"
"image": "https://shiftori.app/ogp.png",
"inLanguage": "ja",
"provider": {
"@type": "Organization",
"name": "シフトリ",
"url": "https://shiftori.app"
}
}
</script>
Expand Down
24 changes: 24 additions & 0 deletions public/llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# シフトリ

> シフトリは少人数のお店向けのシフト管理SaaSです。スタッフはアカウント登録やアプリのインストール不要で、オーナーが送るリンクを開くだけで希望シフトを提出できます。

## 特徴

- スタッフはアカウント登録不要(リンクを開いて日付をタップするだけで提出できる)
- アプリのインストール不要(スマホのブラウザで完結)
- オーナーの登録だけで使いはじめられる
- 募集・希望収集・確定通知までワンクリックで進むシンプルな流れ
- LINE・Excel・メモに散っていたシフトづくりの情報をひとつの画面にまとめる
- 2人から20人くらいの少人数のお店(飲食店・美容室・個人経営のサービス業など)を想定

## 使い方

1. オーナーが募集期間を決めてスタッフにリンクを配る
2. スタッフがスマホのブラウザで希望シフトを提出する
3. 集まった希望を見て調整し、確定シフトをワンクリックで全員に通知する

## リンク

- [サービスサイト](https://shiftori.app/)
- [利用規約](https://shiftori.app/terms)
- [プライバシーポリシー](https://shiftori.app/privacy)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("formatShiftTimeRange", () => {
expect(formatShiftTimeRange("14:00", "25:00")).toBe("14:00〜翌1:00");
});

it("24:00 ちょうどは 翌0:00 になる", () => {
it("24:00ちょうどは翌0:00になる", () => {
expect(formatShiftTimeRange("09:00", "24:00")).toBe("09:00〜翌0:00");
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ export const Variants: Story = {
<Section label="締切が近い (3日以内)">
<HeroSummary shop={SHOP} recruitments={[dueSoon, confirmed]} {...NOOP} />
</Section>
<Section label="やることなし">
<Section label="提出待ち (締切まで余裕あり)">
<HeroSummary shop={SHOP} recruitments={[calm, confirmed]} {...NOOP} />
</Section>
<Section label="やることなし (open募集ゼロ)">
<HeroSummary shop={SHOP} recruitments={[confirmed]} {...NOOP} />
</Section>
<Section label="初回ログイン時">
<WelcomeHero onSetupClick={() => {}} />
</Section>
Expand Down
Loading