Merge remote-tracking branch 'origin/codex/task-138-final-scoreboard-polish'

This commit is contained in:
devRaGonSa
2026-05-20 12:22:16 +02:00
31 changed files with 858 additions and 67 deletions

View File

@@ -0,0 +1,165 @@
---
id: TASK-135
title: Fix RCON match detail faction assets and icon display
status: done
type: frontend
team: Frontend Senior
supporting_teams:
- Experto en interfaz
roadmap_item: rcon-full-data
priority: high
---
# TASK-135 - Fix RCON Match Detail Faction Assets And Icon Display
## Goal
Polish faction presentation in the historical RCON match detail scoreboard so faction icons load reliably, are visibly larger, and no longer show long descriptive faction subtitles.
## Context
The historical match detail page already renders a RCON/materialized match detail view with a scoreboard-like result block, player table, local faction image paths, and local map/faction assets being added by the user.
The current direction is acceptable, especially the central result with score, map, Warfare and winner. Remaining rough details are visual and asset-related:
- German/Eje icon is not visible, likely due to wrong asset path or name.
- Faction icons are too small.
- Faction subtitles such as `Fuerzas estadounidenses` and `Ejército alemán` should be removed.
- Event timeline must remain hidden.
- Confidence/source/base must remain hidden.
- No `snapshot` wording.
- No Elo/MVP blocks.
- No Comunidad Hispana #03.
## Files to Read First
- `AGENTS.md`
- `ai/architecture-index.md`
- `ai/repo-context.md`
- `ai/orchestrator/frontend-senior.md`
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
## Expected Files to Modify
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
- this task file when moving it from `ai/tasks/pending` to `ai/tasks/done`
If additional files are necessary, document why in the task outcome.
## Implementation Requirements
1. Work from a dedicated branch: `codex/task-135-faction-assets`.
2. Verify faction assets exist in `frontend/assets/img/factions`.
3. Use `.webp` paths, not `.svg`, for faction icons:
- `frontend/assets/img/factions/us.webp`
- `frontend/assets/img/factions/germany.webp`
- `frontend/assets/img/factions/soviets.webp`
- `frontend/assets/img/factions/britain.webp`
4. Fix the German/Eje icon not displaying.
5. Add graceful fallback if any faction image is missing.
6. Increase faction icon size in the scoreboard layout.
7. Remove long faction subtitle text such as:
- `Fuerzas estadounidenses`
- `Ejército alemán`
- `Fuerzas británicas`
- `Ejército soviético`
8. Keep only main side/faction labels:
- `Aliados`
- `Eje`
- `Soviéticos`, `Británicos` or `USA` only if needed
9. Do not show long descriptive subtitles under faction labels.
10. Keep the winner marker if it remains visually subtle.
11. Preserve the player table.
12. Keep the event timeline hidden.
13. Keep confidence/source/base hidden.
14. Do not add implementation/debug text to the UI.
## Constraints
- Keep the change small and focused.
- Preserve the dark HLL Vietnam visual identity.
- Do not introduce frameworks or dependencies.
- Do not modify backend behavior unless a minimal fallback contract issue is discovered and documented.
- Do not modify unrelated files.
- Do not reintroduce event timeline, confidence/source/base cards, `snapshot` wording, Elo/MVP blocks or Comunidad Hispana #03.
## Validation Commands
Run the relevant checks before marking this task done:
- `node --check frontend/assets/js/historico-partida.js`
- `node --check frontend/assets/js/historico.js`
- `node --check frontend/assets/js/historico-recent-live.js`
- `python -m compileall backend/app`
- `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- `docker compose up -d --build backend frontend`
- `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
## Manual Verification
Open:
- `http://localhost:8080/historico.html`
- `http://localhost:8080/historico-partida.html?server=comunidad-hispana-02&match=comunidad-hispana-02%3A1779178461%3A1779183861%3Acarentanwarfare`
Then hard refresh with `Ctrl+F5` and verify:
- Score remains `3 : 2`.
- Winner remains `Aliados`.
- Map remains `Carentan`.
- Duration remains `1 h 30 min`.
- German/Eje icon is visible.
- Faction icons are larger than before.
- Long faction descriptions are gone.
- AntonioPruna still shows 1 kill, 0 deaths, `M1 GARAND`.
- Victim row still shows `death_by` AntonioPruna.
- No timeline/events section is visible.
- No confidence/source/base cards are visible.
- No `snapshot`, Elo/MVP block or Comunidad Hispana #03 appears.
## Git Requirements
- Use branch `codex/task-135-faction-assets`.
- Move only this task from `ai/tasks/pending` to `ai/tasks/done` when complete.
- Add `Outcome` and `Validation Result` sections before completing the task.
- Stage only intended files.
- Commit and push the branch.
- Final git status must be clean.
## Outcome
- Updated `frontend/assets/js/historico-partida.js` to use the required local `.webp` faction paths for USA, Germany, Soviets and Britain.
- Renamed the user-provided faction assets in `frontend/assets/img/factions/` from short names to the required contract names: `germany.webp`, `soviets.webp` and `britain.webp`.
- Removed visible long faction subtitles from the scoreboard side blocks while preserving the main `Aliados` and `Eje` labels and the subtle winner marker.
- Increased faction emblem sizing in `frontend/assets/css/historico-scoreboard-detail.css` and added a silent missing-image fallback class so a broken image does not show a visible broken icon.
- Event timeline, confidence/source/base cards, snapshot wording, Elo/MVP blocks and Comunidad Hispana #03 were not reintroduced.
## Validation Result
- PASS: `node --check frontend/assets/js/historico-partida.js`
- PASS: `node --check frontend/assets/js/historico.js`
- PASS: `node --check frontend/assets/js/historico-recent-live.js`
- PASS: `python -m compileall backend/app`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- Note: the script completed successfully but emitted existing `ResourceWarning` messages from backend unittest sqlite connections.
- PASS: `docker compose up -d --build backend frontend`
- PASS: `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- PASS after stopping already-running advanced historical workers: `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
- The first attempt failed because the backend could not set SQLite journal mode while the historical worker services were active against the mounted DB. `docker compose stop historical-runner rcon-historical-worker` cleared the validation conflict.
- Manual/rendered verification: Browser plugin was listed, but the required Node runtime tool was not exposed in this session; used local Chrome headless fallback.
- Verified DOM contains `3 : 2`, `Ganador: Aliados`, `Carentan`, `1 h 30 min`, `AntonioPruna`, `M1 GARAND`, `us.webp` and `germany.webp`.
- Verified long faction descriptions are absent from rendered DOM.
- Verified timeline section remains hidden.
- Captured desktop and mobile screenshots outside the repository at `C:\Temp\task-135-match-detail.png` and `C:\Temp\task-135-match-detail-mobile.png`.
## Change Budget
- Prefer fewer than 5 modified files.
- Prefer changes under 200 lines when feasible.
- Split follow-up work into separate tasks if scope grows.

