2026-06-28-dashboard-workbench-minimal-entry.md 26 KB

Dashboard Workbench Minimal Entry Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Bootstrap Git for the project, extend the login token to carry the stylist username, and shrink the dashboard into a minimal-entry page where only three action cards are interactive.

Architecture: Keep the existing login response shape, but add username to the signed JWT payload so the frontend can render the welcome header without a /me request. Refactor DashboardPage into a presentation-first page that removes unused workbench API calls, keeps the three real entry cards, and renders all other sections as explicit undeveloped placeholders with focused comments and regression tests.

Tech Stack: Git, NestJS 11, Prisma, Jest, Supertest, React 19, React Router 7, Vitest, Testing Library, i18next

Global Constraints

  • 工作台只暴露当前真正可用的入口,不展示“看似能用”的半成品能力。
  • 未开发区域要明确可感知,不做假数据充数,不伪装成已实现功能。
  • 当前版本能删掉的无效逻辑就删掉,只保留对 UI 占位真正有帮助的结构。
  • 前端优先复用现有页面布局和 copy,避免不必要的重写。
  • 用户名展示优先复用登录 token,而不是为了工作台额外增加 /me 依赖。
  • 注释必须具体说明未来要恢复哪类业务数据,不写泛泛的 TODO。
  • 当前版本只保留 3 个主操作卡片可点击,其余区域全部不可进入新页面。
  • 本 case 不新增新的工作台专属后端接口。
  • 当前项目尚未被 Git 管理,因此需要先初始化仓库,再将登录 case 形成基线提交,然后在新的分支上开发本 case,开发完成后再合并回主分支。
  • 禁止在代码中使用硬编码的绝对路径。
  • 需要排除 AI Agent 过程文件,如 .learnings、.superpowers。

File Structure

  • .gitignore: root-level Git ignore file for node_modules, build artifacts, env files, and agent process folders.
  • backend/src/modules/auth/auth.service.ts: add username to the signed JWT payload while preserving the existing login response body.
  • backend/src/modules/auth/auth.service.spec.ts: assert the backend signs a JWT payload that includes username.
  • backend/test/auth.e2e-spec.ts: verify a real login token contains username after decoding.
  • frontend/src/services/http/session.ts: add a focused token-decoding helper that safely reads the current session identity from the stored JWT.
  • frontend/src/services/http/__tests__/session.test.ts: regression tests for token decoding success and malformed-token fallback.
  • frontend/src/pages/dashboard/DashboardPage.tsx: remove workbench/statistics API usage, read the username from session token, hide the welcome stats line, and replace undeveloped blocks with disabled UI plus comments.
  • frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx: assert the dashboard shows the token-derived username, keeps only three working entry links, and renders undeveloped sections as disabled placeholders.
  • frontend/src/i18n/locales/zh/translation.json: add dashboard placeholder copy for undeveloped cards, sections, and disabled buttons.
  • frontend/src/i18n/locales/ja/translation.json: add the same dashboard placeholder copy in Japanese.
  • docs/superpowers/specs/2026-06-28-stylist-login-minimal-closed-loop-design.md: update the login token contract to mention username.

Task 1: Initialize Git And Create The Login Baseline

Files:

  • Create: .gitignore

Interfaces:

  • Produces: Git repository initialized at /Users/yuchen/Documents/hairstylist
  • Produces: default branch main
  • Produces: feature branch feature/dashboard-workbench-minimal-entry

  • [ ] Step 1: Create the root Git ignore file before initializing the repository

    # Root workspace ignores
    .DS_Store
    .idea/
    .vscode/
    .learnings/
    .superpowers/
    
    # Dependency directories
    node_modules/
    frontend/node_modules/
    backend/node_modules/
    
    # Build output
    dist/
    build/
    coverage/
    frontend/dist/
    backend/dist/
    
    # Environment files
    .env
    .env.local
    .env.*.local
    frontend/.env
    frontend/.env.local
    backend/.env
    backend/.env.local
    
    # Logs
    *.log
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    pnpm-debug.log*
    
  • [ ] Step 2: Initialize Git and rename the default branch to main

Run:

git init
git branch -M main
git status --short

Expected: Git reports a new repository on branch main, and tracked candidates appear as untracked files.

  • Step 3: Stage the existing login case as the baseline commit

Run:

git add .
git status --short

