# MyKindlebox: Shows Section — Design Document ## Overview Add a **Shows** section to MyKindlebox that pulls live show listings from the ARBA show search page, lets users filter and browse upcoming shows, build personal show events, assign rabbits from their herd, track judges and placements, and generate a show entries report for email. --- ## 1. ARBA Data Source **Source URL:** `https://arba.net/show-search/` The page renders a DataTables-powered table client-side from an embedded dataset. The table columns map to the following data fields: | Field | Notes | |---|---| | `sanctionNumber` | e.g. `YOA4567` — unique per show | | `showDateStart` | String, format varies (`06/26/2026` or `6/27/2026`) | | `stateProvinceCountry` | State/province abbreviation; blank for international | | `district` | ARBA district number (1–9+); blank for international | | `city` | City name | | `showInformation` | Club name + show type (e.g. `OPEN ALL BREED SHOW`, `YOUTH SPECIALTY BREED SHOW, HOLLAND LOP`) | | `showAddress` | Full street address or just city/state if no address on file | | `mapsUrl` | Google Maps link (may be absent) | | `showSecretary` | Secretary name | | `email` | Secretary email | | `catalogUrl` | PDF catalog link on `arba.net` (optional, ~20% of shows) | ### Data Fetch Strategy The ARBA page renders via JavaScript (DataTables). Options: **Option A — Server-side proxy scrape (recommended)** - Node.js backend route: `GET /api/shows/arba` - Use `puppeteer` or `playwright` headless browser to load the page, wait for the table to render, then extract the full dataset as JSON. - Cache result in SQLite with a `fetched_at` timestamp. Refresh on first request of the day (ARBA updates Mon–Fri during business hours). - Cache table: `arba_shows_cache` with columns: `id`, `fetched_at`, `data` (JSON blob). **Option B — Parse embedded data** - The DataTables source may be an inline JSON variable in the page source. Inspect and parse with `cheerio` if available — faster than headless browser, but brittle if ARBA changes rendering. Recommendation: start with Option B, fall back to headless if the JSON isn't in source. ### Parsed Show Fields After scraping, normalize to: ```json { "sanctionNumber": "YOA4567", "date": "2026-06-26", "state": "SC", "district": 9, "city": "LAURENS", "clubName": "FLINT FIRE UNITED RABBIT REBELS", "showType": "OPEN ALL BREED SHOW", "showTypeFull": "OPEN ALL BREED SHOW", "specialBreed": null, "address": "186 FAIRGROUNDS RD, LAURENS, SC 29360", "mapsUrl": "https://local.google.com/maps/?q=...", "secretary": "SHELLY KURTZ", "email": "SKURTZSHOWSECRETARY@GMAIL.COM", "catalogUrl": null } ``` Parse `showInformation` to split into `clubName`, `showType` (OPEN/YOUTH ALL BREED or SPECIALTY), and `specialBreed` (the breed name after the last comma in specialty shows). --- ## 2. Shows Browser (Read-Only View) ### Route `/shows` — top-level nav item in MyKindlebox sidebar. ### Filters (above the table) | Filter | Type | Behavior | |---|---|---| | State / Province | Multi-select dropdown | Populated dynamically from cached data. Includes blank option for international. | | District | Multi-select dropdown | Values 1–9 plus blank. | | Date From | Date picker | Defaults to today's date. | | Date To | Date picker | Defaults to empty (no upper bound). | | Show Type | Multi-select | ALL BREED OPEN, ALL BREED YOUTH, SPECIALTY OPEN, SPECIALTY YOUTH, FAIR SHOW | | Keyword | Text input | Filters against club name, city, secretary | All filters are client-side after the initial load. A **Reset Filters** button restores defaults. A **Refresh Data** button (with last-fetched timestamp) triggers a server-side re-scrape. ### Results Table Columns: - Date - State - District - City - Club / Show Name (parsed from showInformation) - Show Type - Specialty Breed (blank if all-breed) - Secretary - Email (mailto link) - Address (links to Maps URL if present) - Catalog (PDF icon link if present) - Action — **"Add to Event"** button Default sort: Date ascending, then State. Pagination: 50 rows per page, client-side. Each row is selectable. Selecting multiple rows enables a bulk **"Add selected to Event"** action in the toolbar. --- ## 3. Data Model Additions ### `show_events` table A user-created event grouping one or more ARBA shows together (e.g. a weekend away with 4 shows across 2 days). ```sql CREATE TABLE show_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, name TEXT NOT NULL, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); ``` ### `show_event_shows` table Junction: which ARBA shows are included in an event. ```sql CREATE TABLE show_event_shows ( id INTEGER PRIMARY KEY AUTOINCREMENT, show_event_id INTEGER NOT NULL, sanction_number TEXT NOT NULL, show_date TEXT NOT NULL, club_name TEXT NOT NULL, show_type TEXT NOT NULL, specialty_breed TEXT, city TEXT, state TEXT, address TEXT, secretary TEXT, secretary_email TEXT, catalog_url TEXT, maps_url TEXT, display_order INTEGER DEFAULT 0, FOREIGN KEY (show_event_id) REFERENCES show_events(id) ON DELETE CASCADE ); ``` Store a snapshot of the ARBA data at time of adding (the ARBA listing may change or disappear). ### `show_event_judges` table Judges assigned per show within an event. ```sql CREATE TABLE show_event_judges ( id INTEGER PRIMARY KEY AUTOINCREMENT, show_event_show_id INTEGER NOT NULL, judge_name TEXT NOT NULL, judge_arba_number TEXT, notes TEXT, FOREIGN KEY (show_event_show_id) REFERENCES show_event_shows(id) ON DELETE CASCADE ); ``` ### `show_event_entries` table Rabbits entered into a specific show within an event. ```sql CREATE TABLE show_event_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, show_event_show_id INTEGER NOT NULL, rabbit_id INTEGER NOT NULL, entry_class TEXT, entry_number TEXT, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (show_event_show_id) REFERENCES show_event_shows(id) ON DELETE CASCADE, FOREIGN KEY (rabbit_id) REFERENCES rabbits(id) ); ``` `entry_class` is a free-text field — user fills in the class the rabbit is competing in (e.g. `Sr Buck`, `6-8 Doe`). ### `show_event_placements` table Post-show placement tracking per entry. ```sql CREATE TABLE show_event_placements ( id INTEGER PRIMARY KEY AUTOINCREMENT, entry_id INTEGER NOT NULL UNIQUE, placement TEXT, legs_earned INTEGER DEFAULT 0, bos BOOLEAN DEFAULT 0, bob BOOLEAN DEFAULT 0, bis BOOLEAN DEFAULT 0, ris BOOLEAN DEFAULT 0, notes TEXT, FOREIGN KEY (entry_id) REFERENCES show_event_entries(id) ON DELETE CASCADE ); ``` `placement` is free text to handle variety: `1st`, `2nd`, `BOB`, `BOSV`, etc. The boolean flags (`bob`, `bos`, `bis`, `ris`) are quick toggles for rollup stats. --- ## 4. Show Event Management ### Route `/shows/events` — list of user's show events. `/shows/events/:id` — event detail / management page. `/shows/events/new` — create new event. ### Event List View - Cards or table showing event name, date range of included shows, number of shows, number of rabbits entered. - Quick access to open/edit/delete. ### Event Detail View Organized in tabs or sections: **Shows Tab** - List of all ARBA shows added to this event, sorted by date. - Each show card shows: date, sanction #, club name, show type, city/state, secretary email, catalog link. - Per show: Judge(s) management (add/edit/remove judges with name and optional ARBA number). - Button to remove a show from the event. - Button to add more shows (opens the browser modal). **Entries Tab** - Per-show accordion or grouped list. - Each show shows the rabbits entered into it. - "Add Rabbit" opens a picker from the user's herd — searchable by name, tattoo, breed, variety. - Each entry row: Rabbit name, tattoo, breed, variety, entry class (editable inline), notes. - Remove entry button. - After the show date, an "Enter Placements" toggle switches the row into placement-entry mode: placement text field, legs +/- counter, BOB/BOS/BIS/RIS checkboxes. **Results Summary Tab** - Aggregate view of all placements across the event. - Rabbits sorted by total legs earned. - Highlight BOB/BOS/BIS/RIS with badge icons. - Read-only — auto-generated from placement data. --- ## 5. Show Entries Report Accessible from the Entries tab as **"Generate Entries Report"**. Output is a plain-text formatted block in a modal with a **"Copy to Clipboard"** button. Suitable for pasting directly into an email to the show secretary. ### Report Format ``` SHOW ENTRIES — [Event Name] Generated: [date] ============================================== [Club Name] — [Show Type] Sanction #: [YOA1234] Date: [MM/DD/YYYY] Secretary: [Name] <[email]> ============================================== RABBITS ENTERED: 1. [Rabbit Name] ([Tattoo]) Breed: [Breed] / [Variety] Class: [Entry Class] Owner: [User's rabbitry name or full name] ARBA #: [user's ARBA membership number if stored] 2. [Rabbit Name] ([Tattoo]) Breed: [Breed] / [Variety] Class: [Entry Class] Owner: [User's rabbitry name or full name] ARBA #: [user's ARBA membership number if stored] [Additional rabbits...] ============================================== [Next Show in Event if applicable...] ============================================== ``` Per-show sections are repeated for each show in the event. If a rabbit is entered in multiple shows within the event, it appears under each show it is entered in. ### Report Settings (optional, stored per user) - Include/exclude owner ARBA number - Include/exclude rabbit registration numbers (if tracked in herd) - Custom header/footer text block --- ## 6. UI / UX Notes ### Adding Shows to an Event Flow 1. User clicks **"Add to Event"** on a row in the shows browser. 2. Modal appears: "Add to which event?" with dropdown of existing events + "Create new event" option. 3. If creating new, prompt for event name and optional notes inline. 4. Confirm — show is added to event. Toast confirmation with link to the event. Alternatively, from the event detail page, a **"Browse Shows"** button opens the full browser in a slide-over panel where selecting shows adds them directly to the event in context. ### Placement Entry UX - Placement entry should feel fast. After the show, the user flips a toggle on each row: "Mark Results." - Tab key should advance through fields (placement → legs → BOB → BOS checkboxes). - Legs can be incremented by clicking + since this is a count field (0–3 per show). ### Linking to Existing Herd Data - Rabbit picker pulls from the `rabbits` table already in MyKindlebox. - Filter by breed and variety to find the right animal quickly. - Show tattoo prominently — this is the primary show identifier. - If a rabbit is already entered in another show within the same event, show a small indicator (useful when the same rabbit runs in multiple shows on the same weekend). --- ## 7. Backend API Endpoints | Method | Route | Description | |---|---|---| | GET | `/api/shows/arba` | Returns cached ARBA show list. Triggers refresh if stale (>24h). | | POST | `/api/shows/arba/refresh` | Force re-scrape. | | GET | `/api/shows/events` | List user's show events. | | POST | `/api/shows/events` | Create new event. | | GET | `/api/shows/events/:id` | Event detail with shows, judges, entries, placements. | | PATCH | `/api/shows/events/:id` | Update event name/notes. | | DELETE | `/api/shows/events/:id` | Delete event. | | POST | `/api/shows/events/:id/shows` | Add an ARBA show to an event. | | DELETE | `/api/shows/events/:id/shows/:showId` | Remove a show from an event. | | POST | `/api/shows/events/:id/shows/:showId/judges` | Add a judge to a show. | | DELETE | `/api/shows/events/:id/shows/:showId/judges/:judgeId` | Remove a judge. | | POST | `/api/shows/events/:id/shows/:showId/entries` | Add a rabbit entry. | | DELETE | `/api/shows/events/:id/shows/:showId/entries/:entryId` | Remove a rabbit entry. | | PUT | `/api/shows/events/:id/shows/:showId/entries/:entryId/placement` | Set or update placement for an entry. | | GET | `/api/shows/events/:id/report` | Returns the formatted entries report as plain text. | --- ## 8. Implementation Phases **Phase 1 — ARBA Browser** - Scraper/cache layer (Node, SQLite). - `/shows` route with filter UI and results table. - No event functionality yet — just browse and view. **Phase 2 — Event Management** - `show_events`, `show_event_shows` tables and all CRUD. - "Add to Event" flow from browser. - Event list and detail views (Shows tab only). **Phase 3 — Entries** - `show_event_entries` table. - Rabbit picker integration. - Entries tab in event detail. - Entries report generation. **Phase 4 — Judges & Placements** - `show_event_judges` and `show_event_placements` tables. - Judge management UI per show. - Placement entry UI per entry. - Results Summary tab. --- ## 9. Resolved Decisions | Topic | Decision | |---|---| | ARBA scrape reliability | No user fallback. If the scraper fails, surface an error state in the UI and log it server-side. The cache remains available until it can be refreshed. | | ARBA membership number | Not required to show rabbits; not collected or included in the entries report. | | Rabbitry name | Already stored as part of farm setup — pull from existing profile for the report header. No new field needed. | | Mobile filter bar | Filters collapse to a drawer on small screens; wide table scrolls horizontally. No special redesign needed. | | Cavy shows | Filter out at the scrape/normalization layer. Any show with `CAVY` in the `showInformation` field is excluded before being written to the cache. | | International shows | Only US shows displayed. Rows with a blank or non-US `stateProvinceCountry` field are excluded at the normalization layer. |