View File

@@ -0,0 +1,168 @@
---
id: TASK-136
title: Add RCON match detail map hero image
status: done
type: frontend
team: Frontend Senior
supporting_teams:
- Experto en interfaz
- Disenador grafico
roadmap_item: rcon-full-data
priority: high
---
# TASK-136 - Add RCON Match Detail Map Hero Image
## Goal
Add local map image support to the historical RCON match detail page and show the current match map image prominently in the top hero/header area.
## Context
The historical match detail page currently shows the central result data well enough, but the top hero/header area should include the match map image, preferably on the right side. The community logo can stay where it is or be reduced if needed, but the map image should become a clear first-viewport visual element.
For the known Carentan match, the UI must map the displayed map to:
- `frontend/assets/img/maps/carentan-day.webp`
The implementation should use local map assets under `frontend/assets/img/maps` and hide the image area gracefully when no asset is available.
## Files to Read First
- `AGENTS.md`
- `ai/architecture-index.md`
- `ai/repo-context.md`
- `ai/orchestrator/frontend-senior.md`
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
## Expected Files to Modify
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
- this task file when moving it from `ai/tasks/pending` to `ai/tasks/done`
If additional files are necessary, document why in the task outcome.
## Implementation Requirements
1. Work from a dedicated branch: `codex/task-136-map-hero-image`.
2. Add map image support on `historico-partida.html` / `historico-partida.js`.
3. Display the current map image in the top hero/header area, preferably on the right side.
4. Keep the community logo where it is or reduce it if needed, while adding a prominent map image to the right.
5. Use map assets from `frontend/assets/img/maps`.
6. Add a robust JavaScript mapper from map name / `pretty_name` to asset path.
7. For the known Carentan match, map to `frontend/assets/img/maps/carentan-day.webp`.
8. Account for likely available asset names:
- `carentan-day.webp`
- `stmereeglise-day.webp`
- `stmariedumont-day.webp`
- `utahbeach-day.webp`
- `omahabeach-day.webp`
- `purpleheartlane-rain.webp`
- `hurtgenforest-day.webp`
- `foy-day.webp`
- `kursk-day.webp`
- `kharkov-day.webp`
- `driel-day.webp`
- `elalamein-day.webp`
- `tobruk-day.webp`
- `tobruk-dawn.webp`
- `mortain-day.webp`
- `hill400-day.webp`
- `elsenbornridge-day.webp`
- `smolensk-day.webp`
9. If no map image is found, hide the image area gracefully.
10. Keep the layout responsive.
11. Do not show implementation/debug text in the UI.
12. Preserve the player table.
13. Keep event timeline hidden.
14. Keep confidence/source/base hidden.
## Constraints
- Keep the change small and focused.
- Preserve the dark HLL Vietnam visual identity.
- Do not introduce frameworks or dependencies.
- Do not modify backend unless a minimal API field issue is strictly necessary and documented.
- Do not modify unrelated files.
- Do not show `snapshot` wording, Elo/MVP blocks or Comunidad Hispana #03.
## Validation Commands
Run the relevant checks before marking this task done:
- `node --check frontend/assets/js/historico-partida.js`
- `node --check frontend/assets/js/historico.js`
- `node --check frontend/assets/js/historico-recent-live.js`
- `python -m compileall backend/app`
- `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- `docker compose up -d --build backend frontend`
- `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
## Manual Verification
Open:
- `http://localhost:8080/historico.html`
- `http://localhost:8080/historico-partida.html?server=comunidad-hispana-02&match=comunidad-hispana-02%3A1779178461%3A1779183861%3Acarentanwarfare`
Then hard refresh with `Ctrl+F5` and verify:
- Score remains `3 : 2`.
- Winner remains `Aliados`.
- Map remains `Carentan`.
- Duration remains `1 h 30 min`.
- Map image appears in the hero/right area.
- For the known Carentan match, the image uses `frontend/assets/img/maps/carentan-day.webp`.
- Layout remains responsive on desktop and mobile.
- AntonioPruna still shows 1 kill, 0 deaths, `M1 GARAND`.
- Victim row still shows `death_by` AntonioPruna.
- No implementation/debug text appears.
- No timeline/events section is visible.
- No confidence/source/base cards are visible.
- No `snapshot`, Elo/MVP block or Comunidad Hispana #03 appears.
## Git Requirements
- Use branch `codex/task-136-map-hero-image`.
- Move only this task from `ai/tasks/pending` to `ai/tasks/done` when complete.
- Add `Outcome` and `Validation Result` sections before completing the task.
- Stage only intended files.
- Commit and push the branch.
- Final git status must be clean.
## Outcome
- Added a hidden map-media slot to `frontend/historico-partida.html` and reveal it only when the current match resolves to a known local map asset.
- Added `resolveMapImagePath` in `frontend/assets/js/historico-partida.js`, mapping the available local map names including the known Carentan match to `./assets/img/maps/carentan-day.webp`.
- Added the map assets under `frontend/assets/img/maps/` because the task explicitly depends on local map image files.
- Updated the historical hero CSS so the logo, match copy and map image form a responsive desktop header and collapse cleanly on mobile.
- Shortened the RCON hero subtitle to keep the narrow mobile header from clipping while preserving the player table, hidden timeline, hidden confidence/source/base details and existing scoreboard data.
## Validation Result
- PASS: `node --check frontend/assets/js/historico-partida.js`
- PASS: `node --check frontend/assets/js/historico.js`
- PASS: `node --check frontend/assets/js/historico-recent-live.js`
- PASS: `python -m compileall backend/app`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- Note: the script completed successfully but emitted existing `ResourceWarning` messages from backend unittest sqlite connections.
- PASS: `docker compose up -d --build backend frontend`
- PASS: `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- PASS: `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
- Manual/rendered verification: Browser plugin was listed, but the required Node runtime tool was not exposed in this session; used local Chrome headless fallback.
- Verified rendered DOM contains `./assets/img/maps/carentan-day.webp`, `3 : 2`, `Ganador: Aliados`, `Carentan`, `1 h 30 min`, `AntonioPruna` and `M1 GARAND`.
- Verified timeline section remains hidden and no `snapshot`, Elo/MVP block or Comunidad Hispana #03 appeared in the rendered checks.
- Captured screenshots outside the repository at `C:\Temp\task-136-match-detail-final.png` and `C:\Temp\task-136-match-detail-mobile-final-3.png`.
## Change Budget
- Prefer fewer than 5 modified files.
- Prefer changes under 200 lines when feasible.
- Split follow-up work into separate tasks if scope grows.