Expected: node_modules, backend/dist, .learnings, and .superpowers do not appear in the staging list because .gitignore excludes them.

  • Step 4: Create the baseline commit for the already-finished login case

Run:

git commit -m "chore: baseline stylist login case"
git log --oneline -n 1

Expected: the latest commit message is chore: baseline stylist login case.

  • Step 5: Create the feature branch for the dashboard case

Run:

git checkout -b feature/dashboard-workbench-minimal-entry
git branch --show-current

Expected: the current branch is feature/dashboard-workbench-minimal-entry.

Task 2: Add username To The Login Token And Lock It With Backend Tests

Files:

  • Modify: backend/src/modules/auth/auth.service.ts
  • Modify: backend/src/modules/auth/auth.service.spec.ts
  • Modify: backend/test/auth.e2e-spec.ts
  • Modify: docs/superpowers/specs/2026-06-28-stylist-login-minimal-closed-loop-design.md

Interfaces:

  • Consumes: AuthService.login(input: StylistLoginDto): Promise<LoginResultDto>
  • Produces: JWT payload { sub: number; email: string; username: string }
  • Produces: unchanged login response body { token, tokenName, stylistId, name, email }

  • [ ] Step 1: Extend the backend unit test so it fails until username is signed

    // backend/src/modules/auth/auth.service.spec.ts
    it('signs the jwt payload with username when credentials are valid', async () => {
    prisma.hairstylistUser.findFirst.mockResolvedValue({
    id: BigInt(1),
    username: 'Ogawa',
    email: 'stylist@example.com',
    password: await hash('Passw0rd!', 10),
    isDeleted: 0,
    });
    jwtService.signAsync.mockResolvedValue('signed.jwt.token');
    
    await service.login({
    email: 'stylist@example.com',
    password: 'Passw0rd!',
    remember: false,
    });
    
    expect(jwtService.signAsync).toHaveBeenCalledWith(
    {
      sub: 1,
      email: 'stylist@example.com',
      username: 'Ogawa',
    },
    expect.objectContaining({
      expiresIn: '7d',
    }),
    );
    });
    
  • [ ] Step 2: Extend the e2e test so it decodes a real login token and expects username

    // backend/test/auth.e2e-spec.ts
    it('/api/v1/auth/stylist/login (POST) returns a token payload with username', async () => {
    const response = await request(app.getHttpServer())
    .post('/api/v1/auth/stylist/login')
    .send({
      email: 'stylist@example.com',
      password: 'Passw0rd!',
      remember: false,
    })
    .expect(201);
    
    const payload = await jwtService.verifyAsync<{
    sub: number;
    email: string;
    username: string;
    }>(response.body.data.token);
    
    expect(payload.sub).toBe(1);
    expect(payload.email).toBe('stylist@example.com');
    expect(payload.username).toBe('Ogawa');
    });
    
  • [ ] Step 3: Run the backend tests first and confirm they fail on the missing field

Run:

npm test -- auth.service.spec.ts --runInBand
npm run test:e2e -- auth.e2e-spec.ts

Expected: at least one assertion fails because jwtService.signAsync does not yet receive username and the decoded token lacks username.

  • [ ] Step 4: Implement the minimal backend change in AuthService

    // backend/src/modules/auth/auth.service.ts
    const token = await this.jwtService.signAsync(
    {
    sub: Number(stylist.id),
    email: stylist.email,
    username: stylist.username,
    },
    {
    expiresIn:
      (this.configService.get<string>('JWT_EXPIRES_IN') ?? '7d') as StringValue,
    },
    );
    
  • [ ] Step 5: Update the login spec so the token contract matches the implementation

    ## Token 设计
    
    - 本 case 只签发单个 access token。
    - token 类型采用 JWT。
    - payload 只包含最小必要身份字段,例如 `sub`、`email`、`username`。
    - `username` 供工作台欢迎区直接消费,不再额外请求 `/me` 接口。
    - 不在 token 中塞入模板数量、客户统计等业务数据,避免 token 承担缓存职责。
    
  • [ ] Step 6: Re-run the focused backend tests and confirm they pass

Run:

npm test -- auth.service.spec.ts --runInBand
npm run test:e2e -- auth.e2e-spec.ts

