# Plan 010: Create Shared Component Library

**Priority**: MEDIUM
**Effort**: 1-2 weeks
**Status**: NOT STARTED
**Risk Level**: LOW - Code organization

---

## Problem Statement

SBA and PD frontends have duplicate component implementations. When a component is updated in one frontend, it must be manually synchronized to the other.

Current state:
- `frontend-sba/components/` - Full implementation (~20 components)
- `frontend-pd/components/` - Minimal (mostly stubs)

## Impact

- **DRY Violation**: Same code in two places
- **Maintenance**: Updates require changes in both places
- **Consistency**: Risk of drift between implementations
- **Testing**: Need to test same component twice

## Proposed Structure

```
strat-gameplay-webapp/
├── packages/
│   └── shared-ui/              # Shared component library
│       ├── package.json
│       ├── nuxt.config.ts      # Nuxt module config
│       ├── components/
│       │   ├── Game/
│       │   ├── Decisions/
│       │   ├── Gameplay/
│       │   ├── Substitutions/
│       │   └── UI/
│       ├── composables/
│       ├── types/
│       └── styles/
├── frontend-sba/               # SBA-specific
│   └── nuxt.config.ts          # Extends shared-ui
└── frontend-pd/                # PD-specific
    └── nuxt.config.ts          # Extends shared-ui
```

## Implementation Steps

### Step 1: Create Package Structure (1 hour)

```bash
cd /mnt/NV2/Development/strat-gameplay-webapp

# Create shared package
mkdir -p packages/shared-ui/{components,composables,types,styles}

# Initialize package
cd packages/shared-ui
```

Create `packages/shared-ui/package.json`:

```json
{
  "name": "@strat-gameplay/shared-ui",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": {
      "import": "./index.ts"
    },
    "./components/*": {
      "import": "./components/*"
    },
    "./composables/*": {
      "import": "./composables/*"
    },
    "./types/*": {
      "import": "./types/*"
    }
  },
  "dependencies": {
    "vue": "^3.4.0"
  },
  "devDependencies": {
    "@nuxt/kit": "^3.14.0",
    "typescript": "^5.3.0"
  },
  "peerDependencies": {
    "nuxt": "^3.14.0"
  }
}
```

### Step 2: Create Nuxt Module (2 hours)

Create `packages/shared-ui/nuxt.config.ts`:

```typescript
import { defineNuxtModule, addComponentsDir, addImportsDir } from '@nuxt/kit'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'

export default defineNuxtModule({
  meta: {
    name: '@strat-gameplay/shared-ui',
    configKey: 'sharedUi',
  },
  defaults: {
    prefix: '',  // No prefix for components
  },
  setup(options, nuxt) {
    const runtimeDir = dirname(fileURLToPath(import.meta.url))

    // Add components
    addComponentsDir({
      path: join(runtimeDir, 'components'),
      prefix: options.prefix,
      pathPrefix: false,
    })

    // Add composables
    addImportsDir(join(runtimeDir, 'composables'))

    // Add types
    nuxt.hook('prepare:types', ({ tsConfig }) => {
      tsConfig.include = tsConfig.include || []
      tsConfig.include.push(join(runtimeDir, 'types/**/*.d.ts'))
    })
  },
})
```

Create `packages/shared-ui/index.ts`:

```typescript
// Export module
export { default } from './nuxt.config'

// Export types
export * from './types/game'
export * from './types/player'
export * from './types/websocket'

// Export composables
export { useWebSocket } from './composables/useWebSocket'
export { useGameActions } from './composables/useGameActions'
```

### Step 3: Move Shared Components (4 hours)

Identify components that are league-agnostic:

**Fully Shared (move to packages/shared-ui):**
- `components/UI/` - All UI primitives
- `components/Game/ScoreBoard.vue` - League-agnostic
- `components/Game/GameBoard.vue` - League-agnostic
- `components/Game/CurrentSituation.vue` - League-agnostic
- `components/Gameplay/DiceRoller.vue` - League-agnostic
- `components/Gameplay/PlayResult.vue` - League-agnostic

**League-Specific (keep in frontend-*):**
- `components/Decisions/` - Different for SBA vs PD
- Player cards - Different data structures
- Scouting views - PD only

Move shared components:

```bash
# Move UI components
cp -r frontend-sba/components/UI packages/shared-ui/components/

# Move Game components
cp -r frontend-sba/components/Game packages/shared-ui/components/

# Move Gameplay components
cp -r frontend-sba/components/Gameplay packages/shared-ui/components/

# Move composables
cp frontend-sba/composables/useWebSocket.ts packages/shared-ui/composables/
cp frontend-sba/composables/useGameActions.ts packages/shared-ui/composables/

# Move types
cp frontend-sba/types/game.ts packages/shared-ui/types/
cp frontend-sba/types/player.ts packages/shared-ui/types/
cp frontend-sba/types/websocket.ts packages/shared-ui/types/
```

### Step 4: Create Theme System (2 hours)

Create `packages/shared-ui/styles/themes.ts`:

```typescript
export interface LeagueTheme {
  primary: string
  secondary: string
  accent: string
  background: string
  surface: string
  text: string
  textMuted: string
}

export const sbaTheme: LeagueTheme = {
  primary: '#1e40af',      // Blue
  secondary: '#3b82f6',
  accent: '#f59e0b',       // Amber
  background: '#0f172a',
  surface: '#1e293b',
  text: '#f8fafc',
  textMuted: '#94a3b8',
}

export const pdTheme: LeagueTheme = {
  primary: '#7c3aed',      // Purple
  secondary: '#a78bfa',
  accent: '#10b981',       // Emerald
  background: '#0f172a',
  surface: '#1e293b',
  text: '#f8fafc',
  textMuted: '#94a3b8',
}
```

