From e53af6a0e79cc55a418bd812d7b9ce747d67701c Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 13 Dec 2025 18:39:20 +0100 Subject: [PATCH] wip --- .roo/rules-orchestrator/rules.md | 210 +++++++------- apps/website/app/leagues/[id]/page.tsx | 41 +-- .../app/leagues/[id]/stewarding/page.tsx | 18 ++ apps/website/app/races/[id]/page.tsx | 38 +-- apps/website/app/races/[id]/results/page.tsx | 47 ++- .../components/leagues/PenaltyCardMenu.tsx | 57 ++++ .../website/components/leagues/PenaltyFAB.tsx | 23 ++ .../components/leagues/QuickPenaltyModal.tsx | 89 ++++-- .../components/races/InlinePenaltyButton.tsx | 41 +++ .../website/components/races/ResultsTable.tsx | 17 ++ apps/website/lib/di-config.ts | 11 + apps/website/lib/di-container.ts | 10 + apps/website/lib/di-tokens.ts | 1 + docs/THEME.md | 270 ++++++------------ docs/VOICE.md | 220 +++++--------- packages/racing/application/index.ts | 1 + .../use-cases/ImportRaceResultsUseCase.ts | 27 +- race-results-page.png | Bin 0 -> 68996 bytes .../RaceResultsUseCases.test.ts | 21 ++ .../unit/website/InlinePenaltyButton.test.tsx | 123 ++++++++ 20 files changed, 762 insertions(+), 503 deletions(-) create mode 100644 apps/website/components/leagues/PenaltyCardMenu.tsx create mode 100644 apps/website/components/leagues/PenaltyFAB.tsx create mode 100644 apps/website/components/races/InlinePenaltyButton.tsx create mode 100644 race-results-page.png create mode 100644 tests/unit/website/InlinePenaltyButton.test.tsx diff --git a/.roo/rules-orchestrator/rules.md b/.roo/rules-orchestrator/rules.md index c0a194409..48dc97ee9 100644 --- a/.roo/rules-orchestrator/rules.md +++ b/.roo/rules-orchestrator/rules.md @@ -1,133 +1,127 @@ # 🧭 Orchestrator ## Purpose -Interpret the user's intent, gather complete context, -and delegate work as clear, cohesive subtasks to the correct experts. +Interpret the user’s intent, gather full context, decide the correct execution domain, +and delegate a **single, clear task** to **exactly one expert**. + The Orchestrator never performs expert work. --- -## User Supremacy -- The user overrides all internal rules. -- The Orchestrator must stop all ongoing processes and adapt immediately when the user issues a new instruction. -- No reinterpretation or negotiation. +## Absolute Rule: NEVER BOTH +A task MUST NEVER be assigned to both frontend and backend at the same time. + +If a user request touches frontend and backend: +- the Orchestrator MUST split it into **separate subtasks** +- each subtask is delegated **independently** +- each subtask targets **exactly one domain** + +There is NO exception to this rule. + +--- + +## Mandatory Domain Decision (Before Every Delegation) +Before delegating ANY task, the Orchestrator MUST explicitly decide: + +- **Frontend** +OR +- **Backend** + +If the task cannot be clearly classified: +- do NOT delegate +- use Clarification first + +The Orchestrator MUST NOT guess. +The Orchestrator MUST NOT default to backend. + +--- + +## Frontend Routing Rules +Delegate to **Frontend Coder** if the task involves ANY of: +- React / Next.js +- pages, layouts, routes +- JSX / TSX +- UI components +- hooks, props, state +- styling, CSS, Tailwind +- DOM behavior +- UX flows +- client/server components +- frontend validation +- view models used only by UI + +If any item applies → Frontend Coder ONLY. + +--- + +## Backend Routing Rules +Delegate to **Backend Coder** if the task involves ANY of: +- domain logic +- application logic +- use cases / interactors +- DTOs (application-level) +- repositories +- ports / adapters +- persistence +- services +- business rules +- backend validation +- infrastructure + +If any item applies → Backend Coder ONLY. + +--- + +## Task Splitting Rule +If a user request includes: +- frontend changes AND backend changes + +The Orchestrator MUST: +1. create a frontend subtask +2. create a backend subtask +3. delegate them separately +4. never combine them +5. never delegate “both” to one coder --- ## Context Responsibility -The Orchestrator MUST provide: -- exact file paths -- content excerpts when needed -- constraints -- expected output -- what must NOT be touched -- any relevant test or behavior definition +For every delegation, the Orchestrator MUST provide: +- exact file paths +- exact scope +- exact operations +- constraints +- what must NOT be touched +- expected outcome -Experts must NEVER collect context themselves. +Experts MUST NOT gather context themselves. --- -## Task Grouping -The Orchestrator MUST: -- **merge related work into one cohesive subtask** -- **split unrelated work into multiple subtasks** -- assign each subtask to exactly one expert -- never mix concerns or layers +## User Supremacy +If the user explicitly says: +- “ignore separation” +- “do frontend only” +- “do backend only” -A subtask must always be: -- self-contained -- minimal -- fully scoped -- executable - ---- - -## TODO List Responsibility (Critical) -The Orchestrator MUST maintain a **strict, accurate TODO list**. - -Rules: -1. When the user gives ANY instruction → - **the Orchestrator MUST generate or update a TODO list.** - -2. TODO list must contain **ONLY outstanding, unfinished work**. - - No completed items. - - No redundant items. - - No invented tasks. - - No assumptions. - -3. Each TODO item must be: - - explicit - - actionable - - minimal - - atomic (one responsibility per item) - -4. The TODO list MUST represent the **true, current state** of what remains. - - If something is already done → DO NOT list it - - If something is irrelevant → DO NOT list it - - If something is repeated → collapse to one item - -5. The TODO list is the **single source of truth** for remaining work. - -6. Experts NEVER update TODOs. - Only the Orchestrator modifies TODOs. - -7. After each expert result: - - The Orchestrator MUST update the TODO list (finish/remove completed items, keep only outstanding ones). - ---- - -## Delegation Rules -A delegation MUST be: -- direct -- unambiguous -- fully scoped -- context-complete -- zero explanations -- no options -- no reasoning - -Format guidelines: -- “Here is the context.” -- “Here is the task.” -- “Do exactly this and nothing else.” - ---- - -## Interruptibility -When the user issues a new instruction: -- stop all running tasks -- discard previous assumptions -- rebuild TODO list -- delegate new work - ---- - -## Efficiency -The Orchestrator MUST: -- minimize the number of subtasks -- avoid duplicated work -- ensure no overlapping instructions -- keep the workflow deterministic +The Orchestrator MUST obey exactly as stated. --- ## Forbidden The Orchestrator MUST NOT: -- perform expert-level reasoning -- propose solutions -- give architecture opinions -- write plans -- describe implementations -- output long explanations -- generate TODOs that are already done -- expand or reduce user intent -- run tests -- edit files +- delegate mixed frontend/backend tasks +- say “both” +- let coders decide responsibility +- assume backend by default +- rush to delegation without classification +- merge unrelated work --- ## Completion -A step is complete when: -- the assigned expert returns the result -- the TODO list is updated to reflect ONLY what is still outstanding -- the Orchestrator either delegates the next TODO or waits for user input \ No newline at end of file +A delegation is valid only when: +- exactly one domain is chosen +- exactly one expert is assigned +- context is complete +- task scope is single-purpose \ No newline at end of file diff --git a/apps/website/app/leagues/[id]/page.tsx b/apps/website/app/leagues/[id]/page.tsx index 692918697..d97709955 100644 --- a/apps/website/app/leagues/[id]/page.tsx +++ b/apps/website/app/leagues/[id]/page.tsx @@ -6,6 +6,7 @@ import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import JoinLeagueButton from '@/components/leagues/JoinLeagueButton'; import LeagueActivityFeed from '@/components/leagues/LeagueActivityFeed'; +import EndRaceModal from '@/components/leagues/EndRaceModal'; import DriverIdentity from '@/components/drivers/DriverIdentity'; import DriverSummaryPill from '@/components/profile/DriverSummaryPill'; import SponsorInsightsCard, { @@ -68,6 +69,7 @@ export default function LeagueDetailPage() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [refreshKey, setRefreshKey] = useState(0); + const [endRaceModalRaceId, setEndRaceModalRaceId] = useState(null); const currentDriverId = useEffectiveDriverId(); const membership = getMembership(leagueId, currentDriverId); @@ -347,22 +349,7 @@ export default function LeagueDetailPage() { {membership?.role === 'admin' && ( + + {showQuickPenaltyModal && ( + + )} ); @@ -298,6 +337,8 @@ export default function RaceResultsPage() { fastestLapTime={fastestLapTime ?? 0} penalties={penalties} currentDriverId={currentDriverId ?? ''} + isAdmin={isAdmin} + onPenaltyClick={handlePenaltyClick} /> ) : ( <> diff --git a/apps/website/components/leagues/PenaltyCardMenu.tsx b/apps/website/components/leagues/PenaltyCardMenu.tsx new file mode 100644 index 000000000..68a7db92a --- /dev/null +++ b/apps/website/components/leagues/PenaltyCardMenu.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { useState } from 'react'; +import { MoreVertical, Edit, Trash2 } from 'lucide-react'; +import Button from '../ui/Button'; + +interface PenaltyCardMenuProps { + onEdit: () => void; + onVoid: () => void; +} + +export default function PenaltyCardMenu({ onEdit, onVoid }: PenaltyCardMenuProps) { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + + {isOpen && ( + <> +
setIsOpen(false)} + /> +
+ + +
+ + )} +
+ ); +} \ No newline at end of file diff --git a/apps/website/components/leagues/PenaltyFAB.tsx b/apps/website/components/leagues/PenaltyFAB.tsx new file mode 100644 index 000000000..3edae0a3c --- /dev/null +++ b/apps/website/components/leagues/PenaltyFAB.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { Plus } from 'lucide-react'; +import Button from '../ui/Button'; + +interface PenaltyFABProps { + onClick: () => void; +} + +export default function PenaltyFAB({ onClick }: PenaltyFABProps) { + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/apps/website/components/leagues/QuickPenaltyModal.tsx b/apps/website/components/leagues/QuickPenaltyModal.tsx index ea848799b..1654d9b22 100644 --- a/apps/website/components/leagues/QuickPenaltyModal.tsx +++ b/apps/website/components/leagues/QuickPenaltyModal.tsx @@ -3,14 +3,21 @@ import React, { useState } from 'react'; import { useRouter } from 'next/navigation'; import { getQuickPenaltyUseCase } from '@/lib/di-container'; -import type { Driver } from '@gridpilot/racing/application'; import Button from '@/components/ui/Button'; import { AlertTriangle, Clock, Flag, Zap } from 'lucide-react'; +interface DriverOption { + id: string; + name: string; +} + interface QuickPenaltyModalProps { - raceId: string; - drivers: Driver[]; + raceId?: string; + drivers: DriverOption[]; onClose: () => void; + preSelectedDriver?: DriverOption; + adminId: string; + races?: { id: string; track: string; scheduledAt: Date }[]; } const INFRACTION_TYPES = [ @@ -28,8 +35,9 @@ const SEVERITY_LEVELS = [ { value: 'severe', label: 'Severe', description: 'Heavy penalty' }, ] as const; -export default function QuickPenaltyModal({ raceId, drivers, onClose }: QuickPenaltyModalProps) { - const [selectedDriver, setSelectedDriver] = useState(''); +export default function QuickPenaltyModal({ raceId, drivers, onClose, preSelectedDriver, adminId, races }: QuickPenaltyModalProps) { + const [selectedRaceId, setSelectedRaceId] = useState(raceId || ''); + const [selectedDriver, setSelectedDriver] = useState(preSelectedDriver?.id || ''); const [infractionType, setInfractionType] = useState(''); const [severity, setSeverity] = useState(''); const [notes, setNotes] = useState(''); @@ -39,21 +47,24 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose }: QuickPen const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!selectedDriver || !infractionType || !severity) return; + if (!selectedRaceId || !selectedDriver || !infractionType || !severity) return; setLoading(true); setError(null); try { const useCase = getQuickPenaltyUseCase(); - await useCase.execute({ - raceId, + const command: any = { + raceId: selectedRaceId, driverId: selectedDriver, - adminId: 'driver-1', // TODO: Get from current user context + adminId, infractionType: infractionType as any, severity: severity as any, - notes: notes.trim() || undefined, - }); + }; + if (notes.trim()) { + command.notes = notes.trim(); + } + await useCase.execute(command); // Refresh the page to show updated results router.refresh(); @@ -72,24 +83,52 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose }: QuickPen

Quick Penalty

+ {/* Race Selection */} + {races && !raceId && ( +
+ + +
+ )} + {/* Driver Selection */}
- + {preSelectedDriver ? ( +
+ {preSelectedDriver.name} +
+ ) : ( + + )}
{/* Infraction Type */} @@ -175,7 +214,7 @@ export default function QuickPenaltyModal({ raceId, drivers, onClose }: QuickPen type="submit" variant="primary" className="flex-1" - disabled={loading || !selectedDriver || !infractionType || !severity} + disabled={loading || !selectedRaceId || !selectedDriver || !infractionType || !severity} > {loading ? 'Applying...' : 'Apply Penalty'} diff --git a/apps/website/components/races/InlinePenaltyButton.tsx b/apps/website/components/races/InlinePenaltyButton.tsx new file mode 100644 index 000000000..0e5ace36e --- /dev/null +++ b/apps/website/components/races/InlinePenaltyButton.tsx @@ -0,0 +1,41 @@ +'use client'; + +import React from 'react'; +import { AlertTriangle } from 'lucide-react'; +import Button from '../ui/Button'; + +interface DriverDTO { + id: string; + name: string; +} + +interface InlinePenaltyButtonProps { + driver: DriverDTO; + onPenaltyClick?: (driver: DriverDTO) => void; + isAdmin: boolean; +} + +export default function InlinePenaltyButton({ + driver, + onPenaltyClick, + isAdmin, +}: InlinePenaltyButtonProps) { + if (!isAdmin) return null; + + const handleButtonClick = () => { + if (onPenaltyClick) { + onPenaltyClick(driver); + } + }; + + return ( + + ); +} \ No newline at end of file diff --git a/apps/website/components/races/ResultsTable.tsx b/apps/website/components/races/ResultsTable.tsx index 6bdc19a73..0061efca8 100644 --- a/apps/website/components/races/ResultsTable.tsx +++ b/apps/website/components/races/ResultsTable.tsx @@ -2,6 +2,7 @@ import Link from 'next/link'; import { AlertTriangle, ExternalLink } from 'lucide-react'; +import InlinePenaltyButton from './InlinePenaltyButton'; type PenaltyTypeDTO = | 'time_penalty' @@ -41,6 +42,8 @@ interface ResultsTableProps { fastestLapTime?: number | undefined; penalties?: PenaltyData[]; currentDriverId?: string | undefined; + isAdmin?: boolean; + onPenaltyClick?: (driver: DriverDTO) => void; } export default function ResultsTable({ @@ -50,6 +53,8 @@ export default function ResultsTable({ fastestLapTime, penalties = [], currentDriverId, + isAdmin = false, + onPenaltyClick, }: ResultsTableProps) { const getDriver = (driverId: string): DriverDTO | undefined => { return drivers.find((d) => d.id === driverId); @@ -118,6 +123,7 @@ export default function ResultsTable({ Points +/- Penalties + {isAdmin && Actions} @@ -246,6 +252,17 @@ export default function ResultsTable({ — )} + {isAdmin && ( + + {driver && onPenaltyClick && ( + + )} + + )} ); })} diff --git a/apps/website/lib/di-config.ts b/apps/website/lib/di-config.ts index 5214a64f8..41b034677 100644 --- a/apps/website/lib/di-config.ts +++ b/apps/website/lib/di-config.ts @@ -103,6 +103,7 @@ import { FileProtestUseCase, ReviewProtestUseCase, ApplyPenaltyUseCase, + QuickPenaltyUseCase, RequestProtestDefenseUseCase, SubmitProtestDefenseUseCase, GetSponsorDashboardUseCase, @@ -988,6 +989,15 @@ export function configureDIContainer(): void { ) ); + container.registerInstance( + DI_TOKENS.QuickPenaltyUseCase, + new QuickPenaltyUseCase( + penaltyRepository, + raceRepository, + leagueMembershipRepository + ) + ); + container.registerInstance( DI_TOKENS.RequestProtestDefenseUseCase, new RequestProtestDefenseUseCase(protestRepository, raceRepository, leagueMembershipRepository) @@ -1182,6 +1192,7 @@ export function configureDIContainer(): void { raceRepository, leagueRepository, resultRepository, + driverRepository, standingRepository, importRaceResultsPresenter ) diff --git a/apps/website/lib/di-container.ts b/apps/website/lib/di-container.ts index e61ab5bde..74a0d98d7 100644 --- a/apps/website/lib/di-container.ts +++ b/apps/website/lib/di-container.ts @@ -57,6 +57,7 @@ import type { FileProtestUseCase, ReviewProtestUseCase, ApplyPenaltyUseCase, + QuickPenaltyUseCase, RequestProtestDefenseUseCase, SubmitProtestDefenseUseCase, GetSponsorDashboardUseCase, @@ -490,6 +491,11 @@ class DIContainer { return getDIContainer().resolve(DI_TOKENS.ApplyPenaltyUseCase); } + get quickPenaltyUseCase(): QuickPenaltyUseCase { + this.ensureInitialized(); + return getDIContainer().resolve(DI_TOKENS.QuickPenaltyUseCase); + } + get getRaceProtestsUseCase(): GetRaceProtestsUseCase { this.ensureInitialized(); return getDIContainer().resolve(DI_TOKENS.GetRaceProtestsUseCase); @@ -871,6 +877,10 @@ export function getApplyPenaltyUseCase(): ApplyPenaltyUseCase { return DIContainer.getInstance().applyPenaltyUseCase; } +export function getQuickPenaltyUseCase(): QuickPenaltyUseCase { + return DIContainer.getInstance().quickPenaltyUseCase; +} + export function getGetRaceProtestsUseCase(): GetRaceProtestsUseCase { return DIContainer.getInstance().getRaceProtestsUseCase; } diff --git a/apps/website/lib/di-tokens.ts b/apps/website/lib/di-tokens.ts index 5e16cad1a..ddcb136af 100644 --- a/apps/website/lib/di-tokens.ts +++ b/apps/website/lib/di-tokens.ts @@ -63,6 +63,7 @@ export const DI_TOKENS = { FileProtestUseCase: Symbol.for('FileProtestUseCase'), ReviewProtestUseCase: Symbol.for('ReviewProtestUseCase'), ApplyPenaltyUseCase: Symbol.for('ApplyPenaltyUseCase'), + QuickPenaltyUseCase: Symbol.for('QuickPenaltyUseCase'), RequestProtestDefenseUseCase: Symbol.for('RequestProtestDefenseUseCase'), SubmitProtestDefenseUseCase: Symbol.for('SubmitProtestDefenseUseCase'), diff --git a/docs/THEME.md b/docs/THEME.md index 6a6ab7b4d..a14b2a773 100644 --- a/docs/THEME.md +++ b/docs/THEME.md @@ -1,229 +1,141 @@ -# GridPilot Theme — “Smooth Performance Dark” -*A modern, ultra-polished, buttery-smooth interface that feels engineered, premium, and joyful — without losing the seriousness of sim racing.* +# GridPilot Theme — “Motorsport Infrastructure, Smoothly Engineered” + +*A precise, professional motorsport interface with premium smoothness — engineered for trust, control, and long-term use.* --- -# 1. Design Philosophy +## 1. Design Philosophy GridPilot should feel like: -- **a precision instrument**, not a toy -- **a premium dashboard**, not a corporate SaaS page -- **smooth and responsive**, not flashy -- **crafted**, not overdesigned -- **racing-inspired**, not gamer-edgy +- **race control software**, not a game UI +- **infrastructure**, not a startup product +- **engineered**, not styled +- **stable and authoritative**, yet pleasant to use +- **built for years**, not trends -It combines: -- the readability & seriousness of motorsport tools -- with the soft, fluid, polished feel of a high-end app +The goal is not excitement. +The goal is **confidence**. Think: -**"iRacing x Apple UI x Motorsport telemetry aesthetics"**. +**“FIA race control x timing screens x modern tooling — with smooth interaction.”** --- -# 2. Visual Style +## 2. Visual Style ### Core Aesthetic: -- dark, matte background -- soft gradients (subtle, not neon) -- elegant glows only where needed -- crisp typography -- generous spacing -- smooth UI hierarchy transitions -- layer depth through blur + shadow stacking (but tasteful) +- dark, neutral background +- minimal gradients (almost invisible) +- restrained highlights only for meaning +- strict hierarchy +- dense, readable layouts +- smooth transitions only where state changes -### Color Palette: -- **Deep Graphite:** `#0E0F11` (main background) -- **Iron Gray:** `#181B1F` (cards & panels) -- **Charcoal Outline:** `#22262A` (borders) -- **Primary Blue:** `#198CFF` (accents, active states) -- **Performance Green:** `#6FE37A` (success) -- **Warning Amber:** `#FFC556` (markers) -- **Subtle Neon Aqua:** `#43C9E6` (interactive glow effects) - -Colors are **precise**, not noisy. +Everything should look: +**intentional, measured, and calm.** --- -# 3. Animation Philosophy — “Buttery Smooth, Never Distracting” +### Color Palette (refined) -Animations in GridPilot should: -- feel like a **fast steering rack**: sharp + controlled -- feel **premium**, not “flashy” -- be **motivated**, not ornamental -- communicate **state change** clearly +- **Graphite Black:** `#0E0F11` +- **Panel Gray:** `#171A1E` +- **Border Gray:** `#22262A` +- **Primary Accent:** `#198CFF` (used sparingly) +- **Success Green:** `#6FE37A` +- **Warning Amber:** `#FFC556` +- **Critical Red:** `#E35C5C` -**Animation Style:** -- low-spring, high-damping motion -- small distances, high velocity -- micro-easing, Apple-like rebound -- intelligent inertia -- zero stutter - -**Target vibe:** -> “Everything feels alive and responsive, like the UI wants to race with you.” +No neon. +No playful colors. +Color = meaning, not decoration. --- -# 4. Where Animations Should Shine +## 3. Motion & Interaction -### ✔ Hover Interactions -Buttons + cards get: -- subtle upscale (`1.0 → 1.03`) -- color bloom -- ambient glow (low opacity, soft spread) +### Animation Philosophy +Motion exists only to: +- confirm an action +- show hierarchy +- indicate state change -### ✔ Page Transitions -- fade + slide (30–50px) -- layered parallax for content panels -- 150–250ms total +Never to impress. -Feels warm, inviting, non-static. +### Characteristics: +- short durations (120–200ms) +- low amplitude +- no exaggerated easing +- no elastic bounces +- no decorative movement -### ✔ Filters & Tabs -- sliding underline indicator -- smooth kinetic scrolling -- minimal ripple or highlight - -### ✔ Dialogs & Panels -- spring pop (`scale 0.96 → 1`) -- soft drop shadow expansion -- background blur fade-in - -### ✔ Table Row Expand / Collapse -- height transition: 150ms -- opacity fade-in: 120ms -- chevron rotation: 180ms - -Feels like unfolding technical data — perfect for racing nerds. - -### ✔ Notifications -- slide-in from top right -- friction-based deceleration -- micro-bounce at rest state +**Motion should feel like a well-damped suspension — not a show car.** --- -# 5. What NOT to animate +## 4. Where Motion Is Allowed -To avoid becoming “too modern = startup SaaS = untrustworthy”: +- button press feedback +- panel open / close +- table row expansion +- state changes (pending → approved → completed) +- subtle loading indicators -**No:** -- giant hero animations -- unnecessary motion in typography -- floating shapes / illustration wobble -- confetti / particle effects -- autoplay video backgrounds -- mobile-app style “over cute” transitions +If motion does not improve clarity → remove it. + +--- + +## 5. What We Explicitly Avoid + +- hero animations +- animated backgrounds +- glowing UI chrome +- playful hover gimmicks +- “app store” aesthetics +- anything that reduces trust GridPilot must feel: -**professional → premium → but still understated.** +**reliable before it feels beautiful.** --- -# 6. Component Design Rules - -### Cards -- slightly rounded (6–8px) -- soft shadow (blur 20–28px) -- subtle ambient noise texture (optional) -- gentle hover glow - -### Buttons -- pill shape (but not too round) -- glossy gradient *only when hovered* -- laser-sharp outline on active state -- fast press down animation (`75ms`) +## 6. Components ### Tables -- high-density, readable -- animated sort indicators -- fade-in rows on update -- highlight row on hover +- primary UI element +- dense, readable +- fixed column logic +- no playful effects + +### Cards +- functional grouping +- no visual dominance +- secondary to tables ### Modals -- glassy blurred background -- smooth opening -- soft drop-shadow bloom -- quick responsive closing +- simple +- fast +- decisive --- -# 7. Typography +## 7. Typography -A modern, premium sans-serif: -- **Inter** -- **Roboto Flex** -- or **Plus Jakarta Sans** +- neutral sans-serif +- excellent numeric readability +- no personality fonts -Font weight: -- light + regular for body -- semibold for headings -- numeric fields medium or monospaced (for racing aesthetics) - -Typography motion: -- heading fade-in -- numeric counters animate upward subtly (60–120ms) +Primary goal: +**information clarity, not brand expression.** --- -# 8. UX Tone +## 8. Design Principle Summary -GridPilot should **feel**: +GridPilot is: +- not gamer UI +- not esports branding +- not corporate SaaS -- confident -- calm -- minimal -- smart -- “built by people who actually race” -- respectful of the user’s time -- not corporate -- not recruiter-slick -- not childish gamer UI - -But also: -**pleasant, smooth, and delightful to interact with.** - ---- - -# 9. Comparative Inspirations - -### From iRacing UI: -- dark palette -- density -- data-first layout -- serious tone - -### From VRS: -- technical clarity -- motorsport professionalism - -### From Apple UI: -- smooth transitions -- subtle bounce -- soft shadows -- tasteful blur - -### From SimHub / Racelab: -- functional panels -- high readability -- non-intrusive visuals - -GridPilot combines all these influences without looking like any of them directly. - ---- - -# 10. Goal Summary - -**GridPilot = the “luxury cockpit dashboard” of league racing platforms.** - -- dark, technical look -- premium smoothness -- fast, precise interactions -- functional layouts -- no corporate noise -- no SaaS gimmicks -- no over-the-top neon gamer aesthetic - -Just clean, fast, beautiful racing software -that feels as nice to use as a fresh lap in a good rhythm. \ No newline at end of file +It is: +**modern motorsport infrastructure software.** \ No newline at end of file diff --git a/docs/VOICE.md b/docs/VOICE.md index e82a9169b..c8912ba64 100644 --- a/docs/VOICE.md +++ b/docs/VOICE.md @@ -1,197 +1,123 @@ -# GridPilot — Voice & Tone Guide -*A calm, clear, confident voice built for sim racers.* +# GridPilot — Voice & Tone Guide + +*A calm, neutral, authoritative voice for motorsport infrastructure.* --- ## 1. Core Personality -GridPilot’s voice is: +GridPilot is: +- calm +- neutral +- precise +- experienced +- understated -### **Calm** -Never loud, never chaotic, never dramatic. - -### **Clear** -Short sentences. Direct meaning. Zero fluff. - -### **Competent** -We sound like people who know racing and know what matters. - -### **Friendly, not goofy** -Approachable and human — without memes, slang, or cringe. - -### **Non-corporate** -We avoid startup jargon and “enterprise” tone completely. +We do not excite. +We inform. --- ## 2. How GridPilot Sounds -### **Direct** -We make the point quickly. No fillers. +### Clear +Short sentences. +No ambiguity. -**Example:** -“Standings updated.” +### Neutral +No opinion. +No hype. +No exaggeration. -### **Minimal** -We remove unnecessary words. -Writing should feel clean — like the UI. +### Competent +We assume the user understands racing. -### **Honest** -We never overpromise or exaggerate. -GridPilot says what it does, nothing more. - -### **Human** -Natural phrasing, like talking to another racer. +### Professional +We sound like race officials, not marketers. --- ## 3. What We Avoid -### ❌ Corporate language -“scalable solution,” “empower,” “revolutionize,” “synergy” +- marketing language +- motivational phrasing +- emotional exaggeration +- jokes +- slang +- buzzwords +- storytelling tone -### ❌ Startup hype -“game-changing,” “disruptive,” “next-gen” - -### ❌ Gamer slang -“let’s gooo,” “pog,” “EZ clap,” emojis everywhere - -### ❌ Sales pressure -“Sign up now!!! Limited time only!!!” - -### ❌ Over-explaining -No long paragraphs to say something simple. +GridPilot is not here to entertain. +It is here to **operate**. --- ## 4. Tone by Context -### **Landing Page** -- calm -- confident -- benefit-focused -- no hype -- welcoming +### UI +- direct +- factual +- tool-like -Example: -“League racing should feel organized. GridPilot brings everything into one place.” +Examples: +- “Season created.” +- “Driver approved.” +- “Penalty applied.” --- -### **In-App UI** -- crisp -- tool-like -- neutral -- small sentences +### Errors +- neutral +- calm +- no blame -Examples: -“Race added.” -“Results imported.” -“Penalty applied.” +Examples: +- “Action failed.” +- “Invalid input.” +- “Try again.” --- -### **Notifications** -- non-intrusive -- soft -- clear +### Notifications +- short +- non-intrusive -Examples: -“Your next race is tomorrow.” -“Standings updated.” +Examples: +- “Race starts in 30 minutes.” +- “Results updated.” --- -### **Errors** -- helpful -- steady -- no blame +### Emails +- factual +- brief +- no sales language -Examples: -“Something went wrong. Try again.” -“Connection lost. Reconnecting…” +Example: +“Your season schedule has been updated.” --- -### **Emails** -- friendly -- simple -- short +## 5. Writing Rules -Example: -“Your season starts next week. Here’s the schedule.” +- remove unnecessary adjectives +- use active voice +- one idea per sentence +- no filler +- no persuasion --- -## 5. Writing Style Rules +## 6. Phrases We Prefer -### **Short sentences** -Easy to read. Easy to scan. - -### **Simple verbs** -Join, manage, view, race, update. - -### **Active voice** -“GridPilot updates your standings.” -Not: -“Your standings are being updated…” - -### **No marketing padding** -We never pretend to be bigger than we are. - -### **Every sentence should have purpose** -No filler words. No decorative language. +- “All data in one place.” +- “Clear standings.” +- “Structured seasons.” +- “Consistent rules.” +- “No spreadsheets.” --- -## 6. Phrases We Like +## 7. One-line Voice Summary -- “Everything in one place.” -- “Your racing identity.” -- “Clean standings.” -- “No spreadsheets.” -- “Race. We handle the rest.” -- “Built for league racing.” -- “Clear. Simple. Consistent.” - -These reinforce clarity and confidence. - ---- - -## 7. Phrases We Never Use - -- “premium experience” -- “unlock your potential” -- “cutting-edge AI” -- “transform the sim racing landscape” -- “we’re disrupting the industry” -- “epic,” “insane,” “crazy good,” etc. - -Anything salesy, dramatic, childish, or corporate is banned. - ---- - -## 8. Emotional Goals - -GridPilot should make people feel: - -### **In control** -Information is clear and predictable. - -### **Supported** -Admin work is easier. Driver info is organized. - -### **Respected** -We never talk down to users. - -### **Focused** -The tone keeps attention on racing, not us. - -### **Confident** -The platform feels stable and trustworthy. - ---- - -## 9. One-line Voice Summary - -**GridPilot speaks like a calm, competent racer who explains things clearly — never loud, never corporate, never cringe.** \ No newline at end of file +**GridPilot speaks like race control: calm, precise, and trustworthy.** \ No newline at end of file diff --git a/packages/racing/application/index.ts b/packages/racing/application/index.ts index 2b6dba64a..a363c5d1b 100644 --- a/packages/racing/application/index.ts +++ b/packages/racing/application/index.ts @@ -29,6 +29,7 @@ export * from './use-cases/GetLeagueStatsUseCase'; export * from './use-cases/FileProtestUseCase'; export * from './use-cases/ReviewProtestUseCase'; export * from './use-cases/ApplyPenaltyUseCase'; +export * from './use-cases/QuickPenaltyUseCase'; export * from './use-cases/GetRaceProtestsUseCase'; export * from './use-cases/GetRacePenaltiesUseCase'; export * from './use-cases/RequestProtestDefenseUseCase'; diff --git a/packages/racing/application/use-cases/ImportRaceResultsUseCase.ts b/packages/racing/application/use-cases/ImportRaceResultsUseCase.ts index 69b62f6be..e20324166 100644 --- a/packages/racing/application/use-cases/ImportRaceResultsUseCase.ts +++ b/packages/racing/application/use-cases/ImportRaceResultsUseCase.ts @@ -1,6 +1,7 @@ import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; +import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; import { Result } from '../../domain/entities/Result'; import type { AsyncUseCase } from '@gridpilot/shared/application'; @@ -35,6 +36,7 @@ export class ImportRaceResultsUseCase private readonly raceRepository: IRaceRepository, private readonly leagueRepository: ILeagueRepository, private readonly resultRepository: IResultRepository, + private readonly driverRepository: IDriverRepository, private readonly standingRepository: IStandingRepository, public readonly presenter: IImportRaceResultsPresenter, ) {} @@ -57,15 +59,22 @@ export class ImportRaceResultsUseCase throw new BusinessRuleViolationError('Results already exist for this race'); } - const entities = results.map((dto) => - Result.create({ - id: dto.id, - raceId: dto.raceId, - driverId: dto.driverId, - position: dto.position, - fastestLap: dto.fastestLap, - incidents: dto.incidents, - startPosition: dto.startPosition, + // Lookup drivers by iracingId and create results with driver.id + const entities = await Promise.all( + results.map(async (dto) => { + const driver = await this.driverRepository.findByIRacingId(dto.driverId); + if (!driver) { + throw new BusinessRuleViolationError(`Driver with iRacing ID ${dto.driverId} not found`); + } + return Result.create({ + id: dto.id, + raceId: dto.raceId, + driverId: driver.id, + position: dto.position, + fastestLap: dto.fastestLap, + incidents: dto.incidents, + startPosition: dto.startPosition, + }); }), ); diff --git a/race-results-page.png b/race-results-page.png new file mode 100644 index 0000000000000000000000000000000000000000..be0a7fc211dcd80b5047f05d845b11a2196629c5 GIT binary patch literal 68996 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 { const presenter = new FakeImportRaceResultsPresenter(); + const driverRepository = { + findById: async (): Promise => null, + findByIRacingId: async (iracingId: string): Promise => { + // Mock finding driver by iracingId + if (iracingId === 'driver-1') { + return Driver.create({ id: 'driver-1', iracingId: 'driver-1', name: 'Driver One', country: 'US' }); + } + if (iracingId === 'driver-2') { + return Driver.create({ id: 'driver-2', iracingId: 'driver-2', name: 'Driver Two', country: 'GB' }); + } + return null; + }, + findAll: async (): Promise => [], + create: async (): Promise => { throw new Error('Not implemented'); }, + update: async (): Promise => { throw new Error('Not implemented'); }, + delete: async (): Promise => { throw new Error('Not implemented'); }, + exists: async (): Promise => false, + existsByIRacingId: async (): Promise => false, + }; + const useCase = new ImportRaceResultsUseCase( raceRepository, leagueRepository, resultRepository, + driverRepository, standingRepository, presenter, ); diff --git a/tests/unit/website/InlinePenaltyButton.test.tsx b/tests/unit/website/InlinePenaltyButton.test.tsx new file mode 100644 index 000000000..148e295ff --- /dev/null +++ b/tests/unit/website/InlinePenaltyButton.test.tsx @@ -0,0 +1,123 @@ +import { describe, it, expect, vi } from 'vitest'; +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; + +// Mock() Button component +vi.mock('../../../apps/website/components/ui/Button', () => ({ + default: ({ onClick, children, className, title, variant }: any) => ( + + ), +})); + +import InlinePenaltyButton from '../../../apps/website/components/races/InlinePenaltyButton'; + +describe('InlinePenaltyButton', () => { + const mockDriver = { id: 'driver-1', name: 'Test Driver' }; + const mockOnPenaltyClick = vi.fn(); + + it('should not render when user is not admin', () => { + render( + + ); + + const button = screen.queryByTestId('penalty-button'); + expect(button).not.toBeInTheDocument(); + }); + + it('should render when user is admin', () => { + render( + + ); + + const button = screen.getByTestId('penalty-button'); + expect(button).toBeInTheDocument(); + expect(button).toHaveAttribute('title', 'Issue penalty to Test Driver'); + expect(button).toHaveAttribute('data-variant', 'danger'); + }); + + it('should call onPenaltyClick when button is clicked', () => { + render( + + ); + + const button = screen.getByTestId('penalty-button'); + fireEvent.click(button); + + expect(mockOnPenaltyClick).toHaveBeenCalledTimes(1); + expect(mockOnPenaltyClick).toHaveBeenCalledWith(mockDriver); + }); + + it('should not crash when onPenaltyClick is not provided', () => { + render( + + ); + + const button = screen.getByTestId('penalty-button'); + + // Should not crash when clicked without onPenaltyClick + expect(() => fireEvent.click(button)).not.toThrow(); + }); + + it('should have proper button styling for spacing', () => { + render( + + ); + + const button = screen.getByTestId('penalty-button'); + + // Check that button has proper spacing classes + expect(button).toHaveClass('p-1.5'); + expect(button).toHaveClass('min-h-[32px]'); + expect(button).toHaveClass('w-8'); + expect(button).toHaveClass('h-8'); + expect(button).toHaveClass('rounded-full'); + expect(button).toHaveClass('flex'); + expect(button).toHaveClass('items-center'); + expect(button).toHaveClass('justify-center'); + }); + + it('should render AlertTriangle icon with proper sizing', () => { + render( + + ); + + const button = screen.getByTestId('penalty-button'); + const icon = button.querySelector('svg'); + + expect(icon).toBeInTheDocument(); + expect(icon).toHaveClass('w-4'); + expect(icon).toHaveClass('h-4'); + expect(icon).toHaveClass('flex-shrink-0'); + }); +}); \ No newline at end of file