Expected: both suites pass, and the login token decodes with username.

  • [ ] Step 7: Commit the token-contract change

    git add backend/src/modules/auth/auth.service.ts \
    backend/src/modules/auth/auth.service.spec.ts \
    backend/test/auth.e2e-spec.ts \
    docs/superpowers/specs/2026-06-28-stylist-login-minimal-closed-loop-design.md
    git commit -m "feat: include username in login token"
    

Task 3: Add A Safe Session Identity Helper And Render The Token Username In The Welcome Header

Files:

  • Modify: frontend/src/services/http/session.ts
  • Create: frontend/src/services/http/__tests__/session.test.ts
  • Modify: frontend/src/pages/dashboard/DashboardPage.tsx
  • Create: frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx

Interfaces:

  • Produces: getSessionIdentity(): { username: string | null }
  • Consumes: getToken(): string | null
  • Produces: DashboardPage welcome header uses token-derived username first, then falls back to pageCopy.greetingName

  • [ ] Step 1: Write the failing token-decoder unit test

    // frontend/src/services/http/__tests__/session.test.ts
    import { describe, expect, it } from 'vitest'
    import { clearSession, getSessionIdentity, setSession } from '../session'
    
    function createToken(payload: Record<string, unknown>) {
    const encoded = btoa(JSON.stringify(payload)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
    return `header.${encoded}.signature`
    }
    
    describe('session identity', () => {
    it('reads username from the current jwt payload', () => {
    setSession(createToken({ sub: 1, email: 'stylist@example.com', username: 'Ogawa' }), 'Authorization')
    
    expect(getSessionIdentity()).toEqual({
      username: 'Ogawa',
    })
    })
    
    it('returns nulls for malformed tokens', () => {
    setSession('not-a-jwt', 'Authorization')
    
    expect(getSessionIdentity()).toEqual({
      username: null,
    })
    })
    })
    
  • [ ] Step 2: Write the failing dashboard test for the personalized greeting

    // frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx
    import { HelmetProvider } from 'react-helmet-async'
    import { MemoryRouter, Route, Routes } from 'react-router-dom'
    import { render, screen } from '@testing-library/react'
    import '../../../../i18n'
    import { setSession } from '../../../services/http/session'
    import { DashboardPage } from '../DashboardPage'
    
    function createToken(payload: Record<string, unknown>) {
    const encoded = btoa(JSON.stringify(payload)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
    return `header.${encoded}.signature`
    }
    
    it('shows the username from the login token in the welcome header', async () => {
    setSession(createToken({ sub: 1, email: 'stylist@example.com', username: 'Ogawa' }), 'Authorization')
    
    render(
    <HelmetProvider>
      <MemoryRouter initialEntries={['/zh/dashboard']}>
        <Routes>
          <Route path="/:locale/dashboard" element={<DashboardPage />} />
        </Routes>
      </MemoryRouter>
    </HelmetProvider>,
    )
    
    expect(await screen.findByRole('heading', { name: /早上好|下午好|晚上好/i })).toBeInTheDocument()
    expect(screen.getByText('Ogawa')).toBeInTheDocument()
    })
    
  • [ ] Step 3: Run the new frontend tests and confirm they fail

Run:

npm run test -- src/services/http/__tests__/session.test.ts src/pages/dashboard/__tests__/DashboardPage.test.tsx

Expected: failures because getSessionIdentity does not exist yet and DashboardPage still depends on getStylistMe().

  • [ ] Step 4: Implement the token-decoding helper in session.ts

    // frontend/src/services/http/session.ts
    type SessionIdentity = {
    username: string | null
    }
    
    function decodeJwtSegment(segment: string): Record<string, unknown> | null {
    try {
    const normalized = segment.replace(/-/g, '+').replace(/_/g, '/')
    const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=')
    return JSON.parse(atob(padded)) as Record<string, unknown>
    } catch {
    return null
    }
    }
    
    export function getSessionIdentity(): SessionIdentity {
    const token = getToken()
    if (!token) {
    return { username: null }
    }
    
    const [, payload] = token.split('.')
    if (!payload) {
    return { username: null }
    }
    
    const decoded = decodeJwtSegment(payload)
    return {
    username: typeof decoded?.username === 'string' ? decoded.username : null,
    }
    }
    
  • [ ] Step 5: Refactor DashboardPage to consume the local token identity instead of /me

    // frontend/src/pages/dashboard/DashboardPage.tsx
    import { useMemo } from 'react'
    import { getSessionIdentity } from '../../services/http/session'
    
    export function DashboardPage() {
    const { locale, path, copy } = usePageContext()
    const pageCopy = copy.dashboardPage
    const sessionIdentity = useMemo(() => getSessionIdentity(), [])
    const stylistName = sessionIdentity.username || pageCopy.greetingName
    
    const now = useMemo(() => new Date(), [])
    const dateLabel = useMemo(
    () =>
      new Intl.DateTimeFormat(locale === 'ja' ? 'ja-JP' : 'zh-CN', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        weekday: 'long',
      }).format(now),
    [locale, now],
    )
    
    return (
    <DashboardLayout>
      <section className="stylist-workbench-top ipad-top-space-margin">
        <div className="container">
          <div className="row align-items-end justify-content-between mb-4">
            <div className="col-lg-10">
              <span className="bg-base-color d-inline-block mb-15px fw-600 text-white text-uppercase border-radius-30px ps-20px pe-20px fs-12">{pageCopy.badge}</span>
              <h1 className="fw-700 text-dark-gray ls-minus-1px mb-10px">
                {greeting}
                <br />
                {stylistName}
              </h1>
              <p className="fs-18 lh-30 mb-0">{pageCopy.summary}</p>
            </div>
            <div className="col-lg-4 text-lg-end mt-3 mt-lg-0">
              <span className="d-block fs-14 text-medium-gray">{dateLabel}</span>
            </div>
          </div>
        </div>
      </section>
    </DashboardLayout>
    )
    }
    
  • [ ] Step 6: Re-run the new frontend tests and confirm the welcome header now passes

Run:

npm run test -- src/services/http/__tests__/session.test.ts src/pages/dashboard/__tests__/DashboardPage.test.tsx

Expected: the token helper tests pass, and the dashboard test renders Ogawa without any /me mock.

  • [ ] Step 7: Commit the session-identity change

    git add frontend/src/services/http/session.ts \
    frontend/src/services/http/__tests__/session.test.ts \
    frontend/src/pages/dashboard/DashboardPage.tsx \
    frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx
    git commit -m "feat: read dashboard username from session token"
    

Task 4: Shrink The Dashboard To Three Real Entry Cards And Explicit Undeveloped UI

Files:

  • Modify: frontend/src/pages/dashboard/DashboardPage.tsx
  • Modify: frontend/src/i18n/locales/zh/translation.json
  • Modify: frontend/src/i18n/locales/ja/translation.json
  • Modify: frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx

Interfaces:

  • Produces: only three interactive links: /:locale/customers/new, /:locale/simulation, /:locale/templates
  • Produces: no calls to getWorkbenchData, getCustomerDashboardSummary, getTemplateDashboardSummary, or getStylistMe
  • Produces: undeveloped dashboard sections with disabled buttons and explicit placeholder copy

  • [ ] Step 1: Expand the dashboard test so it fails until the page is fully shrunk

    // frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx
    it('keeps only three real entry links and hides the welcome stats line', async () => {
    setSession(createToken({ sub: 1, email: 'stylist@example.com', username: 'Ogawa' }), 'Authorization')
    
    render(
    <HelmetProvider>
      <MemoryRouter initialEntries={['/zh/dashboard']}>
        <Routes>
          <Route path="/:locale/dashboard" element={<DashboardPage />} />
        </Routes>
      </MemoryRouter>
    </HelmetProvider>,
    )
    
    expect(screen.queryByText(/今日预约 .* 件 \/ 待跟进 .* 件/i)).not.toBeInTheDocument()
    
    const entryLinks = screen.getAllByRole('link').filter((element) => {
    const href = element.getAttribute('href') || ''
    return href === '/zh/customers/new' || href === '/zh/simulation' || href === '/zh/templates'
    })
    
    expect(entryLinks).toHaveLength(3)
    })
    
    it('renders undeveloped placeholders for stats and workbench panels', async () => {
    setSession(createToken({ sub: 1, email: 'stylist@example.com', username: 'Ogawa' }), 'Authorization')
    
    render(
    <HelmetProvider>
      <MemoryRouter initialEntries={['/zh/dashboard']}>
        <Routes>
          <Route path="/:locale/dashboard" element={<DashboardPage />} />
        </Routes>
      </MemoryRouter>
    </HelmetProvider>,
    )
    
    expect(screen.getAllByText('当前版本暂未开发').length).toBeGreaterThanOrEqual(4)
    expect(screen.getAllByRole('button', { name: '当前版本暂未开发' }).length).toBeGreaterThanOrEqual(3)
    })
    
  • [ ] Step 2: Add the missing localized placeholder copy in both translation files

    // frontend/src/i18n/locales/zh/translation.json
    "dashboardPage": {
    "undeveloped": {
    "statValue": "--",
    "badge": "当前版本暂未开发",
    "body": "当前版本仅保留工作台入口,后续将从工作台聚合数据恢复该区域。",
    "disabledAction": "当前版本暂未开发"
    }
    }
    
    // frontend/src/i18n/locales/ja/translation.json
    "dashboardPage": {
    "undeveloped": {
    "statValue": "--",
    "badge": "このバージョンでは未対応",
    "body": "このバージョンではワークベンチ入口のみ提供し、後続で集約データを接続します。",
    "disabledAction": "このバージョンでは未対応"
    }
    }
    
  • [ ] Step 3: Run the dashboard test again and confirm it fails on the old API-driven UI

Run:

npm run test -- src/pages/dashboard/__tests__/DashboardPage.test.tsx

Expected: failures because the page still shows the headline stat line, renders data-driven sections, and keeps extra interactive links.

  • [ ] Step 4: Remove the old workbench logic and replace it with explicit undeveloped placeholders

    // frontend/src/pages/dashboard/DashboardPage.tsx
    import { useMemo } from 'react'
    import { Link } from 'react-router-dom'
    
    const undevelopedPanels = ['today', 'followUp', 'records'] as const
    const undevelopedStats = ['todayCustomers', 'followUp', 'weeklyRecords', 'templates'] as const
    
    export function DashboardPage() {
    const { locale, path, copy } = usePageContext()
    const pageCopy = copy.dashboardPage
    const undevelopedCopy = pageCopy.undeveloped
    
    return (
    <DashboardLayout>
      <section className="stylist-workbench-top ipad-top-space-margin">
        <div className="container">
          <div className="row align-items-end justify-content-between mb-4">
            <div className="col-lg-10">
              <span className="bg-base-color d-inline-block mb-15px fw-600 text-white text-uppercase border-radius-30px ps-20px pe-20px fs-12">{pageCopy.badge}</span>
              <h1 className="fw-700 text-dark-gray ls-minus-1px mb-10px">{greeting}<br />{stylistName}</h1>
              <p className="fs-18 lh-30 mb-0">{pageCopy.summary}</p>
            </div>
            <div className="col-lg-4 text-lg-end mt-3 mt-lg-0">
              <span className="d-block fs-14 text-medium-gray">{dateLabel}</span>
              {/* 当前版本未开发欢迎区统计,后续从 workbench 聚合接口恢复预约与跟进真实数据。 */}
            </div>
          </div>
    
          <div className="row row-cols-1 row-cols-lg-3 row-cols-md-3 g-4 mb-5">
            {/* keep only the three real entry cards for this version */}
          </div>
    
          <div className="row row-cols-2 row-cols-lg-4 row-cols-md-4 g-3 mb-5">
            {undevelopedStats.map((key) => (
              <div key={key} className="col">
                <div className="workbench-stat bg-white border-radius-8px p-20px opacity-75">
                  <span className="d-block fs-26 fw-800 text-dark-gray">{undevelopedCopy.statValue}</span>
                  <span className="fs-14 d-block">{pageCopy.stats[key]}</span>
                  <span className="fs-13 text-medium-gray d-block mt-2">{undevelopedCopy.badge}</span>
                </div>
              </div>
            ))}
          </div>
        </div>
      </section>
    
      <section className="pt-0">
        <div className="container">
          <div className="row g-4">
            {undevelopedPanels.map((panelKey) => (
              <div key={panelKey} className={panelKey === 'records' ? 'col-12' : panelKey === 'today' ? 'col-xl-7' : 'col-xl-5'}>
                <div className="workbench-panel bg-white border-radius-8px box-shadow-extra-large p-30px h-100 opacity-75">
                  <div className="d-flex align-items-center justify-content-between mb-25px">
                    <div>
                      <span className="fs-14 fw-700 text-base-color text-uppercase">{pageCopy.panels[panelKey].badge}</span>
                      <h2 className="fw-700 text-dark-gray fs-26 mb-0">{pageCopy.panels[panelKey].title}</h2>
                    </div>
                    <button type="button" className="btn btn-small btn-rounded btn-transparent-light-gray border border-1 border-color-dark-gray fw-700" disabled>
                      {undevelopedCopy.disabledAction}
                    </button>
                  </div>
                  {/* 当前版本未开发该工作台区块,后续从 workbench 聚合接口恢复列表数据与导航动作。 */}
                  <p className="fs-15 text-medium-gray mb-0">{undevelopedCopy.body}</p>
                </div>
              </div>
            ))}
          </div>
        </div>
      </section>
    </DashboardLayout>
    )
    }
    
  • [ ] Step 5: Re-run the focused dashboard regression test and confirm only the intended UI remains

Run:

npm run test -- src/pages/dashboard/__tests__/DashboardPage.test.tsx

Expected: the test passes with exactly three interactive entry links, no welcome stats line, and disabled undeveloped sections.

  • [ ] Step 6: Commit the dashboard shrink

    git add frontend/src/pages/dashboard/DashboardPage.tsx \
    frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx \
    frontend/src/i18n/locales/zh/translation.json \
    frontend/src/i18n/locales/ja/translation.json
    git commit -m "feat: shrink dashboard to minimal entry page"
    

Task 5: Run Verification, Update Diagnostics, And Merge Back To main

Files:

  • Modify: docs/superpowers/specs/2026-06-28-dashboard-workbench-minimal-entry-design.md (only if the implemented copy or disabled-button behavior differs from the spec during execution)

Interfaces:

  • Consumes: all commits on feature/dashboard-workbench-minimal-entry
  • Produces: merged main branch with a clean working tree

  • [ ] Step 1: Run the backend and frontend verification commands

Run:

cd /Users/yuchen/Documents/hairstylist/backend && npm test -- auth.service.spec.ts --runInBand && npm run test:e2e -- auth.e2e-spec.ts
cd /Users/yuchen/Documents/hairstylist/frontend && npm run test -- src/services/http/__tests__/session.test.ts src/pages/dashboard/__tests__/DashboardPage.test.tsx src/pages/public/__tests__/login-page.test.tsx src/shared/__tests__/RequireAuth.test.tsx
cd /Users/yuchen/Documents/hairstylist/frontend && npm run typecheck

Expected: all targeted backend/frontend tests pass, and frontend type checking succeeds.

  • Step 2: Check diagnostics on the changed frontend and backend files

Files to inspect:

backend/src/modules/auth/auth.service.ts
backend/src/modules/auth/auth.service.spec.ts
backend/test/auth.e2e-spec.ts
frontend/src/services/http/session.ts
frontend/src/services/http/__tests__/session.test.ts
frontend/src/pages/dashboard/DashboardPage.tsx
frontend/src/pages/dashboard/__tests__/DashboardPage.test.tsx
frontend/src/i18n/locales/zh/translation.json
frontend/src/i18n/locales/ja/translation.json

Expected: no newly introduced diagnostics remain unresolved.

  • [ ] Step 3: Update the dashboard spec only if the implementation forced a small copy or structure adjustment

    ## 验收标准
    
    - 页面上只有 3 个主操作卡片可以点击。
    - 4 个统计卡显示为未开发态,不再依赖真实统计请求。
    - 今日顾客、待跟进顾客、最近施术记录 3 个区块显示为未开发态,并使用禁用按钮而非真实跳转。
    
  • [ ] Step 4: Commit the final docs-or-fixup changes if Step 3 modified anything

    git add docs/superpowers/specs/2026-06-28-dashboard-workbench-minimal-entry-design.md
    git commit -m "docs: align dashboard spec with implementation"
    

Expected: skip this step entirely if no spec text changed.

  • Step 5: Merge the feature branch back into main

Run:

git checkout main
git merge --no-ff feature/dashboard-workbench-minimal-entry
git status --short

Expected: the merge succeeds without conflicts, and git status --short is empty.

  • Step 6: Verify the final branch history

Run:

git log --oneline --decorate -n 5
git branch --show-current

Expected: the current branch is main, and the merge commit includes the feature branch work.