View File

@@ -0,0 +1,165 @@
---
id: TASK-137
title: Add public scoreboard link to match detail
status: done
type: frontend
team: Frontend Senior
supporting_teams:
- Backend Senior
- Arquitecto Python
roadmap_item: rcon-full-data
priority: high
---
# TASK-137 - Add Public Scoreboard Link To Match Detail
## Goal
Show a safe public scoreboard action on the historical match detail page when the current match has a valid public `match_url`.
## Context
The historical detail page should let users open the public scoreboard match page for the same match when a safe link is available. The button should not appear for broken, missing or unsafe URLs.
Known constraints:
- Use the existing backend `match_url` field when available.
- If a materialized RCON detail has no `match_url`, use existing scoreboard correlation/link resolver if already present.
- If correlation requires backend changes, keep them minimal and covered by tests.
- Keep allowlist safety for scoreboard URLs:
- `https://scoreboard.comunidadhll.es/`
- `https://scoreboard.comunidadhll.es:5443/`
- Recent match cards must keep their current behavior.
- Detail page must still work without the external link.
## Files to Read First
- `AGENTS.md`
- `ai/architecture-index.md`
- `ai/repo-context.md`
- `ai/orchestrator/frontend-senior.md`
- `ai/orchestrator/backend-senior.md`
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- backend historical/read-model modules that expose match detail and `match_url`
## Expected Files to Modify
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
- minimal backend historical/read-model or tests only if required for existing `match_url` correlation
- this task file when moving it from `ai/tasks/pending` to `ai/tasks/done`
If additional files are necessary, document why in the task outcome.
## Implementation Requirements
1. Work from a dedicated branch: `codex/task-137-scoreboard-link`.
2. Show a visible button/link in the match detail page with one of these labels:
- `Ver en Scoreboard`
- `Ver detalles completos`
3. The button must open the public scoreboard match page in a new tab.
4. Only show the button when a safe `match_url` exists.
5. Do not show broken links.
6. Use the existing backend `match_url` field when available.
7. If materialized RCON detail has no `match_url`, use existing scoreboard correlation/link resolver if already present.
8. If correlation requires backend changes, keep them minimal and covered by tests.
9. Preserve allowlist safety for scoreboard URLs:
- `https://scoreboard.comunidadhll.es/`
- `https://scoreboard.comunidadhll.es:5443/`
10. Recent match cards must keep their current behavior.
11. Detail page must still work without the external link.
12. Preserve the player table.
13. Keep event timeline hidden.
14. Keep confidence/source/base hidden.
15. Do not show implementation/debug text in the UI.
## Constraints
- Keep the change minimal and focused.
- Preserve the dark HLL Vietnam visual identity.
- Do not introduce frameworks or dependencies.
- Do not create a broad backend refactor.
- Do not modify unrelated files.
- Do not reintroduce event timeline, confidence/source/base cards, `snapshot` wording, Elo/MVP blocks or Comunidad Hispana #03.
## Validation Commands
Run the relevant checks before marking this task done:
- `node --check frontend/assets/js/historico-partida.js`
- `node --check frontend/assets/js/historico.js`
- `node --check frontend/assets/js/historico-recent-live.js`
- `python -m compileall backend/app`
- `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- `docker compose up -d --build backend frontend`
- `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
## Manual Verification
Open:
- `http://localhost:8080/historico.html`
- `http://localhost:8080/historico-partida.html?server=comunidad-hispana-02&match=comunidad-hispana-02%3A1779178461%3A1779183861%3Acarentanwarfare`
Then hard refresh with `Ctrl+F5` and verify:
- Score remains `3 : 2`.
- Winner remains `Aliados`.
- Map remains `Carentan`.
- Duration remains `1 h 30 min`.
- `Ver en Scoreboard` or equivalent only appears when a valid `match_url` exists.
- The scoreboard action opens a safe public scoreboard URL in a new tab.
- No broken or unsafe link is shown when `match_url` is absent or invalid.
- Recent match cards keep their current behavior.
- AntonioPruna still shows 1 kill, 0 deaths, `M1 GARAND`.
- Victim row still shows `death_by` AntonioPruna.
- No timeline/events section is visible.
- No confidence/source/base cards are visible.
- No `snapshot`, Elo/MVP block or Comunidad Hispana #03 appears.
## Git Requirements
- Use branch `codex/task-137-scoreboard-link`.
- Move only this task from `ai/tasks/pending` to `ai/tasks/done` when complete.
- Add `Outcome` and `Validation Result` sections before completing the task.
- Stage only intended files.
- Commit and push the branch.
- Final git status must be clean.
## Outcome
- Updated the historical match detail action label to `Ver en Scoreboard`.
- Tightened the detail-page external action safety check to only show links from trusted public scoreboard origins:
- `https://scoreboard.comunidadhll.es`
- `https://scoreboard.comunidadhll.es:5443`
- Restricted accepted scoreboard paths to `/games` and `/games/...`.
- Left recent match card behavior unchanged.
- Updated the historical UI regression script to expect the new detail-page action label.
- No backend changes were needed because the existing RCON read model already exposes `match_url` when safe correlation exists.
## Validation Result
- PASS: `node --check frontend/assets/js/historico-partida.js`
- PASS: `node --check frontend/assets/js/historico.js`
- PASS: `node --check frontend/assets/js/historico-recent-live.js`
- PASS: `python -m compileall backend/app`
- PASS after updating the expected label: `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- Note: the script completed successfully but emitted existing `ResourceWarning` messages from backend unittest sqlite connections.
- PASS: `docker compose up -d --build backend frontend`
- PASS: `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- PASS: `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
- Additional endpoint check: the known Carentan match detail returns `match_url: null`, so no external action should render for that match.
- Manual/rendered verification: Browser plugin was listed, but the required Node runtime tool was not exposed in this session; used local Chrome headless fallback.
- Verified rendered DOM for the known Carentan match does not include `Ver en Scoreboard` or `historical-match-card__link` when `match_url` is absent.
- Verified rendered DOM still contains `3 : 2`, `Ganador: Aliados`, `Carentan`, `1 h 30 min`, `AntonioPruna` and `M1 GARAND`.
## Change Budget
- Prefer fewer than 5 modified files.
- Prefer changes under 200 lines when feasible.
- Split follow-up work into separate tasks if scope grows.

View File

@@ -0,0 +1,181 @@
---
id: TASK-138
title: Polish RCON match detail final scoreboard UI
status: done
type: frontend
team: Experto en interfaz
supporting_teams:
- Frontend Senior
- Disenador grafico
roadmap_item: rcon-full-data
priority: high
---
# TASK-138 - Polish RCON Match Detail Final Scoreboard UI
## Goal
Perform a final focused visual polish pass on the historical RCON match detail page so it more closely resembles the public scoreboard style while preserving the HLL Vietnam dark tactical theme.
## Context
The match detail page already renders the important RCON/materialized match data. The central result direction is good, but the page still needs a final scoreboard-style polish and removal of rough labels.
The user likes the central result:
- score
- map
- Warfare
- winner
The page should avoid implementation/source noise and should not expose event timeline, confidence/source/base, snapshot wording, Elo/MVP blocks or Comunidad Hispana #03.
## Files to Read First
- `AGENTS.md`
- `ai/architecture-index.md`
- `ai/repo-context.md`
- `ai/orchestrator/frontend-senior.md`
- `ai/orchestrator/ui-expert.md`
- `ai/orchestrator/graphic-designer.md`
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
## Expected Files to Modify
- `frontend/historico-partida.html`
- `frontend/assets/js/historico-partida.js`
- relevant CSS used by `historico-partida.html`
- this task file when moving it from `ai/tasks/pending` to `ai/tasks/done`
If additional files are necessary, document why in the task outcome.
## Implementation Requirements
1. Work from a dedicated branch: `codex/task-138-final-scoreboard-polish`.
2. Make the match detail page resemble the public scoreboard style more closely:
- large central score
- side labels left/right
- bigger faction icons
- map/mode/winner around the central score
- compact metadata below
3. Remove `Partida RCON materializada` from the hero subtitle.
4. Replace the hero subtitle with only the server name or server + match date if available.
5. Keep `Datos disponibles` only if it still makes sense.
6. If `Datos disponibles` no longer fits the polished direction, use a more scoreboard-like heading.
7. Ensure these are not visible:
- `Confianza`
- `Fuente`
- `Base`
- `Eventos`
- `Línea de tiempo`
- `MESSAGE`
- `TEAM SWITCH`
- `CONNECTED`
- `KILL`
- `MATCH START`
- `MATCH END`
- `snapshot`
- Elo/MVP blocks
- Comunidad Hispana #03
8. Preserve the player table.
9. Preserve known match values for the Carentan detail page:
- Map: `Carentan`
- Score: `3 : 2`
- Winner: `Aliados`
- Duration: `1 h 30 min`
- AntonioPruna: 1 kill, 0 deaths, `M1 GARAND`
- victim row: `death_by` AntonioPruna
10. Keep visual consistency with the dark HLL Vietnam theme.
11. Keep the layout responsive.
12. Do not show implementation/debug text in the UI.
## Constraints
- Keep the change small and focused.
- Preserve the dark HLL Vietnam visual identity.
- Do not introduce frameworks or dependencies.
- Do not modify backend unless a tiny presentation contract issue is strictly necessary and documented.
- Do not modify unrelated files.
- Do not add Elo/MVP blocks.
- Do not add Comunidad Hispana #03.
- Do not reintroduce event timeline, confidence/source/base cards or `snapshot` wording.
## Validation Commands
Run the relevant checks before marking this task done:
- `node --check frontend/assets/js/historico-partida.js`
- `node --check frontend/assets/js/historico.js`
- `node --check frontend/assets/js/historico-recent-live.js`
- `python -m compileall backend/app`
- `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- `docker compose up -d --build backend frontend`
- `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
## Manual Verification
Open:
- `http://localhost:8080/historico.html`
- `http://localhost:8080/historico-partida.html?server=comunidad-hispana-02&match=comunidad-hispana-02%3A1779178461%3A1779183861%3Acarentanwarfare`
Then hard refresh with `Ctrl+F5` and verify:
- Score remains `3 : 2`.
- Winner remains `Aliados`.
- Map remains `Carentan`.
- Duration remains `1 h 30 min`.
- Hero does not show `Partida RCON materializada`.
- The scoreboard area has a large central score, left/right sides, larger faction icons, and compact metadata.
- AntonioPruna still shows 1 kill, 0 deaths, `M1 GARAND`.
- Victim row still shows `death_by` AntonioPruna.
- No timeline/events section is visible.
- No confidence/source/base cards are visible.
- No event labels such as `MESSAGE`, `TEAM SWITCH`, `CONNECTED`, `KILL`, `MATCH START` or `MATCH END` appear.
- No `snapshot`, Elo/MVP block or Comunidad Hispana #03 appears.
- Player table is preserved and readable.
- Layout remains responsive on desktop and mobile.
## Git Requirements
- Use branch `codex/task-138-final-scoreboard-polish`.
- Move only this task from `ai/tasks/pending` to `ai/tasks/done` when complete.
- Add `Outcome` and `Validation Result` sections before completing the task.
- Stage only intended files.
- Commit and push the branch.
- Final git status must be clean.
## Outcome
- Replaced the hero subtitle with only the server name, removing RCON implementation wording from the visible hero.
- Renamed the main detail heading from `Datos disponibles` to `Marcador final`.
- Increased the scoreboard score column and faction emblem sizing while preserving the left/right side layout, central score, map, mode, winner and compact metadata.
- Removed the unused hidden timeline markup from `frontend/historico-partida.html` so event labels such as `Eventos` and `Linea de tiempo` are no longer present in the match detail DOM.
- Preserved the player table, map hero, safe scoreboard action behavior and existing HLL Vietnam dark tactical styling.
## Validation Result
- PASS: `node --check frontend/assets/js/historico-partida.js`
- PASS: `node --check frontend/assets/js/historico.js`
- PASS: `node --check frontend/assets/js/historico-recent-live.js`
- PASS: `python -m compileall backend/app`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- PASS: `powershell -ExecutionPolicy Bypass -File scripts/run-rcon-data-pipeline-tests.ps1`
- Note: the script completed successfully but emitted existing `ResourceWarning` messages from backend unittest sqlite connections.
- PASS: `docker compose up -d --build backend frontend`
- PASS: `Invoke-WebRequest "http://localhost:8000/health" | Select-Object -ExpandProperty Content`
- PASS: `Invoke-WebRequest "http://localhost:8000/api/historical/recent-matches?server=all-servers&limit=10" | Select-Object -ExpandProperty Content`
- Manual/rendered verification: Browser plugin was listed, but the required Node runtime tool was not exposed in this session; used local Chrome headless fallback.
- Verified rendered DOM contains `Marcador final`, `3 : 2`, `Ganador: Aliados`, `Carentan`, `1 h 30 min`, `AntonioPruna` and `M1 GARAND`.
- Verified rendered DOM does not include `Partida RCON materializada`, `Confianza`, `Fuente`, `Base`, `Eventos`, `Linea de tiempo`, event labels, `snapshot`, Elo/MVP blocks or Comunidad Hispana #03.
- Captured screenshots outside the repository at `C:\Temp\task-138-match-detail-final.png`, `C:\Temp\task-138-match-detail-mobile-final.png` and `C:\Temp\task-138-match-detail-mobile-score-final.png`.
## Change Budget
- Prefer fewer than 5 modified files.
- Prefer changes under 200 lines when feasible.
- Split follow-up work into separate tasks if scope grows.

