takt/builtins/en/knowledge/frontend.md
nrslib ea7ce54912 takt: # タスク指示書: resources/ → builtins/ リネーム + export-cc 修正
## 概要
`resources/` ディレクトリを `builtins/` にリネームし、用途を明確化。同時に export-cc コマンドを拡張して全リソースをコピーするように修正する。

---

## タスク一覧

### 1. ディレクトリリネーム(優先度: 高)

| 変更前 | 変更後 |
|--------|--------|
| `resources/` | `builtins/` |
| `resources/global/{lang}/` | `builtins/{lang}/`(global/ 階層を除去) |
| `resources/project/` | `builtins/project/` |
| `resources/skill/` | `builtins/skill/` |

### 2. 不要ファイル削除(優先度: 高)

- `builtins/{lang}/prompts/` を削除
  - 対象: `interactive-system.md`, `interactive-summary.md`
  - 理由: コードから未参照、実体は `src/shared/prompts/`

### 3. コード修正 — パス参照(優先度: 高)

`resources` → `builtins`、`global/{lang}` → `{lang}` に更新:

| ファイル | 修正内容 |
|----------|----------|
| `src/infra/resources/index.ts` | `getResourcesDir()`, `getGlobalResourcesDir()`, `getLanguageResourcesDir()` 等のパス |
| `src/infra/config/paths.ts` | `getBuiltinPiecesDir()`, `getBuiltinPersonasDir()` |
| `src/infra/config/global/initialization.ts` | `copyLanguageConfigYaml()` |
| `src/infra/config/loaders/pieceCategories.ts` | `getLanguageResourcesDir()` 参照 |
| `src/features/config/ejectBuiltin.ts` | `getLanguageResourcesDir()` 参照 |
| `src/features/config/deploySkill.ts` | `getResourcesDir()` 参照 |

### 4. export-cc 修正(優先度: 高)

ファイル: `src/features/config/deploySkill.ts`

**現状**: pieces/ と personas/ のみコピー

**修正後**:
- `builtins/{lang}/` 全体を `~/.claude/skills/takt/` にコピー
- `skill/` のファイル(SKILL.md, references/, takt-command.md)は従来通り
- サマリー表示を新リソースタイプ(stances, instructions, knowledge 等)に対応
- confirm メッセージ修正:
  - 現状: `'上書きしますか?'`
  - 修正後: `'既存のスキルファイルをすべて削除し、最新版に置き換えます。続行しますか?'`

### 5. テスト修正(優先度: 中)

| ファイル | 修正内容 |
|----------|----------|
| `src/__tests__/initialization.test.ts` | `getLanguageResourcesDir` のパス期待値 |
| `src/__tests__/piece-category-config.test.ts` | mock パス |
| その他 `resources` パスを参照しているテスト | パス更新 |

### 6. ビルド・パッケージ設定(優先度: 中)

| ファイル | 修正内容 |
|----------|----------|
| `package.json` | `files` フィールドで `resources/` → `builtins/` |
| `tsconfig.json` | `resources/` への参照があれば更新 |
| `.gitignore` | 必要に応じて更新 |

### 7. ドキュメント(優先度: 低)

- `CLAUDE.md` の Directory Structure セクションを更新
- JSDoc コメントから `prompts/` 記述を削除

---

## 制約

- `builtins/{lang}/` のフラット構造は変更不可(ピースYAML内の相対パス依存)
- eject のセーフティ(skip-if-exists)は変更不要
- export-cc のセーフティ(SKILL.md 存在チェック + confirm)は維持

---

## 確認方法

- `npm run build` が成功すること
- `npm test` が全てパスすること
- `takt init` / `takt eject` / `takt export-cc` が正常動作すること
2026-02-07 14:46:20 +09:00

15 KiB

Frontend Knowledge

Component Design

Do not write everything in one file. Always split components.

Required splits:

  • Has its own state → Must split
  • JSX over 50 lines → Split
  • Reusable → Split
  • Multiple responsibilities → Split
  • Independent section within page → Split
Criteria Judgment
Component over 200 lines Consider splitting
Component over 300 lines REJECT
Display and logic mixed Consider separation
Props drilling (3+ levels) Consider state management
Component with multiple responsibilities REJECT

