working companion prototype

This commit is contained in:
2025-11-24 23:32:36 +01:00
parent e7978024d7
commit e2bea9a126
175 changed files with 23227 additions and 3519 deletions

View File

@@ -4,6 +4,44 @@ This document provides a technical deep-dive into GridPilot's Clean Architecture
---
## iRacing Automation Strategy
**IMPORTANT**: Understanding the distinction between iRacing's interfaces is critical for our automation approach.
### Two iRacing Interfaces
1. **iRacing Website (members.iracing.com)**: Standard HTML/DOM web application accessible at `https://members-ng.iracing.com/`. This is where hosted session management lives. Being a standard web application, it can be automated with browser automation tools like **Playwright** or **Puppeteer**. This is 100% legal and our preferred approach.
2. **iRacing Desktop App (Electron)**: The iRacing desktop application is a sandboxed Electron app. Its DOM is inaccessible, and any modification violates iRacing's Terms of Service. This is why tools like iRefined were shut down.
### Automation Rules
**Allowed Approaches:**
- ✅ Browser automation of the iRacing website using Playwright/Puppeteer
- ✅ Standard DOM manipulation and interaction via browser automation APIs
**Forbidden Approaches:**
- ❌ DOM automation inside the iRacing Electron desktop app
- ❌ Script injection into the desktop client
- ❌ Any client modification (similar to what got iRefined shut down)
### Technology Stack
- **Primary**: Playwright for browser automation of members.iracing.com
- **Alternative**: Puppeteer (if Playwright isn't suitable for specific use cases)
### Development vs Production Mode
- **Development Mode**: Launches a Playwright-controlled browser to automate the real iRacing website
- **Production Mode**: Same as development - browser automation targeting members.iracing.com
- **Test Mode**: Uses mocked browser automation (no real browser interaction)
### HTML Fixtures (resources/iracing-hosted-sessions/)
The HTML files in `resources/iracing-hosted-sessions/` are **static snapshots for reference and testing**. They help developers understand the iRacing UI structure and serve as fixtures for E2E tests. Production automation always targets the REAL iRacing website at members-ng.iracing.com.
---
## 1. Overview
### Clean Architecture Principles
@@ -151,7 +189,7 @@ GridPilot's monorepo structure enforces layer boundaries through directory organ
└── /companion # Electron desktop app
├── /main # Electron main process
├── /renderer # Electron renderer (React)
└── /automation # Nut.js browser automation scripts
└── /automation # Playwright browser automation scripts
```
### Import Rules (Enforced via ESLint)
@@ -639,7 +677,7 @@ function useCreateLeague() {
**Main Process (Node.js)**
- Handles IPC from renderer process
- Invokes use cases (directly calls application layer, not via HTTP)
- Manages Nut.js automation workflows
- Manages Playwright browser automation workflows
```typescript
// Electron IPC handler
@@ -648,7 +686,7 @@ ipcMain.handle('create-iracing-session', async (event, sessionData) => {
const result = await useCase.execute(sessionData);
if (result.requiresAutomation) {
// Trigger Nut.js automation
// Trigger Playwright browser automation
await automationService.createSessionInBrowser(result.sessionDetails);
}
@@ -660,22 +698,38 @@ ipcMain.handle('create-iracing-session', async (event, sessionData) => {
- UI for session creation, result monitoring
- IPC communication with main process
**Nut.js Automation** ([`AutomationService`](../src/apps/companion/automation/AutomationService.ts))
**Playwright Browser Automation** ([`PlaywrightAutomationAdapter`](../packages/infrastructure/adapters/automation/PlaywrightAutomationAdapter.ts))
GridPilot uses Playwright for all automation tasks. This is the only automation approach—there is no OS-level automation or fallback.
```typescript
export class AutomationService {
import { chromium, Browser, Page } from 'playwright';
export class PlaywrightAutomationAdapter {
private browser: Browser | null = null;
private page: Page | null = null;
async createSessionInBrowser(details: SessionDetails): Promise<void> {
// 1. Launch browser via Nut.js
await mouse.click(/* iRacing icon position */);
// 1. Launch browser via Playwright
this.browser = await chromium.launch({ headless: false });
this.page = await this.browser.newPage();
// 2. Navigate to session creation page
await keyboard.type('https://members.iracing.com/membersite/CreateSession');
// 2. Navigate to iRacing session creation page
await this.page.goto('https://members-ng.iracing.com/web/racing/hosted/create');
// 3. Fill form fields
await this.fillField('session-name', details.name);
await this.fillField('track', details.track);
// 3. Fill form fields using DOM selectors
await this.page.fill('[data-testid="session-name"]', details.name);
await this.page.selectOption('[data-testid="track-select"]', details.track);
// 4. Submit form
await this.clickButton('create-session');
await this.page.click('[data-testid="create-session-button"]');
// 5. Wait for confirmation
await this.page.waitForSelector('[data-testid="session-created-confirmation"]');
}
async cleanup(): Promise<void> {
await this.browser?.close();
}
}
```
@@ -1045,4 +1099,4 @@ test('User creates league via Web Client', async ({ page }) => {
*This architecture documentation will evolve as GridPilot matures. All changes must maintain Clean Architecture principles and the dependency rule.*
*Last Updated: 2025-11-21*
*Last Updated: 2025-11-23*