|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +DigiScript is a full-stack web application for cueing theatrical shows. It provides real-time script display, cue management, and show control capabilities. |
| 8 | + |
| 9 | +**Stack**: Vue.js 2 frontend + Python Tornado backend + SQLite database |
| 10 | + |
| 11 | +**Version Requirements**: Node 24.x, Python 3.13.x |
| 12 | + |
| 13 | +## Development Commands |
| 14 | + |
| 15 | +### Frontend Development |
| 16 | + |
| 17 | +```bash |
| 18 | +# Build frontend (outputs to ../server/static/) |
| 19 | +cd client |
| 20 | +npm ci |
| 21 | +npm run build |
| 22 | + |
| 23 | +# Linting |
| 24 | +npm run lint # ESLint with auto-fix |
| 25 | +npm run ci-lint # CI mode (no auto-fix) |
| 26 | +``` |
| 27 | + |
| 28 | +### Backend Development |
| 29 | + |
| 30 | +```bash |
| 31 | +# Run server (serves at http://localhost:8080) |
| 32 | +cd server |
| 33 | +python3 main.py |
| 34 | + |
| 35 | +# Linting and formatting |
| 36 | +ruff check server/ # Fast linting (replaces pylint) |
| 37 | +ruff format server/ # Code formatting (replaces black) |
| 38 | + |
| 39 | +# Testing |
| 40 | +pytest # Run all tests |
| 41 | +pytest test/ # Run specific directory |
| 42 | +``` |
| 43 | + |
| 44 | +### Docker Deployment |
| 45 | + |
| 46 | +```bash |
| 47 | +# Full stack with Prometheus/Grafana monitoring |
| 48 | +docker-compose up -d |
| 49 | +``` |
| 50 | + |
| 51 | +## Architecture |
| 52 | + |
| 53 | +### Frontend Architecture (Vue.js 2) |
| 54 | + |
| 55 | +**State Management**: Vuex with modular structure |
| 56 | +- `store/modules/user/` - User authentication and settings |
| 57 | +- `store/modules/websocket/` - WebSocket connection management |
| 58 | +- `store/modules/system/` - System-wide state |
| 59 | +- `store/modules/show/` - Show data |
| 60 | +- `store/modules/script/` - Script content |
| 61 | +- `store/modules/scriptConfig/` - Script configuration |
| 62 | +- State persisted to localStorage via vuex-persistedstate |
| 63 | + |
| 64 | +**Component Organization**: |
| 65 | +- `views/` - Page-level components (routes) |
| 66 | +- `vue_components/` - Reusable components organized by feature |
| 67 | +- `vue_components/config/show/` - Complex nested structure for show configuration: |
| 68 | + - `script/` - Script editing interface |
| 69 | + - `cues/` - Cue management |
| 70 | + - `cast/` - Cast assignment |
| 71 | + - `mics/` - Microphone allocation |
| 72 | + |
| 73 | +**Build Process**: Vite builds to `../server/static/` (co-located with backend) |
| 74 | +- Manual chunking: `bootstrap-vendor.js`, `vue-vendor.js`, `utils-vendor.js`, `websocket-vendor.js`, `vendor.js` |
| 75 | + |
| 76 | +### Backend Architecture (Python Tornado) |
| 77 | + |
| 78 | +**Decorator-Based Routing**: Controllers use `@route()` decorator for automatic registration |
| 79 | +- Controllers in `controllers/api/` are auto-discovered via `module_discovery.py` |
| 80 | +- No manual route registration required |
| 81 | + |
| 82 | +**SQLAlchemy 2.0**: Recently migrated from 1.x |
| 83 | +- **IMPORTANT**: Use `select()` API, not legacy `Query` interface |
| 84 | +- Example: `session.execute(select(Model).where(...))` not `session.query(Model).filter(...)` |
| 85 | + |
| 86 | +**RBAC System**: Fine-grained permissions via `rbac/rbac.py` |
| 87 | +- Role/permission bitmasks for access control |
| 88 | +- Integrated into controller decorators |
| 89 | + |
| 90 | +**JWT Authentication**: Stateless auth with refresh tokens |
| 91 | +- JWT service: `utils/web/jwt_service.py` |
| 92 | +- Token validation on protected routes |
| 93 | + |
| 94 | +**Database Migrations**: Alembic in `alembic_config/` |
| 95 | +- Auto-checked on server startup |
| 96 | +- Generate migrations: `alembic revision --autogenerate -m "description"` |
| 97 | + |
| 98 | +**Script Revision System**: Complex FK relationships require careful handling |
| 99 | + |
| 100 | +**Core Principle - Lines Exist Within Association Context**: |
| 101 | +- A `ScriptLine` only exists within the context of a `ScriptLineRevisionAssociation` |
| 102 | +- Lines are NOT independently shared across revisions |
| 103 | +- When creating a new revision, the `line_id` is copied (same line object reused) |
| 104 | +- Lines should ONLY be deleted when NO associations reference them |
| 105 | + |
| 106 | +**FK References That Must Be Checked Before Line Deletion**: |
| 107 | +- `ScriptLineRevisionAssociation.line_id` (primary reference) |
| 108 | +- `ScriptLineRevisionAssociation.next_line_id` (linked list pointer) |
| 109 | +- `ScriptLineRevisionAssociation.previous_line_id` (linked list pointer) |
| 110 | +- `CueAssociation.line_id` (revision-scoped cue placement) |
| 111 | + |
| 112 | +**Revision-Scoped Associations** (must be migrated when line objects change): |
| 113 | +- `CueAssociation`: `(revision_id, line_id, cue_id)` - where a cue appears |
| 114 | +- `ScriptCuts`: `(revision_id, line_part_id)` - which line parts are cut |
| 115 | + |
| 116 | +**Line Update Pattern** (`controllers/api/show/script/script.py:609-662`): |
| 117 | +When updating a line via PATCH, new `ScriptLine` and `ScriptLinePart` objects are created. |
| 118 | +ALL revision-scoped associations MUST be migrated to the new objects: |
| 119 | +1. Migrate `CueAssociation`: `(revision, old_line_id)` → `(revision, new_line_id)` |
| 120 | +2. Migrate `ScriptCuts`: `(revision, old_line_part_id)` → `(revision, new_line_part_id)` |
| 121 | +3. Use `part_index` to map old line_parts to new line_parts |
| 122 | + |
| 123 | +**Cascade Deletion Pattern** (`models/script.py:127-169`): |
| 124 | +Use `post_delete` hooks with `session.no_autoflush` context: |
| 125 | +- `post_delete`: Executes AFTER association removed (avoids FK errors during cascade) |
| 126 | +- `no_autoflush`: Prevents lazy loading from triggering premature autoflush mid-cascade |
| 127 | +- Check ALL FK references before deleting orphaned lines |
| 128 | + |
| 129 | +**Testing Script Revisions**: |
| 130 | +- ✅ Use API endpoints to create test data (POST/PATCH/DELETE) |
| 131 | +- ❌ Avoid manually creating FK references that can't occur via API |
| 132 | +- ❌ Don't create lines referenced only as `next_line_id`/`previous_line_id` (not as `line_id`) |
| 133 | + |
| 134 | +See Issue #670 / PR #758 for detailed implementation examples. |
| 135 | + |
| 136 | +### WebSocket Messaging Protocol |
| 137 | + |
| 138 | +**Server → Client Message Format**: |
| 139 | +```json |
| 140 | +{ |
| 141 | + "OP": "SOME_OP_CODE", |
| 142 | + "DATA": {}, |
| 143 | + "ACTION": "SOME_ACTION" |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +**Client Handling** (in `client/src/store/store.js`): |
| 148 | +1. Vuex mutation `SOCKET_ONMESSAGE` - Called for every message, handles OP codes |
| 149 | +2. Vuex action `SOME_ACTION` - Called if `ACTION` key is present in message |
| 150 | + |
| 151 | +This pattern enables real-time show updates, cue triggering, and multi-client synchronization. |
| 152 | + |
| 153 | +## Key Technical Details |
| 154 | + |
| 155 | +### Recent Migrations |
| 156 | +- **SQLAlchemy 2.0**: Use `select()` API, not legacy query methods |
| 157 | +- **Ruff**: Replaces black, isort, and pylint for Python code quality |
| 158 | +- **Vite**: Frontend build system (replaced webpack) |
| 159 | + |
| 160 | +### Important Version Notes |
| 161 | +- **Vue Version**: Vue 2.7 (NOT Vue 3) - different API, especially Composition API |
| 162 | +- **Bootstrap**: Bootstrap 4.6 + Bootstrap Vue 2.23 |
| 163 | +- **Python**: 3.13.x with asyncio support |
| 164 | +- **Node**: 24.x (npm 11.x) |
| 165 | + |
| 166 | +### Configuration |
| 167 | +- **Server config**: `server/conf/digiscript.json` (created at runtime) |
| 168 | +- **Database**: SQLite file, path configurable in server config |
| 169 | +- **Settings system**: Hierarchical, editable via API endpoints |
| 170 | + |
| 171 | +## Project Structure Highlights |
| 172 | + |
| 173 | +### Backend Key Directories |
| 174 | +- `controllers/` - Request handlers with `@route()` decorator |
| 175 | +- `controllers/ws_controller.py` - WebSocket handler for real-time messaging |
| 176 | +- `models/` - SQLAlchemy ORM models (2.0 style) |
| 177 | +- `schemas/` - Marshmallow serialization schemas for API responses |
| 178 | +- `rbac/` - Role-based access control implementation |
| 179 | +- `utils/module_discovery.py` - Dynamic module loading for controllers/models |
| 180 | +- `alembic_config/` - Database migration scripts |
| 181 | + |
| 182 | +### Frontend Key Directories |
| 183 | +- `store/modules/` - Vuex modular store pattern (feature-based modules) |
| 184 | +- `vue_components/config/show/` - Complex nested show configuration UI |
| 185 | +- `js/http-interceptor.js` - Global fetch/HTTP request interceptor |
| 186 | +- `js/customValidators.js` - Vuelidate custom validation rules |
| 187 | + |
| 188 | +## CI/CD Pipeline |
| 189 | + |
| 190 | +GitHub Actions workflows (`.github/workflows/`): |
| 191 | +- `nodelint.yml` - ESLint on client code |
| 192 | +- `pylint.yml` - Ruff linting on server code |
| 193 | +- `python-test.yml` - Run pytest suite |
| 194 | +- `check-database-migrations.yml` - Verify Alembic migrations |
| 195 | +- `docker-image-build.yml` / `docker-publish.yml` - Container builds |
| 196 | + |
| 197 | +## Important Notes from Tim |
| 198 | + |
| 199 | +- Always run formatting checks on both client and server side code before committing to avoid CI build failures |
| 200 | +- When opening issues in GitHub, always add the `claude` label to them |
| 201 | +- When working on new features and creating pull requests, create a feature branch that will target the `dev` branch |
| 202 | +- When adding new features, or changing the behaviour of existing ones, make sure to update the user documentation in the docs/ directory, including adding screenshots as needed |
| 203 | +- Use the reStructuredText format for docstrings for Python code |
0 commit comments