View File

@@ -18,10 +18,10 @@
.historical-scoreboard-layout__main { .historical-scoreboard-layout__main {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) minmax(220px, 320px) minmax(0, 1fr); grid-template-columns: minmax(0, 1fr) minmax(240px, 340px) minmax(0, 1fr);
align-items: center; align-items: center;
gap: 22px; gap: 22px;
padding: 24px 28px; padding: 28px 30px;
border: 1px solid rgba(210, 182, 118, 0.2); border: 1px solid rgba(210, 182, 118, 0.2);
border-radius: 24px; border-radius: 24px;
background: background:
@@ -32,14 +32,14 @@
.historical-scoreboard-side { .historical-scoreboard-side {
display: grid; display: grid;
grid-template-columns: 76px minmax(0, 1fr); grid-template-columns: 124px minmax(0, 1fr);
align-items: center; align-items: center;
gap: 18px; gap: 18px;
min-height: 116px; min-height: 148px;
} }
.historical-scoreboard-side--axis { .historical-scoreboard-side--axis {
grid-template-columns: minmax(0, 1fr) 76px; grid-template-columns: minmax(0, 1fr) 124px;
text-align: right; text-align: right;
} }
@@ -51,12 +51,17 @@
order: 1; order: 1;
} }
.historical-scoreboard-side.is-emblem-missing,
.historical-scoreboard-side--axis.is-emblem-missing {
grid-template-columns: minmax(0, 1fr);
}
.historical-scoreboard-side__emblem { .historical-scoreboard-side__emblem {
width: 76px; width: 124px;
height: 76px; height: 124px;
border-radius: 999px; border-radius: 999px;
object-fit: contain; object-fit: contain;
padding: 8px; padding: 10px;
border: 1px solid rgba(210, 182, 118, 0.26); border: 1px solid rgba(210, 182, 118, 0.26);
background: background:
radial-gradient(circle at center, rgba(210, 182, 118, 0.15), transparent 60%), radial-gradient(circle at center, rgba(210, 182, 118, 0.15), transparent 60%),
@@ -75,12 +80,6 @@
text-transform: uppercase; text-transform: uppercase;
} }
.historical-scoreboard-side__text span {
color: var(--text-soft);
font-size: 0.86rem;
letter-spacing: 0.04em;
}
.historical-scoreboard-side__text em { .historical-scoreboard-side__text em {
color: var(--accent-strong); color: var(--accent-strong);
font-style: normal; font-style: normal;
@@ -111,7 +110,7 @@
.historical-scoreboard-center__score { .historical-scoreboard-center__score {
color: var(--text); color: var(--text);
font-size: clamp(4rem, 8vw, 7rem); font-size: clamp(4.6rem, 8.6vw, 7.6rem);
line-height: 0.9; line-height: 0.9;
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
@@ -176,7 +175,7 @@
.historical-scoreboard-side, .historical-scoreboard-side,
.historical-scoreboard-side--axis { .historical-scoreboard-side--axis {
grid-template-columns: 70px minmax(0, 1fr); grid-template-columns: 96px minmax(0, 1fr);
text-align: left; text-align: left;
} }
@@ -184,4 +183,9 @@
.historical-scoreboard-side--axis .historical-scoreboard-side__text { .historical-scoreboard-side--axis .historical-scoreboard-side__text {
order: initial; order: initial;
} }
.historical-scoreboard-side__emblem {
width: 96px;
height: 96px;
}
} }

View File

@@ -22,15 +22,20 @@
.historical-hero__copy { .historical-hero__copy {
display: grid; display: grid;
gap: 24px; gap: 24px;
min-width: 0;
} }
.historical-hero__layout { .historical-hero__layout {
display: grid; display: grid;
grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); grid-template-columns: minmax(190px, 260px) minmax(0, 0.82fr) minmax(320px, 0.9fr);
align-items: center; align-items: center;
gap: 32px; gap: 32px;
} }
.historical-hero__layout > * {
min-width: 0;
}
.historical-logo-frame { .historical-logo-frame {
width: min(300px, 100%); width: min(300px, 100%);
min-height: 220px; min-height: 220px;
@@ -42,6 +47,45 @@
.historical-hero__text { .historical-hero__text {
max-width: 60ch; max-width: 60ch;
overflow-wrap: anywhere;
}
.historical-map-hero {
position: relative;
overflow: hidden;
min-height: 220px;
margin: 0;
border: 1px solid rgba(210, 182, 118, 0.2);
border-radius: 18px;
background:
linear-gradient(180deg, rgba(19, 24, 16, 0.9), rgba(10, 13, 9, 0.72));
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.04),
0 18px 40px rgba(0, 0, 0, 0.24);
}
.historical-map-hero[hidden] {
display: none;
}
.historical-map-hero::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
border: 1px solid rgba(210, 182, 118, 0.12);
border-radius: inherit;
background:
linear-gradient(90deg, rgba(7, 9, 7, 0.24), transparent 34%),
linear-gradient(180deg, transparent 54%, rgba(7, 9, 7, 0.44));
pointer-events: none;
}
.historical-map-hero__image {
width: 100%;
height: 100%;
min-height: 220px;
object-fit: cover;
} }
.historical-content { .historical-content {
@@ -657,7 +701,24 @@
} }
.historical-hero__layout { .historical-hero__layout {
display: flex; display: grid;
grid-template-columns: 1fr;
}
.historical-hero__copy,
.historical-hero__copy > div,
.historical-hero__text {
width: 100%;
max-width: 100%;
}
.historical-map-hero {
width: 100%;
min-height: 190px;
}
.historical-map-hero__image {
min-height: 190px;
} }
.historical-selector { .historical-selector {

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -20,6 +20,8 @@ document.addEventListener("DOMContentLoaded", () => {
timelineNote: document.getElementById("match-detail-timeline-note"), timelineNote: document.getElementById("match-detail-timeline-note"),
timelineState: document.getElementById("match-detail-timeline-state"), timelineState: document.getElementById("match-detail-timeline-state"),
timelineGrid: document.getElementById("match-detail-timeline-grid"), timelineGrid: document.getElementById("match-detail-timeline-grid"),
mapHero: document.getElementById("match-detail-map-hero"),
mapImage: document.getElementById("match-detail-map-image"),
}; };
if (!serverSlug || !matchId) { if (!serverSlug || !matchId) {
@@ -64,8 +66,9 @@ function renderMatchDetail(item, nodes) {
const mapName = item.map?.pretty_name || item.map?.name || "Mapa no disponible"; const mapName = item.map?.pretty_name || item.map?.name || "Mapa no disponible";
const serverName = item.server?.name || item.server?.slug || "Servidor no disponible"; const serverName = item.server?.name || item.server?.slug || "Servidor no disponible";
nodes.title.textContent = mapName; nodes.title.textContent = mapName;
nodes.summary.textContent = `${serverName} - ${formatDetailSubtitle(item)}`; nodes.summary.textContent = serverName;
nodes.note.textContent = ""; nodes.note.textContent = "";
renderMapHero(item, mapName, nodes);
nodes.grid.innerHTML = renderScoreboardDetail(item, { mapName, serverName }); nodes.grid.innerHTML = renderScoreboardDetail(item, { mapName, serverName });
renderPlayerSection(item, nodes); renderPlayerSection(item, nodes);
hideTimelineSection(nodes); hideTimelineSection(nodes);
@@ -74,6 +77,28 @@ function renderMatchDetail(item, nodes) {
nodes.grid.hidden = false; nodes.grid.hidden = false;
} }
function renderMapHero(item, mapName, nodes) {
if (!nodes.mapHero || !nodes.mapImage) {
return;
}
const mapImagePath = resolveMapImagePath(item, mapName);
if (!mapImagePath) {
nodes.mapImage.removeAttribute("src");
nodes.mapImage.alt = "";
nodes.mapHero.hidden = true;
return;
}
nodes.mapImage.src = mapImagePath;
nodes.mapImage.alt = mapName;
nodes.mapImage.onerror = () => {
nodes.mapImage.removeAttribute("src");
nodes.mapHero.hidden = true;
};
nodes.mapHero.hidden = false;
}
function renderScoreboardDetail(item, { mapName, serverName }) { function renderScoreboardDetail(item, { mapName, serverName }) {
const result = item.result || {}; const result = item.result || {};
const alliedScore = Number.isFinite(Number(result.allied_score)) const alliedScore = Number.isFinite(Number(result.allied_score))
@@ -130,20 +155,21 @@ function renderScoreboardDetail(item, { mapName, serverName }) {
} }
function renderScoreboardSide({ sideClass, emblem, sideLabel, factionLabel, isWinner }) { function renderScoreboardSide({ sideClass, emblem, sideLabel, factionLabel, isWinner }) {
const fallbackLabel = factionLabel || sideLabel;
return ` return `
<div class="historical-scoreboard-side ${sideClass} ${isWinner ? "is-winner" : ""}"> <div class="historical-scoreboard-side ${sideClass} ${isWinner ? "is-winner" : ""}">
<img <img
class="historical-scoreboard-side__emblem" class="historical-scoreboard-side__emblem"
src="${escapeHtml(emblem)}" src="${escapeHtml(emblem)}"
alt="${escapeHtml(factionLabel)}" alt="${escapeHtml(fallbackLabel)}"
width="96" width="128"
height="96" height="128"
loading="lazy" loading="lazy"
decoding="async" decoding="async"
onerror="this.hidden = true; this.closest('.historical-scoreboard-side').classList.add('is-emblem-missing');"
/> />
<div class="historical-scoreboard-side__text"> <div class="historical-scoreboard-side__text">
<strong>${escapeHtml(sideLabel)}</strong> <strong>${escapeHtml(sideLabel)}</strong>
<span>${escapeHtml(factionLabel)}</span>
${isWinner ? "<em>Ganador</em>" : ""} ${isWinner ? "<em>Ganador</em>" : ""}
</div> </div>
</div> </div>
@@ -208,7 +234,7 @@ function renderPlayerRow(player) {
} }
function renderActions(item, actionsNode) { function renderActions(item, actionsNode) {
const matchUrl = normalizeExternalMatchUrl(item.match_url); const matchUrl = normalizeSafePublicScoreboardMatchUrl(item.match_url);
if (!matchUrl) { if (!matchUrl) {
actionsNode.innerHTML = ""; actionsNode.innerHTML = "";
actionsNode.hidden = true; actionsNode.hidden = true;
@@ -221,7 +247,7 @@ function renderActions(item, actionsNode) {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Abrir en scoreboard Ver en Scoreboard
</a> </a>
`; `;
actionsNode.hidden = false; actionsNode.hidden = false;
@@ -235,12 +261,12 @@ function resolveMatchFactions(item, mapName) {
if (/(kursk|stalingrad|kharkov)/.test(normalizedMap)) { if (/(kursk|stalingrad|kharkov)/.test(normalizedMap)) {
return { return {
allied: { allied: {
label: "Ejercito sovietico", label: "Sovieticos",
emblem: "./assets/img/factions/soviets.svg", emblem: "./assets/img/factions/soviets.webp",
}, },
axis: { axis: {
label: "Ejercito aleman", label: "Eje",
emblem: "./assets/img/factions/germany.svg", emblem: "./assets/img/factions/germany.webp",
}, },
}; };
} }
@@ -248,30 +274,60 @@ function resolveMatchFactions(item, mapName) {
if (/(driel|elalamein|el alamein|tobruk)/.test(normalizedMap)) { if (/(driel|elalamein|el alamein|tobruk)/.test(normalizedMap)) {
return { return {
allied: { allied: {
label: "Fuerzas britanicas", label: "Britanicos",
emblem: "./assets/img/factions/britain.svg", emblem: "./assets/img/factions/britain.webp",
}, },
axis: { axis: {
label: normalizedMap.includes("tobruk") || normalizedMap.includes("elalamein") label: normalizedMap.includes("tobruk") || normalizedMap.includes("elalamein")
? "Afrika Korps" ? "Afrika Korps"
: "Ejercito aleman", : "Eje",
emblem: "./assets/img/factions/germany.svg", emblem: "./assets/img/factions/germany.webp",
}, },
}; };
} }
return { return {
allied: { allied: {
label: "Fuerzas estadounidenses", label: "USA",
emblem: "./assets/img/factions/us.svg", emblem: "./assets/img/factions/us.webp",
}, },
axis: { axis: {
label: "Ejercito aleman", label: "Eje",
emblem: "./assets/img/factions/germany.svg", emblem: "./assets/img/factions/germany.webp",
}, },
}; };
} }
function resolveMapImagePath(item, mapName) {
const normalizedMap = normalizeLookupText(
`${item.map?.name || ""} ${item.map?.pretty_name || ""} ${mapName || ""}`,
).replaceAll(" ", "");
const mapAssetByKey = {
carentan: "carentan-day.webp",
driel: "driel-day.webp",
elalamein: "elalamein-day.webp",
elsenbornridge: "elsenbornridge-day.webp",
foy: "foy-day.webp",
hill400: "hill400-day.webp",
hurtgenforest: "hurtgenforest-day.webp",
kharkov: "kharkov-day.webp",
kursk: "kursk-day.webp",
mortain: "mortain-day.webp",
omahabeach: "omahabeach-day.webp",
purpleheartlane: "purpleheartlane-rain.webp",
smolensk: "smolensk-day.webp",
stmariedumont: "stmariedumont-day.webp",
stmereeglise: "stmereeglise-day.webp",
tobrukdawn: "tobruk-dawn.webp",
tobruk: "tobruk-day.webp",
utahbeach: "utahbeach-day.webp",
};
const matchedKey = Object.keys(mapAssetByKey).find((key) =>
normalizedMap.includes(key),
);
return matchedKey ? `./assets/img/maps/${mapAssetByKey[matchedKey]}` : "";
}
function normalizeLookupText(value) { function normalizeLookupText(value) {
return String(value || "") return String(value || "")
.normalize("NFD") .normalize("NFD")
@@ -281,19 +337,6 @@ function normalizeLookupText(value) {
.trim(); .trim();
} }
function formatDetailSubtitle(item) {
if (item.capture_basis === "rcon-materialized-admin-log") {
return "Partida RCON materializada";
}
if (item.capture_basis === "rcon-competitive-window") {
return "Partida RCON registrada";
}
if (item.result_source === "public-scoreboard-match") {
return "Partida del scoreboard";
}
return "Partida historica";
}
function formatTeamSide(value) { function formatTeamSide(value) {
const normalized = String(value || "").toLowerCase(); const normalized = String(value || "").toLowerCase();
if (normalized === "allies" || normalized === "allied") { if (normalized === "allies" || normalized === "allied") {
@@ -414,13 +457,18 @@ function formatMatchTimestamp(item, kind) {
return "No disponible"; return "No disponible";
} }
function normalizeExternalMatchUrl(value) { function normalizeSafePublicScoreboardMatchUrl(value) {
if (typeof value !== "string" || !value.trim()) { if (typeof value !== "string" || !value.trim()) {
return ""; return "";
} }
try { try {
const url = new URL(value.trim()); const url = new URL(value.trim());
return ["http:", "https:"].includes(url.protocol) ? url.href : ""; const allowedOrigins = new Set([
"https://scoreboard.comunidadhll.es",
"https://scoreboard.comunidadhll.es:5443",
]);
const isAllowedPath = url.pathname === "/games" || url.pathname.startsWith("/games/");
return allowedOrigins.has(url.origin) && isAllowedPath ? url.href : "";
} catch (error) { } catch (error) {
return ""; return "";
} }

View File

@@ -44,6 +44,18 @@
</p> </p>
</div> </div>
</div> </div>
<figure class="historical-map-hero" id="match-detail-map-hero" hidden>
<img
class="historical-map-hero__image"
id="match-detail-map-image"
src=""
alt=""
width="960"
height="540"
loading="eager"
decoding="async"
/>
</figure>
</div> </div>
</div> </div>
</header> </header>
@@ -54,7 +66,7 @@
<div class="panel__header historical-panel__header"> <div class="panel__header historical-panel__header">
<div> <div>
<p class="eyebrow eyebrow--section">Partida historica</p> <p class="eyebrow eyebrow--section">Partida historica</p>
<h2 id="match-detail-panel-title">Datos disponibles</h2> <h2 id="match-detail-panel-title">Marcador final</h2>
<p class="historical-panel__note" id="match-detail-note"> <p class="historical-panel__note" id="match-detail-note">
Leyendo el detalle interno de la partida. Leyendo el detalle interno de la partida.
</p> </p>
@@ -94,19 +106,6 @@
</table> </table>
</div> </div>
</div> </div>
<div class="historical-detail-section" id="match-detail-timeline-section" hidden>
<div class="historical-detail-section__header">
<div>
<p class="eyebrow eyebrow--section">Eventos</p>
<h3>Linea de tiempo</h3>
</div>
<p class="historical-panel__note" id="match-detail-timeline-note">
Revisando eventos registrados para esta partida.
</p>
</div>
<p class="historical-state" id="match-detail-timeline-state" hidden></p>
<div class="historical-summary-grid" id="match-detail-timeline-grid" hidden></div>
</div>
<div class="historical-match-card__actions" id="match-detail-actions" hidden></div> <div class="historical-match-card__actions" id="match-detail-actions" hidden></div>
</div> </div>
</section> </section>

View File

@@ -59,7 +59,7 @@ Assert-Contains $historicoJs "Ver detalles" `
"Recent match cards no longer include the internal detail fallback label." "Recent match cards no longer include the internal detail fallback label."
Assert-NotContains $historicoJs "item.match_url || item.source_url" ` Assert-NotContains $historicoJs "item.match_url || item.source_url" `
"Recent match cards must not trust legacy source_url fallback." "Recent match cards must not trust legacy source_url fallback."
Assert-Contains $historicoPartidaJs "Abrir en scoreboard" ` Assert-Contains $historicoPartidaJs "Ver en Scoreboard" `
"Match detail page no longer includes the external scoreboard action label." "Match detail page no longer includes the external scoreboard action label."
Assert-Contains $historicoPartidaJs 'rel="noopener noreferrer"' ` Assert-Contains $historicoPartidaJs 'rel="noopener noreferrer"' `
"External scoreboard links must keep rel noopener noreferrer." "External scoreboard links must keep rel noopener noreferrer."