Good Component:

  • Single responsibility: Does one thing well
  • Self-contained: Dependencies are clear
  • Testable: Side effects are isolated

Component Classification:

Type Responsibility Example
Container Data fetching, state management UserListContainer
Presentational Display only UserCard
Layout Arrangement, structure PageLayout, Grid
Utility Common functionality ErrorBoundary, Portal

Directory Structure:

features/{feature-name}/
├── components/
│   ├── {feature}-view.tsx      # Main view (composes children)
│   ├── {sub-component}.tsx     # Sub-components
│   └── index.ts
├── hooks/
├── types.ts
└── index.ts

State Management

Child components do not modify their own state. They bubble events to parent, and parent manipulates state.

// ❌ Child modifies its own state
const ChildBad = ({ initialValue }: { initialValue: string }) => {
  const [value, setValue] = useState(initialValue)
  return <input value={value} onChange={e => setValue(e.target.value)} />
}

// ✅ Parent manages state, child notifies via callback
const ChildGood = ({ value, onChange }: { value: string; onChange: (v: string) => void }) => {
  return <input value={value} onChange={e => onChange(e.target.value)} />
}

const Parent = () => {
  const [value, setValue] = useState('')
  return <ChildGood value={value} onChange={setValue} />
}

Exception (OK for child to have local state):

  • UI-only temporary state (hover, focus, animation)
  • Completely local state that doesn't need to be communicated to parent
Criteria Judgment
Unnecessary global state Consider localizing
Same state managed in multiple places Needs normalization
State changes from child to parent (reverse data flow) REJECT
API response stored as-is in state Consider normalization
Inappropriate useEffect dependencies REJECT

State Placement Guidelines:

State Nature Recommended Placement
Temporary UI state (modal open/close, etc.) Local (useState)
Form input values Local or form library
Shared across multiple components Context or state management library
Server data cache Data fetching library (TanStack Query, etc.)

Data Fetching

API calls are made in root (View) components and passed to children via props.

// ✅ CORRECT - Fetch at root, pass to children
const OrderDetailView = () => {
  const { data: order, isLoading, error } = useGetOrder(orderId)
  const { data: items } = useListOrderItems(orderId)

  if (isLoading) return <Skeleton />
  if (error) return <ErrorDisplay error={error} />

  return (
    <OrderSummary
      order={order}
      items={items}
      onItemSelect={handleItemSelect}
    />
  )
}

// ❌ WRONG - Child fetches its own data
const OrderSummary = ({ orderId }) => {
  const { data: order } = useGetOrder(orderId)
  // ...
}

When UI state changes affect parameters (week switching, filters, etc.):

Manage state at View level and pass callbacks to components.

// ✅ CORRECT - State managed at View level
const ScheduleView = () => {
  const [currentWeek, setCurrentWeek] = useState(startOfWeek(new Date()))
  const { data } = useListSchedules({
    from: format(currentWeek, 'yyyy-MM-dd'),
    to: format(endOfWeek(currentWeek), 'yyyy-MM-dd'),
  })

  return (
    <WeeklyCalendar
      schedules={data?.items ?? []}
      currentWeek={currentWeek}
      onWeekChange={setCurrentWeek}
    />
  )
}

// ❌ WRONG - Component manages state + data fetching
const WeeklyCalendar = ({ facilityId }) => {
  const [currentWeek, setCurrentWeek] = useState(...)
  const { data } = useListSchedules({ facilityId, from, to })
  // ...
}

Exceptions (component-level fetching allowed):

Case Reason
Infinite scroll Depends on scroll position (internal UI state)
Search autocomplete Real-time search based on input value
Independent widget Notification badge, weather, etc. Completely unrelated to parent data
Real-time updates WebSocket/Polling auto-updates
Modal detail fetch Fetch additional data only when opened

Decision criteria: "Is there no point in parent managing this / Does it not affect parent?"

Criteria Judgment
Direct fetch in component Separate to Container layer
No error handling REJECT
Loading state not handled REJECT
No cancellation handling Warning
N+1 query-like fetching REJECT

Shared Components and Abstraction

Common UI patterns should be shared components. Copy-paste of inline styles is prohibited.

// ❌ WRONG - Copy-pasted inline styles
<button className="p-2 text-[var(--text-secondary)] hover:...">
  <X className="w-5 h-5" />
