15 KiB
15 KiB
Crew Feature Implementation Plan
Overview
Implement a comprehensive Crew system for Granblue Fantasy team management, including crew formation, role management, invitations, Unite and Fight (GW) event tracking with individual and crew-wide scoring, and interactive visualizations.
Workflow
- Commit often in logical groups
- Track commit hashes next to completed tasks:
[abc1234] - Terse, informal commit messages
Phase 1: Core Crew Infrastructure
Database Migrations
- 1.
create_crews[9b01aa0]
crews:
- id: uuid (PK)
- name: string (required, max 100)
- gamertag: string (text-only tag for profiles)
- granblue_crew_id: string (in-game ID, unique)
- description: text
- timestamps
- 2.
create_crew_memberships[9b01aa0]
crew_memberships:
- id: uuid (PK)
- crew_id: uuid (FK, required)
- user_id: uuid (FK, required)
- role: integer (0=member, 1=vice_captain, 2=captain)
- retired: boolean (default: false)
- retired_at: datetime
- timestamps
Indices:
- unique on [crew_id, user_id]
- partial unique on [user_id] WHERE retired=false (one active crew per user)
- 3.
add_show_gamertag_to_users[9b01aa0]
Add: show_gamertag: boolean (default: true)
Backend Files to Create
| Status | File | Purpose |
|---|---|---|
[x] [9b01aa0] |
app/models/crew.rb |
Crew model with associations, validations |
[x] [9b01aa0] |
app/models/crew_membership.rb |
Membership with role enum, retirement logic |
[x] [e98e594] |
app/controllers/api/v1/crews_controller.rb |
CRUD, leave, transfer captain |
[x] [e98e594] |
app/controllers/api/v1/crew_memberships_controller.rb |
Promote, demote, remove |
[x] [e98e594] |
app/controllers/concerns/crew_authorization_concern.rb |
Officer/captain checks |
[x] [e98e594] |
app/blueprints/api/v1/crew_blueprint.rb |
Crew serialization |
[x] [e98e594] |
app/blueprints/api/v1/crew_membership_blueprint.rb |
Membership serialization |
[x] [872b6fd] |
app/errors/crew_errors.rb |
Custom errors (restructured for Zeitwerk) |
Backend Files to Modify
| Status | File | Changes |
|---|---|---|
[x] [9b01aa0] |
app/models/user.rb |
Add crew associations, implement in_same_crew_as? |
[x] [9b01aa0] |
app/models/user.rb |
Update collection_viewable_by? for crew_only |
| [ ] | app/blueprints/api/v1/user_blueprint.rb |
Add gamertag and crew info to minimal view |
[x] [e98e594] |
config/routes.rb |
Add crew routes |
Backend Tests
| Status | File | Purpose |
|---|---|---|
[x] [872b6fd] |
spec/models/crew_spec.rb |
Crew model specs (18 examples) |
[x] [872b6fd] |
spec/models/crew_membership_spec.rb |
CrewMembership model specs (16 examples) |
[x] [872b6fd] |
spec/models/user_spec.rb |
User crew association specs (22 examples) |
[x] [872b6fd] |
spec/requests/crews_controller_spec.rb |
Crews API specs (15 examples) |
[x] [872b6fd] |
spec/requests/crew_memberships_controller_spec.rb |
Memberships API specs (13 examples) |
[x] [872b6fd] |
spec/factories/crews.rb |
Crew factory |
[x] [872b6fd] |
spec/factories/crew_memberships.rb |
CrewMembership factory |
API Endpoints
POST /crews - Create crew (user becomes captain)
GET /crew - Current user's crew
PUT /crew - Update crew (officers only)
GET /crew/members - List members
POST /crew/leave - Leave crew (not captain)
POST /crews/:id/transfer_captain - Transfer ownership
PUT /crews/:id/memberships/:id - Update member role
DELETE /crews/:id/memberships/:id - Remove member
Frontend (hensei-web)
Pages:
/crew- Dashboard (redirect to create if no crew)/crew/create- Create crew form/crew/settings- Crew settings (officers)/crew/members- Member management
Components:
components/crew/CrewDashboard/components/crew/CrewHeader/components/crew/CrewMemberList/components/crew/CreateCrewForm/
Phase 2: Invitations System
Database Migration
create_crew_invitations
crew_invitations:
- id: uuid (PK)
- crew_id: uuid (FK, required)
- user_id: uuid (FK, required - invitee)
- invited_by_id: uuid (FK, required)
- status: integer (0=pending, 1=accepted, 2=rejected, 3=expired)
- expires_at: datetime
- timestamps
Indices:
- on [crew_id, user_id, status]
- on [user_id, status]
Backend Files to Create
| Status | File | Purpose |
|---|---|---|
| [ ] | app/models/crew_invitation.rb |
Invitation model with accept/reject |
| [ ] | app/controllers/api/v1/crew_invitations_controller.rb |
Send, list, accept, reject |
API Endpoints
POST /crews/:id/invitations - Send invitation (officers)
GET /crews/:id/invitations - List crew's invitations
GET /invitations/pending - User's pending invitations
POST /invitations/:id/accept - Accept invitation
POST /invitations/:id/reject - Reject invitation
Frontend
Pages:
/crew/join- View and respond to invitations
Components:
components/crew/CrewInvitationBanner/- Top-of-page banner in Headercomponents/crew/JoinCrewSection/- Invitation list with accept/reject
Modify:
components/Header/- Add invitation banner for pending invites
Phase 3: GW Events & Basic Scoring
Database Migrations
- 1.
create_gw_events(admin-managed via Database CMS)
gw_events:
- id: uuid (PK)
- name: string (required)
- element: integer (required, uses GranblueEnums::ELEMENTS)
- start_date: date (required)
- end_date: date (required)
- event_number: integer (GW #XX, unique)
- timestamps
- 2.
create_crew_gw_participations
crew_gw_participations:
- id: uuid (PK)
- crew_id: uuid (FK)
- gw_event_id: uuid (FK)
- preliminary_ranking: bigint
- final_ranking: bigint
- timestamps
Unique: [crew_id, gw_event_id]
- 3.
create_gw_crew_scores(crew-wide round scores)
gw_crew_scores:
- id: uuid (PK)
- crew_gw_participation_id: uuid (FK)
- round: integer (0=prelims, 1=interlude, 2-5=finals day 1-4)
- crew_score: bigint
- opponent_score: bigint
- opponent_name: string
- opponent_granblue_id: string
- victory: boolean
- timestamps
Unique: [crew_gw_participation_id, round]
- 4.
create_gw_individual_scores
gw_individual_scores:
- id: uuid (PK)
- crew_gw_participation_id: uuid (FK)
- crew_membership_id: uuid (FK, nullable)
- phantom_player_id: uuid (FK, nullable)
- round: integer
- score: bigint (default: 0)
- is_cumulative: boolean (default: false)
- recorded_by_id: uuid (FK)
- timestamps
Constraints: Exactly one of crew_membership_id or phantom_player_id must be set
Backend Files to Create
| Status | File | Purpose |
|---|---|---|
| [ ] | app/models/gw_event.rb |
Event model with element, dates |
| [ ] | app/models/crew_gw_participation.rb |
Links crew to event |
| [ ] | app/models/gw_crew_score.rb |
Crew-level round scores |
| [ ] | app/models/gw_individual_score.rb |
Individual scores with permission checks |
| [ ] | app/controllers/api/v1/gw_events_controller.rb |
CRUD (admin create/update) |
| [ ] | app/controllers/api/v1/crew_gw_participations_controller.rb |
Join event, get participation |
| [ ] | app/controllers/api/v1/gw_crew_scores_controller.rb |
Crew score entry |
| [ ] | app/controllers/api/v1/gw_individual_scores_controller.rb |
Individual + batch entry |
| [ ] | app/blueprints/api/v1/gw_*_blueprint.rb |
Serializers for all GW models |
API Endpoints
# GW Events (admin creates via CMS)
GET /gw_events - List all events
GET /gw_events/:id - Show event
POST /gw_events - Create event (admin)
PUT /gw_events/:id - Update event (admin)
# Participations
POST /gw_events/:id/participations - Join event
GET /crew/gw_participations - Crew's participations
GET /crew/gw_participations/:id - Single participation with scores
# Scores
POST /gw_events/:eid/participations/:pid/crew_scores - Add crew score
PUT /gw_events/:eid/participations/:pid/crew_scores/:id - Update crew score
POST /gw_events/:eid/participations/:pid/individual_scores - Add individual
POST /gw_events/:eid/participations/:pid/individual_scores/batch - Batch entry
Frontend
Pages:
/crew/gw- GW overview (list of events)/crew/gw/[eventId]- Event detail with scores/crew/gw/[eventId]/scores- Score entry interface
Components:
components/gw/GwEventCard/components/gw/GwScoreBoard/components/gw/GwCrewScoreForm/components/gw/GwIndividualScoreForm/
Phase 4: Phantom Players & Retired Members
Database Migration
create_phantom_players
phantom_players:
- id: uuid (PK)
- crew_id: uuid (FK)
- name: string (required)
- granblue_id: string
- notes: text
- claimed_by_id: uuid (FK to users, nullable)
- claimed_from_membership_id: uuid (FK, nullable)
- claim_confirmed: boolean (default: false)
- timestamps
Unique: [crew_id, granblue_id] WHERE granblue_id IS NOT NULL
Backend Files to Create
| Status | File | Purpose |
|---|---|---|
| [ ] | app/models/phantom_player.rb |
Phantom with claim flow |
| [ ] | app/controllers/api/v1/phantom_players_controller.rb |
CRUD, assign, confirm |
Claim Flow
- Captain creates phantom player (name, granblue_id, notes)
- Real user joins crew
- Captain assigns phantom to user (
claimed_by_idset) - User confirms claim (
claim_confirmed= true) - All phantom scores transfer to user's membership
Retirement Flow
- Member leaves or is removed
CrewMembership.retired= true,retired_at= now- Historical scores remain linked to membership
- No new scores can be added to retired members
- Scores visible to current crew members
API Endpoints
GET /crews/:id/phantom_players - List phantoms
POST /crews/:id/phantom_players - Create phantom
PUT /crews/:id/phantom_players/:id - Update phantom
DELETE /crews/:id/phantom_players/:id - Delete phantom
POST /crews/:id/phantom_players/:id/assign - Assign to user
POST /crews/:id/phantom_players/:id/confirm_claim - User confirms
Frontend
Components:
components/phantom/PhantomPlayerList/components/phantom/PhantomPlayerForm/components/phantom/PhantomClaimDialog/
Phase 5: Batch Score Entry & Visualization
Backend
- Batch Endpoint Enhancement:
POST /gw_events/:eid/participations/:pid/individual_scores/batch
Body: { scores: [{ player_id, player_type, round, score }, ...] }
- Aggregation Endpoints:
GET /crew/gw_participations/:id/leaderboard - Player rankings
GET /crew/gw_participations/:id/chart_data - Time series for charts
Frontend - Batch Entry
- Component:
components/gw/GwBatchScoreEntry/- Spreadsheet-style grid (players as rows, rounds as columns)
- Tab/Enter navigation
- Paste from spreadsheet support
- Toggle between individual and batch modes
Frontend - Visualization (Apache ECharts)
Install: npm install echarts echarts-for-react
Components:
components/gw/GwScoreChart/- Main interactive chartcomponents/gw/GwPlayerRanking/- Sortable player leaderboardcomponents/gw/GwProgressChart/- Progress over time
Chart Features:
- Individual focus: Player rankings, personal progress, personal bests
- Time axis control: Filter by event date range
- Player selection: Toggle which players are visible
- Event filter: Compare across multiple GW events
Phase 6: Profile Integration & Polish
Backend
- Update UserBlueprint (
app/blueprints/api/v1/user_blueprint.rb):
view :minimal do
# ... existing fields ...
field :gamertag, if: ->(_, user, _) {
user.show_gamertag && user.crew&.gamertag.present?
} do |user|
user.crew&.gamertag
end
end
Frontend
User Settings:
- Add toggle: "Show crew gamertag on profile"
- Located in existing settings page
Profile Display:
- Show gamertag badge next to username when enabled
- Format:
[CREW] Usernameor similar styling
Admin Pages:
/admin/gw-events- GW event management for Database CMS
Authorization Matrix
| Action | Captain | Vice Captain | Member | Non-member |
|---|---|---|---|---|
| View crew dashboard | Yes | Yes | Yes | No |
| Edit crew info | Yes | Yes | No | No |
| Send invitations | Yes | Yes | No | No |
| Promote to VC | Yes | No | No | No |
| Demote VC | Yes | No | No | No |
| Remove member | Yes | Yes | No | No |
| Transfer captain | Yes | No | No | No |
| Record own score | Yes | Yes | Yes | No |
| Record others' scores | Yes | Yes | No | No |
| Manage phantoms | Yes | Yes | No | No |
| View GW history | Yes | Yes | Yes | No |
Key Files Reference
Backend - To Create
app/models/
crew.rb
crew_membership.rb
crew_invitation.rb
phantom_player.rb
gw_event.rb
crew_gw_participation.rb
gw_crew_score.rb
gw_individual_score.rb
app/controllers/api/v1/
crews_controller.rb
crew_memberships_controller.rb
crew_invitations_controller.rb
phantom_players_controller.rb
gw_events_controller.rb
crew_gw_participations_controller.rb
gw_crew_scores_controller.rb
gw_individual_scores_controller.rb
app/blueprints/api/v1/
crew_blueprint.rb
crew_membership_blueprint.rb
crew_invitation_blueprint.rb
phantom_player_blueprint.rb
gw_event_blueprint.rb
crew_gw_participation_blueprint.rb
gw_crew_score_blueprint.rb
gw_individual_score_blueprint.rb
app/controllers/concerns/
crew_authorization_concern.rb
app/errors/
crew_errors.rb
Backend - To Modify
app/models/user.rb # Add crew associations
app/blueprints/api/v1/user_blueprint.rb # Add gamertag
config/routes.rb # Add all crew routes
Frontend - To Create
app/[locale]/crew/
page.tsx
create/page.tsx
settings/page.tsx
members/page.tsx
join/page.tsx
gw/
page.tsx
[eventId]/
page.tsx
scores/page.tsx
components/
crew/
CrewDashboard/
CrewHeader/
CrewMemberList/
CrewInvitationBanner/
CreateCrewForm/
JoinCrewSection/
gw/
GwEventCard/
GwScoreBoard/
GwCrewScoreForm/
GwIndividualScoreForm/
GwBatchScoreEntry/
GwScoreChart/
GwPlayerRanking/
GwProgressChart/
phantom/
PhantomPlayerList/
PhantomPlayerForm/
PhantomClaimDialog/
stores/
crewStore.ts
gwStore.ts
Frontend - To Modify
components/Header/index.tsx # Add invitation banner
app/[locale]/settings/ # Add gamertag toggle
Implementation Order
- Phase 1 - Core crew (can deploy independently)
- Phase 2 - Invitations (enables recruiting)
- Phase 3 - GW events & scoring (core value feature)
- Phase 4 - Phantom players (completes scoring for non-users)
- Phase 5 - Batch entry & visualization (polish & UX)
- Phase 6 - Profile integration (final touches)
Each phase is deployable independently after Phase 1.