Create `packages/shared-ui/composables/useTheme.ts`:

```typescript
import { inject, provide, ref, readonly } from 'vue'
import type { LeagueTheme } from '../styles/themes'
import { sbaTheme } from '../styles/themes'

const THEME_KEY = Symbol('theme')

export function provideTheme(theme: LeagueTheme) {
  const themeRef = ref(theme)
  provide(THEME_KEY, readonly(themeRef))
  return themeRef
}

export function useTheme(): LeagueTheme {
  const theme = inject<LeagueTheme>(THEME_KEY)
  if (!theme) {
    console.warn('No theme provided, using SBA default')
    return sbaTheme
  }
  return theme
}
```

### Step 5: Update Frontends to Use Shared Package (2 hours)

Update `frontend-sba/package.json`:

```json
{
  "dependencies": {
    "@strat-gameplay/shared-ui": "workspace:*"
  }
}
```

Update `frontend-sba/nuxt.config.ts`:

```typescript
export default defineNuxtConfig({
  modules: [
    '@strat-gameplay/shared-ui',
  ],
  sharedUi: {
    prefix: '',
  },
  // Override theme
  runtimeConfig: {
    public: {
      league: 'sba',
    },
  },
})
```

Update `frontend-sba/app.vue`:

```vue
<script setup lang="ts">
import { provideTheme, sbaTheme } from '@strat-gameplay/shared-ui'

provideTheme(sbaTheme)
</script>
```

### Step 6: Setup Workspace (1 hour)

Create/update root `package.json`:

```json
{
  "name": "strat-gameplay-webapp",
  "private": true,
  "workspaces": [
    "packages/*",
    "frontend-sba",
    "frontend-pd"
  ],
  "scripts": {
    "dev:sba": "cd frontend-sba && npm run dev",
    "dev:pd": "cd frontend-pd && npm run dev",
    "build": "npm run build --workspaces",
    "test": "npm run test --workspaces"
  }
}
```

Install dependencies:

```bash
cd /mnt/NV2/Development/strat-gameplay-webapp
npm install  # or bun install
```

### Step 7: Update Imports in Frontends (2 hours)

Update components to import from shared package:

**Before:**
```typescript
// frontend-sba/pages/game/[id].vue
import ScoreBoard from '~/components/Game/ScoreBoard.vue'
import { useWebSocket } from '~/composables/useWebSocket'
```

**After:**
```typescript
// frontend-sba/pages/game/[id].vue
// ScoreBoard auto-imported from shared-ui module
// useWebSocket auto-imported from shared-ui module
```

### Step 8: Add Shared Tests (2 hours)

Create `packages/shared-ui/tests/`:

```typescript
// packages/shared-ui/tests/components/ScoreBoard.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ScoreBoard from '../components/Game/ScoreBoard.vue'

describe('ScoreBoard', () => {
  it('renders team scores', () => {
    const wrapper = mount(ScoreBoard, {
      props: {
        homeScore: 5,
        awayScore: 3,
        homeTeamName: 'Home Team',
        awayTeamName: 'Away Team',
      },
    })

    expect(wrapper.text()).toContain('5')
    expect(wrapper.text()).toContain('3')
  })

  it('applies theme colors', () => {
    // Test theme integration
  })
})
```

### Step 9: Documentation (1 hour)

Create `packages/shared-ui/README.md`:

```markdown
# @strat-gameplay/shared-ui

Shared Vue 3 components for Strat Gameplay frontends.

## Usage

Add to your Nuxt config:

```typescript
export default defineNuxtConfig({
  modules: ['@strat-gameplay/shared-ui'],
})
```

## Components

### Game Components
- `ScoreBoard` - Displays game score, inning, count
- `GameBoard` - Diamond visualization
- `CurrentSituation` - Batter/pitcher matchup

### UI Components
- `ActionButton` - Styled action button
- `ToggleSwitch` - Boolean toggle
- `ButtonGroup` - Button group container

## Composables

- `useWebSocket` - WebSocket connection management
- `useGameActions` - Type-safe game action emitters
- `useTheme` - Theme injection/consumption

## Theming

Provide theme at app root:

```vue
<script setup>
import { provideTheme, sbaTheme } from '@strat-gameplay/shared-ui'
provideTheme(sbaTheme)
</script>
```
```

## Verification Checklist

- [ ] Shared package builds without errors
- [ ] SBA frontend works with shared components
- [ ] PD frontend works with shared components
- [ ] Themes apply correctly per league
- [ ] Tests pass for shared components
- [ ] No duplicate component code remains

## Migration Strategy

1. **Phase 1**: Create package, move UI primitives
2. **Phase 2**: Move game display components
3. **Phase 3**: Move composables and types
4. **Phase 4**: Refactor league-specific components

## Rollback Plan

If issues arise:
1. Remove shared-ui from module list
2. Restore original component imports
3. Keep shared package for future use

## Dependencies

- None (can be implemented independently)

## Notes

- Consider Storybook for component documentation
- May want to publish to private npm registry eventually
- Future: Add design tokens for full design system