</button>

// ✅ CORRECT - Use shared component
<IconButton onClick={onClose} aria-label="Close">
  <X className="w-5 h-5" />
</IconButton>

Patterns to make shared components:

  • Icon buttons (close, edit, delete, etc.)
  • Loading/error displays
  • Status badges
  • Tab switching
  • Label + value display (detail screens)
  • Search input
  • Color legends

Avoid over-generalization:

// ❌ WRONG - Forcing stepper variant into IconButton
export const iconButtonVariants = cva('...', {
  variants: {
    variant: {
      default: '...',
      outlined: '...',  // ← Stepper-specific, not used elsewhere
    },
    size: {
      medium: 'p-2',
      stepper: 'w-8 h-8',  // ← Only used with outlined
    },
  },
})

// ✅ CORRECT - Purpose-specific component
export function StepperButton(props) {
  return (
    <button className="w-8 h-8 rounded-full border ..." {...props}>
      <Plus className="w-4 h-4" />
    </button>
  )
}

Signs to make separate components:

  • Implicit constraints like "this variant is always with this size"
  • Added variant is clearly different from original component's purpose
  • Props specification becomes complex on the usage side

Abstraction Level Evaluation

Conditional branch bloat detection:

Pattern Judgment
Same conditional in 3+ places Extract to shared component → REJECT
Props-based branching with 5+ types Consider component split
Nested ternaries in render Early return or component separation → REJECT
Type-based render branching Consider polymorphic components

Abstraction level mismatch detection:

Pattern Problem Fix
Data fetching logic mixed in JSX Hard to read Extract to custom hook
Business logic mixed in component Responsibility violation Separate to hooks/utils
Style calculation logic scattered Hard to maintain Extract to utility function
Same transformation in multiple places DRY violation Extract to common function

Good abstraction examples:

// ❌ Conditional bloat
function UserBadge({ user }) {
  if (user.role === 'admin') {
    return <span className="bg-red-500">Admin</span>
  } else if (user.role === 'moderator') {
    return <span className="bg-yellow-500">Moderator</span>
  } else if (user.role === 'premium') {
    return <span className="bg-purple-500">Premium</span>
  } else {
    return <span className="bg-gray-500">User</span>
  }
}

// ✅ Abstracted with Map
const ROLE_CONFIG = {
  admin: { label: 'Admin', className: 'bg-red-500' },
  moderator: { label: 'Moderator', className: 'bg-yellow-500' },
  premium: { label: 'Premium', className: 'bg-purple-500' },
  default: { label: 'User', className: 'bg-gray-500' },
}

function UserBadge({ user }) {
  const config = ROLE_CONFIG[user.role] ?? ROLE_CONFIG.default
  return <span className={config.className}>{config.label}</span>
}
// ❌ Mixed abstraction levels
function OrderList() {
  const [orders, setOrders] = useState([])
  useEffect(() => {
    fetch('/api/orders')
      .then(res => res.json())
      .then(data => setOrders(data))
  }, [])

  return orders.map(order => (
    <div>{order.total.toLocaleString()} USD</div>
  ))
}

// ✅ Aligned abstraction levels
function OrderList() {
  const { data: orders } = useOrders()  // Hide data fetching

  return orders.map(order => (
    <OrderItem key={order.id} order={order} />
  ))
}

Frontend and Backend Separation of Concerns

Display Format Responsibility

Backend returns "data", frontend converts to "display format".

// ✅ Frontend: Convert to display format
export function formatPrice(amount: number): string {
  return `$${amount.toLocaleString()}`
}

export function formatDate(date: Date): string {
  return format(date, 'MMM d, yyyy')
}
Criteria Judgment
Backend returns display strings Suggest design review
Same format logic copy-pasted Unify to utility function
Inline formatting in component Extract to function

Domain Logic Placement (Smart UI Elimination)

Domain logic (business rules) belongs in the backend. Frontend only displays and edits state.

What is domain logic:

  • Aggregate business rules (stock validation, price calculation, status transitions)
  • Business constraint validation
  • Invariant enforcement

Frontend responsibilities:

  • Display state received from server
  • Collect user input and send commands to backend
  • Manage UI-only temporary state (focus, hover, modal open/close)
  • Display format conversion (formatting, sorting, filtering)
