From 18fd1e8437562eb8f0405aaba3b74c7b175bc6bb Mon Sep 17 00:00:00 2001 From: Darren Neimke Date: Tue, 20 Oct 2026 19:00:07 +1030 Subject: [PATCH] Replace verbose docs with AGENTS.md context and board-config-generator skill --- .agents/skills.json | 5 + .cursor/skills/async-ux-pipeline/SKILL.md | 38 ++- .../skills/board-config-generator/SKILL.md | 72 +++++ .github/copilot-instructions.md | 55 +--- AGENTS.md | 45 +++ README.md | 11 +- docs/design-language.md | 2 +- docs/developer-guide.md | 215 -------------- docs/images/user-workflow-diagram.mermaid | 0 docs/match-review.md | 67 ----- docs/navigation.md | 212 -------------- docs/saved-games.md | 143 ---------- docs/state-management.md | 263 ------------------ docs/user-workflow.md | 53 ---- 14 files changed, 151 insertions(+), 1030 deletions(-) create mode 100644 .agents/skills.json create mode 100644 .cursor/skills/board-config-generator/SKILL.md create mode 100644 AGENTS.md delete mode 100644 docs/developer-guide.md delete mode 100644 docs/images/user-workflow-diagram.mermaid delete mode 100644 docs/match-review.md delete mode 100644 docs/navigation.md delete mode 100644 docs/saved-games.md delete mode 100644 docs/state-management.md delete mode 100644 docs/user-workflow.md diff --git a/.agents/skills.json b/.agents/skills.json new file mode 100644 index 0000000..d66cff1 --- /dev/null +++ b/.agents/skills.json @@ -0,0 +1,5 @@ +{ + "entries": [ + { "path": ".cursor/skills" } + ] +} diff --git a/.cursor/skills/async-ux-pipeline/SKILL.md b/.cursor/skills/async-ux-pipeline/SKILL.md index 46d3927..6b923f8 100644 --- a/.cursor/skills/async-ux-pipeline/SKILL.md +++ b/.cursor/skills/async-ux-pipeline/SKILL.md @@ -1,34 +1,42 @@ --- name: async-ux-pipeline description: >- - Turns a UI friction report into a short spec, implements it against this - repo's design language, proves the result in the browser without asking - the user to click through, and ships it as a pull request. Use when the user reports UI friction, requests - a component or layout change, dispatches a feature to finish while they are - away, or mentions the async UX pipeline. + Evaluates user feedback, pain points, or feature ideas against AGENTS.md + personas and journeys, pushes back or writes a short spec, implements it + against the design language, proves the result in the browser without asking + the user to click through, and ships a pull request. Use when the user + reports UI friction, shares user feedback, requests a feature or layout + change, or mentions the async UX pipeline. --- # Async UX-to-Code Pipeline -Finish the UI change while the user is away. Take a raw friction report (screenshot, note, or bullets), write a short spec, implement the smallest change, leave proof, and open a pull request. Do not ask them to open the app, refresh, log in, or click through states. +Take raw user feedback, a pain point, or a feature request, evaluate it against `AGENTS.md`, implement the smallest valid change while the user is away, verify it in the browser, and open a pull request. Do not ask the user to open the app, refresh, or click through states. ## Repository binding -This app is the Field Hockey Event Tracker: static HTML, vanilla JavaScript, and Jest. UI rules live in `.cursor/rules/design-language.mdc` and `docs/design-language.md`. Follow those for colour, components, touch size, and breakpoints. This skill does not restate them. +Read `AGENTS.md` (personas, journeys, push-back rules) and `docs/design-language.md` (`.cursor/rules/design-language.mdc`) before changing markup, CSS, or JavaScript. -## Stage 1: Friction intake +## Stage 1: Product triage & spec (`a → b → c`) -Before writing code, state three lines: +Before writing code, evaluate the request against `AGENTS.md`: -- **Problem**: what is clunky or high-effort. -- **Target state**: what the screen does after the change. -- **Verification**: the check that proves it. +1. **Persona & Journey (`a`)**: Name the target persona (*Dedicated Analyst*, *Bench Coach*, *Drafted Helper*, or *Head Coach*) and journey. +2. **Audit & Push-Back Check (`b → c`)**: Inspect current code against the persona's quality bar and the **Push-Back Rules** in `AGENTS.md`: + - Does it add taps, dialogs, or shifting buttons during live capture? + - Can it be solved purely with a board JSON configuration (use `board-config-generator` instead)? + - Does it help an expert persona while overloading a casual coder or halftime coach? + - **If a rule is violated**: Stop and push back with the concrete tradeoff and a better alternative. +3. **Three-line spec (`c`)**: When the change is sound, state: + - **Problem**: which persona/journey is blocked or high-effort, and why. + - **Target state**: what the screen/code does after the change. + - **Verification**: the automated test and browser check that prove it. Use the full pipeline for a screen or interaction change. For a copy or token tweak, skip the failing-test step and still do the browser check. ## Stage 2: Design language -Read `docs/design-language.md` before changing markup, CSS, or JavaScript that builds UI. +Follow `docs/design-language.md`: CSS tokens and semantic classes in `src/styles.css`, Tailwind utilities for layout and spacing only, $\ge 44\text{px}$ touch targets. ## Stage 3: Tests @@ -60,13 +68,13 @@ Every change ships through a pull request. Never commit or push to `main`. 1. Start from an up-to-date `main` and create `feature/`. 2. Commit with a message that says what the user gets, push the branch, and open a PR against `main` with `gh pr create`. -3. In the PR body, cover the problem, what changed, behaviour that stays the same, and testing (the `npm test` count and the widths and flows checked in the browser). +3. In the PR body, cover the persona/problem, what changed, behaviour that stays the same, and testing (the `npm test` count and the widths and flows checked in the browser). 4. Leave merging to the user. ## Done when - Behaviour changes have a green `npm test` -- The design-language rule still holds +- `AGENTS.md` persona quality bars and `docs/design-language.md` rules hold - The browser check used the direct URL, with a saved game when the view needs one - A pull request is open against `main`, and nothing was pushed to `main` directly - The reply leads with what changed, the test result, what the screen showed, and the PR link diff --git a/.cursor/skills/board-config-generator/SKILL.md b/.cursor/skills/board-config-generator/SKILL.md new file mode 100644 index 0000000..4f00ba4 --- /dev/null +++ b/.cursor/skills/board-config-generator/SKILL.md @@ -0,0 +1,72 @@ +--- +name: board-config-generator +description: >- + Designs and validates JSON button board configurations for the Field Hockey + Event Tracker based on coaching analysis requirements, coder persona, or + external coding schemes (MyVA / Hudl Sportscode). Use when the user wants to + create, adapt, or simplify a coding board, map lead/lag clip timings, or + solve a tracking requirement via configuration rather than code. +--- + +# Board Configuration Generator + +Turn analysis requirements or external highlight schemes into a validated JSON board configuration for `src/components/config.js` or `#configure`. + +## References + +- Validation & schema: `src/components/config.js` (`validateConfig`, `HOCKEY_ONE_ZONED_CONFIG`) +- MyVA lead/lag timings: `docs/myva-lead-lag-times.md` +- Sticky zones & team rows spec: `docs/feat/sticky-zones/spec.md` +- Personas & sideline constraints: `AGENTS.md` + +## Step 1: Match the board to the Coder Persona + +If the target coder or analysis scope is underspecified, ask at most 2 questions: + +1. **Coder Persona** (from `AGENTS.md`): + - **Drafted Helper / Casual Coder**: Avoid sticky zones and shared-row toggles. Use explicit per-team rows (`"team": 1`, `"team": 2`) with 4–6 self-explanatory essentials (`A25E`, `CE`, `SHOT`, `PC`, `GOAL`, `CARD`) and `START`/`END`. + - **Dedicated Analyst / Bench Coach**: Use sticky zones (`zones: { values: ["1", "2", "3", "4"], default: "1", perspective: "mirrored" }`) for zoned buttons (`"zoned": true` on `BS`, `BW`) to prevent button explosion, dedicated team rows (`"team": 1`, `"team": 2`) for high-frequency actions, and a shared `"team": "selected"` row for lower-frequency scoring/cards (`APS`, `PS`, `GOAL`, `SO`, `GREEN`, `YELLOW`, `RED`). +2. **Downstream Tool**: + - **MyVA / Sportscode**: Match exact code names and `lead`/`lag` seconds from `docs/myva-lead-lag-times.md`. + +## Step 2: Schema & Ergonomic Rules + +Every board JSON must pass `validateConfig(config)` in `src/components/config.js`: + +```json +{ + "zones": { "values": ["1", "2", "3", "4"], "default": "1", "perspective": "mirrored" }, + "rowDefs": [ + { + "gridCols": "grid-cols-3", + "buttons": [ + { "event": "START", "text": "START", "color": "btn-gray", "lead": 1, "lag": 1 }, + { "event": "END", "text": "END", "color": "btn-gray", "lead": 1, "lag": 1 }, + { "event": "UMP", "text": "UMP", "color": "btn-gray", "lead": 7, "lag": 7 } + ] + }, + { + "team": 1, + "grids": [ + { + "gridCols": "grid-cols-6", + "buttons": [ + { "event": "BS", "text": "BS", "zoned": true, "lead": 10, "lag": 10 }, + { "event": "CE", "text": "CE", "lead": 10, "lag": 10 } + ] + } + ] + } + ] +} +``` + +- **Colours**: Non-team buttons must use one of `btn-blue`, `btn-red`, `btn-green`, `btn-yellow`, `btn-orange`, `btn-gray`. Team rows (`"team": 1`, `2`, or `"selected"`) take their colour and code prefix at game start from `teamSetupDialog`. +- **Zones**: Any button with `"zoned": true` requires a top-level `zones` object (`values`: non-empty string array, `default`: member of `values`, `perspective`: `"mirrored"` or `"pitch"`). +- **Clip timings**: `lead` and `lag` must be numbers $\ge 0$ (seconds before and after the button press in exported Sportscode XML; default is `5` when omitted). Reference `docs/myva-lead-lag-times.md` for standard timings (`SHOT` 15/5, `GOAL` 15/5, `PC` 2/10, `GREEN` 20/130, `YELLOW` 20/360). +- **Phone width check (`390px`)**: Use `grid-cols-3` or `grid-cols-4` for readable labels on phones; only use `grid-cols-8` for 2–4 character codes (`BS`, `BW`, `OH`, `CE`, `PC`). + +## Step 3: Deliver & Verify + +1. Validate the generated JSON by running `validateConfig` against it (via Jest or `node -e`). +2. Output the formatted JSON ready for **Configure → Replace from clipboard**, or if the user asked for a built-in preset, register it in `BUILT_IN_NAMED_CONFIGS` in `src/components/config.js` and run `npm test`. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 76b8ae7..7743d0a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,54 +1,3 @@ -# Custom Instructions for GitHub Copilot +# GitHub Copilot Instructions -## !IMPORTANT: Read Carefully - -- Evaluate all input. Think critically and double check your assumptions before making any code changes -- Make sure your advice is grounded in the project context and is backed by evidence. - -## About Me / My Project Context - -1. **Project Goal:** I am building a client-side web application using **HTML**, **Tailwind CSS** for styling, and **vanilla JavaScript**. Frameworks or complex libraries (other than Tailwind and Jest) should generally be avoided unless specifically requested or absolutely necessary. -2. **Application Purpose:** The primary function is to provide a user interface (specifically a form) for capturing event data during a sporting game. This data will likely be stored temporarily (e.g., in memory or `localStorage`) and potentially prepared for later analysis or transmission. -3. **Technology Stack:** Strictly **HTML5**, **Tailwind CSS**, and modern **Vanilla JavaScript (ES6+ minimum)**. No jQuery, React, Vue, Angular, etc., unless I explicitly ask to explore options involving them. -4. **Testing Framework:** We are using **Jest** for unit testing, focusing on testing **ES6 modules**. Code should be structured to facilitate this. -5. **Target Environment:** Modern web browsers. Assume support for ES6+ features. -6. **Skill Level:** I am looking for expert-level guidance, but appreciate clear explanations. - -## How Copilot Should Behave - -1. **Expert Guidance:** Act as an expert web developer specializing in modern vanilla JavaScript, HTML, Tailwind CSS, Jest testing, accessibility (a11y), and user experience (UX). -2. **Modern JavaScript (ES6+):** - - All JavaScript code suggestions **must** use modern ES6+ syntax and features (e.g., `const`/`let`, arrow functions, classes, template literals, Promises, `async`/`await`, ES Modules, spread/rest operators, destructuring). - - Avoid `var` and older patterns unless discussing compatibility or historical context. -3. **Design Language and Tailwind CSS Usage:** - - Follow [`docs/design-language.md`](../docs/design-language.md). Colour, borders, elevation and type come from the CSS tokens and semantic classes in `src/styles.css` (`page`, `page-header`, `surface`, `btn btn-*`, `field`, `tabs`, `banner`, `modal`, and others). - - Use Tailwind utilities for **layout and spacing only** (`flex`, `grid`, `gap-*`, `mt-*`, `hidden`, responsive prefixes). Don't add Tailwind colour utilities (`bg-blue-600`, `text-gray-700`) or new hex values in markup or JavaScript. - - When a new colour or component is needed, add a token and a documented class to `src/styles.css` and list it in the design language doc. - - Never rename the board colour classes (`btn-blue`, `btn-red`, `btn-green`, `btn-yellow`, `btn-orange`, `btn-gray`) or `team-color-*`; saved board JSON depends on them. - - Check layouts at phone (~390px), iPad (~768px) and laptop (~1280px) widths, with 44px minimum touch targets. -4. **Explain Decisions & Tradeoffs:** - - When providing code snippets or architectural suggestions, **always explain the reasoning** behind the chosen approach. - - Discuss potential **tradeoffs** (e.g., performance vs. readability, simplicity vs. extensibility). - - If multiple valid approaches exist, **present them as options**, outlining the pros and cons of each. -5. **Focus on Maintainability & Testability (with Jest):** - - Structure code for **readability, maintainability, and effective unit testing using Jest**. - - Emphasize **separation of concerns** (HTML structure, JS logic, Tailwind for presentation). - - Suggest **modular patterns using ES6 Modules (`import`/`export`)** that are easily importable and testable in Jest. Aim for pure functions where possible. - - Promote clear function/variable naming and add comments for complex logic. -6. **User Experience (UX) Focus:** - - Proactively offer suggestions to **improve the user experience**, especially regarding the form interaction. - - Consider aspects like intuitive layout (using Tailwind for responsiveness), clear feedback to the user, efficient data entry, and validation. - - Suggest appropriate **client-side form validation** techniques. -7. **Accessibility (a11y):** - - Ensure generated HTML structure is semantically correct and compatible with Tailwind usage. - - Suggest ARIA attributes where appropriate, especially for interactive elements styled with Tailwind that might lack default semantics. - - Keep keyboard navigation and screen reader compatibility in mind. -8. **Error Handling:** Include suggestions for robust client-side error handling (e.g., using `try...catch`). -9. **Vanilla JS First (Logic):** For application *logic*, **always attempt a vanilla JavaScript solution first**. Only suggest external JS libraries if a vanilla approach is genuinely impractical or overly complex, and clearly justify the need. -10. **Performance Considerations:** Briefly touch upon performance implications where relevant (e.g., efficient DOM manipulation, event delegation, minimizing JS bundle size if applicable). - -## How to Use These Instructions Effectively - -- **Be Specific:** Ask for "Tailwind classes for a responsive card" or "a vanilla JS function to handle form submission, making sure it's testable with Jest." -- **Iterate:** Use Copilot to generate initial code (HTML with Tailwind, JS functions), then ask for explanations, tests, or alternative implementations. "Write a Jest test for this function." "Can you refactor this using `async`/`await`?" -- **Review Critically:** Always review suggestions for correctness, adherence to Tailwind best practices, testability, and overall fit within your project. +Read and follow [`AGENTS.md`](../AGENTS.md) for product context, personas, user journeys, and push-back rules, and [`docs/design-language.md`](../docs/design-language.md) for UI tokens, semantic classes, and layout rules. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..68233f1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,45 @@ +# Field Hockey Event Tracker — Agent Context + +Static client-side web app (`src/index.html`, `src/timeline-video.html`, ES6 modules, Tailwind for layout/spacing, semantic CSS tokens in `src/styles.css`, Jest tests in `tests/`). + +## Operating Loop (`a → b → c → d`) + +When the user provides feedback, a pain point, or a feature idea: + +1. **(a) Contextualize**: Identify which **Persona** and **Journey** below the input affects. +2. **(b) Audit**: Inspect the current code and behaviour against that persona's goal and constraints. +3. **(c) Challenge or Design**: Apply the **Push-Back Rules**. If the idea violates one or hurts another persona, push back and propose an alternative before writing code. Otherwise define the smallest change that solves it. +4. **(d) Execute**: Use `.cursor/skills/async-ux-pipeline/SKILL.md` for app/UI changes or `.cursor/skills/board-config-generator/SKILL.md` for coding board layouts. + +--- + +## Personas & Non-Negotiable Quality Bars + +| Persona | Setting & Attention | Core Goal | Non-Negotiable UX & Quality Bar | +| :--- | :--- | :--- | :--- | +| **1. Dedicated Analyst** | Video tower / tech bench (phone, tablet, or laptop); laptop post-match. **100% focused on coding**; fluent in shorthand (`BS1`–`4`, `BW`, `A25E`, `CE`, `ASC`, `APS`). | Code high-density spatial/tactical events across back-to-back games; export Sportscode XML to **MyVA / Hudl Sportscode**. | • Clock runs continuously with the camera and survives reloads (`draftService`).
• `Complete` saves to `Saved Games` and immediately resets `Event Capture` (remembering team codes/colours) for the next match.
• Sportscode XML preserves per-button `lead`/`lag` clip windows. | +| **2. Bench Coach / Manager** | Dugout (phone one-handed or tablet) in outdoor sun/weather. **Split attention** (managing subs, cards, players). | Log events by **eyes-on-pitch spatial memory**, glance down to verify, and recover from mis-taps immediately. | • Buttons never shift position mid-game.
• High-contrast outdoor legibility; touch targets $\ge 44\text{px}$ (`--touch-min`).
• Clock bar shows `Last: · mm:ss` for 0.5s verification.
• 1-tap `Undo` (`Ctrl+Z`) and `Log → Recent` delete while clock runs. | +| **3. Drafted Helper** | Sideline/stands (phone portrait). Injured player or volunteer **handed a phone 5 mins before pushback**. | Capture core match events (`A25E`, `CE`, `SHOT`, `PC`, `GOAL`, `CARD`) without messing up. | • Must be able to code without learning cryptic analyst shorthand or tracking dual sticky state (zones `1`–`4` + shared team toggle) during fast transitions. | +| **4. Self-Sufficient Head Coach** | **Halftime (2 mins):** glances at coder's device.
**Post-match (15 mins):** laptop + projector in `Match Review`. | Validate halftime gut feel; turn exported XML + match MP4 into **6–10 starred teaching clips** in under 15 minutes. | • `Log → Statistics` must answer Team 1 vs Team 2 (`A25E`, `CE`, `SHOT`, `PC`, `GOAL`) at a glance.
• `Match Review` (`timeline-video.html`) must filter events, seek accurately to the start of the play (`time - lead`), align with video, and step through `★ Favorites` in **Presentation Mode**. | + +--- + +## Core Journeys & Entry Points + +1. **Live Capture (`index.html#event-capture`)**: Pick board (`#boardSelect`, locked once started) → `Start` (prompts for Team 1 & Team 2 codes/colours when `usesTeams(config)` is true) → log events (`1`–`4` changes sticky zone from Team 1's perspective, mirrored `5 - zone` for Team 2 on `"zoned": true` buttons; `T` toggles team on `"team": "selected"` rows) → `Finish` (`Complete` saves to `fieldHockeyGames_v1` and resets; `Discard` clears draft). +2. **In-Game Recovery & Halftime Check (`index.html#log`)**: Opened from `Log` in the clock bar while a game is active (redirects to `#event-capture` if no active game). Defaults to `Recent` tab (delete any of last 10 events); `Timeline`, `XML` (`Copy XML`), and `Statistics` tabs. +3. **Saved Games & XML Handoff (`index.html#saved-games` & `index.html#log/`)**: Lists saved games. Primary action (`Review` or card click) opens `timeline-video.html?game=`. Overflow menu (`⋮`) holds `View log` (read-only `#log/`, hides `Recent` tab, never touches live game state), `Export XML` (downloads Sportscode ``), `Rename`, and `Delete`. `Import XML` accepts Sportscode XML and legacy `` XML. +4. **Match Review (`timeline-video.html?game=`)**: Loads game from `fieldHockeyGames_v1`, uploads local video (`MP4`/`WebM` client-side), filters by event type or `★ Favorites`, seeks video on tag click, adds post-match tags/notes (`#add-event-fab`), and runs full-width **Presentation Mode** (`< 1/N >` playlist + `#presentation-overlay`). +5. **Board Configuration (`index.html#configure`)**: Power-user JSON editor (`rowDefs`, `zones`, `team`, `lead`/`lag`). Validated by `validateConfig` in `src/components/config.js`; stored in `fieldHockeyConfig_v1` (active) and `fieldHockeyNamedConfigs_v1` (named presets). + +--- + +## Push-Back Rules (Enforce in Step `c`) + +1. **Eyes-on-pitch rule**: Push back on any change that adds taps, confirmation dialogs, or layout shifts during live event capture. +2. **Config over code**: If a tracking requirement can be solved by a JSON board configuration (`rowDefs`, `zones`, `team`, `lead`/`lag`), use `board-config-generator` instead of adding application code. +3. **Persona isolation**: Never degrade the *Drafted Helper* or *Bench Coach* experience to serve an *Expert Analyst* edge case—isolate advanced complexity behind named board configurations or progressive disclosure. +4. **Protected contracts**: + - Never rename board colour classes (`btn-blue`, `btn-red`, `btn-green`, `btn-yellow`, `btn-orange`, `btn-gray`) or `team-color-*`. + - Never break `localStorage` keys (`fieldHockeyGames_v1`, `fieldHockeyConfig_v1`, `fieldHockeyNamedConfigs_v1`) or Sportscode XML `` / `` structure. + - Follow `docs/design-language.md` and `.cursor/rules/design-language.mdc` for all UI changes (tokens in `src/styles.css`, Tailwind for layout/spacing only). diff --git a/README.md b/README.md index 48420c9..49eab6b 100644 --- a/README.md +++ b/README.md @@ -159,14 +159,9 @@ npm run test:coverage ## Documentation -Additional documentation is available in the `docs/` directory: - -- [Match Review](docs/match-review.md) - Video synchronization features -- [Navigation](docs/navigation.md) - Router implementation and deep linking -- [Saved Games](docs/saved-games.md) - Game data storage architecture -- [State Management](docs/state-management.md) - Application state design -- [User Workflow](docs/user-workflow.md) - Current user experience -- [Developer Guide](docs/developer-guide.md) - Contributing guidelines +- [Agent Context & Product Rules](AGENTS.md) - Personas, user journeys, and product quality bars +- [Design Language](docs/design-language.md) - CSS tokens, semantic classes, and layout rules +- [MyVA Lead & Lag Timings](docs/myva-lead-lag-times.md) - Clip window reference for Sportscode XML exports ## License & Acknowledgments diff --git a/docs/design-language.md b/docs/design-language.md index 0e2ce22..3b2d301 100644 --- a/docs/design-language.md +++ b/docs/design-language.md @@ -109,7 +109,7 @@ Match Review (`body.review-page`) uses the full viewport. `review-header` / `rev ``` -Views sit inside `
`. Register them with the router as described in [Developer Guide](developer-guide.md#working-with-the-router). +Views sit inside `
`. Register them with the router in `src/main.js` and `src/components/router.js` (`viewToHashMap` and `hashToViewMap`). ### A form row diff --git a/docs/developer-guide.md b/docs/developer-guide.md deleted file mode 100644 index 446746e..0000000 --- a/docs/developer-guide.md +++ /dev/null @@ -1,215 +0,0 @@ -# Developer Guide - -## Developer Getting Started - -### Installation - -1. **Clone the repository**: - - ```bash - git clone https://github.com/dneimke/simple-coding.git - cd coding-tool - ``` - -2. **Install dependencies**: - - ```bash - npm install - ``` - -### Running Locally - -You can run the application using any of these methods: - -1. **VS Code Live Server (Recommended)**: - - Install the [Live Server extension](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) for VS Code - - Right-click on `src/index.html` and select "Open with Live Server" - - The application will open in your default browser - -2. **Any HTTP Server**: - - If you have Python installed: - - ```bash - # Python 3.x - python -m http.server --directory src 8080 - ``` - - - Then open `http://localhost:8080` in your browser - -3. **NPM http-server package**: - - ```bash - npm install -g http-server - http-server src - ``` - -The application runs entirely in the browser with no backend dependencies. - -### Testing - -The project uses Jest for testing: - -```bash -# Run tests once -npm test - -# Run tests with coverage report -npm test:coverage - -# Run tests in watch mode during development -npm run test:watch -``` - -After running tests with coverage, open `coverage/lcov-report/index.html` in a browser to view the detailed coverage report. - -## Project Structure - -### Key Directories and Files - -- `src/` - Application source code - - `index.html` - Main HTML entry point - - `main.js` - Main JavaScript entry point - - `components/` - UI components and reusable elements - - `services/` - Business logic services (state management, storage, etc.) - - `utils/` - Helper functions and utilities - - `assets/` - Static assets like icons, sample data, etc. - -- `tests/` - Jest test files mirroring the structure of `src/` -- `docs/` - Project documentation -- `coverage/` - Test coverage reports (generated when running tests with coverage) - -### Architecture Overview - -The application follows a modular ES6 architecture with: - -1. **Component-Based Structure**: UI elements are organized into reusable components -2. **Service Layer**: Handles business logic, data management, state, and storage -3. **Utility Modules**: Provide helper functions for common operations -4. **Event-Based Communication**: Components communicate through the notification service - -## Development Workflow - -### Making Changes - -1. **Creating New Components**: - - Create a new file in the appropriate directory under `src/components/` - - Export your component using ES6 modules - - Import and use the component where needed - -2. **Adding New Features**: - - Create or modify relevant components, services, or utilities - - Update the main application logic in `main.js` if necessary - - Add tests for new functionality - -### Testing Guidelines - -1. **Writing Tests**: - - All tests are located in the `tests/` directory - - Tests should mirror the structure of the `src/` directory - - Each module should have a corresponding `.test.js` file - -2. **Test Coverage**: - - Aim for at least 30% code coverage (minimum threshold set in Jest config) - - Focus on testing core functionality and business logic - -### Code Style - -- The project uses modern ES6+ JavaScript features -- No external frameworks are used (Vanilla JS only with Tailwind CSS for styling) -- Code should be clean, modular, and well-documented - -## Technical Details - -### Storage - -- The application uses `localStorage` for persisting games and configuration -- The storage key structure follows a versioned approach (e.g., `fieldHockeyGames_v1`) - -### DOM Manipulation - -- Direct DOM manipulation is performed using the standard Web APIs -- Helper functions in `utils/domUtils.js` provide common DOM operations - -### Testing Environment - -- Jest with jsdom for frontend testing -- Babel for transpilation during tests -- Coverage thresholds are set to 30% for statements, branches, functions, and lines - -## Common Tasks - -### Adding a New Event Type - -1. Update the configuration schema in `components/config.js` -2. Add any necessary helper functions in relevant utils -3. Update tests to cover the new event type - -### Modifying the UI - -1. Read the [Design Language](design-language.md). It lists the tokens and semantic classes (`page`, `surface`, `btn`, `field`, `modal`, `tabs` and more) that every screen uses. -2. Locate the relevant component in `src/components/` -3. Update the markup and associated JavaScript, composing design language classes for colour, borders and type, and Tailwind utilities for layout and spacing only -4. Add new colours or components to `src/styles.css` as tokens and documented classes, not as one-off hex values or Tailwind colour utilities -5. Test the changes with unit tests and visually at phone (~390px), iPad (~768px) and laptop (~1280px) widths - -### Working with the Router - -The application implements a custom router that handles navigation between views using hash-based routing: - -1. **Router Configuration**: - - The `Router` class is initialized in `main.js` with view and tab mappings - - View mappings connect DOM elements to internal view names - - Tab mappings handle sub-navigation within specific views (like the Log view) - -2. **Adding a New View**: - - Create a new view container element in `index.html` - - Add a navigation link with the appropriate hash fragment - - Update the router initialization in `main.js` to include the new view - - Add the view name and hash to `viewToHashMap` and `hashToViewMap` in `components/router.js` - -3. **Navigation Concepts**: - - Hash-based navigation: URL fragments like `#event-capture` control which view is shown - - Route guards: Checks like ensuring an active game exists before showing the Log view - - Deep linking: Direct navigation to specific views from external pages - -4. **Example: Adding a "Help" View**: - - ```javascript - // 1. Add the view to index.html, inside
- - - // 2. Add a navigation link inside .app-nav-links - - - Help - - - // 3. Update router initialization in main.js - const router = new Router( - { - // ...existing views - Help: { view: document.getElementById('help-view'), button: document.getElementById('navHelp') } - }, - // ...tabs config - ); - - // 4. Update the hash maps in router.js - this.viewToHashMap = { - // ...existing mappings - 'Help': 'help' - }; - ``` - -For more detailed information about the navigation system, see [Navigation](navigation.md). - -## Additional Resources - -- Check the `README.md` for an overview of the application -- See `docs/design-language.md` for tokens, classes and layout rules -- See `docs/user-workflow.md` for understanding the user experience flow -- Explore `docs/match-review.md` and `docs/saved-games.md` for feature-specific documentation diff --git a/docs/images/user-workflow-diagram.mermaid b/docs/images/user-workflow-diagram.mermaid deleted file mode 100644 index e69de29..0000000 diff --git a/docs/match-review.md b/docs/match-review.md deleted file mode 100644 index 1e39525..0000000 --- a/docs/match-review.md +++ /dev/null @@ -1,67 +0,0 @@ -# Match Review Timeline Video Feature Documentation - -The Match Review Timeline Video feature allows coaches, players, and analysts to review game events synchronized with video footage, enabling detailed analysis of field hockey matches. - -## Overview - -The Match Review interface combines a timeline panel that displays recorded game events with a video player that can be navigated based on these events. This integration provides a powerful tool for post-match analysis, player development, and strategy planning. - -## Key Components - -### Timeline Panel (Left Side) - -The Timeline panel provides a chronological view of all recorded match events: - -- **Game Title**: The game chosen from the Saved Games list is loaded automatically. Its name is the page heading above the video and the tag list, kept to one line so a long name does not push the tags down. Hover the heading to read the full name. To review a different game, return to Saved Games -- **Event Filtering**: Filter timeline events by type to focus on specific aspects of play. Active filters show as compact tags under the dropdown -- **Saved tags**: A single-line list. Each row is the time, the tag name, a favorite star, and delete. The list scrolls inside the side panel on a laptop, and under the video on a phone -- **Event Interaction**: Click a tag to jump to that moment in the video - -### Video Player (Right Side) - -The video player section allows you to: - -- **Upload Video**: Add match footage via the "Upload Video" button in the toolbar -- **Add tag**: With a video loaded, "Add tag" sits in the toolbar so it does not cover the player controls -- **Standard Controls**: Play, pause, skip, and adjust volume using the player controls under the picture -- **Synchronized Playback**: The video automatically jumps to the timestamp of selected events -- **Support for Common Formats**: Compatible with standard video formats (MP4, WebM, etc.) - -## How to Use - -1. **Select a Game**: - - Open **Saved Games** from the navigation bar and click **Review** (or the game's name) - - Match Review opens at `timeline-video.html?game=` with that game's events in the timeline - - To review another match, go back to Saved Games and choose it there - - View log, Export XML, Rename, and Delete are in each game's **More** menu on the Saved Games list - -2. **Filter Events** (if needed): - - Click the filter dropdown to select specific event types - - Use the "All events" checkbox to quickly toggle between all events and filtered view - - Apply filters using the blue button at the bottom of the dropdown - - View currently active filters via tags displayed below the dropdown - -3. **Upload Match Video**: - - Click the "Upload Video" button to add footage for the selected game - - Compatible formats will automatically load into the player - -4. **Navigate Through Events**: - - Scroll through the timeline to view all recorded events - - Click on any event to jump to that specific moment in the video - - Use the video player controls for fine-tuned navigation - -5. **Review and Analyze**: - - Use the synchronized system to analyze specific plays, techniques, or tactical situations - - Navigate between key moments without manually searching through footage - -## Best Practices - -- For optimal performance, use compressed video formats -- Consider filtering events by type when focusing on specific aspects (e.g., only penalties or goals) -- Use the timeline to quickly navigate between important game moments rather than manually scrubbing through video - -## Technical Notes - -- Video files are processed client-side without being uploaded to any server -- Game data is stored locally in your browser's storage -- Supported video formats depend on your browser (MP4 is universally supported) diff --git a/docs/navigation.md b/docs/navigation.md deleted file mode 100644 index c47784d..0000000 --- a/docs/navigation.md +++ /dev/null @@ -1,212 +0,0 @@ -# Navigation System - -## Overview - -The Field Hockey Event Tracker implements a modern navigation system using a vanilla JavaScript router with hash-based navigation. This document explains how the navigation works, including the router implementation, deep linking capabilities, and view transitions. - -## Router Architecture - -The application uses a custom `Router` class located in `src/components/router.js` that handles all navigation between views. This class is responsible for: - -1. **Hash-Based Routing**: Using URL fragments (e.g., `#event-capture`) to determine which view to display -2. **View Management**: Showing/hiding view components based on the current route -3. **Navigation State**: Managing the active/inactive state of navigation buttons -4. **Tab Navigation**: Handling sub-navigation within specific views (e.g., tabs within the Log view) - -The router works in conjunction with the application's state management system to ensure consistent navigation state across the application. - -## Deep Linking - -Deep linking allows users to navigate directly to specific views using URL hash fragments. This enables: - -- Bookmarking specific views -- Sharing direct links to specific views -- Using browser back/forward navigation -- Entering the application at a specific view - -Available deep links include: - -- `index.html#event-capture` - Event Capture view -- `index.html#log` - Log view for the game in progress (opened from **Log** on the clock bar while a game is running; not a main-menu item; requires an active game) -- `index.html#log/` - Read-only Log view for the saved game with the given id (no active game needed) -- `index.html#configure` - Configure view -- `index.html#saved-games` - Saved Games view (the entry point for Match Review) -- `timeline-video.html?game=` - Match Review with the given saved game loaded - -## View Mapping - -The router maintains two mapping objects to translate between internal view names and URL hash fragments: - -```javascript -// Internal view names → URL hash fragments -viewToHashMap = { - 'EventCapture': 'event-capture', - 'Config': 'configure', - 'Log': 'log', - 'SavedGames': 'saved-games' -}; - -// URL hash fragments → Internal view names -hashToViewMap = { - 'event-capture': 'EventCapture', - 'configure': 'Config', - 'log': 'Log', - 'saved-games': 'SavedGames' -}; -``` - -## Navigation Flow Example - -Here's an end-to-end example of what happens during a navigation event: - -### 1. User-Initiated Navigation - -When a user clicks on the "Configure" navigation link: - -```html -Configure -``` - -### 2. Hash Change Detection - -The browser's URL changes to `index.html#configure`, which triggers the `hashchange` event. The Router's `_handleHashChange` method is called: - -```javascript -_handleHashChange() { - // Get the hash without the # character - const hash = window.location.hash.substring(1); - - if (hash) { - const viewName = this.hashToViewMap[hash]; - if (viewName) { - // Check prerequisites... - - // Show the requested view - this._showViewWithoutHashUpdate(viewName); - } else { - // Invalid hash, default to EventCapture - // ... - } - } else { - // No hash, default to EventCapture - // ... - } -} -``` - -### 3. View Name Translation - -The router converts the hash fragment `"configure"` to the internal view name `"Config"` using the `hashToViewMap`. - -### 4. Prerequisites Check - -For certain views, the router checks if prerequisites are met. The hash is split on `/`, so `log/` maps to the Log view with a saved game id. The id is stored in `ui.logGameId` (or `null` for every other route), and only the plain `#log` route requires an active game: - -```javascript -const [hash, param] = window.location.hash.substring(1).split('/'); -const savedGameId = viewName === 'Log' && param ? decodeURIComponent(param) : null; -stateService.setState('ui.logGameId', savedGameId); - -if (viewName === 'Log' && !savedGameId) { - const hasActiveGame = stateService.getState('game.hasCurrentGame'); - if (!hasActiveGame) { - // Default to EventCapture if no active game - this._updateHashSilently(hash); - this.showView('event-capture'); - return; - } -} -``` - -When the Log view is shown, `main.js` renders either the saved game's `events` (with a "Saved game" banner and a Back to Saved Games button) or the live game's `loggedEvents` (with a Back to capture link). Viewing a saved game never changes the live game state. The Log view has no main-menu button. A live log keeps Event Capture current; a saved-game log keeps Saved Games current. - -### 5. State Update - -The router updates the application state through the state service: - -```javascript -_showViewWithoutHashUpdate(viewName) { - stateService.setState('ui.currentView', viewName); -} -``` - -### 6. View Change Handler - -The state change triggers the `_handleViewChange` method, which: - -- Hides all views by adding the `hidden` class -- Shows the requested view by removing the `hidden` class -- Updates navigation button styles to highlight the active route -- Updates tab visibility within the view if applicable - -```javascript -_handleViewChange = (viewName) => { - // Hide all views - Object.values(this.views).forEach(({ view, button }) => { - view.classList.add('hidden'); - button.classList.remove('text-white', 'hover:text-white'); - button.classList.add('text-gray-400', 'hover:text-white'); - }); - - // Show requested view - const selectedView = this.views[viewName]; - if (selectedView) { - selectedView.view.classList.remove('hidden'); - selectedView.button.classList.add('text-white', 'hover:text-white'); - selectedView.button.classList.remove('text-gray-400', 'hover:text-white'); - } -}; -``` - -### 7. UI Update Complete - -The user now sees the Configure view, with the "Configure" navigation button highlighted, and the URL updated to `index.html#configure`. - -## Route Guard Pattern - -The router implements a simple "route guard" pattern to prevent access to views that require certain prerequisites: - -```javascript -// Example: the live Log view requires an active game; #log/ for a saved game does not -if (viewName === 'Log' && !savedGameId) { - const hasActiveGame = stateService.getState('game.hasCurrentGame'); - if (!hasActiveGame) { - // Redirect to default view - this._updateHashSilently('event-capture'); - this.showView('EventCapture'); - return; - } -} -``` - -## Cross-Page Navigation - -When navigating between separate HTML pages (e.g., from `index.html` to `timeline-video.html`), traditional anchor links are used with hash fragments for direct view targeting: - -```html - -Configure -``` - -This ensures that when users navigate from the Match Review page back to the main application, they land directly on their intended view. - -Match Review has no navigation item of its own. Selecting a game in the Saved Games view navigates to `timeline-video.html?game=`, and the review page selects that game on load. Games without an `id` are assigned one when the Saved Games list is rendered. - -## Best Practices Implemented - -The router implementation follows several industry best practices: - -1. **History API**: Using `history.replaceState()` to update URLs without causing page refreshes -2. **Decoupled Views**: Views are completely independent of the router -3. **State Management**: Navigation state is managed through the state service -4. **Conditional Routing**: Route guards protect views with prerequisites -5. **Clean URLs**: Hash-based routing provides human-readable URLs - -## Future Enhancements - -Potential improvements to the navigation system could include: - -1. **Route Parameters**: Enhance the router to support parameters in routes (e.g., `#saved-games/123` to load a specific game) -2. **Transition Animations**: Add smooth transitions between views -3. **Nested Routes**: Support for hierarchical routing structures -4. **Query Parameters**: Support for query parameters in addition to hash fragments diff --git a/docs/saved-games.md b/docs/saved-games.md deleted file mode 100644 index 8069e09..0000000 --- a/docs/saved-games.md +++ /dev/null @@ -1,143 +0,0 @@ -# Saved Games Architecture Documentation - -## Architecture Overview - -The functionality for managing saved games is distributed across multiple files in the project. The codebase shows an architectural migration from simple utility functions to a more structured, object-oriented approach with `gameStorageManager`. - -```mermaid -graph TD - subgraph "Game Storage Management" - GSM[gameStorageManager] --> SS[storageService] - SS --> LS[localStorage] - end - - subgraph "Legacy Utilities" - style GU fill:#f9d6d6,stroke:#d88,stroke-width:2px - GU[gameUtils.js] -.forwards to.-> GSM - Utils[utils.js] -.re-exports.-> GU - end - - subgraph "Consumers" - MAIN[main.js] --> GSM - MAIN -.legacy usage.-> GU - TVJ[timeline-video.js] --> GSM - IU[importUtils.js] --> GSM - TESTS[Tests] --> GUFT[gameUtilsForTest.js] - GUFT --> LS - end -``` - -## Storage Components - -### [`src/services/gameStorageManager.js`](../src/services/gameStorageManager.js) - -This is the primary class responsible for managing game storage operations: - -- **`getAllGames()`**: Retrieves all saved games from localStorage -- **`getGameByIndex()`**: Gets a specific game by its index -- **`updateGame()`**: Updates a saved game with new data -- **`removeGame()`**: Removes a game from storage -- **`saveGame()`**: Saves a new game to storage - -The class uses a singleton pattern with an instance exported as `gameStorageManager`. - -### [`src/services/storageService.js`](../src/services/storageService.js) - -This is a lower-level service that directly interacts with the browser's localStorage: - -- **`getItem()`**: Retrieves and parses data from localStorage -- **`setItem()`**: Stores data in localStorage, handling errors - -## Legacy Utilities (Deprecated) - -### [`src/utils/gameUtils.js`](../src/utils/gameUtils.js) - -This file contains previously used utility functions that are now deprecated in favor of the `gameStorageManager`: - -- **`saveGameToLocalStorage()`**: ⚠️ DEPRECATED - Now forwards to `gameStorageManager.saveGame()` -- **`loadSavedGames()`**: ⚠️ DEPRECATED - Now forwards to `gameStorageManager.getAllGames()` -- **`updateSavedGame()`**: ⚠️ DEPRECATED - Now forwards to `gameStorageManager.updateGame()` -- **`deleteSavedGame()`**: ⚠️ DEPRECATED - Now forwards to `gameStorageManager.removeGame()` - -These functions include deprecation warnings and forward calls to the corresponding methods in `gameStorageManager`. - -Non-storage related functions remain in this file: - -- **`computeGameStatistics()`**: Calculates statistics from game data -- **`createGameCard()`**: Creates UI cards for saved games -- **`loadConfig()`**: Loads configuration data -- **`saveConfig()`**: Saves configuration data - -### [`src/utils/utils.js`](../src/utils/utils.js) - -This file re-exports the deprecated functions from `gameUtils.js` with warning comments: - -```javascript -// The functions below are deprecated - use gameStorageManager instead -saveGameToLocalStorage, // ⚠️ Deprecated: Use gameStorageManager.saveGame() -loadSavedGames, // ⚠️ Deprecated: Use gameStorageManager.getAllGames() -deleteSavedGame, // ⚠️ Deprecated: Use gameStorageManager.removeGame() -updateSavedGame // ⚠️ Deprecated: Use gameStorageManager.updateGame() -``` - -### [`src/utils/gameUtilsForTest.js`](../src/utils/gameUtilsForTest.js) - -A simplified version of game utility functions used for testing purposes. - -## Consumer Usage Analysis - -### [`src/main.js`](../src/main.js) - -The main application file contains the `renderSavedGames()` function which: - -1. Gets all saved games using `gameStorageManager.getAllGames()` -2. Creates UI cards for each game with load/rename/delete actions -3. Sets up an `onLoad` handler for each game that: - - Checks for an active game and prompts for confirmation - - Uses `gameState.setGameState()` to restore the saved game state - - Updates the UI elements appropriately - - Shows a success message - -### [`src/js/timeline-video.js`](../src/js/timeline-video.js) - -This file contains timeline-specific game loading functionality: - -- **`getSavedGames()`**: Gets saved games from localStorage -- **`loadGame(gameId)`**: Loads a specific game by ID and updates the timeline view - -## Flow of Loading a Saved Game - -1. The user navigates to the saved games view -2. The `renderSavedGames()` function in `main.js` fetches all saved games using `gameStorageManager.getAllGames()` -3. For each game, a card is created with an `onLoad` handler -4. When the user clicks to load a game: - - The handler checks if there's an active game and asks for confirmation - - It then updates the application state with the saved game data using `gameState.setGameState()` - -## Migration Status - -The codebase is currently in a transition phase: - -1. **Current State**: The `gameStorageManager` class is the primary storage mechanism, with legacy functions now forwarding to it. - -2. **Consumers**: - - Some files like `main.js` have been updated to use `gameStorageManager` directly - - Legacy code paths may still use the old utility functions, which now forward to `gameStorageManager` - -3. **Next Steps**: - - Replace all direct usages of the deprecated utility functions with `gameStorageManager` calls - - Once all consumers have been migrated, the deprecated functions can be safely removed - - Update tests to use `gameStorageManager` instead of `gameUtilsForTest.js` - -4. **Completion Timeline**: The migration should be completed by the next major release to ensure consistency across the codebase. - - The UI is updated to reflect the loaded game - -In the timeline view (timeline-video.js), a separate `loadGame()` function is used that: - -1. Gets the saved games using `getSavedGames()` -2. Loads the specific game by ID -3. Updates the timeline events and renders the timeline - -## Storage Keys - -Both implementations use the same storage key: `fieldHockeyGames_v1` - this ensures compatibility between different parts of the application. diff --git a/docs/state-management.md b/docs/state-management.md deleted file mode 100644 index 32e816d..0000000 --- a/docs/state-management.md +++ /dev/null @@ -1,263 +0,0 @@ -# State Management Implementation - -## Overview - -This document outlines how state is managed in the Field Hockey Event Tracker application. The application uses a custom state management system built around the pub/sub (publisher/subscriber) pattern, which provides a centralized way to manage application state and notify components when changes occur. - -## Core Component: StateService - -The `stateService` module is the heart of the state management solution, providing: - -1. **Centralized State Storage** - - Maintains a single source of truth for application state - - Initial state structure with logical sections: - - ```javascript - _state: { - game: { - isActive: false, - isRunning: false, - elapsedTime: 0, - loggedEvents: [], - hasCurrentGame: false - }, - ui: { - currentView: 'EventCapture', - currentTab: 'timelineView' - } - } - ``` - -2. **State Access & Manipulation Methods** - - `getState(path)` - Retrieves state values using dot notation paths - - `setState(path, newValue, merge)` - Updates state values and notifies subscribers - -3. **Subscription System** - - `subscribe(path, handler)` - Registers callbacks for state changes - - Returns an unsubscribe function to prevent memory leaks - -## How Components Subscribe to State - -The `GameState` class demonstrates a typical subscription pattern: - -```javascript -constructor(timerDisplay, eventLog, eventButtons) { - // Store UI references - this.eventButtons = eventButtons; - this.eventLog = eventLog; - this.timerDisplay = timerDisplay; - - // Subscribe to game state changes - stateService.subscribe('game', this._handleStateChange.bind(this)); - - // Initialize UI with current state - this._updateUI(stateService.getState('game')); -} - -_handleStateChange(gameState) { - this._updateUI(gameState); -} -``` - -This pattern allows the `GameState` component to: - -1. Subscribe to changes in the 'game' state slice -2. Respond to those changes by updating the UI - -## Notification Flow Example - -When an event is added during gameplay: - -1. The `addEvent` method in `GameState` modifies state: - - ```javascript - addEvent(event) { - const gameState = stateService.getState('game'); - - // Validation checks... - - const events = [...gameState.loggedEvents, event]; - stateService.setState('game.loggedEvents', events); - } - ``` - -2. Inside `setState`, the state is updated and notifications are triggered: - - ```javascript - setState(path, newValue, merge = false) { - // Update the state... - - // Notify subscribers - this._notify(path, this.getState(path)); - } - ``` - -3. The `_notify` method handles notification propagation: - - Notifies direct subscribers of the path - - Also notifies subscribers of parent paths (cascading notifications) - -4. The `_handleStateChange` callback in `GameState` is invoked: - - ```javascript - _handleStateChange(gameState) { - this._updateUI(gameState); - } - ``` - -5. The `_updateUI` method refreshes UI components: - - ```javascript - _updateUI(gameState) { - if (gameState.loggedEvents && this.eventLog) { - this.eventLog.render(gameState.loggedEvents); - } - - // Update other UI elements... - } - ``` - -## Advanced Features - -1. **Path-based Subscriptions** - Components can subscribe to specific state slices, reducing unnecessary updates. - -2. **Automatic Parent Path Notifications** - Updating a nested path (`game.loggedEvents`) also notifies subscribers of parent paths (`game`). - -3. **Object Merging** - When updating objects, the `merge` parameter allows shallow merging with existing state. - -4. **Unsubscribe Support** - Every subscription returns a function to remove the subscription, preventing memory leaks: - - ```javascript - const unsubscribe = stateService.subscribe('path', handler); - // Later when no longer needed: - unsubscribe(); - ``` - -## Implementation Details - -The state management system is implemented in `src/services/stateService.js` and follows these key principles: - -### 1. Immutability - -State updates create new objects rather than mutating existing ones, which helps with: - -- Preventing unintended side effects -- Supporting undo/redo functionality in the future -- Easier debugging and state tracking - -### 2. Path-based State Access - -The dot notation path system allows components to: - -- Access deeply nested state values with simple syntax -- Subscribe to specific parts of the state tree -- Receive updates only when relevant data changes - -### 3. Centralized Updates - -All state mutations go through `setState()`, which ensures: - -- Consistent state update patterns -- Proper notification of all subscribers -- A single entry point for potential logging or middleware - -## Usage Guidelines - -When implementing new components that need to interact with application state: - -1. **Identify Required State** - Determine which pieces of state the component needs -2. **Subscribe to Relevant Paths** - Use specific paths to minimize unnecessary updates -3. **Handle Cleanup** - Store and call the unsubscribe function when the component is no longer needed -4. **Avoid Direct State Mutation** - Always use `setState()` to update state - -## Benefits of This Approach - -This state management approach provides several advantages: - -1. **Lightweight** - No external dependencies required -2. **Modular** - Components can subscribe only to what they need -3. **Testable** - Pure functions and ES6 modules make testing straightforward -4. **Efficient** - Targeted updates prevent unnecessary re-renders -5. **Understandable** - Simple API with predictable behavior - -## Subscription and Notification Flow Diagram - -The diagram below illustrates how components interact with the state management system: - -```ascii -┌─────────────────────────────────────────────────────────────────────────┐ -│ │ -│ State Management System │ -│ │ -├─────────────────────────────────────────┬───────────────────────────────┤ -│ │ │ -│ ┌───────────────────┐ │ ┌─────────────────────────┐ │ -│ │ │ │ │ │ │ -│ │ Central State │◄─────────────────┘ │ Subscriber Registry │ │ -│ │ │ Updates State │ │ │ -│ └──────────┬────────┘ └─────────────┬───────────┘ │ -│ │ │ │ -└─────────────┼────────────────────────────────────────────┼──────────────┘ - │ │ - │ │ - ▼ │ -┌─────────────────────────┐ │ -│ │ │ -│ 1. State Change Event │ │ -│ │ │ -└──────────────┬──────────┘ │ - │ │ - │ │ - ▼ │ -┌─────────────────────────┐ │ -│ │ │ -│ 2. _notify() Called │ │ -│ │ │ -└──────────────┬──────────┘ │ - │ │ - │ │ - ▼ │ -┌─────────────────────────┐ Looks Up ┌─────▼───────────────┐ -│ │◄───────Handlers─────────┤ │ -│ 3. Find Subscribers │ │ Subscribed │ -│ for Path │ │ Components │ -└──────────────┬──────────┘ │ │ - │ └─────────────────────┘ - │ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ │ -│ 4. Execute Callback for Each Subscriber with Updated State │ -│ │ -└─────────────────────────────────────────────────────────────┘ - │ - │ - ▼ -┌─────────────────────────┐ ┌────────────────────────┐ -│ │ │ │ -│ Component A │ │ Component B │ -│ _handleStateChange() │ │ _handleStateChange() │ -│ │ │ │ -└─────────────────────────┘ └────────────────────────┘ - │ │ - │ │ - ▼ ▼ -┌─────────────────────────┐ ┌────────────────────────┐ -│ │ │ │ -│ Update UI Elements │ │ Update UI Elements │ -│ │ │ │ -└─────────────────────────┘ └────────────────────────┘ -``` - -### Example Scenario - -In this example: - -1. A user clicks a button to log an event during gameplay -2. `GameState.addEvent()` calls `stateService.setState('game.loggedEvents', events)` -3. `setState()` updates the internal state and calls `_notify()` -4. `_notify()` finds all subscribers for 'game.loggedEvents' and 'game' -5. Each subscriber's handler function is called with the updated state -6. Components (GameState, EventLog) update their UI elements based on the new state - -This pattern ensures that all components depending on a particular part of the state are updated when changes occur, while components not dependent on that state remain unaffected. diff --git a/docs/user-workflow.md b/docs/user-workflow.md deleted file mode 100644 index b7297ce..0000000 --- a/docs/user-workflow.md +++ /dev/null @@ -1,53 +0,0 @@ -# Current User Experience and Workflow for the Game Tracker - -## Current User Experience - -The current user experience for creating and retrieving games follows this lifecycle: - -1. **Choosing a board**: - - The **Board** selector in the clock bar lists the saved configurations before a game starts. A first visit opens on the built-in `hockey-one-zoned` board. Picking another saved configuration makes it the active configuration (the same as saving it on the Configure page). - - Once a game is in progress the selector is hidden. Complete or discard the game to choose a different board. - - `hockey-one-zoned` is the [sticky zone board](feat/sticky-zones/spec.md): a zone bar (Zones 1-4, from Team 1's perspective) replaces the per-zone BS buttons. BS and BW log the active zone, mirrored for Team 2, e.g. `FIRE BS2` and `TIGERS BS3`. Tap a zone or press 1-4 to change it. Zone changes are not logged. - - On `hockey-one-zoned` the scoring and card buttons (APS, PS, GOAL, SO, GREEN, YELLOW, RED) are a single set with a team toggle. Tap a team (or press T) and the buttons log for that team, e.g. `PERTH GOAL`, until you switch again. - -2. **Starting a Game**: - - The user navigates to the Event Capture page. - - The game session begins when the user clicks the "Start Game" button. This action enables event buttons and starts the timer. - - On a board with team rows (such as `hockey-one-zoned`), Start first asks for a code and colour for Team 1 and Team 2. Every team event is logged as `{CODE} {EVENT}`, and the codes are remembered for the next game. - -3. **Recording Events**: - - During the session, the user can log events by clicking the event buttons. These events are recorded with timestamps relative to the session's elapsed time. - - The clock bar shows the most recent event beside the timer. **Undo** (or Ctrl+Z / Cmd+Z) removes it straight away, while running or paused, and a message confirms what was removed. - - **Log** on the clock bar opens the live log while a game is running. It is not in the main menu, and it leaves with the game when you complete or discard. The **Recent** tab (the default for the live game) lists the last 10 events, newest first, each with a **Delete** button. **Back to capture** returns to the board. Saved games' logs don't show this tab, so they stay read-only. - - The Event Capture page has extra space below the buttons so you can scroll the menu off screen while capturing. - -4. **Completing a Game**: - - Once the session is complete, the user opens **Finish** in the clock bar and chooses **Complete**. A dialog shows the event count and clock, and asks for the game name. On a team board the name is filled in from the team codes (for example `FIRE vs TIGERS`). - - Cancel or Escape leaves the capture in progress. - - Complete saves the game to Saved Games under that name, or with no name if the field is left blank, then resets the capture: the timer and event log return to empty. - -5. **Loading a Game**: - - If no session is currently active (i.e., the timer is stopped and no elapsed time is recorded), the "Load Game" button on the Log page becomes available. - - Clicking "Load Game" retrieves the saved game state from `localStorage` and restores the events and elapsed time to the application. - -6. **Direct Navigation (Deep Linking)**: - - The application supports direct navigation to specific views using URL hash fragments. - - Users can bookmark specific views or share links that go directly to a particular view. - - Available deep links: - - `index.html#event-capture` - Navigate to the Event Capture view - - `index.html#log` - Navigate to the Log view for the game in progress (opened from **Log** on the clock bar; requires an active game) - - `index.html#log/` - Navigate to the read-only Log view for a saved game - - `index.html#configure` - Navigate to the Configure view - - `index.html#saved-games` - Navigate to the Saved Games view - - The navigation system will automatically redirect to the Event Capture view if trying to access the Log view without an active game. - -7. **Reviewing a Game**: - - The user opens the Saved Games view and clicks **Review** (or the game's name) to open it in Match Review (`timeline-video.html?game=`). Match Review is not a separate navigation item. - - Each saved game's **More** menu (the ⋮ button) holds View log, Export XML, Rename, and Delete. - - **View log** opens the Log view (Timeline, XML, and Statistics tabs) for that game at `index.html#log/`. A banner names the saved game and links back to Saved Games. This is read-only and does not replace a game in progress. - - **Export XML** downloads the game as a Sportscode XML file (`` with `` and ``). Teams, date and favourites are not included. - - **Import XML** accepts Sportscode XML files, as well as files in the older `` format. Because Sportscode files carry no teams, the teams prompt defaults to the file name. - - The timeline panel displays all recorded events, and the video player allows synchronized playback of the match footage. - - From the Match Review page, deep links allow returning directly to specific views in the main application. - -This flow ensures that users can efficiently manage game sessions while preventing conflicts between active and saved states. For more details on the navigation system, see [Navigation](navigation.md).