Tournament detail: round carousel + standings pagination - #129
Open
Franc3s-bot wants to merge 186 commits into
Open
Tournament detail: round carousel + standings pagination#129Franc3s-bot wants to merge 186 commits into
Franc3s-bot wants to merge 186 commits into
Conversation
…cespo#46) Fixed the backend import issues with faction char and improved the startup script to avoid opening new windows and handle dynamic ports.
…provements # Conflicts: # backend/analytics/factions.py # backend/analytics/ships.py
…CORS completely for dashboard
…R crash on extreme filters
… with search bar - Add GET /api/tournaments/locations endpoint returning distinct locations - Split location filter into 3 dynamic sub-filters (Continent, Country, City) - Countries update based on selected Continents, Cities based on selected Countries - Add mini search bar to filter location entries - Remove duplicate Unknown entries from continent list
…tructure - Move ShipChassisFilter below Faction on ships, cards, lists, squadrons pages - Remove accordion expand/collapse; make it a flat static section - Ship icons in filter inherit text color (uncolored) - Faction symbols remain colored next to ship names - Respects specificity hierarchy: Sort By > Faction > Ship Chassis
Closes Francespo#37. This PR fixes three critical bugs in the analytics modules: 1. **Tournament filters ignored**: Added centralized location filtering via �pply_tournament_filters() and updated SQL filters. 2. **Lists count 0**: Added lists aggregation to core.py. 3. **Frontend filters ignored**: Merged main, resolved UI conflicts, and fixed a Svelte a11y bug (�11y_consider_explicit_label) that caused a 500 error.
# Conflicts: # backend/analytics/factions.py # backend/analytics/ships.py
…provements # Conflicts: # backend/analytics/ships.py # backend/api/ships.py # frontend/src/routes/lists/+page.svelte # frontend/src/routes/ships/+page.svelte
…ion bugs (Francespo#45) Closes Francespo#42. ## Technical Changes - Replaced SSR data loading in `+page.ts` with reactive client-side `$effect` fetching to resolve route navigation desync and crashes. - Replaced hardcoded 'XWA / Analysis Mode' title with the `<ContentSourceToggle />` component in the header. - Implemented reactive data fetching based on the global `filters.dataSource` state to seamlessly switch between XWA and Legacy data sets without requiring a page reload.
Closes Francespo#38 ## Summary Adds the Ships / Chassis page with filtering capabilities and fixes the hover scaling behavior on ship cards. ## Changes ### Backend - **New** `backend/routers/ships.py`: FastAPI router exposing `GET /api/ships` with optional `faction` and `source` query params. - **Modified** `backend/main.py`: Registers the ships router under `/api` prefix. - **Fixed** `backend/utils/xwing_data/core.py`: Corrected `ROOT_DIR` path (`parents[4]` to `parents[3]`). ### Frontend - **New** `frontend/src/routes/ships/+page.svelte`: Ships page with chassis filter dropdown, faction filter, responsive card grid, and **hover scale on entire card container** (not inner icon). - **Modified** `frontend/src/routes/+layout.svelte`: Added navigation bar with Ships link. - **Modified** `frontend/vite.config.js`: API proxy to FastAPI backend. ## Technical Rationale The hover scaling fix places `transform: scale(1.06)` on the outer `.ship-card` container rather than inner icon wrappers, ensuring the entire card elevates uniformly on hover.
# Conflicts: # frontend/src/routes/squadrons/+page.svelte
Closes Francespo#43. - Alphabetized SortSelector options list. - Changed SVG arrows to use distinct upward/downward pointing icons. - Updated Squadrons page to use SortSelector component. - Used getFactionLabel instead of raw faction key in SquadronRowCard.
Removes remaining garbage files and logs from main.
Closes Francespo#44. Identified 6 missing ship icons in xwing-miniatures.css by comparing with JSON data. Added mappings for ieininterceptor, delta7baethersprite, cr90corelliancorvette, iesebomber, and upsilonclassshuttle. Trident remains without icon as per user feedback. --------- Co-authored-by: Francesco Esposito <95753785+Francespo@users.noreply.github.com>
…rancespo#57) ## Description This PR fixes the accidental creation of a local font directory in the frontend and establishes a single source of truth using a symlink. - Migrated centered xwing-miniatures.css to external_data/xwing-miniatures-font/dist/. - Removed local rontend/static/fonts/xwing-miniatures-font/ directory. - Created a directory junction (symlink) pointing to the source in external_data. - Squashed branch history into a single clean commit. Co-authored-by: Francesco Esposito <95753785+Francespo@users.noreply.github.com>
Closes Francespo#52. \n\n### Changes Made:\n- **Standardized Labeling**: All unknown tournament formats and scenarios now use the label 'Unknown'.\n- **Fixed UNK Badges**: Verified that unknown formats correctly display the 'UNK' badge in the Tournament Browser list.\n- **Sidebar UI Polish**:\n - Restored horizontal dividers between all major filter sections.\n - Fixed double divider issue when Active Filters list is empty.\n - Restored bottom borders for accordion items.\n - Standardized vertical spacing across all sections (Date, Location, Format, Platform, Search, and Sort) by removing redundant margins and padding for a clean, contiguous look. --------- Co-authored-by: Francesco Esposito <95753785+Francespo@users.noreply.github.com>
34 pyright errors fixed via type-ignore comments and one bug fix. No SQL aggregation, list table schema, cache, or prewarm behavior changed. - Add 'from datetime import date' import (was missing) - Add # pyright: ignore[reportIndexIssue,reportOptionalSubscript] for Tournament.location[...] JSONB subscript column expressions (the static type is Location | None but runtime is JSONB) - Add # pyright: ignore[reportAttributeAccessIssue] for row.continent/country/city on Row[Any,Any,Any] - Add # pyright: ignore[reportAttributeAccessIssue,reportOptionalMemberAccess] for Tournament.name.ilike, Tournament.format.in_, Tournament.source.in_ (InstrumentedAttribute hidden by Pydantic field declarations) - Add # pyright: ignore[reportAttributeAccessIssue] for sort_attr.asc/desc and various int|None -> int arg-type sites Bug fix: date_start / date_end query params are now parsed via date.fromisoformat() before comparison with Tournament.date (date). On invalid input the filter is skipped silently rather than raising a 500. The plan explicitly authorized this trade-off.
17 pyright errors fixed via imports, narrowing, and two real bug
fixes. No SQL aggregation, list table schema, cache, or prewarm
behavior changed.
- Add imports: from typing import Any, cast; Source; BaseScraper
- _delete_existing_tournament: change 'if existing:' to
'if existing is not None:', capture eid = existing.id with assert,
wrap column refs in cast(Any, ...) so pyright picks SQLAlchemy's
ColumnOperators overload (returns ColumnElement[bool]) for
delete(...).where(...)
- save_tournament_data: assign m.get('p1_name_temp') / 'p2_name_temp'
to locals and isinstance(..., str)-guard before .strip()
- save_tournament_data: guard team_id_map[...] = ts.id with
'if ts.id is not None:' (always set in practice; pyright doesn't
know)
- main(): add narrowing guard 'if date_from is None or date_to is
None: return 1' so executor.submit(scrape_platform, ...) and the
direct call get non-Optional dates
- build_scrapers / _split_scrapers: tighten return type to
list[tuple[str, BaseScraper]] (was object), so main()'s loop sees
the concrete type and scraPer.list_tournaments() is a valid access
Two real bug fixes (beyond type-only changes):
1. Match(...) constructor was missing tournament_id=tournament.id
in the dict-based path. The object-based path set it
post-construction; the dict path was silently writing NULL FKs.
2. _write_sqlite: PlayerStanding(...) was also missing
tournament_id=tournament.id for the same reason. The local
variable tournament_id must be set on every persisted
PlayerStanding row.
4 real implicit-any errors fixed via JSDoc. No build or runtime
behavior changed. Remaining diagnostics on this file are
environmental (no node_modules / @types/node in this worktree) and
go away when npm install is run in the user's environment.
- logConfig: @param {string} label, @param {unknown} value
- envSummary: @type {Record<string, string | undefined>}
- host: inline @type {string} on the .map((host) => ...) parameter
in resolveAllowedHosts
Initialize `.slim/codemap.json` baseline (78 source files) and add hierarchical `codemap.md` documents: - Root atlas: project responsibility, entry points, directory map, cross-cutting data flow, integration points. - backend/: FastAPI app, engine, SQLModel ORM, retried DB-init. - backend/routers/: thin APIRouter layer (note: bulk of routes now in backend/api/). - backend/api/: 10 versioned routers, Pydantic schemas, transactional detail reads, Ko-fi webhook, list-enrichment seam. - backend/scrapers/: ListFortress/Longshanks/Rollbetter/YASB ingest. - backend/data/: geocoding_cache.json write-through store. - backend/data_structures/: canonical enums and Location value object. - backend/analytics/: SQL-first filtering + Python aggregation. - backend/utils/: geocoding, dedup, XWS canonicalization, YASB URLs. - backend/utils/xwing_data/: cached wrapper over xwing-data2 JSON. - backend/scripts/: operational scripts (scraper, dedup, migrations). - frontend/: SvelteKit 2 + Svelte 5 runes app, stores, API client, server proxy, routes. Register `AGENTS.md` with the standard Repository Map section so OpenCode auto-discovers the codemap on session start.
Resolve pre-existing LSP/static-analysis warnings across 4 files: - backend/api/tournaments.py (34 errors) - backend/scripts/scrape_tournaments.py (17 errors) - backend/api/formatters.py + backend/api/schemas.py (10 errors) - frontend/vite.config.js (3 implicit-any errors) Includes two real bug fixes surfaced during type cleanup: - Match(...) and PlayerStanding(...) constructors in scrape_tournaments.py were missing tournament_id, silently writing NULL FKs in the dict-based save path and the _write_sqlite artifact path.
Replaces PR Francespo#92's partial fix with a from-scratch mobile shell, a centralized filter URL-sync layer, and a stack of targeted perf and responsiveness fixes. The 5 filterable routes no longer maintain their own URL-building logic; the store owns it. Mobile shell (new components): - MobileTopBar: sticky top chrome for <md viewports (hamburger + brand) - MobileNavDrawer: left-edge nav drawer, focus-trapped, ARIA dialog, sourced from Sidebar.NAV_LINKS module export - MobileFilterDrawer: right-edge filter sheet accepting a children + optional footer snippet - MobileFilterTrigger: FAB with active-filter count badge, <lg only - DebouncedTextInput: 250ms-debounced search input Filter system (filters.svelte.ts): - toSearchParams(routeId): per-route whitelist serializer with deterministic key order (round-trip identity for the echo guard) - applyFromSearchParams(params): URL -> store, partial-update only - selectedFactions / sortBy / sortDirection: moved from route-local state into the store - activeChips: (was a getter that re-ran per access) URL sync (lib/sync/urlSync.svelte.ts): - scheduleSync(delayMs?, overlay?): debounced goto() with echo guard, URL-key round-trip identity, replaceState + keepFocus + noScroll - clearPendingSync(): called by onNavigate in +layout.svelte so fast route switches do not corrupt the destination URL Route rewrites (cards, lists, ships, squadrons, tournaments): - Per-field URL-building (20-64 lines each) replaced with 5-10 line thin effect using filters.toSearchParams + scheduleSync - route-local selectedFactions / sortBy / sortDirection removed - echo-guard logic lives in urlSync, not in each route - ~200 lines of duplicated URL plumbing deleted across 5 files Per-route perf + responsive fixes: - ships: 5 inline style attributes per card collapsed to 1 (with CSS custom properties); font-size: 10rem -> clamp(3rem, 18vw, 8rem) - squadron/[signature]: 6-col pilot table collapses to stacked card layout on <sm - ship/[xws]: right-edge gradient fade hints at horizontal scroll on <sm - cards: dynamic import('$app/navigation') -> static goto import in tab handlers - +page.svelte (dashboard): chartAction uses chart.update(newConfig) instead of destroy/recreate; chart.js auto-import hoisted to module - TournamentFilters: $derived(() => fn()) misuse -> $derived.by Misc: - Sidebar: NAV_LINKS exported as the single source of truth for nav entries (consumed by both desktop sidebar and mobile drawer) - FilterPanel: rewritten to desktop-only with a children snippet; routes pass the same snippet to MobileFilterDrawer for mobile - MobileFilterDrawer / MobileNavDrawer: migrated from deprecated $app/stores to $app/state - +layout.svelte: client-only $effect calls filters.applyFromSearchParams on mount, onNavigate cancels pending syncs Touch targets: hamburger, nav links, FAB, sort direction button, content-source toggle, drawer close buttons all >= 44x44. Active filter count visible on the FAB badge and the drawer header. Behavior: empty filters.sortBy / sortDirection produce empty URL params which the backend fills with its own Query() defaults (cards/ships/squadrons/lists: 'Popularity' or 'Games'; tournaments: 'Date'), so first-visit sort order is preserved.
The backend's startup cache prewarm was racing with db-seed's pg_restore. The backend depends_on db-seed used condition: service_started, which fires as soon as the db-seed container starts (before the dump is actually loaded). The prewarm then computed analytics against an empty database and cached empty results, which the cache never invalidated (scrape_meta is missing from the dump, so the version-based invalidation never fires). Add a healthcheck to db-seed that polls the `list` table for a non-zero row count, and change the backend to depend on service_healthy. This ensures the dump is fully restored before the backend (and its prewarm) starts, so the cache is seeded with real data on the first run.
Allows choosing the Vite dev server port on startup: bash scripts/local_dev/up.sh --port 4444 bash scripts/local_dev/up.sh --port=4444 Default is unchanged (3333). Validates the port is a number between 1 and 65535 and rejects unknown arguments. Also adds --help / -h. The four hardcoded 3333 references (ORIGIN, vite --port, healthcheck URL, log-parser fallback) now use $VITE_PORT / $DEFAULT_PORT.
- Add min-w-0 to main and card to prevent flex overflow on mobile - Split layout into mobile (2-row) and desktop (3-column) at sm breakpoint - Mobile: title + format chip + player count inline, date + location below - Desktop: format badge column, info column, player count column - Format label/color hoisted to @const per iteration for clarity - Ensure 44px min tap target, proper truncation, no horizontal scroll
…tion, stats fix, upgrades Round 13–22 frontend improvements: Browser pages (tournaments, squadrons, lists, cards, ships): - Remove DATE label from tournament rows; wrap location in styled pill - Replace boxed 'N x FOUND' pills with faded-text below title (no box) - Remove ActiveChips from main content; active filters only in sidebar - Fix squadron count to show backend total (8663) instead of page capacity - Apply custom Toggle to all checkboxes (squadrons, lists, cards, ships) - Add SortBy component to every list section across all browser pages Filter system: - Create Toggle component (squared, 2px-radius, black bg, white check) - Fix multi-select format bug (isFormatsAtDefault URL round-trip) - Fix urlSync echo loop and stale-URL race condition - Fix overlay-merge loop in urlSync (params.set multi-value bug) - Add Format count badge to TournamentFilters label - Add overflow-x-hidden to FilterPanel, MobileFilterDrawer, layout Ship detail page: - Add FactionIcon component (plain '?' for unknown factions, XWing glyph for known) - Replace 21 callsites with FactionIcon across 14 files - Fix match score colors (winner green pill, loser red, tie grey) - Fix Gauntlet 5-color gradient (start%/end% hard-stop syntax) - Fix ships page filter to honor selectedChips (frontend bug) - Remove all glows from ship detail hero card Sort and layout: - Remove SortSelector from filter panels (replaced by inline SortBy) - Add SortBy to dashboard sections, pilot detail, upgrade detail, etc. - Fix SortBy labels: 'Popularity' → 'Games'/'Lists' - Remove rounded rectangle wrapper from dashboard SortBy - Fix Cards page SortBy to match filter dropdown options - Remove 'Compatibility' option from pilot detail Top Configurations Back navigation: - BackLink uses history.back() with href fallback for fresh tabs - All 6 detail pages benefit automatically (useHistory=true default) Stats and data: - Backend: fix ship_list_filter_clause with mode=all for AND semantics - Backend: add list_id to PlayerStandingData schema - Backend: add win_rate to aggregate_list_stats output - Backend: clamp swiss_wins/losses/rank default from -1 to 0 - Data migration script for clamping negative stats - Fix squadron win rate (was always 0% — use backend win_rate field) Miscellaneous: - Fix multi-faction ship gradient on ships browser page - Fix content-source filter on pilot detail page - Mobile: add Epic toggle to top bar, TournamentFilters to drawer - Desktop sidebar: XWA/LEGACY/Epic compact toggle - Add info tooltip to Tournament Filters label - Fix horizontal scrollbar in filter column - Remove all glows from ship detail hero card
All conflicts resolved in favor of dev (HEAD) which has the superior architecture: normalized list table, SQL GROUP BY queries, filter_helpers, ScrapeMeta model, BaseScraper type hints, coerce_list_json, pool tuning, and the full mobile-first UI rebuild. Upstream changes (minor: duplicate imports, pyright annotations already present in dev) are subsets of what dev already contains. Conflict files: - .devcontainer/ (2): node feature versions, gemini-cli vs sshd - .gitignore: slim/deepwork + playwright paths vs duplicate node_modules - backend/analytics/ (5): SQL GROUP BY vs Python-loop aggregation - backend/api/ (4): normalized list table queries vs legacy Python loops - backend/infra (3): ScrapeMeta model, pool_size, coerce_list_json import - backend/scripts/ (2): list table persistence, BaseScraper types - frontend/+page.svelte: meta-snapshot URL builder (HEAD's params approach)
Co-authored-by: GitHub Copilot <copilot@users.noreply.github.com> Co-authored-by: Frances-bot <263591795+Franc3s-bot@users.noreply.github.com>
58,603 playerstanding rows had swiss_wins=-1, swiss_losses=-1 as scraper sentinels (rollbetter). COALESCE(..., 0) does not help since -1 is not NULL, causing negative game counts and broken winrates. Wrap all COALESCE in SUM() with GREATEST(0, ...) across: - core.py (pilots + upgrades queries) - ships.py - squadrons.py - lists.py - factions.py (Python-side max(0, ...)) Database rows also cleaned to 0.
…tream local-dev scripts
Preview deployments were failing because the SSR API routes used the public backend URL (e.g. https://127.api.dev.m3tacron.com/api) which: 1. Requires DNS wildcard records that may not match Coolify's generated domain pattern (127.dev.api.m3tacron.com vs 127.api.dev.m3tacron.com) 2. Causes HTTP->HTTPS redirect loops when url.protocol is http: behind a reverse proxy (Traefik) Now preview deployments talk directly to the backend container via the internal Docker Compose network (http://backend:8888/api), which is faster, simpler, and works regardless of external DNS configuration.
SvelteKit's url.hostname is derived from the ORIGIN env var, not the actual request Host header. When ORIGIN=https://dev.m3tacron.com is set for preview deployments, url.hostname resolves to dev.m3tacron.com instead of 127.dev.m3tacron.com, causing the host-based preview URL matching to fail. Now we check ENV_VAR_SOURCE=preview or COOLIFY_BRANCH.startsWith('pull/') to detect preview deployments and route API calls to the internal Docker network (http://backend:8888/api) before host matching.
|
The preview deployment for francespo/m3tacron:main is ready. 🟢 Open frontend | Open backend | Open Build Logs | Open Application Logs Last updated at: 2026-08-01 09:24:40 CET |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restructure the tournament detail page to fix excessive page length in tournaments with many participants/matches.
Match rounds → navigable carousel
max-height: 100vh - 120px) with internal scrollStandings → paginated
No backend changes needed — all data is already loaded; only the rendering was restructured.