Criteria Judgment
Price calculation/stock validation in frontend Move to backend → REJECT
Status transition rules in frontend Move to backend → REJECT
Business validation in frontend Move to backend → REJECT
Recalculating server-computable values in frontend Redundant → REJECT

Good vs Bad Examples:

// ❌ BAD - Business rules in frontend
function OrderForm({ order }: { order: Order }) {
  const totalPrice = order.items.reduce((sum, item) =>
    sum + item.price * item.quantity, 0
  )
  const canCheckout = totalPrice >= 100 && order.items.every(i => i.stock > 0)

  return <button disabled={!canCheckout}>Checkout</button>
}

// ✅ GOOD - Display state received from server
function OrderForm({ order }: { order: Order }) {
  // totalPrice, canCheckout are received from server
  return (
    <>
      <div>{formatPrice(order.totalPrice)}</div>
      <button disabled={!order.canCheckout}>Checkout</button>
    </>
  )
}
// ❌ BAD - Status transition logic in frontend
function TaskCard({ task }: { task: Task }) {
  const canStart = task.status === 'pending' && task.assignee !== null
  const canComplete = task.status === 'in_progress' && /* complex conditions... */

  return (
    <>
      <button onClick={startTask} disabled={!canStart}>Start</button>
      <button onClick={completeTask} disabled={!canComplete}>Complete</button>
    </>
  )
}

// ✅ GOOD - Server returns allowed actions
function TaskCard({ task }: { task: Task }) {
  // task.allowedActions = ['start', 'cancel'], etc., calculated by server
  const canStart = task.allowedActions.includes('start')
  const canComplete = task.allowedActions.includes('complete')

  return (
    <>
      <button onClick={startTask} disabled={!canStart}>Start</button>
      <button onClick={completeTask} disabled={!canComplete}>Complete</button>
    </>
  )
}

Exceptions (OK to have logic in frontend):

Case Reason
UI-only validation UX feedback like "required field", "max length" (must also validate on server)
Client-side filter/sort Changing display order of lists received from server
Display condition branching UI control like "show details if logged in"
Real-time feedback Preview display during input

Decision criteria: "Would the business break if this calculation differs from the server?"

  • YES → Place in backend (domain logic)
  • NO → OK in frontend (display logic)

Performance

Criteria Judgment
Unnecessary re-renders Needs optimization
Large lists without virtualization Warning
Unoptimized images Warning
Unused code in bundle Check tree-shaking
Excessive memoization Verify necessity

Optimization Checklist:

  • Are React.memo / useMemo / useCallback appropriate?
  • Are large lists using virtual scroll?
  • Is Code Splitting appropriate?
  • Are images lazy loaded?

Anti-patterns:

// ❌ New object every render
<Child style={{ color: 'red' }} />

// ✅ Constant or useMemo
const style = useMemo(() => ({ color: 'red' }), []);
<Child style={style} />

Accessibility

Criteria Judgment
Interactive elements without keyboard support REJECT
Images without alt attribute REJECT
Form elements without labels REJECT
Information conveyed by color only REJECT
Missing focus management (modals, etc.) REJECT

Checklist:

  • Using semantic HTML?
  • Are ARIA attributes appropriate (not excessive)?
  • Is keyboard navigation possible?
  • Does it make sense with a screen reader?
  • Is color contrast sufficient?

TypeScript/Type Safety

Criteria Judgment
Use of any type REJECT
Excessive type assertions (as) Needs review
No Props type definition REJECT
Inappropriate event handler types Needs fix

Frontend Security

Criteria Judgment
dangerouslySetInnerHTML usage Check XSS risk
Unsanitized user input REJECT
Sensitive data stored in frontend REJECT
CSRF token not used Needs verification

Testability

Criteria Judgment
No data-testid, etc. Warning
Structure difficult to test Consider separation
Business logic embedded in UI REJECT

Anti-pattern Detection

REJECT if found:

Anti-pattern Problem
God Component All features concentrated in one component
Prop Drilling Deep props bucket brigade
Inline Styles abuse Maintainability degradation
useEffect hell Dependencies too complex
Premature Optimization Unnecessary memoization
Magic Strings Hardcoded strings
Hidden Dependencies Child components with hidden API calls
Over-generalization Components forced to be generic