diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 267bafb6e..3af7bddf6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,19 @@ -# Code Owners - iURi Pescadores -# Este archivo define quién debe revisar cambios en partes específicas del código +# Extra de acero: cualquier cambio sensible pide review explícito. +# Orden importa: reglas más específicas abajo. -# Guardião de comandos – só Cristian revisa -/docs/CursorGuardianRules.json @cristianbarnes -/backend/core/cursor_guardian.py @cristianbarnes +# GitHub config +.github/* @Cheewye +.github/** @Cheewye + +# UI contracts (docs) – cambios deben pasar por el dueño +docs/ui/** @Cheewye + +# Map / Dock / Forecast (frontend) – cambios requieren review del dueño +frontend/src/components/maps/** @Cheewye +frontend/src/pages/** @Cheewye +frontend/src/lib/** @Cheewye +frontend/src/config/** @Cheewye +frontend/src/data/** @Cheewye + +# Fallback: todo lo demás (opcional, si no querés bloquear todo el repo, borrar estas dos líneas) +* @Cheewye diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3b0b7a937..bee75463e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,52 +1,58 @@ -## 📋 PR Template - iURi Veracity Enforcement - -### 🎯 Scope (What) - - - -### 🚫 Non-goals (What NOT) - - - -### 🔍 Verification Status - -#### CI_VERIFIED - -**CI Run:** [Link to Actions Run](https://github.com/Cheewye/iuri-react-codex/actions/runs/XXX) -**Status:** ✅ PASSED | ❌ FAILED -**Evidence:** All checks green, no failing tests, lint clean - -#### PROD_VERIFIED - -**Latest Check:** [Link to PROD Verifier Run](https://github.com/Cheewye/iuri-react-codex/actions/runs/XXX) -**Status:** ✅ PROD_VERIFIED | ❌ PROD_FAIL | ⚠️ REPO_ONLY -**Evidence:** See job summary and artifacts `prod-evidence-{run_id}` - -### 🏷️ Labels Required - -- [ ] `veracity:prod-verified` - PROD_VERIFIED status confirmed -- [ ] `veracity:ci-verified` - All CI checks passed -- [ ] `veracity:repo-only` - Code changes only, no prod verification -- [ ] `type:feature` - New feature -- [ ] `type:fix` - Bug fix -- [ ] `type:refactor` - Code improvement -- [ ] `type:docs` - Documentation only - -### ⚠️ Breaking Changes - -- [ ] No breaking changes -- [ ] Breaking changes listed below: - - -### 📝 Testing - -- [ ] Unit tests added/updated -- [ ] Integration tests added/updated -- [ ] Manual testing performed -- [ ] No tests needed (docs/config only) - ---- - -**⚡ VERACITY ENFORCEMENT**: This PR template is enforced by `tools/claim_lint.py` in CI. Claims like "fixed", "working", "verified" require PROD_VERIFIED: or CI_VERIFIED: tags to merge. - -**🔗 REQUIRED LINKS**: Both CI_VERIFIED and PROD_VERIFIED links must be provided and valid for merge. +# Map/Dock/Forecast PR Gate (UI Contract v1 RJ) + +## Scope +- [ ] This PR does **not** change backend / infra. +- [ ] Changes are limited to the intended scope (map / dock / forecast) and do not introduce new hidden toggles or toasts. + +## Required: Appendix E + Appendix F +**I confirm:** +- [ ] ✅ passes **Appendix E (Checklist P0)** +- [ ] ✅ keeps **Appendix F (Sources of Truth + Data on Hold)** intact +- [ ] ✅ (If applicable) keeps **Appendix G (Dock collision-free)** intact + +> If any checkbox above is unchecked, this PR must not be merged. + +## What changed (2–6 bullets) +- +- +- + +## UX verification (manual) +Paste *what you actually checked* (no promises): + +### RJ targets +- [ ] Guanabara: AIS default behavior verified +- [ ] Barra São João: traffic-light is always visible (even if Barra toggle OFF) +- [ ] Dock does not cover Leaflet Layers dropdown (top-right) +- [ ] `layers=1` hides Dock (auto-hide) and Layers remains usable + +### Data integrity / “no silent failure” +- [ ] No toasts for: hold / fetch error / slow / abort +- [ ] Inline states present: `On hold`, `(fetch error)`, `slow`, `abort` +- [ ] Fail-open verified (last good data stays visible on refresh failure) +- [ ] “Copy debug” contains required fields (Appendix F4) + +## Evidence +### Build +Paste: +- `npm -C frontend run build` summary line + `EXIT_CODE=0` + +### Conflict markers +Paste: +- `rg -n "^(<{7}|={7}|>{7})" frontend/src || true` (should be empty) + +### Version / Prod (if already deployed) +Paste: +- `curl -fsS https://iuriapp.com/version.json | head -c 200 && echo` + +### Debug JSON (Copy debug) +Paste one Copy debug sample (sanitized if needed). + +## Risk & rollback +- Risk level: [ ] Low [ ] Medium [ ] High +- Rollback plan (1 sentence): revert PR / revert commit / toggle flag (if exists) + +## Reviewer checklist (fast) +- [ ] UI Contract invariants still true (Dock, Layers, Data on Hold) +- [ ] No new duplicated controls (Dock vs Preparation vs Forecast) +- [ ] No regressions in RJ defaults diff --git a/docs/ui/FISHER_UI_CONTRACT_v1.en.md b/docs/ui/FISHER_UI_CONTRACT_v1.en.md new file mode 100644 index 000000000..5d7f5f502 --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1.en.md @@ -0,0 +1,175 @@ +# iURi Fisher UI Contract v1 + +**Version:** 1 · **Language:** EN +**Other versions:** [ES](./FISHER_UI_CONTRACT_v1.md) · [PT](./FISHER_UI_CONTRACT_v1.pt.md) + +--- + +## Objective + +A showable, reliable map for fishers, with: + +- **navigation** (signals, zones, depth), +- **safety** (bar/alerts), +- **local context** (FMAP: landing/ice/etc.), +- **departure decision** (forecast: wind/waves/tides/currents), + +with no silent failures and no hidden UI. + +--- + +## 0) Definitions (so “buoys” are not mixed up) + +| Term | Definition | +|------|------------| +| **Signals (Seamarks)** | Nautical navigation signals (channels, marks, hazards). Typical source: OpenSeaMap tiles. | +| **Buoys (PNBOIA)** | Buoys/stations (e.g. meteorological/oceanographic). They are not “nautical signals”. | +| **FMAP Signals (seed)** | Local points/polygons (landing, ice, etc.) from FMAP, versioned as seed. | + +The UI must label them this way to avoid confusion. + +--- + +## 1) UI surfaces (and responsibilities) + +### A) Live Map (`/map?start=realtime`) + +Live map, quick interaction. It does not explain much, it just operates. + +### B) “Layers” (Leaflet dropdown top-right) + +- **ONLY** Base Maps (map styles). +- **Forbidden** to mix in operational layers (GPS, Buoys, AIS, etc.). + +### C) Fisher Dock (operational layers) + +- Single place for operational layers (toggles + status). +- Does not hide in the header. +- Does not cover the “Layers” dropdown. + +### D) Preparation + +“Before departure” setup: port, checklist, guided presets. Can turn defaults on/off (but live control stays in Dock). + +### E) Forecast + +Forecast screens (wind/waves/tides/currents) with clear states: **OK** / **Loading** / **Data on hold** / **Error**. + +### F) Smart Fishing + +Optional and clearly separate from “core safety”. + +--- + +## 2) Fisher Dock v1 + +### Layout/UX + +- Collapsible (preferred over draggable). +- Supported snap positions: **Left** / **Right** / **Bottom**. +- Default **Right** (vertically centered). +- **Auto-hide** when `layers=1` (so it does not cover the layers/styles panel). +- No toasts for errors: **everything inline** (minimal noise). + +### Toggles (7) — core + +1. **GPS** (My Location) +2. **Signals** (Seamarks) +3. **Zones** +4. **Buoys** (PNBOIA) +5. **Bar** +6. **AIS** (default OFF, considered “advanced”) +7. **FMAP** (Local signals) + +### Actions (not a toggle) + +- **Report:** action button (opens screen/flow). Does not count as a toggle. + +### Defaults (v1) + +| Layer | Default | Notes | +|-------|---------|--------| +| Signals (Seamarks) | ON | — | +| Zones | ON | — | +| FMAP | ON | No port: “near my location”; with port: “port resources” | +| GPS | ON | — | +| Buoys (PNBOIA) | ON | Always visible as “at hand” toggle | +| AIS | OFF | — | +| Bar | ON if applicable | If not applicable to area: OFF and/or “not available” | + +--- + +## 3) “No silent failure” contract (mandatory) + +Every operational layer shows in the Dock: + +- **State:** OFF | LOADING | OK | ERROR | HOLD +- If **ERROR:** short message + **Copy debug** button +- If **OK:** relevant counters (e.g. Loaded/Visible, tilesRequested/Loaded/Error) + +### For Buoys (PNBOIA) + +- **Loaded** = N (full list) +- **Visible** = M (by viewport) +- **Go to buoys** button when Loaded > 0 and Visible = 0 +- **Fail-open:** if a good list was already loaded, it is not cleared by a later failure. + +### For Signals (Seamarks) + +- **Counters:** tilesRequested / tilesLoaded / tilesError +- If tiles do not load: show **ERROR (tiles)** without breaking the map. + +### For FMAP + +- Show **one single row:** “FMAP Signals (seed)” + counts + **Go** / **Copy** +- “Choclazo” details only with `debug=1`. + +--- + +## 4) Copy debug (standard) + +**Copy debug** button per layer, with JSON including: + +- `version.gitCommit` +- `href` +- `layer`: status / error / loaded / visible +- `timing`: loadingMs / slow / abortMs / abortReason (if applicable) +- `map`: zoom + bounds +- `stateHint` (if applicable) + +This is the “black box” so we do not depend on the console. + +--- + +## 5) Preparation vs Dock (rule) + +- **Preparation** sets **defaults** (what is on when entering, based on port/conditions). +- **Dock** controls **live** (actual toggle at the moment). +- **Do not duplicate toggles** between both. In Preparation only “recommended preset” and **“Apply to Dock”** buttons. + +--- + +## 6) MVP “showable” (Definition of Done v1) + +To say *“this can be shown to a fisher”*: + +- [ ] **GPS** works and is visible (marker + circle + tooltip). +- [ ] **Signals (Seamarks)** ON by default and actually requests tiles. +- [ ] **Zones** ON by default. +- [ ] **FMAP** ON with something visible (near my location or port resources). +- [ ] **Forecast** shows at least Tides in real form or “HOLD” clearly explained. +- [ ] **No layer** fails silently: everything has state and Copy debug. + +--- + +## 7) Change freeze (operational rule) + +From this contract onward: + +- **Do not move** a layer from its surface (Dock/Preparation/Forecast/Smart Fishing) without editing this doc. +- **Do not mix** “Base maps” with “operational layers” again. +- Every PR that touches the map must explicitly state: **“Contract impact: none / yes (section X)”**. + +--- + +*Living document. Review on each map/Dock release.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1.md b/docs/ui/FISHER_UI_CONTRACT_v1.md new file mode 100644 index 000000000..0796c0eeb --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1.md @@ -0,0 +1,175 @@ +# iURi Fisher UI Contract v1 + +**Versión:** 1 · **Idioma:** ES +**Otras versiones:** [EN](./FISHER_UI_CONTRACT_v1.en.md) · [PT](./FISHER_UI_CONTRACT_v1.pt.md) + +--- + +## Objetivo + +Un mapa mostrable y confiable para pescadores, con: + +- **navegación** (señales, zonas, profundidad), +- **seguridad** (barra/alertas), +- **contexto local** (FMAP: desembarque/hielo/etc.), +- **decisión de salida** (forecast: viento/olas/mareas/corrientes), + +sin “silent failures” y sin UI escondida. + +--- + +## 0) Definiciones (para no mezclar “boyas”) + +| Término | Definición | +|--------|------------| +| **Señales (Seamarks)** | Señales náuticas de navegación (canales, marcas, peligros). Fuente típica: OpenSeaMap tiles. | +| **Boyas (PNBOIA)** | Boyas/estaciones (ej. meteorológicas/oceanográficas). No son “señales náuticas”. | +| **FMAP Señales (seed)** | Puntos/polígonos locales (desembarque, hielo, etc.) extraídos de FMAP, versionados como seed. | + +La UI debe rotularlo así para cortar confusión. + +--- + +## 1) Superficies de UI (y responsabilidades) + +### A) Live Map (`/map?start=realtime`) + +Mapa en vivo, interacción rápida. No explica mucho, solo opera. + +### B) “Layers” (dropdown Leaflet arriba-derecha) + +- **SOLO** Base Maps (estilos de mapa). +- **Prohibido** mezclar ahí capas operativas (GPS, Boyas, AIS, etc.). + +### C) Fisher Dock (capas operativas) + +- Único lugar para capas operativas (toggles + status). +- No se esconde en header. +- No tapa el dropdown “Layers”. + +### D) Preparation + +Configuración “antes de salir”: puerto, checklist, presets guiados. Puede prender/apagar defaults (pero el control live sigue en Dock). + +### E) Forecast + +Pantallas de pronóstico (viento/olas/mareas/corrientes) con estados claros: **OK** / **Loading** / **Data on hold** / **Error**. + +### F) Smart Fishing + +Opcional y claramente separado del “core safety”. + +--- + +## 2) Fisher Dock v1 + +### Layout/UX + +- Colapsable (preferido sobre draggable). +- Snap positions soportadas: **Left** / **Right** / **Bottom**. +- Default en **Right** (centrado vertical). +- **Auto-hide** cuando `layers=1` (para no tapar panel de capas/estilos). +- No toasts para errores: **todo inline** (mínimo ruido). + +### Toggles (7) — core + +1. **GPS** (My Location) +2. **Señales** (Seamarks) +3. **Zonas** +4. **Boyas** (PNBOIA) +5. **Barra** +6. **AIS** (por default OFF, considerado “advanced”) +7. **FMAP** (Señales locales) + +### Acciones (no toggle) + +- **Reportar:** botón de acción (abre pantalla/flow). No cuenta como toggle. + +### Defaults (v1) + +| Capa | Default | Notas | +|------|---------|--------| +| Señales (Seamarks) | ON | — | +| Zonas | ON | — | +| FMAP | ON | Sin puerto: “near my location”; con puerto: “port resources” | +| GPS | ON | — | +| Boyas (PNBOIA) | ON | Siempre visibles como toggle “a mano” | +| AIS | OFF | — | +| Barra | ON si aplica | Si no aplica al área: OFF y/o “not available” | + +--- + +## 3) Contrato “no silent failure” (obligatorio) + +Toda capa operativa muestra en Dock: + +- **Estado:** OFF | LOADING | OK | ERROR | HOLD +- Si **ERROR:** mensaje corto + botón **Copy debug** +- Si **OK:** contadores relevantes (ej. Loaded/Visible, tilesRequested/Loaded/Error) + +### Para Boyas (PNBOIA) + +- **Loaded** = N (lista completa) +- **Visible** = M (por viewport) +- Botón **Go to buoys** cuando Loaded > 0 y Visible = 0 +- **Fail-open:** si ya hubo una lista OK, no se borra por un fallo posterior. + +### Para Señales (Seamarks) + +- **Counters:** tilesRequested / tilesLoaded / tilesError +- Si tiles no cargan: mostrar **ERROR (tiles)** sin romper el mapa. + +### Para FMAP + +- Mostrar **UNA sola fila:** “FMAP Señales (seed)” + counts + **Go** / **Copy** +- Detalles “choclazo” solo con `debug=1`. + +--- + +## 4) Copy debug (estándar) + +Botón **Copy debug** por capa, con JSON incluyendo: + +- `version.gitCommit` +- `href` +- `layer`: status / error / loaded / visible +- `timing`: loadingMs / slow / abortMs / abortReason (si aplica) +- `map`: zoom + bounds +- `stateHint` (si aplica) + +Esto es el “caja negra” para no depender de consola. + +--- + +## 5) Preparation vs Dock (regla) + +- **Preparation** decide **defaults** (qué prende al entrar, basado en puerto/condiciones). +- **Dock** controla el **live** (toggle real en el momento). +- **No duplicar toggles** entre ambos. En Preparation solo “preset recomendado” y botones **“Apply to Dock”**. + +--- + +## 6) MVP “mostrable” (Definition of Done v1) + +Para decir *“esto se puede mostrar a un pescador”*: + +- [ ] **GPS** funciona y se ve (marker + círculo + tooltip). +- [ ] **Señales (Seamarks)** ON por defecto y efectivamente pide tiles. +- [ ] **Zonas** ON por defecto. +- [ ] **FMAP** ON con algo visible (near my location o port resources). +- [ ] **Forecast** muestra al menos Mareas de forma real o “HOLD” claramente explicado. +- [ ] **Ninguna capa** falla en silencio: todo tiene estado y Copy debug. + +--- + +## 7) Freeze de cambios (regla operativa) + +A partir de este contrato: + +- **No se mueve** una capa de superficie (Dock/Preparation/Forecast/Smart Fishing) sin editar este doc. +- **No se vuelve a mezclar** “Base maps” con “capas operativas”. +- Cada PR que toque mapa debe decir explícitamente: **“Contract impact: none / yes (section X)”**. + +--- + +*Documento vivo. Revisar en cada release de mapa/Dock.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1.pt.md b/docs/ui/FISHER_UI_CONTRACT_v1.pt.md new file mode 100644 index 000000000..645e91da3 --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1.pt.md @@ -0,0 +1,175 @@ +# Contrato de UI Fisher iURi v1 + +**Versão:** 1 · **Idioma:** PT +**Outras versões:** [ES](./FISHER_UI_CONTRACT_v1.md) · [EN](./FISHER_UI_CONTRACT_v1.en.md) + +--- + +## Objetivo + +Um mapa mostrável e confiável para pescadores, com: + +- **navegação** (sinais, zonas, profundidade), +- **segurança** (barra/alertas), +- **contexto local** (FMAP: desembarque/gelo/etc.), +- **decisão de saída** (previsão: vento/ondas/marés/correntes), + +sem falhas silenciosas e sem UI escondida. + +--- + +## 0) Definições (para não misturar “boias”) + +| Termo | Definição | +|-------|------------| +| **Sinais (Seamarks)** | Sinais náuticos de navegação (canais, marcas, perigos). Fonte típica: tiles OpenSeaMap. | +| **Boias (PNBOIA)** | Boias/estações (ex. meteorológicas/oceanográficas). Não são “sinais náuticos”. | +| **FMAP Sinais (seed)** | Pontos/polígonos locais (desembarque, gelo, etc.) extraídos do FMAP, versionados como seed. | + +A UI deve rotulá-los assim para cortar confusão. + +--- + +## 1) Superfícies de UI (e responsabilidades) + +### A) Mapa ao vivo (`/map?start=realtime`) + +Mapa ao vivo, interação rápida. Não explica muito, só opera. + +### B) “Layers” (dropdown Leaflet topo-direita) + +- **SOMENTE** Base Maps (estilos de mapa). +- **Proibido** misturar aí camadas operacionais (GPS, Boias, AIS, etc.). + +### C) Fisher Dock (camadas operacionais) + +- Único lugar para camadas operacionais (toggles + status). +- Não se esconde no header. +- Não tapa o dropdown “Layers”. + +### D) Preparation + +Configuração “antes de sair”: porto, checklist, presets guiados. Pode ligar/desligar padrões (mas o controle ao vivo continua no Dock). + +### E) Forecast + +Telas de previsão (vento/ondas/marés/correntes) com estados claros: **OK** / **Loading** / **Data on hold** / **Error**. + +### F) Smart Fishing + +Opcional e claramente separado do “core safety”. + +--- + +## 2) Fisher Dock v1 + +### Layout/UX + +- Recolhível (preferido em relação a arrastável). +- Posições de snap suportadas: **Left** / **Right** / **Bottom**. +- Padrão em **Right** (centrado verticalmente). +- **Auto-hide** quando `layers=1` (para não tapar o painel de camadas/estilos). +- Sem toasts para erros: **tudo inline** (mínimo ruído). + +### Toggles (7) — core + +1. **GPS** (My Location) +2. **Sinais** (Seamarks) +3. **Zonas** +4. **Boias** (PNBOIA) +5. **Barra** +6. **AIS** (por padrão OFF, considerado “advanced”) +7. **FMAP** (Sinais locais) + +### Ações (não é toggle) + +- **Reportar:** botão de ação (abre tela/fluxo). Não conta como toggle. + +### Padrões (v1) + +| Camada | Padrão | Notas | +|--------|--------|--------| +| Sinais (Seamarks) | ON | — | +| Zonas | ON | — | +| FMAP | ON | Sem porto: “near my location”; com porto: “port resources” | +| GPS | ON | — | +| Boias (PNBOIA) | ON | Sempre visíveis como toggle “à mão” | +| AIS | OFF | — | +| Barra | ON se aplicar | Se não aplicar à área: OFF e/ou “not available” | + +--- + +## 3) Contrato “no silent failure” (obrigatório) + +Toda camada operacional mostra no Dock: + +- **Estado:** OFF | LOADING | OK | ERROR | HOLD +- Se **ERROR:** mensagem curta + botão **Copy debug** +- Se **OK:** contadores relevantes (ex. Loaded/Visible, tilesRequested/Loaded/Error) + +### Para Boias (PNBOIA) + +- **Loaded** = N (lista completa) +- **Visible** = M (por viewport) +- Botão **Go to buoys** quando Loaded > 0 e Visible = 0 +- **Fail-open:** se já houve uma lista OK, não se apaga por falha posterior. + +### Para Sinais (Seamarks) + +- **Counters:** tilesRequested / tilesLoaded / tilesError +- Se os tiles não carregam: mostrar **ERROR (tiles)** sem quebrar o mapa. + +### Para FMAP + +- Mostrar **uma única linha:** “FMAP Sinais (seed)” + counts + **Go** / **Copy** +- Detalhes “choclazo” só com `debug=1`. + +--- + +## 4) Copy debug (padrão) + +Botão **Copy debug** por camada, com JSON incluindo: + +- `version.gitCommit` +- `href` +- `layer`: status / error / loaded / visible +- `timing`: loadingMs / slow / abortMs / abortReason (se aplicar) +- `map`: zoom + bounds +- `stateHint` (se aplicar) + +Isto é a “caixa negra” para não depender da consola. + +--- + +## 5) Preparation vs Dock (regra) + +- **Preparation** define **padrões** (o que liga ao entrar, com base em porto/condições). +- **Dock** controla o **ao vivo** (toggle real no momento). +- **Não duplicar toggles** entre os dois. Em Preparation só “preset recomendado” e botões **“Apply to Dock”**. + +--- + +## 6) MVP “mostrável” (Definition of Done v1) + +Para dizer *“isto pode ser mostrado a um pescador”*: + +- [ ] **GPS** funciona e se vê (marker + círculo + tooltip). +- [ ] **Sinais (Seamarks)** ON por padrão e efectivamente pede tiles. +- [ ] **Zonas** ON por padrão. +- [ ] **FMAP** ON com algo visível (near my location ou port resources). +- [ ] **Forecast** mostra pelo menos Marés de forma real ou “HOLD” claramente explicado. +- [ ] **Nenhuma camada** falha em silêncio: tudo tem estado e Copy debug. + +--- + +## 7) Freeze de mudanças (regra operativa) + +A partir deste contrato: + +- **Não se move** uma camada de superfície (Dock/Preparation/Forecast/Smart Fishing) sem editar este doc. +- **Não se volta a misturar** “Base maps” com “camadas operacionais”. +- Cada PR que toque o mapa deve dizer explicitamente: **“Contract impact: none / yes (section X)”**. + +--- + +*Documento vivo. Revisar em cada release de mapa/Dock.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.en.md b/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.en.md new file mode 100644 index 000000000..0fc95ca44 --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.en.md @@ -0,0 +1,651 @@ +# FISHER UI CONTRACT v1 — RJ Addendum (Maricá / Guanabara / Barra de São João) + +**Complements:** [FISHER_UI_CONTRACT_v1](./FISHER_UI_CONTRACT_v1.en.md) · [Badges](./FISHER_UI_CONTRACT_v1_badges.en.md) +**Language:** EN | **Other versions:** [ES](./FISHER_UI_CONTRACT_v1_RJ_addendum.md) · [PT](./FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md) + +**Scope:** This addendum defines **RJ defaults** and **Bar crossing signal** behaviour. +**Target user:** Fishers (fast, predictable, zero “hunt-the-button” UI). + +--- + +## 0) Non-negotiable invariants (v1) + +1. **Top-right “Layers” = Base maps only.** No operational overlays/toggles inside Layers. +2. **Operational layers live only in the Fisher Dock** (GPS, Buoys, AIS, Bar, Zones, Signals, FMAP). +3. **No silent failure:** any fetch/load issue must be shown **inline** (small text), never as a toast. +4. **Dock must not block Layers dropdown.** The user must always be able to open Layers. + +--- + +## 1) Fisher Dock (collapsible) — definitive behaviour + +### 1.1 Expanded state + +Shows the full set of operational controls: + +* **GPS** +* **Buoys (PNBOIA)** +* **AIS** +* **Bar** +* **Zones** +* **Signals (Seamarks)** +* **FMAP (Local resources / seed)** + +Each row can display: + +* ON/OFF state +* small status line (Loaded/Visible; inline “(fetch error)”; etc.) +* **Go** and **Copy debug** where applicable + +### 1.2 Collapsed state + +Collapses into **one single button**: + +* Label: **Fisher** +* Badges (mini indicators): + + * **GPS badge:** ON/OFF (green/gray dot or “GPS ON/OFF”) + * **Alerts badge:** number or red dot if any **P0** alert active (see §4) + +### 1.3 Positioning (3-slot system) + +Dock supports exactly one of these anchors at a time: + +* **Right-center (desktop default)**: vertically centered, right margin, never overlaps top-right Layers. +* **Left-center**: same rules, mirrored. +* **Bottom (mobile default)**: above system UI, does not cover critical map controls. + +### 1.4 Auto-hide rule + +When `layers=1` (Layers overlay open), **Dock auto-hides** (collapsed or expanded), leaving Layers unobstructed. + +### 1.5 Defaults + +* Desktop: **Right-center**, **Expanded on first visit**, then persist user preference. +* Mobile: **Bottom**, **Collapsed by default**, then persist user preference. + +--- + +## 2) RJ Region heuristics (v1) + +Human-readable heuristics; they can be tuned later without changing the UI contract. + +### 2.1 State hint: RJ + +**RJ hint is TRUE** if the **map viewport center** is within: + +* lat: **[-24.6, -20.7]** +* lon: **[-44.9, -40.7]** + +When `stateHint = "RJ"`, preference rules apply (FMAP and Go-to-buoys behaviour can prioritise RJ-tagged items). + +### 2.2 Guanabara (AIS default ON) + +**Guanabara hint is TRUE** if viewport center is within: + +* lat: **[-23.10, -22.70]** +* lon: **[-43.40, -43.00]** + +Rule: + +* If Guanabara hint is TRUE: **AIS = ON by default** (first visit, unless user already chose). +* Otherwise: AIS default stays OFF (unless user already chose). + +### 2.3 Maricá / Barra de São João (context) + +Optional helper boxes (for prioritisation, not required for correctness): + +* **Maricá approx:** lat [-23.05, -22.80], lon [-43.10, -42.70] +* **Barra de São João approx:** lat [-22.62, -22.30], lon [-42.05, -41.75] + +--- + +## 3) FMAP “Near my location” (v1) + +FMAP is **ON by default in RJ**, but what it shows depends on context. + +### 3.1 Center selection (priority) + +Pick the FMAP “center” in this order: + +1. **GPS (“My location”)** if available +2. **Selected Port** if no GPS +3. **Viewport center** as fallback + +### 3.2 Radius rules + +* Base radius: **15 km** +* If results ≤ 3: expand to **30 km** +* If still empty: show inline note **“No data near (30 km)”** but keep **Go** enabled to frame what exists (if any global items exist). + +### 3.3 RJ preference + +If `stateHint = "RJ"` and FMAP items are tagged RJ: + +* Prefer RJ-tagged items **within the radius** +* If none exist within the radius, fall back to the normal selection (no RJ filter). + +### 3.4 What shows when ON with no port selected + +Show only essentials (v1): + +* **Landing / unloading** +* **Ice** +* **Shelter / safe anchorage** (if present) + +Everything else goes behind: + +* **Details** (only visible when `debug=1`) + +### 3.5 UI text + +* Toggle label: **FMAP (Local resources)** +* State line examples: + + * **Near my location (15 km)** + * **Port resources** +* Buttons: **Go**, **Copy debug** + +--- + +## 4) Alerts badge — concrete thresholds (v1) + +Alerts badge counts only **P0** items. Everything else is informational and does not increment the badge. + +### 4.1 P0 alerts (counted) + +* **Bar crossing level = Red (Danger)** → counts as **1** +* (Future, when available) **Severe weather warning** explicitly flagged as “danger/severe” → counts as **1** +* (Future) Any “Do not sail / port closed” authority warning → counts as **1** + +### 4.2 Not counted (v1) + +* Yellow/caution states +* “Data on hold” +* “Backend unavailable” +* PNBOIA slow/timeout if fallback/cached data exists + +Badge behaviour: + +* Collapsed Dock shows a **red dot** or **number** if P0 count > 0. +* Expanded Dock may show a small inline “Alerts: N” at top (optional). + +--- + +## 5) Bar crossing signal (“traffic light”) — RJ rule + +### 5.1 Always-visible rule (non-negotiable) + +The **Bar crossing signal MUST be visible even if the “Bar” toggle is OFF.** +The toggle controls **details/overlays**, not the indicator itself. + +### 5.2 Levels (v1) + +* Green: Safe +* Yellow: Caution +* Red: Danger + +### 5.3 Where it appears + +* Dock collapsed: traffic-light icon + tooltip +* Dock expanded: single row “Bar (São João)” with level + +### 5.4 Alerts mapping + +* Red → counts as P0 alert (increments Alerts badge) +* Yellow/Green → not counted (v1) + +--- + +## 6) Acceptance checklist (RJ addendum) + +* [ ] Layers top-right shows **base maps only** +* [ ] All operational toggles are in Fisher Dock only +* [ ] Dock collapses into one **Fisher** button with GPS + Alerts badges +* [ ] Dock never blocks Layers; auto-hides when `layers=1` +* [ ] In Guanabara area: AIS defaults ON (first visit) +* [ ] FMAP ON by default in RJ; shows “Near my location” rules when no port +* [ ] Bar signal always visible even if Bar toggle OFF; Red increments Alerts badge +* [ ] No toasts for these systems; errors are inline only + +--- + +## Appendix A — UI Strings (fixed) + +**Button (collapsed):** Fisher +**Badges:** GPS, Alerts +**Toggles (expanded):** GPS · Buoys · AIS · Bar · Zones · Signals · FMAP (Local resources) +**FMAP status:** Near my location (15 km) / Port resources +**Actions:** Go · Copy debug + +## Appendix B — Persisted preferences (v1) + +Persist per-device: + +* Dock anchor: Right / Left / Bottom +* Dock state: Collapsed / Expanded +* Toggles: last ON/OFF state (except Guanabara AIS default on first visit) + +## Appendix C — Fisher Standard Stack (RJ) and UI mapping (P0/P1/P2) + +### RJ scope (v1) +- **RJ core**: Maricá + Guanabara + Barra de São João. +- These rules affect **defaults** and **badges** only (they do not remove features). + +### P0 (showable MVP, navigation + safety) +1) **GPS / My location** +- **Lives in**: Dock (toggle **GPS**). +- **RJ default**: ON (asks permission; if denied, stays OFF with clear state). +- **Must render**: marker + permanent tooltip + accuracy circle when lat/lon exists (even if status is not "ok"). + +2) **Nautical signals (Seamarks / OpenSeaMap)** +- **Lives in**: Dock (toggle **Signals**). +- **RJ default**: ON. +- **Must render**: seamarks as a TileLayer (no custom panes). Debug counters: `tilesRequested/tilesLoaded/tilesError`. + +3) **AIS (vessels)** +- **Lives in**: Dock (toggle **AIS**). +- **RJ default**: + - **Guanabara**: ON by default. + - Outside Guanabara: OFF by default (visual noise). +- **UX note**: AIS is operational, never inside the header "Layers" dropdown (Base Maps only). + +4) **Bar risk + traffic-light (semaforito)** +- **Lives in**: + - Dock (toggle **Bar**) controls the detailed overlay/panel. + - **Traffic-light indicator**: always visible in RJ-Barra de São João **even if "Bar" is OFF**. +- **RJ default**: traffic-light visible in Barra de São João. +- **Rule**: indicator does not depend on the toggle (safety beacon); toggle only controls details/capa. + +5) **Buoys (PNBOIA / fixed list)** +- **Lives in**: Dock (toggle **Buoys** + `Loaded/Visible` + **Go** + **Copy debug**). +- **RJ default**: ON (if performance allows). +- **Rule**: `Loaded` = full list; `Visible` = client-side bounds; **Go** = `fitBounds` to all (or "RJ-first" if applicable). + +6) **FMAP (Local resources: landing/ice/shelter)** +- **Lives in**: Dock (single row "FMAP (Local resources)" with counts + Go/Copy; Details only `debug=1`). +- **RJ default**: ON. +- **No port selected**: **Near my location** mode (see Appendix D). +- **Port selected**: **Port resources** mode. + +7) **Report (incidents/notes)** +- **Lives in**: Dock (**Report** button). +- **RJ default**: always visible, and must not be duplicated elsewhere. + +8) **Tides** +- **Lives in**: Forecast (Forecast screen) + a shortcut from Preparation ("Open Forecast"). +- **RJ default**: always accessible (avoid "Data on Hold" if a source exists). + +### P1 (next wave, high value) +- **Wind / Waves** (Forecast): values + trend + alerts. +- **Currents** (Forecast): direction/speed (if a source exists). +- **Channels / anchorage / restricted areas** (Dock: toggle **Zones**). +- **Bathymetry** (Preparation/Forecast): default OFF. + +### P2 (advanced, optional) +- **Smart Fishing**: recommendations, effort, models (never blocks P0). +- **Advanced planning**: approach planner, analytical layers, etc. + +### Golden rule (coherence) +- Header **"Layers" dropdown** = **Base Maps only**. +- All operational layers (GPS/AIS/Buoys/Bar/Signals/Zones/FMAP/Report) = **Dock only**. +- Preparation/Forecast/Smart Fishing are workflow screens, not operational toggles. + + +## Appendix D — Alerts (v1 thresholds) + "Near my location" criteria + RJ rules + +### D1) Alerts badge (Dock collapsed) +**Counts** (P0): +- Bar risk (traffic-light Yellow/Orange/Red) +- High wind +- High waves +- Nearby lightning (if available) +- Low visibility/fog (if available) +- (Optional P1) High current + +**Does NOT count**: +- "Data on Hold" (does not increment; may show `?` in debug if desired) +- Layer fetch errors (shown inline as "(fetch error)", not as a sea alert) + +**Badge logic**: +- `count = number of active alert types` (max display: `9+`) +- `color = worst active severity` (green/yellow/orange/red) + +### D2) v1 thresholds (initial, adjustable) +> Note: these are product v1 thresholds (not science). Calibrate with real fisher feedback and local data. + +**Wind (knots, sustained or gust if that's what you have)** +- Yellow: `>= 15 kt` +- Orange: `>= 20 kt` +- Red: `>= 25 kt` + +**Waves (significant height, meters)** +- Yellow: `>= 1.5 m` +- Orange: `>= 2.0 m` +- Red: `>= 2.5 m` + +**Lightning (if feed exists)** +- Yellow: `<= 30 km` within `<= 60 min` +- Orange: `<= 20 km` within `<= 30 min` +- Red: `<= 10 km` within `<= 30 min` + +**Visibility (if feed exists)** +- Yellow: `< 3 km` +- Orange: `< 2 km` +- Red: `< 1 km` + +**Bar (São João)** +- Traffic-light: + - Green: OK / no alert + - Yellow: caution + - Orange: high risk + - Red: not recommended + - Gray: no data (does not count as an alert; indicator still visible) + +### D3) "FMAP Near my location" (human criteria v1) +**Center (priority)** +1) GPS (my location) if available +2) Selected port (if any) +3) Viewport center + +**Radius** +- Base: **15 km** +- If results `<= 3`: expand to **30 km** +- If still empty: show "no data near", but keep **Go** to fit what exists (if any). + +**RJ hint** +- If `stateHint = RJ` and RJ-tagged resources exist: prioritize RJ within radius. +- If none within radius: fall back to normal filtering (no RJ filter). + +**What to show in Near (no port)** +- Essentials only: + - Landing + - Ice + - Shelter / safe anchorage (if exists) +- Everything else goes to "Details" (only `debug=1`). + +### D4) RJ defaults (v1) +- **Guanabara**: AIS ON by default. +- **Barra de São João**: traffic-light always visible even if "Bar" toggle is OFF. + +## Appendix E — P0 Acceptance Checklist (RJ) (pre-merge) + +> Goal: "no silent failure" + coherent UX. This must pass before merging PRs touching map/Dock/Forecast. + +### Minimal test routes +- Prod or local: + - `/map?start=realtime` + - `/map?start=realtime&layers=1` + - `/map?start=realtime&debug=1` + +### E1) UI invariants (fail any: NO MERGE) +- [ ] Header **"Layers" dropdown** contains **Base Maps only** (0 operational overlays). +- [ ] **Operational layers** (GPS/AIS/Buoys/Bar/Signals/Zones/FMAP/Report) live **only** in the **Fisher Dock**. +- [ ] Fisher Dock **does not cover** the "Layers" dropdown (safe margin/position). +- [ ] With `layers=1`, Dock **auto-hides** (no UI competition). +- [ ] Collapsible Dock: collapses into **one "Fisher" button** with badges (GPS/Alerts). No hidden controls. + +### E2) RJ defaults (expected behavior) +- [ ] **Signals (Seamarks)**: ON by default in RJ. +- [ ] **FMAP**: ON by default in RJ. +- [ ] **AIS**: ON by default **only in Guanabara**. +- [ ] **Barra São João**: traffic-light indicator **always visible** even if "Bar" toggle is OFF. + +### E3) Seamarks (Signals) no silent failure +- [ ] With "Signals" ON, in `debug=1` Copy debug shows: + - `signals.tilesRequested > 0` after a few seconds + - `signals.tilesLoaded` increases (or `tilesError` increases if there is a real issue) +- [ ] If tiles don't load, status is visible (counters) and the screen does not crash. + +### E4) PNBOIA (Buoys) resilient +- [ ] "Buoys" ON shows `Loaded/Visible` (does not stay at 0 forever without explanation). +- [ ] If fetch is slow: + - [ ] **slow-warning** around ~8s (inline, no toast). + - [ ] real **abort** at 30s (if applicable) with reason visible in debug (`fetchErrorName/abortMs`). +- [ ] **Fail-open**: if there was a last good list, it does NOT get cleared on error/timeout/empty. +- [ ] **Go** appears when `Loaded > 0 && Visible == 0` and does `fitBounds` (RJ-first preference if applicable). +- [ ] **Copy debug** includes at least: + - `version.gitCommit`, `lastFetchUrl`, `httpStatus`, `error/fetchErrorName`, `loadedCount`, `visibleCount`, `zoom`, `bounds`, `loadingMs`, `abortMs`, `slow`. + +### E5) FMAP (Local resources) usable, not a "wall of points" +- [ ] Dock shows **one row** "FMAP (Local resources)" with counts + Go + Copy. +- [ ] No raw "point/polygon/point…" listing in normal UI. +- [ ] "Details" exists **only with `debug=1`**. +- [ ] No port: **Near my location** mode (15 km, expands to 30 km if <=3 results). +- [ ] With port: **Port resources** mode. + +### E6) Alerts badge (Dock collapsed) +- [ ] Alerts badge counts only sea alerts (Bar/Wind/Waves/Lightning/Visibility/Current if available). +- [ ] "Data on Hold" does NOT increment the badge. +- [ ] Layer fetch errors do NOT increment the badge (shown inline as "(fetch error)"). + +### E7) No-crash guarantee +- [ ] Toggling Signals, FMAP, Buoys, AIS, Zones, Bar repeatedly does not break the map. +- [ ] No "Something went wrong" screens appear. + +## Appendix F — Sources of Truth + "Data on Hold" Policy (v1, RJ) + +> Purpose: prevent silent failure, prevent false promises, keep fisher-first UX. +> Golden rule: if data is unavailable, say it inline, keep last-good data (fail-open), and offer copyable debug without console. + +### F1) Sources of Truth + +**Base map (tiles)** +- SoT: selected BaseMap provider (OSM/Esri/OpenTopo). +- UI: must always work (if it fails, treat as critical connectivity issue). + +**Seamarks / Nautical signals (OpenSeaMap)** +- SoT: tiles `https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png` +- UI: "Signals" toggle (Dock). +- Debug telemetry: `signals.tilesRequested/tilesLoaded/tilesError`. + +**GPS (My Location)** +- SoT: browser Geolocation and/or backend last-known if available. +- UX: if lat/lon exists, render marker + accuracy circle even when `status != ok`. +- States: `OK` | `No permission` | `Unavailable` | `Stale`. + +**AIS (vessels)** +- SoT: AIS backend (when available + healthy). +- RJ default: ON by default in Guanabara. +- If "Data on Hold", toggle remains visible and shows "On hold". + +**PNBOIA (fixed buoys)** +- Primary SoT: `/api/v1/pnboia/list` (full list). +- Secondary SoT: local cache (TTL) → fallback seed (last resort). +- UX: always shows `Loaded/Visible`. +- Go: shown when `Loaded>0 && Visible==0`. +- Fail-open: never clears last-good list on errors. + +**FMAP (Local resources: landing, ice, shelter, etc.)** +- SoT: versioned seed (v1) + future backend SoT (v2+). +- Default: ON by default, but "near my location/port" rendering avoids a wall of markers. +- Normal UI: one row with counts + Go + Copy. Details only in `debug=1`. + +**Bar (São João)** +- SoT: bar-risk/status module (may be placeholder in v1). +- RJ UX: Barra São João traffic-light is ALWAYS visible even if toggle is OFF. + +**Zones** +- SoT: internal layer (MapLibre/Leaflet), dependent on data availability. +- RJ default: ON if performance remains acceptable. + +### F2) "Data on Hold" Contract (v1) + +**Definition** +- "Data on Hold" means: UI exists and stays reachable, but data is intentionally not published yet (backend incomplete, quality issues, or product decision). + +**Display rules (NO TOASTS)** +- Never auto-toast for hold or fetch errors. +- Always inline in the toggle row: + - `On hold` (product hold) + - `(fetch error)` (technical error) + - `slow` (> 8s) + - `abort` (30s abort, reason in Copy debug) + +**Fail-open (always)** +- If last-good data exists, keep showing it even if refresh fails. +- "On hold" does not delete last-good data, it only blocks refresh. + +**Badges (collapsed Dock)** +- "Data on Hold" does not increment Alerts. +- "fetch error" does not increment Alerts. +- Alerts badge counts only sea alerts (Bar/Wind/Waves/Lightning/Visibility/Current if available). + +### F3) UX states per toggle (fixed text) + +Recommended one-line state format: +- `OK · Loaded: N · Visible: M` +- `On hold` +- `(fetch error)` +- `slow…` +- `abort` + +Buttons: +- `Go` (fitBounds) +- `Copy debug` + +### F4) Copy debug (minimum required fields) + +Copy debug must include: +- `href` +- `version.gitCommit` +- `map.zoom`, `map.bounds` +- Per enabled module: + - `status`, `error` + - key counters (Loaded/Visible or tiles*) + - `lastFetchUrl`, `httpStatus` when applicable + - `loadingMs`, `abortMs`, `fetchErrorName` when applicable + - `stateHint` (RJ) + +### F5) "Near my location" criteria (FMAP v1) + +Center: +1) GPS if present +2) Selected port +3) Viewport center + +Radius: +- 15 km base +- if <=3 results: expand to 30 km +- if empty: show "no data near", keep Go (to frame what exists if any) + +RJ hint: +- If `stateHint=RJ` and RJ-tagged items exist, prioritize RJ items within the radius. + +## Appendix H — RJ Defaults + Alerts Thresholds (v1) +> Scope: RJ (Maricá, Guanabara) + Barra de São João. +> Goal: consistent defaults, explicit thresholds, zero silent failure. + +### H1) RJ Defaults +**H1.1 AIS default ON in Guanabara** +- Rule: AIS initializes **ON** when the “context” is inside Guanabara. +- Context priority: (1) Selected port (2) GPS (my location) (3) Viewport center. +- “Guanabara (v1)” criterion: context point inside the bay (approx bbox in code, documented as “GuanabaraBBoxV1”). +- Persistence: if the user manually toggled AIS, **user preference wins** (do not override). + +**H1.2 Barra São João: traffic-light always visible** +- Bar status is **always visible** as a traffic light (🟢🟡🔴⚪) even if the “Bar” toggle is OFF. +- The “Bar” toggle controls the **overlay layer**, but never hides the traffic light. +- Location: Expanded Dock (row “Barra São João”) + Collapsed Dock (badge). + +### H2) “Alerts” badge: what counts and what does NOT count +**Counts as Alerts:** +- Barra São João is **🟡** or **🔴** (counts 1). +- Official active alerts (when available) in the context area: **Storm/Sea/Navigation** (1 per category, max 3). +- AIS near-risk (when CPA logic exists): CPA < 0.5 nm within 15 min (counts 1). + +**Does NOT count:** +- Data on hold / fetch error / slow… / seamarks tilesError +- PNBOIA empty when fail-open is active (fallback/cache) +- FMAP no data nearby + +### H3) Concrete thresholds (v1) for Barra São João +> Operational guidance. If data is missing: ⚪ On hold. + +- ⚪ On hold: insufficient data. +- 🟢 Safe: Hs < 1.0 m **and** wind < 15 kt +- 🟡 Caution: 1.0 ≤ Hs < 1.5 **or** 15 ≤ wind < 25 kt +- 🔴 Unsafe: Hs ≥ 1.5 **or** wind ≥ 25 kt +- Ebb escalation (if available): 🟢→🟡, 🟡→🔴. + +### H4) Fixed UI strings (new only) +- “Barra São João” +- “Bar risk: Safe / Caution / Unsafe / On hold” +- “AIS default ON in Guanabara (RJ)” + +## Appendix G — Fisher Dock "collision-free" (RJ v1) + +### G.1 Goal +The Fisher Dock must always be reachable and must **never cover the "Layers" control** (Base Maps dropdown). On desktop it can live as an overlay, but with collision rules and auto-repositioning. On mobile it must stay collapsible and low-noise. + +### G.2 Dock states (final) +**Collapsed (default):** +- A single button/pill labeled **"Fisher"**. +- Minimal badges: + - **GPS**: ON/OFF (green/gray). + - **Alerts**: numeric count (e.g. "2") or a red dot if any active alert exists. + +**Expanded:** +- Shows operational toggles + status (e.g. Buoys Loaded/Visible + Go/Copy). +- Closes when: + - tapping "Fisher" again, + - tapping the map (empty area), + - opening `layers=1` (see G.5). + +### G.3 Allowed positions (3 anchors) +The Dock may only be placed in one of: +1) **Right-center** (default desktop) +2) **Left-center** (desktop fallback) +3) **Bottom** (default mobile + final fallback) + +The anchor is a persisted preference (Appendix B), but **collision rules override it**. + +### G.4 "Never cover Layers" (forbidden zone) +Define a **forbidden no-overlap zone** protecting the top-right "Layers" control: + +- **Layers Safe Zone (desktop):** + - top-right anchored rectangle + - outer margin: 12 px + - reserved minimum size: **W=260 px, H=340 px** + - includes the dropdown-open footprint (not just the button). + +**Invariant:** the Fisher Dock bounding box (collapsed or expanded) must **not intersect** this zone. + +### G.5 Placement + auto-reposition rules +**Desktop (≥ 1024 px):** +1) Try **Right-center**. +2) If it collides with the Layers Safe Zone → move to **Left-center**. +3) If it still collides (or space is tight) → move to **Bottom**. +4) If the user manually selected an anchor, honor it only if it does not collide. + +**Tablet / Mobile (< 1024 px):** +- Default: **Bottom** collapsed. +- Expanded opens upward to avoid covering top controls. + +**Auto-hide:** +- When `layers=1` is active (Layers panel open): the Fisher Dock must be **fully hidden** (not just collapsed). + +### G.6 Size + visual noise limits +To keep the map usable: +- Expanded Dock recommended width: ~160–190 px. +- Expanded state should keep "one row per feature", avoid long lists. +- "FMAP Signals (seed)" must be **a single row** (counts + Go/Copy; Details only in `debug=1`), never a raw "Point/Polygon/Point…" dump. + +### G.7 RJ defaults (without breaking the contract) +- **Guanabara:** AIS may be **ON by default**. +- **Barra de São João:** the **bar traffic-light indicator** must be **always visible** even if the "Bar" toggle is OFF. + - OFF means "no detailed overlays", but the minimal indicator remains visible. + +### G.8 Acceptance criteria (checklist) +- [ ] On desktop, Dock (collapsed and expanded) **never covers** Layers (button nor dropdown). +- [ ] With `layers=1`, Dock disappears (auto-hide). +- [ ] On mobile, Dock starts collapsed at Bottom and does not cover top controls. +- [ ] FMAP shows **one row** only (no point/polygon spam). +- [ ] Bar indicator always visible in Barra de São João (even when Bar OFF). +- [ ] GPS/Alerts badges visible when collapsed. + +--- + +*RJ Addendum to Fisher UI Contract v1. Review on each Dock/Bar/FMAP change in RJ.* + +**Merge rule:** Only merge if **Appendix E (P0 Checklist)** passes and **Appendix F (Sources of Truth + Data on Hold)** remains intact. Any PR touching map/Dock/Forecast must state: “✅ passes Appendix E + Appendix F”. diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.md b/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.md new file mode 100644 index 000000000..56b96adf1 --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.md @@ -0,0 +1,645 @@ +# FISHER UI CONTRACT v1 — Addendum RJ (Maricá / Guanabara / Barra de São João) + +**Complemento de:** [FISHER_UI_CONTRACT_v1](./FISHER_UI_CONTRACT_v1.md) · [Badges](./FISHER_UI_CONTRACT_v1_badges.md) +**Idioma:** ES · **Otras versiones:** [EN](./FISHER_UI_CONTRACT_v1_RJ_addendum.en.md) · [PT](./FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md) + +**Alcance:** Este addendum define **defaults RJ** y el comportamiento del **señal de Barra**. +**Usuario objetivo:** Pescadores (UI rápida, predecible, cero “caza del botón”). + +--- + +## 0) Invariantes no negociables (v1) + +1. **“Layers” (top-right) = solo Base maps.** Sin overlays/toggles operativos dentro de Layers. +2. **Las capas operativas viven solo en el Fisher Dock** (GPS, Boyas, AIS, Barra, Zonas, Señales, FMAP). +3. **Sin silent failure:** cualquier fallo de fetch/load debe mostrarse **inline** (texto pequeño), nunca como toast. +4. **El Dock no debe bloquear el dropdown Layers.** El usuario debe poder abrir Layers siempre. + +--- + +## 1) Fisher Dock (colapsable) — comportamiento definitivo + +### 1.1 Estado expandido + +Muestra el conjunto completo de controles operativos: + +* **GPS** +* **Boyas (PNBOIA)** +* **AIS** +* **Barra** +* **Zonas** +* **Señales (Seamarks)** +* **FMAP (Recursos locales / seed)** + +Cada fila puede mostrar: + +* Estado ON/OFF +* línea de estado pequeña (Loaded/Visible; “(fetch error)” inline; etc.) +* **Go** y **Copy debug** donde aplique + +### 1.2 Estado colapsado + +Se reduce a **un solo botón**: + +* Etiqueta: **Fisher** +* Badges (indicadores mini): + + * **GPS badge:** ON/OFF (punto verde/gris o “GPS ON/OFF”) + * **Alerts badge:** número o punto rojo si hay alguna alerta **P0** activa (ver §4) + +### 1.3 Posicionamiento (sistema de 3 ranuras) + +El Dock admite exactamente uno de estos anclajes: + +* **Derecha-centro (desktop por defecto):** centrado vertical, margen derecho, nunca solapa Layers (top-right). +* **Izquierda-centro:** mismas reglas, espejado. +* **Abajo (móvil por defecto):** por encima de la UI del sistema, no tapa controles críticos del mapa. + +### 1.4 Regla auto-hide + +Cuando `layers=1` (overlay Layers abierto), **el Dock se auto-oculta** (colapsado o expandido), dejando Layers libre. + +### 1.5 Defaults + +* Desktop: **Derecha-centro**, **Expandido en primera visita**, luego persistir preferencia del usuario. +* Móvil: **Abajo**, **Colapsado por defecto**, luego persistir preferencia del usuario. + +--- + +## 2) Heurísticas región RJ (v1) + +Heurísticas legibles; se pueden ajustar después sin cambiar el contrato de UI. + +### 2.1 State hint: RJ + +**RJ hint es TRUE** si el **centro del viewport del mapa** está dentro de: + +* lat: **[-24.6, -20.7]** +* lon: **[-44.9, -40.7]** + +Cuando `stateHint = "RJ"`, aplican reglas de preferencia (FMAP y Go-to-buoys pueden priorizar ítems etiquetados RJ). + +### 2.2 Guanabara (AIS ON por defecto) + +**Guanabara hint es TRUE** si el centro del viewport está dentro de: + +* lat: **[-23.10, -22.70]** +* lon: **[-43.40, -43.00]** + +Regla: + +* Si Guanabara hint es TRUE: **AIS = ON por defecto** (primera visita, salvo que el usuario ya eligió). +* En caso contrario: AIS por defecto sigue OFF (salvo que el usuario ya eligió). + +### 2.3 Maricá / Barra de São João (contexto) + +Cajas auxiliares opcionales (para priorización, no requeridas para corrección): + +* **Maricá aprox:** lat [-23.05, -22.80], lon [-43.10, -42.70] +* **Barra de São João aprox:** lat [-22.62, -22.30], lon [-42.05, -41.75] + +--- + +## 3) FMAP “Near my location” (v1) + +FMAP está **ON por defecto en RJ**, pero lo que muestra depende del contexto. + +### 3.1 Selección de centro (prioridad) + +Elegir el “centro” FMAP en este orden: + +1. **GPS (“Mi ubicación”)** si está disponible +2. **Puerto seleccionado** si no hay GPS +3. **Centro del viewport** como fallback + +### 3.2 Reglas de radio + +* Radio base: **15 km** +* Si resultados ≤ 3: expandir a **30 km** +* Si sigue vacío: mostrar nota inline **“No data near (30 km)”** pero mantener **Go** habilitado para encuadrar lo que exista (si hay ítems globales). + +### 3.3 Preferencia RJ + +Si `stateHint = "RJ"` y los ítems FMAP están etiquetados RJ: + +* Preferir ítems RJ **dentro del radio** +* Si no hay ninguno dentro del radio, volver a la selección normal (sin filtro RJ). + +### 3.4 Qué se muestra cuando ON sin puerto seleccionado + +Solo esenciales (v1): + +* **Desembarque / descarga** +* **Hielo** +* **Refugio / fondeo seguro** (si existe) + +El resto queda en: + +* **Details** (solo visible con `debug=1`) + +### 3.5 Texto UI + +* Etiqueta toggle: **FMAP (Recursos locales)** +* Ejemplos de línea de estado: **Near my location (15 km)**, **Port resources** +* Botones: **Go**, **Copy debug** + +--- + +## 4) Alerts badge — umbrales concretos (v1) + +El badge Alerts cuenta solo ítems **P0**. Todo lo demás es informativo y no incrementa el badge. + +### 4.1 Alertas P0 (cuentan) + +* **Nivel Barra = Rojo (Danger)** → cuenta como **1** +* (Futuro) **Aviso meteorológico severo** explícitamente “danger/severe” → cuenta como **1** +* (Futuro) Aviso de autoridad “No navegar / puerto cerrado” → cuenta como **1** + +### 4.2 No cuenta (v1) + +* Estados amarillo/caution +* “Data on hold” +* “Backend unavailable” +* PNBOIA lento/timeout si existe fallback/cache + +Comportamiento del badge: Dock colapsado muestra **punto rojo** o **número** si P0 > 0. Dock expandido puede mostrar “Alerts: N” inline (opcional). + +--- + +## 5) Señal de Barra (“semáforo”) — regla RJ + +### 5.1 Regla siempre visible (no negociable) + +La **señal de Barra DEBE ser visible aunque el toggle “Barra” esté OFF.** +El toggle controla **detalles/overlays**, no el indicador en sí. + +### 5.2 Niveles (v1) + +* Verde: Safe +* Amarillo: Caution +* Rojo: Danger + +### 5.3 Dónde aparece + +* Dock colapsado: icono semáforo + tooltip +* Dock expandido: fila “Bar (São João)” con nivel + +### 5.4 Mapeo a Alerts + +* Rojo → cuenta como alerta P0 (incrementa badge Alerts) +* Amarillo/Verde → no cuenta (v1) + +--- + +## 6) Checklist de aceptación (addendum RJ) + +* [ ] Layers top-right muestra **solo base maps** +* [ ] Todos los toggles operativos están solo en Fisher Dock +* [ ] Dock se colapsa en un botón **Fisher** con badges GPS + Alerts +* [ ] Dock no bloquea Layers; auto-oculta cuando `layers=1` +* [ ] En zona Guanabara: AIS por defecto ON (primera visita) +* [ ] FMAP ON por defecto en RJ; muestra reglas “Near my location” sin puerto +* [ ] Señal Barra siempre visible aunque toggle Barra OFF; Rojo incrementa badge Alerts +* [ ] Sin toasts para estos sistemas; errores solo inline + +--- + +## Apéndice A — Textos fijos de UI + +**Botón (colapsado):** Fisher +**Badges:** GPS, Alerts +**Toggles (expandido):** GPS · Boyas · AIS · Barra · Zonas · Señales · FMAP (Recursos locales) +**Estado FMAP:** Near my location (15 km) / Port resources +**Acciones:** Go · Copy debug + +## Apéndice B — Preferencias persistidas (v1) + +Persistir por dispositivo: + +* Ancla del dock: Derecha / Izquierda / Abajo +* Estado del dock: Colapsado / Expandido +* Toggles: último estado ON/OFF (excepto AIS default ON en Guanabara en primera visita) + +## Apéndice C — Lista estándar del pescador (RJ) y mapeo UI (P0/P1/P2) + +### Alcance RJ (v1) +- **RJ core**: Maricá + Guanabara + Barra de São João. +- Estas reglas solo afectan **defaults** y **badges** (no cambian la disponibilidad de toggles). + +### P0 (MVP mostrable, navegación y seguridad) +1) **GPS / Mi ubicación** +- **Dónde vive**: Dock (toggle **GPS**). +- **Default RJ**: ON (pide permiso; si no hay permiso, queda OFF con estado claro). +- **Qué debe mostrar**: marker + tooltip permanente + accuracy circle cuando hay lat/lon (aunque el status no sea "ok"). + +2) **Señales náuticas (Seamarks / OpenSeaMap)** +- **Dónde vive**: Dock (toggle **Señales**). +- **Default RJ**: ON. +- **Qué debe mostrar**: seamarks como TileLayer (sin panes custom). Contadores en debug: `tilesRequested/tilesLoaded/tilesError`. + +3) **AIS (barcos)** +- **Dónde vive**: Dock (toggle **AIS**). +- **Default RJ**: + - **Guanabara**: ON por defecto. + - Fuera de Guanabara: OFF por defecto (ruido visual). +- **Nota UX**: AIS es "operativo", nunca dentro del dropdown "Layers" (que es solo Base Maps). + +4) **Barra (riesgo de barra) + semaforito** +- **Dónde vive**: + - Dock (toggle **Barra**) controla el overlay/explicación detallada. + - **Semaforito**: siempre visible en RJ-Barra de São João **aunque "Barra" esté OFF**. +- **Default RJ**: semaforito visible en Barra de São João. +- **Regla**: el semaforito NO depende del toggle (es "safety beacon"); el toggle solo controla "detalles/capa". + +5) **Boyas (PNBOIA / lista fija)** +- **Dónde vive**: Dock (toggle **Boyas** + estado `Loaded/Visible` + **Go** + **Copy debug**). +- **Default RJ**: ON (si el rendimiento lo permite). +- **Regla**: `Loaded` = lista completa; `Visible` = por bounds client-side; **Go** = `fitBounds` a todas (o "RJ-first" si aplica). + +6) **FMAP (Recursos locales: desembarque/hielo/refugio)** +- **Dónde vive**: Dock (fila única "FMAP (Recursos locales)" con counts + Go/Copy; Details solo `debug=1`). +- **Default RJ**: ON. +- **Sin puerto seleccionado**: modo **Near my location** (ver Apéndice D). +- **Con puerto seleccionado**: modo **Port resources**. + +7) **Reportar (señales/incidentes)** +- **Dónde vive**: Dock (toggle/botón **Reportar**). +- **Default RJ**: visible siempre, pero no debe duplicarse en dos lugares. + +8) **Mareas (Tides)** +- **Dónde vive**: Forecast (pantalla Forecast) + atajo desde Preparation ("Open Forecast"). +- **Default RJ**: siempre accesible (no "Data on Hold" si hay fuente). + +### P1 (muy valioso, siguiente ola) +- **Viento / Olas** (Forecast): valores + tendencia + alertas. +- **Corrientes** (Forecast): dirección/velocidad (si existe fuente). +- **Canales / zonas de fondeo / áreas restringidas** (Dock: toggle **Zonas**). +- **Bathymetry** (Preparation/Forecast): por defecto OFF. + +### P2 (sofisticado, opcional) +- **Smart Fishing**: recomendaciones, esfuerzo, modelos (nunca bloquea P0). +- **Optimización avanzada**: "approach planner", capas analíticas, etc. + +### Regla de oro (coherencia) +- Dropdown **"Layers" (header)** = **solo Base Maps**. +- **Todo lo operativo** (GPS/AIS/Boyas/Barra/Señales/Zonas/FMAP/Reportar) = **solo Dock**. +- Preparation/Forecast/Smart Fishing son "pantallas" (workflow), no toggles operativos. + + +## Apéndice D — Alerts (umbrales v1) + criterios "Near my location" + reglas RJ + +### D1) Badge de Alerts (Dock colapsado) +**Qué cuenta** (P0): +- Barra (semaforito Amarillo/Naranja/Rojo) +- Viento alto +- Olas altas +- Rayos cercanos (si existe fuente) +- Visibilidad baja / niebla (si existe fuente) +- (Opcional P1) Corriente alta + +**Qué NO cuenta**: +- "Data on Hold" (no suma, pero puede mostrar `?` en debug si querés) +- Errores de fetch de capas (eso va como "(fetch error)" inline, no como alerta de mar) + +**Cómo se calcula el badge**: +- `count = cantidad de tipos de alerta activos` (máximo mostrado: `9+`) +- `color = severidad máxima activa` (verde/amarillo/naranja/rojo) + +### D2) Umbrales v1 (iniciales, ajustables) +> Nota: Estos umbrales son "producto v1" (no ciencia). Se calibran con feedback real (pescadores) y datos locales. + +**Viento (knots, sostenido o ráfaga si es lo que tenés)** +- Amarillo: `>= 15 kt` +- Naranja: `>= 20 kt` +- Rojo: `>= 25 kt` + +**Olas (altura significativa, metros)** +- Amarillo: `>= 1.5 m` +- Naranja: `>= 2.0 m` +- Rojo: `>= 2.5 m` + +**Rayos (si hay feed)** +- Amarillo: actividad a `<= 30 km` en `<= 60 min` +- Naranja: `<= 20 km` en `<= 30 min` +- Rojo: `<= 10 km` en `<= 30 min` + +**Visibilidad (si hay feed)** +- Amarillo: `< 3 km` +- Naranja: `< 2 km` +- Rojo: `< 1 km` + +**Barra (São João)** +- Semaforito: + - Verde: condiciones OK / sin alerta + - Amarillo: precaución + - Naranja: riesgo alto + - Rojo: no recomendable + - Gris: sin datos (no suma al badge; pero el semaforito sigue visible) + +### D3) "FMAP Near my location" (criterio humano v1) +**Centro (prioridad)** +1) GPS (mi ubicación) si está disponible +2) Puerto seleccionado (si existe) +3) Centro del viewport + +**Radio** +- Base: **15 km** +- Si resultados `<= 3`: expandir a **30 km** +- Si sigue vacío: mostrar "no data near", pero mantener **Go** para encuadrar lo disponible (si hay). + +**RJ hint** +- Si `stateHint = RJ` y existen recursos etiquetados RJ: priorizar RJ dentro del radio. +- Si no hay RJ dentro del radio: caer al filtro normal (sin RJ). + +**Qué se muestra en Near (sin puerto)** +- Solo esenciales: + - Desembarque + - Hielo + - Refugio / fondeo seguro (si existe) +- El resto va a "Details" (solo `debug=1`). + +### D4) Reglas RJ de defaults (v1) +- **Guanabara**: AIS ON por defecto. +- **Barra de São João**: semaforito siempre visible aunque "Barra" OFF. + +## Apéndice E — Acceptance Checklist P0 (RJ) (pre-merge) + +> Objetivo: "no silent failure" + UX coherente. Este checklist debe pasar antes de mergear PRs que toquen mapa/Dock/Forecast. + +### Rutas de prueba (mínimas) +- Prod o local: + - `/map?start=realtime` + - `/map?start=realtime&layers=1` + - `/map?start=realtime&debug=1` + +### E1) Invariantes UI (si falla alguno: NO MERGE) +- [ ] Dropdown **"Layers" (header)** contiene **solo Base Maps** (0 overlays operativos). +- [ ] **Capas operativas** (GPS/AIS/Boyas/Barra/Señales/Zonas/FMAP/Reportar) viven **solo** en el **Fisher Dock**. +- [ ] Fisher Dock **no tapa** el dropdown "Layers" (hay margen/posición segura). +- [ ] Con `layers=1`, el Dock **auto-hide** (no compite con la UI de Layers). +- [ ] Dock colapsable: colapsa a **1 botón "Fisher"** con badges (GPS/Alerts). Sin "escondites". + +### E2) Defaults RJ (comportamiento esperado) +- [ ] **Señales (Seamarks)**: ON por defecto en RJ. +- [ ] **FMAP**: ON por defecto en RJ. +- [ ] **AIS**: ON por defecto **solo en Guanabara**. +- [ ] **Barra São João**: **semaforito visible siempre**, aunque toggle "Barra" esté OFF. + +### E3) Seamarks (Señales) no silenciosas +- [ ] Con "Señales" ON, en `debug=1` el Copy debug muestra: + - `signals.tilesRequested > 0` luego de unos segundos + - `signals.tilesLoaded` sube (o `tilesError` sube si hay problema real) +- [ ] Si no cargan tiles, se ve estado (contadores) y no crashea la pantalla. + +### E4) PNBOIA (Boyas) resiliente +- [ ] Toggle "Boyas" ON muestra estado `Loaded/Visible` (no queda siempre en 0 sin explicación). +- [ ] Si el fetch tarda: + - [ ] **slow-warning** a los ~8s (inline, sin toast). + - [ ] **abort** real a 30s (si aplica) con razón visible en debug (`fetchErrorName/abortMs`). +- [ ] **Fail-open**: si había lista buena previa, NO se vacía por error/timeout/empty. +- [ ] **Go** aparece cuando `Loaded > 0 && Visible == 0` y hace `fitBounds` (preferencia RJ-first si aplica). +- [ ] **Copy debug** incluye al menos: + - `version.gitCommit`, `lastFetchUrl`, `httpStatus`, `error/fetchErrorName`, `loadedCount`, `visibleCount`, `zoom`, `bounds`, `loadingMs`, `abortMs`, `slow`. + +### E5) FMAP (Recursos locales) usable y no "choclazo" +- [ ] En Dock se ve **1 sola fila** "FMAP (Recursos locales)" con counts + Go + Copy. +- [ ] No hay listado crudo "point/polygon/point…" en UI normal. +- [ ] "Details" existe **solo con `debug=1`**. +- [ ] Sin puerto: modo **Near my location** (15 km, expandible a 30 km si <=3 resultados). +- [ ] Con puerto: modo **Port resources**. + +### E6) Alerts badge (Dock colapsado) +- [ ] Alerts badge cuenta solo alertas de mar (Barra/Viento/Olas/Rayos/Visibilidad/Corriente si existe). +- [ ] "Data on Hold" NO incrementa el badge. +- [ ] Errores de fetch de capas NO incrementan el badge (van inline "(fetch error)"). + +### E7) No-crash guarantee +- [ ] Activar/desactivar: Señales, FMAP, Boyas, AIS, Zonas, Barra repetidas veces NO rompe el mapa. +- [ ] No aparecen pantallas "Something went wrong". + +## Apéndice F — Sources of Truth + "Data on Hold" Policy (v1, RJ) + +> Propósito: evitar "silent failure", evitar promesas falsas, y mantener UX pescador-first. +> Regla de oro: si un dato no está disponible, el sistema lo dice *inline*, muestra el último bueno si existe (fail-open), y ofrece debug copiables sin consola. + +### F1) Fuentes de Verdad (Source of Truth) + +**Mapa base (tiles)** +- SoT: proveedor del BaseMap seleccionado (OSM/Esri/OpenTopo). +- UI: siempre disponible (si falla, se considera falla crítica de conexión). + +**Seamarks / Señales náuticas (OpenSeaMap)** +- SoT: tiles `https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png` +- UI: toggle "Señales" (Dock). +- Telemetría visible (debug): `signals.tilesRequested/tilesLoaded/tilesError`. + +**GPS (My Location)** +- SoT: navegador (Geolocation API) y/o "last known" del backend si existe. +- UX: si hay lat/lon, se renderiza marcador + círculo de precisión aunque `status != ok`. +- Estados: `OK` | `No permission` | `Unavailable` | `Stale`. + +**AIS (embarcaciones)** +- SoT: backend AIS (cuando exista y esté healthy). +- Default RJ: ON por defecto en Guanabara. +- Si AIS está "Data on Hold", el toggle sigue visible, pero muestra "On hold". + +**PNBOIA (boias fixas)** +- SoT primario: `/api/v1/pnboia/list` (lista completa). +- SoT secundarios: cache local (TTL) → fallback seed (último recurso). +- UX: siempre muestra `Loaded/Visible`. +- Go: aparece si `Loaded>0 && Visible==0`. +- Fail-open: no vacía la última lista buena. + +**FMAP (Recursos locais: desembarque, gelo, refúgio, etc.)** +- SoT: seed versionado (v1) + futura fuente backend (v2+). +- Default: ON por defecto, pero el render "near my location/port" evita choclazo. +- UI normal: 1 fila con counts + Go + Copy. Details solo `debug=1`. + +**Barra (São João)** +- SoT: módulo de riesgo/estado (hoy puede ser manual/placeholder). +- UX RJ: el semaforito de Barra São João es SIEMPRE visible aunque toggle OFF. + +**Zonas** +- SoT: capa interna (MapLibre/Leaflet), dependiente de datos disponibles. +- Default RJ: ON si no rompe rendimiento. + +### F2) Contrato "Data on Hold" (v1) + +**Definición** +- "Data on Hold" significa: *la UI existe, el usuario puede verla, pero los datos no se publican aún* (por backend incompleto, calidad insuficiente, o decisión editorial). + +**Reglas de visualización (NO TOASTS)** +- Nunca toast automático por "hold" o fetch error. +- Siempre inline, en la fila del toggle: + - `On hold` (si está deliberadamente deshabilitado por producto) + - `(fetch error)` (si hubo error técnico) + - `slow` (si tardó > 8s) + - `abort` (si abortó a 30s, con motivo en Copy debug) + +**Fail-open (siempre)** +- Si existe "last good data", se sigue mostrando aunque la nueva carga falle. +- "On hold" NO borra el último dato bueno, solo bloquea refresh. + +**Badges (Dock colapsado)** +- "Data on Hold" NO incrementa Alerts. +- "fetch error" NO incrementa Alerts. +- Alerts solo cuenta alertas de mar (Barra/Viento/Olas/Rayos/Visibilidad/Corriente si existe). + +### F3) Estados UX por toggle (texto fijo) + +Formato de estado recomendado (una sola línea): +- `OK · Loaded: N · Visible: M` +- `On hold` +- `(fetch error)` +- `slow…` +- `abort` + +Botones: +- `Go` (fitBounds) +- `Copy debug` + +### F4) Copy debug (mínimo obligatorio) + +Copy debug debe incluir: +- `href` +- `version.gitCommit` +- `map.zoom`, `map.bounds` +- Por módulo habilitado: + - `status`, `error` + - contadores clave (Loaded/Visible o tiles*) + - `lastFetchUrl`, `httpStatus` cuando aplique + - `loadingMs`, `abortMs`, `fetchErrorName` cuando aplique + - `stateHint` (RJ) + +### F5) Criterio "Near my location" (FMAP v1) + +Centro: +1) GPS si existe +2) Puerto seleccionado +3) Centro del viewport + +Radio: +- 15 km base +- si <=3 resultados: expandir a 30 km +- si vacío: mostrar "no data near", mantener Go (para encuadrar lo disponible si existe) + +RJ hint: +- Si `stateHint=RJ` y hay items RJ, priorizar RJ dentro del radio. + +## Apéndice H — RJ Defaults + Alerts Thresholds (v1) +> Alcance: RJ (Maricá, Guanabara) + Barra de São João. +> Objetivo: defaults coherentes, umbrales explícitos, cero “silent failure”. + +### H1) Defaults RJ (comportamiento por defecto) +**H1.1 AIS default ON en Guanabara** +- Regla: AIS se inicializa **ON** si el “contexto” cae en Guanabara. +- Contexto (prioridad): (1) Puerto seleccionado (2) GPS (mi ubicación) (3) Centro del viewport. +- Criterio “Guanabara (v1)”: punto de contexto dentro de la bahía (bbox aproximado en código, documentado como “GuanabaraBBoxV1”). +- Persistencia: si el usuario cambió AIS manualmente, **su preferencia manda** (no sobreescribir). + +**H1.2 Barra São João: semaforito siempre visible** +- Estado de barra **siempre visible** como semaforito (🟢🟡🔴⚪) incluso si toggle “Barra” está OFF. +- El toggle “Barra” controla la **capa/overlay**, pero no oculta el semaforito. +- Ubicación: Dock expandido (fila “Barra São João”) + Dock colapsado (badge). + +### H2) “Alerts” badge: qué cuenta y qué NO cuenta (v1) +**Cuenta (incrementa contador o enciende rojo):** +- Barra São João en **🟡** o **🔴** (cuenta 1). +- Alertas oficiales activas (cuando existan) en el área de contexto: **Storm/Sea/Navigation** (1 por categoría, máx 3). +- AIS “near-risk” (cuando exista): CPA < 0.5 nm dentro de 15 min (cuenta 1). + +**NO cuenta (nunca incrementa):** +- Data on hold / fetch error / slow… / seamarks tilesError +- PNBOIA vacío con fail-open (fallback/cache) +- FMAP sin data cerca + +### H3) Umbrales concretos (v1) para Barra São João (semaforito) +> Guía operativa. Si faltan datos: ⚪ On hold. + +- ⚪ On hold: datos insuficientes. +- 🟢 Safe: Hs < 1.0 m **y** viento < 15 kt +- 🟡 Caution: 1.0 ≤ Hs < 1.5 **o** 15 ≤ viento < 25 kt +- 🔴 Unsafe: Hs ≥ 1.5 **o** viento ≥ 25 kt +- Escalado por vazante/ebb (si existe): 🟢→🟡, 🟡→🔴. + +### H4) Textos fijos (solo nuevos) +- “Barra São João” +- “Bar risk: Safe / Caution / Unsafe / On hold” +- “AIS default ON in Guanabara (RJ)” + +## Apéndice G — Fisher Dock "collision-free" (RJ v1) + +### G.1 Objetivo +El Fisher Dock debe ser siempre accesible y **nunca tapar el control "Layers"** (dropdown de Base Maps). En desktop puede vivir "afuera" (overlay), pero con reglas de colisión y auto-reubicación. En mobile debe ser colapsable y de bajo ruido visual. + +### G.2 Estados del Dock (definitivo) +**Estado colapsado (default):** +- Se muestra un único botón/"pill" con texto **"Fisher"**. +- Incluye badges mínimos: + - **GPS**: ON/OFF (verde/gris). + - **Alerts**: contador (p.ej. "2") o punto rojo si hay alertas activas. + +**Estado expandido:** +- Muestra los toggles operativos + estados (p.ej. Boyas Loaded/Visible + Go/Copy). +- El Dock expandido se cierra al tocar: + - el botón "Fisher" nuevamente (toggle), + - o el mapa (click en área vacía), + - o abrir `layers=1` (ver G.5). + +### G.3 Posiciones permitidas (3 anclas) +El Dock puede ubicarse SOLO en una de estas posiciones: +1) **Right-center** (default desktop) +2) **Left-center** (fallback desktop) +3) **Bottom** (default mobile y último fallback) + +La posición es una preferencia persistida (ver Apéndice B), pero **queda subordinada a las reglas de colisión**. + +### G.4 Regla "no tapa Layers" (zona prohibida) +Se define una **zona prohibida** (no-overlap) para proteger el control "Layers" (Base Maps) en top-right: + +- **Layers Safe Zone (desktop):** + - rectángulo anclado al **top-right** + - margen exterior: 12 px + - tamaño mínimo reservado: **W=260 px, H=340 px** + - incluye el dropdown abierto (no solo el botón). + +**Invariante:** el bounding box del Fisher Dock (colapsado o expandido) **no puede intersectar** esta zona. + +### G.5 Reglas de colocación y auto-reubicación +**Desktop (≥ 1024 px):** +1) Intentar **Right-center**. +2) Si colisiona con Layers Safe Zone → mover a **Left-center**. +3) Si aún colisiona (o hay poco espacio) → mover a **Bottom**. +4) Si el usuario eligió ancla manualmente, respetarla solo si no colisiona. + +**Tablet / Mobile (< 1024 px):** +- Default: **Bottom** colapsado. +- Expandido abre "hacia arriba" para no tapar controles superiores. + +**Auto-hide:** +- Si `layers=1` está activo (Layers panel abierto): el Fisher Dock debe **ocultarse completamente** (no solo colapsarse). + +### G.6 Tamaños y "ruido visual" (límites) +Para mantener el mapa usable: +- Ancho recomendado del Dock expandido: ~160–190 px. +- En estado expandido, el Dock debe priorizar "1 fila por feature", evitando listas largas. +- "FMAP Señales (seed)" debe aparecer como **una sola fila** (counts + Go/Copy; Details solo `debug=1`), nunca como un listado crudo de "Point/Polygon/Point…". + +### G.7 RJ defaults (sin romper el contrato) +- **Guanabara:** AIS puede estar **ON por defecto**. +- **Barra de São João:** el **semaforito de barra** debe ser **siempre visible** (badge/indicador) aunque el toggle "Barra" esté OFF. + - OFF significa "no renderizar overlays detallados", pero el indicador mínimo permanece visible. + +### G.8 Criterios de aceptación (checklist) +- [ ] En desktop, el Dock (colapsado y expandido) **nunca tapa** el control Layers (botón ni dropdown). +- [ ] Al abrir `layers=1`, el Dock desaparece (auto-hide). +- [ ] En mobile, el Dock inicia colapsado en Bottom y no tapa controles superiores. +- [ ] FMAP aparece como **1 sola fila** (sin "choclazo" de points/polygons). +- [ ] Barra semaforito visible siempre en Barra de São João (aunque Barra OFF). +- [ ] Badges (GPS/Alerts) visibles en estado colapsado. + +--- + +*Addendum RJ al Fisher UI Contract v1. Revisar en cada cambio de Dock/Barra/FMAP en RJ.* + +**Regla de merge:** Solo se mergea si pasa **Apéndice E (Checklist P0)** y **Apéndice F (Sources of Truth + Data on Hold)** se mantiene intacto. Cualquier PR que toque mapa/Dock/Forecast debe indicar: «✅ passes Appendix E + Appendix F». diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md b/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md new file mode 100644 index 000000000..18b760fea --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md @@ -0,0 +1,645 @@ +# FISHER UI CONTRACT v1 — Aditamento RJ (Maricá / Guanabara / Barra de São João) + +**Complemento de:** [FISHER_UI_CONTRACT_v1](./FISHER_UI_CONTRACT_v1.pt.md) · [Badges](./FISHER_UI_CONTRACT_v1_badges.pt.md) +**Idioma:** PT · **Outras versões:** [ES](./FISHER_UI_CONTRACT_v1_RJ_addendum.md) · [EN](./FISHER_UI_CONTRACT_v1_RJ_addendum.en.md) + +**Âmbito:** Este aditamento define **defaults RJ** e o comportamento do **sinal de Barra**. +**Utilizador alvo:** Pescadores (UI rápida, previsível, zero “caça ao botão”). + +--- + +## 0) Invariantes não negociáveis (v1) + +1. **“Layers” (topo-direita) = apenas Base maps.** Sem overlays/toggles operacionais dentro de Layers. +2. **As camadas operacionais vivem só no Fisher Dock** (GPS, Boias, AIS, Barra, Zonas, Sinais, FMAP). +3. **Sem falha silenciosa:** qualquer falha de fetch/load deve ser mostrada **inline** (texto pequeno), nunca como toast. +4. **O Dock não pode bloquear o dropdown Layers.** O utilizador deve poder abrir Layers sempre. + +--- + +## 1) Fisher Dock (recolhível) — comportamento definitivo + +### 1.1 Estado expandido + +Mostra o conjunto completo de controlos operacionais: + +* **GPS** +* **Boias (PNBOIA)** +* **AIS** +* **Barra** +* **Zonas** +* **Sinais (Seamarks)** +* **FMAP (Recursos locais / seed)** + +Cada linha pode mostrar: + +* Estado ON/OFF +* linha de estado pequena (Loaded/Visible; “(fetch error)” inline; etc.) +* **Go** e **Copy debug** onde aplicar + +### 1.2 Estado recolhido + +Reduz-se a **um único botão**: + +* Etiqueta: **Fisher** +* Badges (indicadores mini): + + * **GPS badge:** ON/OFF (ponto verde/cinza ou “GPS ON/OFF”) + * **Alerts badge:** número ou ponto vermelho se houver alguma alerta **P0** activa (ver §4) + +### 1.3 Posicionamento (sistema de 3 posições) + +O Dock suporta exactamente um destes ancoragens: + +* **Direita-centro (desktop por defeito):** centrado verticalmente, margem direita, nunca sobrepõe Layers (topo-direita). +* **Esquerda-centro:** mesmas regras, espelhado. +* **Baixo (móvel por defeito):** acima da UI do sistema, não cobre controlos críticos do mapa. + +### 1.4 Regra auto-hide + +Quando `layers=1` (overlay Layers aberto), **o Dock auto-oculta-se** (recolhido ou expandido), deixando Layers livre. + +### 1.5 Defaults + +* Desktop: **Direita-centro**, **Expandido na primeira visita**, depois persistir preferência do utilizador. +* Móvel: **Baixo**, **Recolhido por defeito**, depois persistir preferência do utilizador. + +--- + +## 2) Heurísticas região RJ (v1) + +Heurísticas legíveis; podem ser afinadas depois sem alterar o contrato de UI. + +### 2.1 State hint: RJ + +**RJ hint é TRUE** se o **centro do viewport do mapa** estiver dentro de: + +* lat: **[-24.6, -20.7]** +* lon: **[-44.9, -40.7]** + +Quando `stateHint = "RJ"`, aplicam-se regras de preferência (FMAP e Go-to-buoys podem priorizar itens etiquetados RJ). + +### 2.2 Guanabara (AIS ON por defeito) + +**Guanabara hint é TRUE** se o centro do viewport estiver dentro de: + +* lat: **[-23.10, -22.70]** +* lon: **[-43.40, -43.00]** + +Regra: + +* Se Guanabara hint é TRUE: **AIS = ON por defeito** (primeira visita, salvo se o utilizador já escolheu). +* Caso contrário: AIS por defeito permanece OFF (salvo se o utilizador já escolheu). + +### 2.3 Maricá / Barra de São João (contexto) + +Caixas auxiliares opcionais (para priorização, não obrigatórias para correcção): + +* **Maricá aprox:** lat [-23.05, -22.80], lon [-43.10, -42.70] +* **Barra de São João aprox:** lat [-22.62, -22.30], lon [-42.05, -41.75] + +--- + +## 3) FMAP “Near my location” (v1) + +FMAP está **ON por defeito em RJ**, mas o que mostra depende do contexto. + +### 3.1 Seleção de centro (prioridade) + +Escolher o “centro” FMAP nesta ordem: + +1. **GPS (“Minha localização”)** se disponível +2. **Porto seleccionado** se não houver GPS +3. **Centro do viewport** como fallback + +### 3.2 Regras de raio + +* Raio base: **15 km** +* Se resultados ≤ 3: expandir para **30 km** +* Se continuar vazio: mostrar nota inline **“No data near (30 km)”** mas manter **Go** activo para enquadrar o que existir (se houver itens globais). + +### 3.3 Preferência RJ + +Se `stateHint = "RJ"` e os itens FMAP estão etiquetados RJ: + +* Preferir itens RJ **dentro do raio** +* Se não houver nenhum dentro do raio, voltar à selecção normal (sem filtro RJ). + +### 3.4 O que se mostra quando ON sem porto seleccionado + +Só essenciais (v1): + +* **Desembarque / descarga** +* **Gelo** +* **Refúgio / fundeio seguro** (se existir) + +O resto fica em: + +* **Details** (só visível com `debug=1`) + +### 3.5 Texto UI + +* Etiqueta toggle: **FMAP (Recursos locais)** +* Exemplos de linha de estado: **Near my location (15 km)**, **Port resources** +* Botões: **Go**, **Copy debug** + +--- + +## 4) Alerts badge — umbrais concretos (v1) + +O badge Alerts conta apenas itens **P0**. Tudo o resto é informativo e não incrementa o badge. + +### 4.1 Alertas P0 (contam) + +* **Nível Barra = Vermelho (Danger)** → conta como **1** +* (Futuro) **Aviso meteorológico severo** explicitamente “danger/severe” → conta como **1** +* (Futuro) Aviso de autoridade “Não navegar / porto fechado” → conta como **1** + +### 4.2 Não conta (v1) + +* Estados amarelo/caution +* “Data on hold” +* “Backend unavailable” +* PNBOIA lento/timeout se existir fallback/cache + +Comportamento do badge: Dock recolhido mostra **ponto vermelho** ou **número** se P0 > 0. Dock expandido pode mostrar “Alerts: N” inline (opcional). + +--- + +## 5) Sinal de Barra (“semaforo”) — regra RJ + +### 5.1 Regra sempre visível (não negociável) + +O **sinal de Barra DEVE ser visível mesmo com o toggle “Barra” OFF.** +O toggle controla **detalhes/overlays**, não o indicador em si. + +### 5.2 Níveis (v1) + +* Verde: Safe +* Amarelo: Caution +* Vermelho: Danger + +### 5.3 Onde aparece + +* Dock recolhido: ícone semáforo + tooltip +* Dock expandido: linha “Bar (São João)” com nível + +### 5.4 Mapeamento para Alerts + +* Vermelho → conta como alerta P0 (incrementa badge Alerts) +* Amarelo/Verde → não conta (v1) + +--- + +## 6) Checklist de aceitação (aditamento RJ) + +* [ ] Layers topo-direita mostra **apenas base maps** +* [ ] Todos os toggles operacionais estão só no Fisher Dock +* [ ] Dock recolhe num botão **Fisher** com badges GPS + Alerts +* [ ] Dock não bloqueia Layers; auto-oculta quando `layers=1` +* [ ] Na zona Guanabara: AIS por defeito ON (primeira visita) +* [ ] FMAP ON por defeito em RJ; mostra regras “Near my location” sem porto +* [ ] Sinal Barra sempre visível mesmo com toggle Barra OFF; Vermelho incrementa badge Alerts +* [ ] Sem toasts para estes sistemas; erros apenas inline + +--- + +## Apêndice A — Textos fixos de UI + +**Botão (colapsado):** Fisher +**Badges:** GPS, Alerts +**Toggles (expandido):** GPS · Boias · AIS · Barra · Zonas · Sinais · FMAP (Recursos locais) +**Status FMAP:** Perto da minha localização (15 km) / Recursos do porto +**Ações:** Go · Copy debug + +## Apêndice B — Preferências persistidas (v1) + +Persistir por dispositivo: + +* Âncora do dock: Direita / Esquerda / Inferior +* Estado do dock: Colapsado / Expandido +* Toggles: último estado ON/OFF (exceto AIS default ON na Guanabara na primeira visita) + +## Apêndice C — Lista padrão do pescador (RJ) e mapeamento UI (P0/P1/P2) + +### Escopo RJ (v1) +- **RJ core**: Maricá + Guanabara + Barra de São João. +- Estas regras afetam apenas **defaults** e **badges** (não removem funcionalidades). + +### P0 (MVP demonstrável, navegação + segurança) +1) **GPS / Minha localização** +- **Onde fica**: Dock (toggle **GPS**). +- **Default RJ**: ON (pede permissão; se negar, fica OFF com estado claro). +- **Deve renderizar**: marcador + tooltip permanente + círculo de precisão quando houver lat/lon (mesmo que o status não seja "ok"). + +2) **Sinais náuticos (Seamarks / OpenSeaMap)** +- **Onde fica**: Dock (toggle **Sinais**). +- **Default RJ**: ON. +- **Deve renderizar**: seamarks como TileLayer (sem panes custom). Contadores no debug: `tilesRequested/tilesLoaded/tilesError`. + +3) **AIS (embarcações)** +- **Onde fica**: Dock (toggle **AIS**). +- **Default RJ**: + - **Guanabara**: ON por padrão. + - Fora da Guanabara: OFF por padrão (ruído visual). +- **Nota UX**: AIS é operacional, nunca dentro do dropdown "Layers" do header (que é só Base Maps). + +4) **Barra (risco de barra) + semáforo** +- **Onde fica**: + - Dock (toggle **Barra**) controla a camada/painel de detalhes. + - **Semáforo**: sempre visível em RJ-Barra de São João **mesmo com "Barra" OFF**. +- **Default RJ**: semáforo visível na Barra de São João. +- **Regra**: o semáforo não depende do toggle (beacon de segurança); o toggle controla apenas detalhes/camada. + +5) **Boias (PNBOIA / lista fixa)** +- **Onde fica**: Dock (toggle **Boias** + `Loaded/Visible` + **Go** + **Copy debug**). +- **Default RJ**: ON (se o desempenho permitir). +- **Regra**: `Loaded` = lista completa; `Visible` = bounds no cliente; **Go** = `fitBounds` para todas (ou "RJ-first" se aplicável). + +6) **FMAP (Recursos locais: desembarque/gelo/refúgio)** +- **Onde fica**: Dock (1 linha "FMAP (Recursos locais)" com counts + Go/Copy; Details só `debug=1`). +- **Default RJ**: ON. +- **Sem porto selecionado**: modo **Perto da minha localização** (ver Apêndice D). +- **Com porto selecionado**: modo **Recursos do porto**. + +7) **Reportar (incidentes/notas)** +- **Onde fica**: Dock (botão **Reportar**). +- **Default RJ**: sempre visível e sem duplicação em outro lugar. + +8) **Marés (Tides)** +- **Onde fica**: Forecast (tela Forecast) + atalho em Preparation ("Open Forecast"). +- **Default RJ**: sempre acessível (evitar "Data on Hold" se houver fonte). + +### P1 (próxima etapa, alto valor) +- **Vento / Ondas** (Forecast): valores + tendência + alertas. +- **Correntes** (Forecast): direção/velocidade (se houver fonte). +- **Canais / zonas de fundeio / áreas restritas** (Dock: toggle **Zonas**). +- **Batimetria** (Preparation/Forecast): default OFF. + +### P2 (avançado, opcional) +- **Smart Fishing**: recomendações, esforço, modelos (nunca bloqueia P0). +- **Planejamento avançado**: approach planner, camadas analíticas, etc. + +### Regra de ouro (coerência) +- Dropdown **"Layers" (header)** = **somente Base Maps**. +- Todas as camadas operacionais (GPS/AIS/Boias/Barra/Sinais/Zonas/FMAP/Reportar) = **somente Dock**. +- Preparation/Forecast/Smart Fishing são telas de workflow, não toggles operacionais. + + +## Apêndice D — Alerts (limiares v1) + critério "Perto da minha localização" + regras RJ + +### D1) Badge de Alerts (Dock colapsado) +**Conta** (P0): +- Barra (semáforo Amarelo/Laranja/Vermelho) +- Vento forte +- Ondas altas +- Raios próximos (se houver feed) +- Baixa visibilidade/neblina (se houver feed) +- (Opcional P1) Corrente forte + +**Não conta**: +- "Data on Hold" (não incrementa; pode mostrar `?` no debug se quiser) +- Erros de fetch de camadas (mostra inline "(fetch error)", não como alerta do mar) + +**Lógica do badge**: +- `count = número de tipos de alerta ativos` (máximo exibido: `9+`) +- `color = pior severidade ativa` (verde/amarelo/laranja/vermelho) + +### D2) Limiares v1 (iniciais, ajustáveis) +> Nota: são limiares de produto v1 (não ciência). Calibrar com feedback real e dados locais. + +**Vento (knots, sustentado ou rajada se for o disponível)** +- Amarelo: `>= 15 kt` +- Laranja: `>= 20 kt` +- Vermelho: `>= 25 kt` + +**Ondas (altura significativa, metros)** +- Amarelo: `>= 1.5 m` +- Laranja: `>= 2.0 m` +- Vermelho: `>= 2.5 m` + +**Raios (se houver feed)** +- Amarelo: `<= 30 km` em `<= 60 min` +- Laranja: `<= 20 km` em `<= 30 min` +- Vermelho: `<= 10 km` em `<= 30 min` + +**Visibilidade (se houver feed)** +- Amarelo: `< 3 km` +- Laranja: `< 2 km` +- Vermelho: `< 1 km` + +**Barra (São João)** +- Semáforo: + - Verde: OK / sem alerta + - Amarelo: atenção + - Laranja: risco alto + - Vermelho: não recomendado + - Cinza: sem dados (não conta como alerta; o semáforo continua visível) + +### D3) "FMAP Perto da minha localização" (critério humano v1) +**Centro (prioridade)** +1) GPS (minha localização) se disponível +2) Porto selecionado (se existir) +3) Centro do viewport + +**Raio** +- Base: **15 km** +- Se resultados `<= 3`: expandir para **30 km** +- Se continuar vazio: mostrar "sem dados perto", mas manter **Go** para enquadrar o que existir (se houver). + +**RJ hint** +- Se `stateHint = RJ` e existirem recursos RJ: priorizar RJ dentro do raio. +- Se não houver RJ dentro do raio: voltar ao filtro normal (sem RJ). + +**O que mostrar no Near (sem porto)** +- Essenciais apenas: + - Desembarque + - Gelo + - Refúgio / fundeio seguro (se existir) +- Todo o resto vai para "Details" (somente `debug=1`). + +### D4) Defaults RJ (v1) +- **Guanabara**: AIS ON por padrão. +- **Barra de São João**: semáforo sempre visível mesmo com toggle "Barra" OFF. + +## Apêndice E — Checklist de Aceite P0 (RJ) (pré-merge) + +> Objetivo: "sem falha silenciosa" + UX coerente. Deve passar antes de mergear PRs que mexem em mapa/Dock/Forecast. + +### Rotas mínimas de teste +- Prod ou local: + - `/map?start=realtime` + - `/map?start=realtime&layers=1` + - `/map?start=realtime&debug=1` + +### E1) Invariantes de UI (falhou: NÃO MERGE) +- [ ] Dropdown **"Layers" (header)** tem **somente Base Maps** (0 overlays operacionais). +- [ ] **Camadas operacionais** (GPS/AIS/Boias/Barra/Sinais/Zonas/FMAP/Reportar) ficam **somente** no **Fisher Dock**. +- [ ] Fisher Dock **não cobre** o dropdown "Layers" (margem/posição segura). +- [ ] Com `layers=1`, Dock **auto-hide** (não compete com a UI). +- [ ] Dock colapsável: colapsa em **1 botão "Fisher"** com badges (GPS/Alerts). Sem controles escondidos. + +### E2) Defaults RJ (comportamento esperado) +- [ ] **Sinais (Seamarks)**: ON por padrão em RJ. +- [ ] **FMAP**: ON por padrão em RJ. +- [ ] **AIS**: ON por padrão **somente em Guanabara**. +- [ ] **Barra São João**: semáforo **sempre visível** mesmo com toggle "Barra" OFF. + +### E3) Seamarks (Sinais) sem falha silenciosa +- [ ] Com "Sinais" ON, em `debug=1` o Copy debug mostra: + - `signals.tilesRequested > 0` após alguns segundos + - `signals.tilesLoaded` sobe (ou `tilesError` sobe se houver problema real) +- [ ] Se tiles não carregarem, o status fica visível (contadores) e a tela não trava. + +### E4) PNBOIA (Boias) resiliente +- [ ] "Boias" ON mostra `Loaded/Visible` (não fica em 0 para sempre sem explicação). +- [ ] Se o fetch estiver lento: + - [ ] **slow-warning** por volta de ~8s (inline, sem toast). + - [ ] **abort** real em 30s (se aplicável) com razão no debug (`fetchErrorName/abortMs`). +- [ ] **Fail-open**: se existia lista boa anterior, NÃO zera por erro/timeout/empty. +- [ ] **Go** aparece quando `Loaded > 0 && Visible == 0` e faz `fitBounds` (RJ-first se aplicável). +- [ ] **Copy debug** inclui no mínimo: + - `version.gitCommit`, `lastFetchUrl`, `httpStatus`, `error/fetchErrorName`, `loadedCount`, `visibleCount`, `zoom`, `bounds`, `loadingMs`, `abortMs`, `slow`. + +### E5) FMAP (Recursos locais) usável, sem "choclazo" +- [ ] Dock mostra **1 linha** "FMAP (Recursos locais)" com counts + Go + Copy. +- [ ] Não existe listagem crua "point/polygon/point…" na UI normal. +- [ ] "Details" existe **somente com `debug=1`**. +- [ ] Sem porto: modo **Perto da minha localização** (15 km, expande para 30 km se <=3 resultados). +- [ ] Com porto: modo **Recursos do porto**. + +### E6) Badge de Alerts (Dock colapsado) +- [ ] Badge conta apenas alertas do mar (Barra/Vento/Ondas/Raios/Visibilidade/Corrente se existir). +- [ ] "Data on Hold" NÃO incrementa o badge. +- [ ] Erros de fetch de camadas NÃO incrementam o badge (aparecem inline "(fetch error)"). + +### E7) Garantia de não travar +- [ ] Alternar Sinais, FMAP, Boias, AIS, Zonas, Barra repetidamente não quebra o mapa. +- [ ] Não aparecem telas "Something went wrong". + +## Apêndice F — Sources of Truth + Política "Data on Hold" (v1, RJ) + +> Propósito: evitar falha silenciosa, evitar promessas falsas, manter UX pescador-first. +> Regra de ouro: se o dado não estiver disponível, informar inline, manter último dado bom (fail-open) e oferecer debug copiável sem console. + +### F1) Fontes de Verdade (Source of Truth) + +**Mapa base (tiles)** +- SoT: provedor do BaseMap selecionado (OSM/Esri/OpenTopo). +- UI: deve sempre funcionar (se falhar, é problema crítico de conectividade). + +**Seamarks / Sinais náuticos (OpenSeaMap)** +- SoT: tiles `https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png` +- UI: toggle "Sinais" (Dock). +- Telemetria (debug): `signals.tilesRequested/tilesLoaded/tilesError`. + +**GPS (Minha localização)** +- SoT: Geolocation do navegador e/ou "last known" do backend se existir. +- UX: se houver lat/lon, renderizar marcador + círculo de precisão mesmo com `status != ok`. +- Estados: `OK` | `Sem permissão` | `Indisponível` | `Desatualizado`. + +**AIS (embarcações)** +- SoT: backend AIS (quando existir e estiver healthy). +- Default RJ: ON por padrão em Guanabara. +- Se estiver "Data on Hold", toggle continua visível e mostra "On hold". + +**PNBOIA (boias fixas)** +- SoT primário: `/api/v1/pnboia/list` (lista completa). +- SoT secundário: cache local (TTL) → seed fallback (último recurso). +- UX: sempre mostra `Loaded/Visible`. +- Go: aparece quando `Loaded>0 && Visible==0`. +- Fail-open: não zera a última lista boa em caso de erro. + +**FMAP (Recursos locais: desembarque, gelo, refúgio, etc.)** +- SoT: seed versionado (v1) + futura fonte backend (v2+). +- Default: ON por padrão, mas o render "perto da minha localização/porto" evita choclazo. +- UI normal: 1 linha com counts + Go + Copy. Details só em `debug=1`. + +**Barra (São João)** +- SoT: módulo de risco/estado (pode ser placeholder no v1). +- UX RJ: semáforo da Barra São João é SEMPRE visível mesmo com toggle OFF. + +**Zonas** +- SoT: camada interna (MapLibre/Leaflet), depende dos dados disponíveis. +- Default RJ: ON se não quebrar performance. + +### F2) Contrato "Data on Hold" (v1) + +**Definição** +- "Data on Hold" significa: a UI existe e permanece acessível, mas os dados ainda não são publicados (backend incompleto, qualidade insuficiente ou decisão de produto). + +**Regras de exibição (SEM TOASTS)** +- Nunca toast automático para hold ou fetch error. +- Sempre inline na linha do toggle: + - `On hold` (hold de produto) + - `(fetch error)` (erro técnico) + - `slow` (> 8s) + - `abort` (abort em 30s, motivo no Copy debug) + +**Fail-open (sempre)** +- Se existir "último dado bom", continuar mostrando mesmo se o refresh falhar. +- "On hold" não apaga dado bom, apenas bloqueia refresh. + +**Badges (Dock colapsado)** +- "Data on Hold" não incrementa Alerts. +- "fetch error" não incrementa Alerts. +- Alerts conta apenas alertas do mar (Barra/Vento/Ondas/Raios/Visibilidade/Corrente se existir). + +### F3) Estados UX por toggle (texto fixo) + +Formato recomendado (uma linha): +- `OK · Loaded: N · Visible: M` +- `On hold` +- `(fetch error)` +- `slow…` +- `abort` + +Botões: +- `Go` (fitBounds) +- `Copy debug` + +### F4) Copy debug (mínimo obrigatório) + +Copy debug deve incluir: +- `href` +- `version.gitCommit` +- `map.zoom`, `map.bounds` +- Para cada módulo habilitado: + - `status`, `error` + - contadores chave (Loaded/Visible ou tiles*) + - `lastFetchUrl`, `httpStatus` quando aplicável + - `loadingMs`, `abortMs`, `fetchErrorName` quando aplicável + - `stateHint` (RJ) + +### F5) Critério "Perto da minha localização" (FMAP v1) + +Centro: +1) GPS se existir +2) Porto selecionado +3) Centro do viewport + +Raio: +- 15 km base +- se <=3 resultados: expandir para 30 km +- se vazio: mostrar "no data near", manter Go (para enquadrar o que existir, se houver) + +RJ hint: +- Se `stateHint=RJ` e houver itens RJ, priorizar itens RJ dentro do raio. + +## Apêndice H — Padrões RJ + Limiares de Alertas (v1) +> Escopo: RJ (Maricá, Guanabara) + Barra de São João. +> Objetivo: padrões coerentes, limiares explícitos, zero falha silenciosa. + +### H1) Padrões RJ +**H1.1 AIS padrão ON em Guanabara** +- Regra: AIS inicia **ON** quando o “contexto” estiver em Guanabara. +- Prioridade do contexto: (1) Porto selecionado (2) GPS (minha localização) (3) Centro do viewport. +- Critério “Guanabara (v1)”: ponto de contexto dentro da baía (bbox aproximado no código, documentado como “GuanabaraBBoxV1”). +- Persistência: se o usuário alternou AIS manualmente, **a preferência do usuário manda** (não sobrescrever). + +**H1.2 Barra São João: semáforo sempre visível** +- Status da barra **sempre visível** como semáforo (🟢🟡🔴⚪) mesmo com toggle “Barra” OFF. +- O toggle “Barra” controla a **camada/overlay**, mas nunca esconde o semáforo. +- Local: Dock expandido (linha “Barra São João”) + Dock colapsado (badge). + +### H2) Badge “Alertas”: o que conta e o que NÃO conta +**Conta como Alertas:** +- Barra São João em **🟡** ou **🔴** (conta 1). +- Alertas oficiais ativos (quando existirem) na área de contexto: **Tempestade/Mar/Navegação** (1 por categoria, máx 3). +- AIS risco próximo (quando existir): CPA < 0.5 nm em 15 min (conta 1). + +**NÃO conta:** +- Data on hold / fetch error / slow… / seamarks tilesError +- PNBOIA vazio com fail-open (fallback/cache) +- FMAP sem dados por perto + +### H3) Limiares concretos (v1) para Barra São João +> Guia operacional. Se faltar dado: ⚪ On hold. + +- ⚪ On hold: dados insuficientes. +- 🟢 Seguro: Hs < 1.0 m **e** vento < 15 kt +- 🟡 Atenção: 1.0 ≤ Hs < 1.5 **ou** 15 ≤ vento < 25 kt +- 🔴 Perigoso: Hs ≥ 1.5 **ou** vento ≥ 25 kt +- Escalonamento na vazante/ebb (se existir): 🟢→🟡, 🟡→🔴. + +### H4) Textos fixos (apenas novos) +- “Barra São João” +- “Risco da barra: Seguro / Atenção / Perigoso / On hold” +- “AIS padrão ON em Guanabara (RJ)” + +## Apêndice G — Fisher Dock "collision-free" (RJ v1) + +### G.1 Objetivo +O Fisher Dock deve ser sempre acessível e **nunca cobrir o controle "Layers"** (dropdown de Mapas Base). No desktop pode ficar como overlay, mas com regras de colisão e auto-reposicionamento. No mobile deve ser colapsável e com baixo ruído visual. + +### G.2 Estados do Dock (definitivo) +**Estado colapsado (padrão):** +- Um único botão/"pill" com o texto **"Fisher"**. +- Badges mínimos: + - **GPS**: ON/OFF (verde/cinza). + - **Alerts**: contador (ex. "2") ou ponto vermelho se houver alerta ativo. + +**Estado expandido:** +- Mostra toggles operacionais + estados (ex. Boias Loaded/Visible + Go/Copy). +- Fecha ao: + - tocar no "Fisher" novamente, + - tocar no mapa (área vazia), + - abrir `layers=1` (ver G.5). + +### G.3 Posições permitidas (3 âncoras) +O Dock só pode ficar em uma destas posições: +1) **Direita-centro** (padrão desktop) +2) **Esquerda-centro** (fallback desktop) +3) **Inferior** (padrão mobile e fallback final) + +A âncora é uma preferência persistida (Apêndice B), mas **as regras de colisão prevalecem**. + +### G.4 Regra "não cobre Layers" (zona proibida) +Definir uma **zona proibida (sem sobreposição)** para proteger o controle "Layers" no canto superior direito: + +- **Layers Safe Zone (desktop):** + - retângulo ancorado no **top-right** + - margem externa: 12 px + - tamanho mínimo reservado: **L=260 px, A=340 px** + - inclui o dropdown aberto (não apenas o botão). + +**Invariante:** o bounding box do Fisher Dock (colapsado ou expandido) **não pode intersectar** essa zona. + +### G.5 Regras de posicionamento e auto-reposicionamento +**Desktop (≥ 1024 px):** +1) Tentar **Direita-centro**. +2) Se colidir com a Layers Safe Zone → mover para **Esquerda-centro**. +3) Se ainda colidir (ou faltar espaço) → mover para **Inferior**. +4) Se o usuário escolheu a âncora manualmente, respeitar apenas se não colidir. + +**Tablet / Mobile (< 1024 px):** +- Padrão: **Inferior** colapsado. +- Expandido abre "para cima" para não cobrir controles superiores. + +**Auto-hide:** +- Se `layers=1` estiver ativo (Layers aberto): o Fisher Dock deve ficar **totalmente oculto** (não apenas colapsado). + +### G.6 Tamanho e "ruído visual" (limites) +Para manter o mapa utilizável: +- Largura recomendada do Dock expandido: ~160–190 px. +- No estado expandido, manter "uma linha por feature", evitando listas longas. +- "FMAP Sinais (seed)" deve aparecer como **uma única linha** (counts + Go/Copy; Details só em `debug=1`), nunca como lista crua "Point/Polygon/Point…". + +### G.7 Defaults RJ (sem quebrar o contrato) +- **Guanabara:** AIS pode ficar **ON por padrão**. +- **Barra de São João:** o **semaforinho da barra** deve ser **sempre visível** mesmo com o toggle "Barra" em OFF. + - OFF significa "sem overlays detalhados", mas o indicador mínimo permanece. + +### G.8 Critérios de aceitação (checklist) +- [ ] No desktop, Dock (colapsado e expandido) **nunca cobre** Layers (botão nem dropdown). +- [ ] Com `layers=1`, Dock some (auto-hide). +- [ ] No mobile, Dock inicia colapsado em Inferior e não cobre controles superiores. +- [ ] FMAP aparece com **uma linha** apenas (sem spam de points/polygons). +- [ ] Semaforinho da barra sempre visível na Barra de São João (mesmo com Barra OFF). +- [ ] Badges (GPS/Alerts) visíveis no estado colapsado. + +--- + +*Aditamento RJ ao Fisher UI Contract v1. Revisar em cada alteração de Dock/Barra/FMAP em RJ.* + +**Regra de merge:** Só se faz merge se passar o **Apêndice E (Checklist P0)** e o **Apêndice F (Sources of Truth + Data on Hold)** se mantiver intacto. Qualquer PR que mexa em mapa/Dock/Forecast deve indicar: «✅ passes Appendix E + Appendix F». diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md b/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md new file mode 100644 index 000000000..22b8c9e4f --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md @@ -0,0 +1,72 @@ +# Concrete thresholds for Alerts (v1) + +**Complements:** [FISHER_UI_CONTRACT_v1_badges](./FISHER_UI_CONTRACT_v1_badges.en.md) +**Language:** EN | **Other versions:** [ES](./FISHER_UI_CONTRACT_v1_alerts_thresholds.md) · [PT](./FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md) + +--- + +## Objective + +Conservative, simple, explainable thresholds. Do not alert for "Data on Hold". + +**General principle:** The Alerts badge only turns on when there is a risk condition with real data. If data is missing, show "Data on Hold" in the UI but do not add to the badge. + +**Levels:** Caution (yellow) = "worth thinking about". Danger (red) = "better not go out / extreme caution". Optional P2: "Info" with no badge. + +--- + +## 1) Bar (P0) + +**Danger (red):** Hs >= 2.0 m and ebb or near low water; or Hs >= 1.6 m and onshore wind >= 15 kn; or bar reported "hazard/high". + +**Caution (yellow):** Hs >= 1.5 m; or onshore wind >= 12 kn; or current >= 1.5 kn. Note: if no per-port onshore data in v1, use "wind >= X" and state "generic wind threshold". + +--- + +## 2) Wind (P1) + +Danger: sustained >= 25 kn or gusts >= 30 kn. Caution: sustained >= 18 kn or gusts >= 22 kn. + +--- + +## 3) Waves / swell (P1) + +Danger: Hs >= 2.5 m. Caution: Hs >= 1.8 m. Optional: period >= 12 s and Hs >= 1.6 m -> Caution. + +--- + +## 4) Tides and currents (P1) + +Current: Danger >= 2.5 kn, Caution >= 1.5 kn. Tide: in v1 use only to modulate Bar; always show in Forecast. + +--- + +## 5) Visibility / storms (P1/P2) + +Visibility: Danger < 1 km, Caution < 2 km. Thunderstorm: Danger high prob (e.g. >= 60%), Caution moderate (e.g. 30-60%). + +--- + +## 6) GPS / system (P0) + +Caution: GPS stale > 5 min. Danger: GPS stale > 15 min; or critical layer hard error (e.g. PNBOIA repeated abort) only if it affects navigation; show "Data unavailable". + +--- + +## What counts / does not count for badge + +**Counts:** Bar Caution/Danger; wind or waves above threshold; strong current; storm/critical visibility; GPS very stale. + +**Does not count:** "Data on Hold"; fetch error for non-critical layers (or with fallback); Smart Fishing; debug. + +--- + +## Single source of truth + +Alerts are computed in a single module (even if shown in Dock/Preparation), with these versioned thresholds (v1/v2). + +**Iron rule: When in doubt, do not alert.** + +--- + +*Alerts thresholds v1. Review on each safety/bar/meteo change.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.md b/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.md new file mode 100644 index 000000000..28fc762c6 --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.md @@ -0,0 +1,124 @@ +# Umbrales concretos para Alerts (v1) + +**Complemento de:** [FISHER_UI_CONTRACT_v1_badges](./FISHER_UI_CONTRACT_v1_badges.md) +**Idioma:** ES · **Otras versiones:** [EN](./FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md) · [PT](./FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md) + +--- + +## Objetivo + +Umbrales conservadores, simples y explicables. **Sin alertar por "Data on Hold".** + +### Principio general + +El badge **Alerts** solo se prende si hay una **condición de riesgo con datos reales**. +Si falta data, se muestra "Data on Hold" en UI pero **NO suma al badge**. + +### Niveles + +- **Caution (amarillo):** "conviene pensarla" +- **Danger (rojo):** "mejor no salir / extrema precaución" +- (Opcional P2: "Info" sin badge) + +--- + +## 1) Barra (P0, el más importante) + +Heurística global (luego por puerto). + +**Danger (rojo)** si se cumple cualquiera: + +- Olas significativas Hs ≥ 2,0 m y marea bajando (ebb) o cerca de bajamar. +- Olas Hs ≥ 1,6 m y viento onshore ≥ 15 kn. +- "Bar condition" explícitamente reportada como "hazard/high" por la fuente (si existe). + +**Caution (amarillo)** si se cumple cualquiera: + +- Hs ≥ 1,5 m (aunque no tengas marea). +- Viento onshore ≥ 12 kn. +- Corriente/ebb fuerte (si hay corriente): ≥ 1,5 kn. + +*Nota:* "onshore" se calcula por el bearing de la costa/entrada del puerto. Si en v1 no existe ese dato por puerto, tratar como "viento ≥ X" sin onshore/offshore y avisar "generic wind threshold". + +--- + +## 2) Viento (P1) + +- **Danger:** sostenido ≥ 25 kn o ráfagas ≥ 30 kn. +- **Caution:** sostenido ≥ 18 kn o ráfagas ≥ 22 kn. + +--- + +## 3) Olas / mar de fondo (P1) + +- **Danger:** Hs ≥ 2,5 m. +- **Caution:** Hs ≥ 1,8 m. +- Opcional: si período ≥ 12 s y Hs ≥ 1,6 m, subir a Caution (mar de fondo largo pega feo en barra/costa). + +--- + +## 4) Mareas y corrientes (P1) + +**Corriente** (si hay dato): + +- **Danger:** ≥ 2,5 kn. +- **Caution:** ≥ 1,5 kn. + +**Marea:** como alerta directa es difícil sin contexto. En v1 usarla solo para modular Barra (§1) y en Forecast mostrarla siempre. + +--- + +## 5) Visibilidad / tormentas (P1/P2 según data) + +**Visibilidad** (si existe): + +- **Danger:** < 1 km. +- **Caution:** < 2 km. + +**Tormenta eléctrica** (si existe probabilidad o flag "thunder"): + +- **Danger:** tormenta detectada / prob alta (ej. ≥ 60 %). +- **Caution:** prob moderada (ej. 30–60 %). + +--- + +## 6) GPS / estado del sistema (P0, no "meteo") + +Para evitar silent failure: + +- **Caution:** GPS stale > 5 min. +- **Danger:** GPS stale > 15 min. +- **Danger:** capas críticas con error "hard" (ej. PNBOIA fetch abort repetido) solo si afecta navegación; mostrarlo como "Data unavailable", sin pánico. + +--- + +## Qué entra y qué NO entra al Alerts badge (regla explícita) + +**Cuenta (badge):** + +- Barra en Caution/Danger. +- Viento u olas superando umbral. +- Corriente fuerte (si existe). +- Tormenta/visibilidad crítica (si existe). +- GPS muy stale (afecta seguridad inmediata). + +**No cuenta:** + +- "Data on Hold". +- "fetch error" de capas no críticas (o si hay fallback). +- Smart Fishing. +- Cosas de debug. + +--- + +## Single source of truth (recomendación UX) + +Los Alerts se calculan en **un único módulo** (aunque se muestren en Dock/Preparation), con estos umbrales versionados (v1/v2). Evita que el badge se vuelva ruido. + +### Frase de hierro + +**Si hay duda, no alertar.** + +--- + +*Umbrales Alerts v1. Revisar en cada cambio de seguridad/barra/meteo.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md b/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md new file mode 100644 index 000000000..417b56878 --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md @@ -0,0 +1,124 @@ +# Umbrais concretos para Alerts (v1) + +**Complemento de:** [FISHER_UI_CONTRACT_v1_badges](./FISHER_UI_CONTRACT_v1_badges.pt.md) +**Idioma:** PT · **Outras versões:** [ES](./FISHER_UI_CONTRACT_v1_alerts_thresholds.md) · [EN](./FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md) + +--- + +## Objetivo + +Umbrais conservadores, simples e explicáveis. **Sem alertar por "Data on Hold".** + +### Princípio geral + +O badge **Alerts** só se acende se houver uma **condição de risco com dados reais**. +Se faltar dados, mostrar "Data on Hold" na UI mas **não somar ao badge**. + +### Níveis + +- **Caution (amarelo):** "convém pensar" +- **Danger (vermelho):** "melhor não sair / precaução extrema" +- (Opcional P2: "Info" sem badge) + +--- + +## 1) Barra (P0, o mais importante) + +Heurística global (depois por porto). + +**Danger (vermelho)** se se cumprir qualquer um: + +- Ondas significativas Hs ≥ 2,0 m e maré a descer (ebb) ou perto de baixa-mar. +- Hs ≥ 1,6 m e vento onshore ≥ 15 kn. +- "Bar condition" explicitamente reportada como "hazard/high" pela fonte (se existir). + +**Caution (amarelo)** se se cumprir qualquer um: + +- Hs ≥ 1,5 m (mesmo sem maré). +- Vento onshore ≥ 12 kn. +- Corrente/ebb forte (se houver corrente): ≥ 1,5 kn. + +*Nota:* "onshore" calcula-se pelo rumo da costa/entrada do porto. Se em v1 não existir esse dado por porto, tratar como "vento ≥ X" sem onshore/offshore e avisar "generic wind threshold". + +--- + +## 2) Vento (P1) + +- **Danger:** sustentado ≥ 25 kn ou rajadas ≥ 30 kn. +- **Caution:** sustentado ≥ 18 kn ou rajadas ≥ 22 kn. + +--- + +## 3) Ondas / mar de fundo (P1) + +- **Danger:** Hs ≥ 2,5 m. +- **Caution:** Hs ≥ 1,8 m. +- Opcional: se período ≥ 12 s e Hs ≥ 1,6 m, subir para Caution (mar de fundo longo bate forte em barra/costa). + +--- + +## 4) Marés e correntes (P1) + +**Corrente** (se houver dado): + +- **Danger:** ≥ 2,5 kn. +- **Caution:** ≥ 1,5 kn. + +**Maré:** como alerta directa é difícil sem contexto. Em v1 usar só para modular Barra (§1) e no Forecast mostrar sempre. + +--- + +## 5) Visibilidade / tempestades (P1/P2 conforme dados) + +**Visibilidade** (se existir): + +- **Danger:** < 1 km. +- **Caution:** < 2 km. + +**Tempestade eléctrica** (se existir probabilidade ou flag "thunder"): + +- **Danger:** tempestade detectada / prob alta (ex. ≥ 60%). +- **Caution:** prob moderada (ex. 30–60%). + +--- + +## 6) GPS / estado do sistema (P0, não "meteo") + +Para evitar falha silenciosa: + +- **Caution:** GPS stale > 5 min. +- **Danger:** GPS stale > 15 min. +- **Danger:** camadas críticas com erro "hard" (ex. PNBOIA fetch abort repetido) só se afectar navegação; mostrar como "Data unavailable", sem pânico. + +--- + +## O que entra e o que NÃO entra no badge Alerts (regra explícita) + +**Conta (badge):** + +- Barra em Caution/Danger. +- Vento ou ondas acima do limiar. +- Corrente forte (se existir). +- Tempestade/visibilidade crítica (se existir). +- GPS muito stale (afecta segurança imediata). + +**Não conta:** + +- "Data on Hold". +- "fetch error" de camadas não críticas (ou se houver fallback). +- Smart Fishing. +- Coisas de debug. + +--- + +## Single source of truth (recomendação UX) + +Os Alerts são calculados num **único módulo** (mesmo que se mostrem no Dock/Preparation), com estes umbrais versionados (v1/v2). Evita que o badge vire ruído. + +### Frase de ferro + +**Em caso de dúvida, não alertar.** + +--- + +*Umbrais Alerts v1. Revisar em cada alteração de segurança/barra/meteo.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_badges.en.md b/docs/ui/FISHER_UI_CONTRACT_v1_badges.en.md new file mode 100644 index 000000000..33d5c892c --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_badges.en.md @@ -0,0 +1,99 @@ +# UI Contract v1 - Fisher Dock + FMAP Near + Badges + +**Complements:** [FISHER_UI_CONTRACT_v1](./FISHER_UI_CONTRACT_v1.en.md) +**Language:** EN | **Other versions:** [ES](./FISHER_UI_CONTRACT_v1_badges.md) | [PT](./FISHER_UI_CONTRACT_v1_badges.pt.md) + +--- + +## Invariants (DO NOT break) + +- The **Layers menu (top-right)** is **Base Maps only** (map styles). Zero operational layers there. **No exceptions.** +- Operational layers (GPS/Buoys/AIS/Bar/Zones/Signals/FMAP) live **only** in the **Fisher Dock**. Operational layers only in Dock (repeated on purpose). +- The Dock **never covers** the Layers dropdown. **Layout rule:** margin or safe area so Layers (top-right) stays always accessible. +- When `layers=1` (Layers panel open), the Fisher Dock **auto-hides**. Hard invariant. + +--- + +## 1) Fisher Dock (collapsible) - definitive behaviour + +### States + +- **Expanded:** shows the 7 toggles + status (Loaded/Visible where applicable) + actions (Go / Copy debug). +- **Collapsed:** reduces to **a single button**: "Fisher". + +### "Fisher" button badges (when collapsed) + +- **GPS badge:** ON/OFF (or green/grey dot). If implemented: show **stale** when position is older than 5 min (e.g. "stale" or distinct icon). +- **Alerts badge:** number (e.g. 2) or red dot when there are active alerts. What counts and what does not: see §4 and addendum [Alerts thresholds v1](./FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md). + +### Layout / Position (3 positions) + +- Supports **3 positions**: Right / Left / Bottom. +- **Desktop default:** Right (vertically centered, with margin so it does not cover Layers). +- **Mobile default:** Bottom (collapsed by default). +- The Dock is **collapsible** (not draggable for now). + +--- + +## 2) FMAP "Near my location" - human v1 criteria + +### Center (priority) + +1. If **GPS** (my location) is available, use that point. +2. If no GPS but **Port is selected**, use the port. +3. If neither, use the **viewport center**. + +### Radius (near) + +- **Base radius:** 15 km. +- If few results: if results <= 3, expand to 30 km. If still empty: show "No data near", but keep **Go** to frame what is available (if there is data outside the radius). + +### RJ hint (stateHint) + +- If stateHint = RJ and there are FMAP resources tagged RJ: prioritise RJ within the radius. If no RJ within the radius, fall back to normal criteria without filter. + +### What is shown by default when FMAP is ON and there is NO port + +Show only the essential: Landing, Ice, Refuge / safe anchorage (if it exists). The rest stays in **Details** (only visible with debug=1). + +--- + +## 3) Copy / Labels (fixed UI text — source of truth) + +- Toggle: "FMAP (Local resources)" +- FMAP status: "Near my location (15 km)" or "Port resources" or "No data near" +- Generic states: "Data on Hold", "fetch error" (when applicable) +- Button: "Go" +- Button: "Copy debug" + +In EN/PT do not change meaning (e.g. keep "Near my location" as is; do not translate "Near" in a odd way). + +--- + +## 4) Alerts badge - v1 definition (what counts and what does not) + +The "Alerts" badge on the Fisher button represents **ONLY** risk conditions with **real data**. It is important: what counts and what does not. + +**Counts (adds to badge):** Bar in Caution/Danger (thresholds in [Alerts thresholds v1](./FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md)); wind or waves above threshold; strong current (if data exists); storm/critical visibility (if exists); GPS very stale. + +**Does NOT count:** "Data on Hold" — shown in UI but does not add to badge. "fetch error" for non-critical layers (or if fallback exists). Smart Fishing, debug. Normal layer status (Buoys ON / AIS ON). + +--- + +## Acceptance criteria (for quick QA) + +Must be testable with these 2 URLs: + +- **`/map?start=realtime`** — Dock visible, toggles working, badges. +- **`/map?start=realtime&layers=1`** — Dock auto-hides; Layers (Base Maps only) accessible. + +Checklist: + +- [ ] Dock collapses to "Fisher" and badges are visible. +- [ ] With layers=1 the Dock hides. +- [ ] FMAP ON with no port shows only essentials and status "Near my location (15 km)". +- [ ] The Layers menu (top-right) shows only Base Maps (no exceptions). + +--- + +*Addendum to Fisher UI Contract v1. Review on each Dock/FMAP change.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_badges.md b/docs/ui/FISHER_UI_CONTRACT_v1_badges.md new file mode 100644 index 000000000..5bcf6b43e --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_badges.md @@ -0,0 +1,120 @@ +# UI Contract v1 — Fisher Dock + FMAP Near + Badges + +**Complemento de:** [FISHER_UI_CONTRACT_v1](./FISHER_UI_CONTRACT_v1.md) +**Idioma:** ES · **Otras versiones:** [EN](./FISHER_UI_CONTRACT_v1_badges.en.md) · [PT](./FISHER_UI_CONTRACT_v1_badges.pt.md) + +--- + +## Invariantes (NO romper) + +- El menú **Layers (top-right)** es **solo Base Maps** (estilos de mapa). Cero capas operativas ahí. **Sin excepciones.** +- Las capas operativas (GPS/Boyas/AIS/Barra/Zonas/Señales/FMAP) viven **solo** en el **Fisher Dock**. Capas operativas solo en Dock (repetido a propósito). +- El Dock **nunca tapa** el dropdown de Layers. **Regla de layout:** margen o safe area tal que Layers (top-right) quede siempre accesible. +- Si `layers=1` (panel Layers abierto), el Fisher Dock **se auto-oculta**. Invariante duro. + +--- + +## 1) Fisher Dock (colapsable) — comportamiento definitivo + +### Estados + +- **Expandido:** muestra los 7 toggles + estado (Loaded/Visible donde aplique) + acciones (Go / Copy debug). +- **Colapsado:** se reduce a **un solo botón**: **“Fisher”**. + +### Badges del botón “Fisher” (cuando está colapsado) + +- **GPS badge:** `ON/OFF` (o punto verde/gris). Si existe: indicar **stale** cuando la posición tenga más de 5 min (ej. "stale" o icono distinto). +- **Alerts badge:** número (`2`) o punto rojo si hay alertas activas. Qué cuenta y qué no: ver §4 y addendum [Umbrales Alerts v1](./FISHER_UI_CONTRACT_v1_alerts_thresholds.md). + +### Layout / Posición (3 posiciones) + +- Soporta **3 posiciones**: `Right` / `Left` / `Bottom`. +- **Default Desktop:** `Right` (centrado vertical, con margen para no tapar Layers). +- **Default Mobile:** `Bottom` (colapsado por default). +- El Dock es **colapsable** (no draggable por ahora). + +--- + +## 2) FMAP “Near my location” — criterio humano v1 + +### Centro (prioridad) + +1. Si hay **GPS** (mi ubicación), usar ese punto. +2. Si no hay GPS pero hay **Puerto seleccionado**, usar el puerto. +3. Si no hay ninguno, usar el **centro del viewport**. + +### Radio (near) + +- **Radio base:** 15 km. +- Si hay pocos resultados: + - Si resultados `<= 3`: expandir a **30 km**. + - Si sigue vacío: mostrar **“No data near”**, pero mantener **Go** para encuadrar lo disponible (si hay data fuera del radio). + +### RJ hint (stateHint) + +- Si `stateHint = RJ` y hay recursos FMAP etiquetados RJ: + - priorizar RJ **dentro del radio**. + - si no hay RJ dentro del radio, volver al criterio normal sin filtro. + +### Qué se muestra por default cuando FMAP está ON y NO hay puerto + +Mostrar solo lo esencial: + +- **Desembarque** +- **Hielo** +- **Refugio / fondeo seguro** (si existe) + +El resto queda para **Details** (solo visible con `debug=1`). + +--- + +## 3) Copy / Labels (texto UI fijo — source of truth) + +- Toggle: **“FMAP (Recursos locales)”** +- Estado FMAP: **“Near my location (15 km)”** o **“Port resources”** o **“No data near”** +- Estados genéricos: **“Data on Hold”**, **“fetch error”** (cuando aplique) +- Botón: **“Go”** +- Botón: **“Copy debug”** + +En EN/PT no cambiar el sentido (p. ej. "Near my location" se mantiene como tal, no traducir "Near" de forma rara). + +--- + +## 4) Alerts badge — definición v1 (qué cuenta y qué no) + +El badge “Alerts” en el botón Fisher representa **SOLO** condiciones de riesgo con **datos reales**. Es muy importante: qué cuenta y qué no. + +**Cuenta (suma al badge):** + +- Barra en Caution/Danger (umbrales en [Umbrales Alerts v1](./FISHER_UI_CONTRACT_v1_alerts_thresholds.md)). +- Viento u olas superando umbral. +- Corriente fuerte (si existe dato). +- Tormenta/visibilidad crítica (si existe). +- GPS muy stale (afecta seguridad inmediata). + +**NO cuenta:** + +- **"Data on Hold"** — se muestra en UI pero no suma al badge. +- "fetch error" de capas no críticas (o si hay fallback). +- Smart Fishing, cosas de debug. +- Estado normal de capas (Boyas ON / AIS ON). + +--- + +## Criterios de aceptación (para QA rápido) + +Deben ser comprobables con estas 2 URLs: + +- **`/map?start=realtime`** — Dock visible, toggles operativos, badges. +- **`/map?start=realtime&layers=1`** — Dock se auto-oculta; Layers (solo Base Maps) accesible. + +Checklist: + +- [ ] Dock colapsa a “Fisher” y badges se ven. +- [ ] Con `layers=1` el Dock se oculta. +- [ ] FMAP ON sin puerto muestra solo esenciales y el estado “Near my location (15 km)”. +- [ ] El menú Layers (top-right) muestra solo Base Maps (sin excepciones). + +--- + +*Addendum al Fisher UI Contract v1. Revisar con cada cambio de Dock/FMAP.* diff --git a/docs/ui/FISHER_UI_CONTRACT_v1_badges.pt.md b/docs/ui/FISHER_UI_CONTRACT_v1_badges.pt.md new file mode 100644 index 000000000..f9746dbaa --- /dev/null +++ b/docs/ui/FISHER_UI_CONTRACT_v1_badges.pt.md @@ -0,0 +1,109 @@ +# Contrato de UI v1 — Fisher Dock + FMAP Near + Badges + +**Complemento de:** [FISHER_UI_CONTRACT_v1](./FISHER_UI_CONTRACT_v1.pt.md) +**Idioma:** PT · **Outras versões:** [ES](./FISHER_UI_CONTRACT_v1_badges.md) · [EN](./FISHER_UI_CONTRACT_v1_badges.en.md) + +--- + +## Invariantes (NÃO quebrar) + +- O menu **Layers (topo-direita)** é **apenas Base Maps** (estilos de mapa). Zero camadas operacionais aí. **Sem exceções.** +- As camadas operacionais (GPS/Boias/AIS/Barra/Zonas/Sinais/FMAP) vivem **só** no **Fisher Dock**. Capas operativas só no Dock (repetido de propósito). +- O Dock **nunca tapa** o dropdown de Layers. **Regra de layout:** margem ou safe area para Layers (topo-direita) ficar sempre acessível. +- Se `layers=1` (painel Layers aberto), o Fisher Dock **auto-oculta-se**. Invariante duro. + +--- + +## 1) Fisher Dock (recolhível) — comportamento definitivo + +### Estados + +- **Expandido:** mostra os 7 toggles + estado (Loaded/Visible onde aplicar) + ações (Go / Copy debug). +- **Recolhido:** reduz-se a **um único botão**: **“Fisher”**. + +### Badges do botão “Fisher” (quando recolhido) + +- **GPS badge:** `ON/OFF` (ou ponto verde/cinza). Se existir: indicar **stale** quando a posição tiver mais de 5 min (ex. "stale" ou ícone distinto). +- **Alerts badge:** número (`2`) ou ponto vermelho se houver alertas ativos. O que conta e o que não: ver §4 e aditamento [Umbrais Alerts v1](./FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md). + +### Layout / Posição (3 posições) + +- Suporta **3 posições**: `Right` / `Left` / `Bottom`. +- **Default Desktop:** `Right` (centrado verticalmente, com margem para não tapar Layers). +- **Default Mobile:** `Bottom` (recolhido por padrão). +- O Dock é **recolhível** (não arrastável por agora). + +--- + +## 2) FMAP “Near my location” — critério humano v1 + +### Centro (prioridade) + +1. Se houver **GPS** (minha localização), usar esse ponto. +2. Se não houver GPS mas houver **Porto seleccionado**, usar o porto. +3. Se não houver nenhum, usar o **centro do viewport**. + +### Raio (near) + +- **Raio base:** 15 km. +- Se poucos resultados: + - Se resultados `<= 3`: expandir para **30 km**. + - Se continuar vazio: mostrar **“No data near”**, mas manter **Go** para enquadrar o disponível (se houver dados fora do raio). + +### RJ hint (stateHint) + +- Se `stateHint = RJ` e houver recursos FMAP etiquetados RJ: + - priorizar RJ **dentro do raio**. + - se não houver RJ dentro do raio, voltar ao critério normal sem filtro. + +### O que se mostra por padrão quando FMAP está ON e NÃO há porto + +Mostrar só o essencial: + +- **Desembarque** +- **Gelo** +- **Refúgio / fundeio seguro** (se existir) + +O resto fica em **Details** (só visível com `debug=1`). + +--- + +## 3) Copy / Labels (texto UI fixo — source of truth) + +- Toggle: **“FMAP (Recursos locais)”** +- Estado FMAP: **“Near my location (15 km)”** ou **“Port resources”** ou **“No data near”** +- Estados genéricos: **“Data on Hold”**, **“fetch error”** (quando aplicar) +- Botão: **“Go”** +- Botão: **“Copy debug”** + +Em EN/PT não mudar o sentido (ex. manter "Near my location" como tal; não traduzir "Near" de forma estranha). + +--- + +## 4) Alerts badge — definição v1 (o que conta e o que não) + +O badge “Alerts” no botão Fisher representa **SOMENTE** condições de risco com **dados reais**. É importante: o que conta e o que não. + +**Conta (soma ao badge):** Barra em Caution/Danger (umbrais em [Umbrais Alerts v1](./FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md)); vento ou ondas acima do limiar; corrente forte (se existir); tempestade/visibilidade crítica (se existir); GPS muito stale. + +**NÃO conta:** “Data on Hold” — mostra-se na UI mas não soma ao badge. “fetch error” de camadas não críticas (ou se houver fallback). Smart Fishing, debug. Estado normal das camadas (Boias ON / AIS ON). + +--- + +## Critérios de aceitação (para QA rápido) + +Devem ser comprováveis com estas 2 URLs: + +- **`/map?start=realtime`** — Dock visível, toggles operativos, badges. +- **`/map?start=realtime&layers=1`** — Dock auto-oculta-se; Layers (só Base Maps) acessível. + +Checklist: + +- [ ] Dock recolhe para “Fisher” e os badges são visíveis. +- [ ] Com `layers=1` o Dock oculta-se. +- [ ] FMAP ON sem porto mostra só essenciais e o estado “Near my location (15 km)”. +- [ ] O menu Layers (topo-direita) mostra só Base Maps (sem exceções). + +--- + +*Aditamento ao Contrato de UI Fisher v1. Revisar em cada alteração de Dock/FMAP.* diff --git a/docs/ui/FISHER_UI_LIST_v1.en.md b/docs/ui/FISHER_UI_LIST_v1.en.md new file mode 100644 index 000000000..ee44cc356 --- /dev/null +++ b/docs/ui/FISHER_UI_LIST_v1.en.md @@ -0,0 +1,112 @@ +# Fisher standard list - UI Contract v1 + +**Mapping to:** Dock vs Preparation vs Forecast vs Smart Fishing +**Priorities:** **P0 (essential today)** / **P1 (highly desirable)** / **P2 (later)** +**Language:** EN | **Other versions:** [ES](./FISHER_UI_LIST_v1.md) | [PT](./FISHER_UI_LIST_v1.pt.md) + +--- + +## P0 - Minimum showable (real MVP) + +### 1) Position and situational awareness + +- **My location (GPS)** -> **Dock**: ON/OFF + status (accuracy/age) + "Go" +- **Seamarks / Nautical signals** (channels, beacons, marks) -> **Dock**: ON/OFF + tile counter (debug only) + no visual overload +- **Buoys (PNBOIA / local)** -> **Dock**: ON/OFF + Loaded/Visible + Go + Copy debug (no silent failure) + +### 2) Immediate risk (safety) + +- **Bar risk** -> **Dock**: ON/OFF + "Alerts" badge if high risk; "Go" (to critical zone/entry) if applicable +- **Active alerts (summary)** -> **Dock (badge)**: only what affects immediate navigation (bar/wind/waves/tides when available) + +### 3) Traffic + +- **AIS (vessels)** -> **Dock**: ON/OFF + "Open details" (only if it does not break UX). Large list/table: outside Dock. + +### 4) Local resources (fisher operation) + +- **FMAP (Local resources)** -> **Dock**: ON by default when **port** exists; if no port: **Near my location**. Show only essentials: landing, ice, refuge/safe anchorage. Go + Copy debug; Details only debug=1. + +### 5) Minimum preparation (before departure) + +- **Select Port** -> **Preparation** +- **Approach / entry/exit route** -> **Preparation** +- **Basic checklist** (comms/fuel/safety) -> **Preparation** (if exists; otherwise P1) + +--- + +## P1 - Highly desirable (so the fisher trusts it) + +### 6) Operational meteo (reliable, no "Data on Hold") + +- **Wind** (now + next hours) -> **Forecast** +- **Waves / swell** -> **Forecast** +- **Precipitation/visibility** (if available) -> **Forecast** +- Rule: if no real data, show "Data on Hold" without breaking the flow (do not block Dock). + +### 7) Tides and currents (core fishing/navigation) + +- **Tides** (height/times) -> **Forecast** (and mini-summary in **Dock**): e.g. "Tide: up 1.2m (14:10)" +- **Currents** (direction/speed) -> **Forecast**. If missing, explicit "Data on Hold" without badge (unless critical in dangerous area). + +### 8) Zones (legal/environmental/operational context) + +- **Zones** (fishing allowed / restriction / protection / closures) -> **Dock**: ON/OFF + simple legend; large details in Preparation or dedicated panel. + +### 9) Reporting / incidents + +- **Report** (quick event) -> **Dock**: smooth open/close, clear back button. If backend is down: inline error (no toast), allow "save local draft" (P2 if not exists). + +--- + +## P2 - Sophisticated (when MVP is stable) + +### 10) Smart Fishing (optimisation) + +- **Smart Fishing** -> **Smart Fishing**: recommendations, effort, prediction, advanced layers. Never blocks basic navigation. + +### 11) Bathymetry / bottom / profiles + +- **Bathymetry** -> **Forecast** or dedicated panel; ON/OFF in Dock only if it does not clutter. + +### 12) Log / trip log and local learning + +- **Trip, catch, conditions log** -> **Preparation / Smart Fishing** + +### 13) External integrations + +- **Notices to mariners / closures** -> **Forecast** (and Alerts badge if critical). + +--- + +## Placement rules (to avoid chaos) + +- **Dock** = "now / on the water": quick toggles + minimal status + Go/Copy. +- **Preparation** = "before leaving": port, approach, port resources, checklist, configuration. +- **Forecast** = "next hours/days": tides, wind, waves, currents, notices. +- **Smart Fishing** = "optimise": everything that is extra, not survival. + +--- + +## Badges (contract) + +- **GPS badge:** ON/OFF (green/grey). +- **Alerts badge:** only immediate risks: Bar at risk; wind/waves/tides/currents above threshold (when available); "Data on Hold" only if something critical for navigation is missing in current context. + +--- + +## "Showable" checklist (definition of success) + +P0 complete and working with no silent failures: + +- [ ] GPS + Go +- [ ] Seamarks visible when ON +- [ ] PNBOIA Loaded/Visible + Go +- [ ] Bar (even "on hold" but without breaking) +- [ ] AIS ON/OFF +- [ ] FMAP essentials appear (port or near my location) +- [ ] Preparation: select port and see basic associated resources + +--- + +*Fisher standard list - UI Contract v1. Review on each release.* diff --git a/docs/ui/FISHER_UI_LIST_v1.md b/docs/ui/FISHER_UI_LIST_v1.md new file mode 100644 index 000000000..9aeb4b2da --- /dev/null +++ b/docs/ui/FISHER_UI_LIST_v1.md @@ -0,0 +1,134 @@ +# Lista estándar del pescador — UI Contract v1 + +**Mapeo a:** Dock vs Preparation vs Forecast vs Smart Fishing +**Prioridades:** **P0 (imprescindible hoy)** / **P1 (muy deseable)** / **P2 (más adelante)** +**Idioma:** ES · **Otras versiones:** [EN](./FISHER_UI_LIST_v1.en.md) · [PT](./FISHER_UI_LIST_v1.pt.md) + +--- + +## P0 — Lo mínimo mostrable (MVP real) + +### 1) Posición y conciencia situacional + +- **Mi ubicación (GPS)** → **Dock** + - ON/OFF + estado (precision/edad) + “Go” +- **Seamarks / Señales náuticas** (canales, balizas, marcas) → **Dock** + - ON/OFF + contador tiles (solo debug) + sin choclazo visual +- **Boyas (PNBOIA / locales)** → **Dock** + - ON/OFF + Loaded/Visible + Go + Copy debug (sin silent failure) + +### 2) Riesgo inmediato (seguridad) + +- **Riesgo de Barra** → **Dock** + - ON/OFF + badge “Alerts” si riesgo alto + - “Go” (llevar a zona/entrada crítica) si aplica +- **Alertas activas (resumen)** → **Dock (badge)** + - solo lo que afecta navegación inmediata (barra/viento/olas/mareas cuando existan) + +### 3) Tráfico + +- **AIS (embarcaciones)** → **Dock** + - ON/OFF + “Open details” (solo si no rompe UX) + - (la lista/tabla grande: fuera del Dock) + +### 4) Recursos locales (operación del pescador) + +- **FMAP (Recursos locales)** → **Dock** + - ON por default cuando hay **puerto**; si no hay puerto: **Near my location** + - Mostrar solo esenciales: desembarque, hielo, refugio/fondeo seguro + - Go + Copy debug; Details solo `debug=1` + +### 5) Preparación mínima (antes de salir) + +- **Select Port (puerto/base)** → **Preparation** +- **Approach / ruta de entrada/salida** → **Preparation** +- **Checklist básico** (comms/combustible/seguridad) → **Preparation** (si existe; si no, P1) + +--- + +## P1 — Muy deseable (para que el pescador confíe) + +### 6) Meteo operativo (confiable, sin “Data on Hold”) + +- **Viento** (ahora + próximas horas) → **Forecast** +- **Olas / mar de fondo** → **Forecast** +- **Precipitación/visibilidad** (si está disponible) → **Forecast** +- Regla: si no hay data real, mostrar “Data on Hold” pero SIN romper el flujo (no bloquear Dock). + +### 7) Mareas y corrientes (core pesca/navegación) + +- **Mareas** (altura/horas) → **Forecast** (y mini-resumen en **Dock**) + - Dock muestra: “Tide: ↑ 1.2m (14:10)” o similar (compacto) +- **Corrientes** (dirección/velocidad) → **Forecast** + - Si no está, queda explícito en “Data on Hold” sin badge (salvo que sea crítico y esté faltando en zona peligrosa). + +### 8) Zonas (contexto legal/ambiental/operativo) + +- **Zonas** (pesca permitida / restricción / protección / cierres) → **Dock** + - ON/OFF + legend simple; detalles grandes van a Preparation o panel dedicado + +### 9) Reporting / incidencias + +- **Reportar** (evento rápido) → **Dock** + - Abrir/cerrar fluido, botón volver claro + - Si backend cae: error inline (no toast), y permitir “guardar borrador local” (P2 si no existe) + +--- + +## P2 — Sofisticado (cuando el MVP ya no se rompe) + +### 10) Smart Fishing (optimización) + +- **Smart Fishing** → **Smart Fishing** + - Recomendaciones, esfuerzo, predicción, capas avanzadas + - Nunca bloquea navegación básica + +### 11) Batimetría / fondos / perfiles + +- **Bathymetry** → **Forecast** o panel dedicado (según UX) + - ON/OFF en Dock solo si no ensucia + +### 12) Diario / bitácora y aprendizaje local + +- **Registro de viajes, capturas, condiciones** → **Preparation / Smart Fishing** + +### 13) Integraciones externas + +- **Avisos a los navegantes / cierres** → **Forecast** (y badge Alerts si crítico) + +--- + +## Reglas de colocación (para que no se vuelva un caos) + +- **Dock** = “ahora / en el agua”: toggles rápidos + estados mínimos + Go/Copy. +- **Preparation** = “antes de salir”: puerto, approach, recursos del puerto, checklist, configuración. +- **Forecast** = “próximas horas/días”: mareas, viento, olas, corrientes, avisos. +- **Smart Fishing** = “optimizar”: todo lo que es plus, no supervivencia. + +--- + +## Badges (contrato) + +- **GPS badge:** ON/OFF (verde/gris). +- **Alerts badge:** solo riesgos inmediatos: + - Barra en riesgo + - Viento/olas/mareas/corrientes superando umbral (cuando existan) + - “Data on Hold” solo si falta algo crítico para navegación en el contexto actual + +--- + +## Checklist de “mostrable” (definición de éxito) + +P0 completo funcionando y sin silent failures: + +- [ ] GPS + Go +- [ ] Seamarks visibles cuando ON +- [ ] PNBOIA Loaded/Visible + Go +- [ ] Barra (aunque sea “on hold” pero sin romper) +- [ ] AIS ON/OFF +- [ ] FMAP esenciales aparecen (puerto o near my location) +- [ ] Preparation: seleccionar puerto y ver recursos básicos asociados + +--- + +*Lista estándar del pescador — UI Contract v1. Revisar con cada release.* diff --git a/docs/ui/FISHER_UI_LIST_v1.pt.md b/docs/ui/FISHER_UI_LIST_v1.pt.md new file mode 100644 index 000000000..4178f6f79 --- /dev/null +++ b/docs/ui/FISHER_UI_LIST_v1.pt.md @@ -0,0 +1,134 @@ +# Lista padrão do pescador — Contrato de UI v1 + +**Mapeamento para:** Dock vs Preparation vs Forecast vs Smart Fishing +**Prioridades:** **P0 (imprescindível hoje)** / **P1 (muito desejável)** / **P2 (mais adiante)** +**Idioma:** PT · **Outras versões:** [ES](./FISHER_UI_LIST_v1.md) · [EN](./FISHER_UI_LIST_v1.en.md) + +--- + +## P0 — O mínimo mostrável (MVP real) + +### 1) Posição e consciência situacional + +- **Minha localização (GPS)** → **Dock** + - ON/OFF + estado (precisão/idade) + “Go” +- **Seamarks / Sinais náuticos** (canais, balizas, marcas) → **Dock** + - ON/OFF + contador de tiles (só debug) + sem choclazo visual +- **Boias (PNBOIA / locais)** → **Dock** + - ON/OFF + Loaded/Visible + Go + Copy debug (sem falha silenciosa) + +### 2) Risco imediato (segurança) + +- **Risco de Barra** → **Dock** + - ON/OFF + badge “Alerts” se risco alto + - “Go” (levar à zona/entrada crítica) se aplicar +- **Alertas activos (resumo)** → **Dock (badge)** + - só o que afecta navegação imediata (barra/vento/ondas/marés quando existirem) + +### 3) Tráfego + +- **AIS (embarcações)** → **Dock** + - ON/OFF + “Open details” (só se não quebrar UX) + - (lista/tabela grande: fora do Dock) + +### 4) Recursos locais (operação do pescador) + +- **FMAP (Recursos locais)** → **Dock** + - ON por padrão quando há **porto**; se não há porto: **Near my location** + - Mostrar só essenciais: desembarque, gelo, refúgio/fundeio seguro + - Go + Copy debug; Details só `debug=1` + +### 5) Preparação mínima (antes de sair) + +- **Select Port (porto/base)** → **Preparation** +- **Approach / rota de entrada/saída** → **Preparation** +- **Checklist básico** (comms/combustível/segurança) → **Preparation** (se existir; senão, P1) + +--- + +## P1 — Muito desejável (para o pescador confiar) + +### 6) Meteo operativo (confiável, sem “Data on Hold”) + +- **Vento** (agora + próximas horas) → **Forecast** +- **Ondas / mar de fundo** → **Forecast** +- **Precipitação/visibilidade** (se disponível) → **Forecast** +- Regra: se não há dados reais, mostrar “Data on Hold” mas SEM quebrar o fluxo (não bloquear Dock). + +### 7) Marés e correntes (core pesca/navegação) + +- **Marés** (altura/horas) → **Forecast** (e mini-resumo no **Dock**) + - Dock mostra: “Tide: ↑ 1.2m (14:10)” ou similar (compacto) +- **Correntes** (direcção/velocidade) → **Forecast** + - Se não estiver, fica explícito em “Data on Hold” sem badge (salvo se crítico em zona perigosa). + +### 8) Zonas (contexto legal/ambiental/operativo) + +- **Zonas** (pesca permitida / restrição / protecção / encerramentos) → **Dock** + - ON/OFF + legenda simples; detalhes grandes em Preparation ou painel dedicado + +### 9) Reporting / incidentes + +- **Reportar** (evento rápido) → **Dock** + - Abrir/fechar fluido, botão voltar claro + - Se backend cair: erro inline (não toast), permitir “guardar rascunho local” (P2 se não existir) + +--- + +## P2 — Sofisticado (quando o MVP já não se quebra) + +### 10) Smart Fishing (optimização) + +- **Smart Fishing** → **Smart Fishing** + - Recomendações, esforço, predição, camadas avançadas + - Nunca bloqueia navegação básica + +### 11) Batimetria / fundos / perfis + +- **Bathymetry** → **Forecast** ou painel dedicado (conforme UX) + - ON/OFF no Dock só se não poluir + +### 12) Diário / bitácora e aprendizagem local + +- **Registo de viagens, capturas, condições** → **Preparation / Smart Fishing** + +### 13) Integrações externas + +- **Avisos aos navegantes / encerramentos** → **Forecast** (e badge Alerts se crítico) + +--- + +## Regras de colocação (para não virar caos) + +- **Dock** = “agora / na água”: toggles rápidos + estados mínimos + Go/Copy. +- **Preparation** = “antes de sair”: porto, approach, recursos do porto, checklist, configuração. +- **Forecast** = “próximas horas/dias”: marés, vento, ondas, correntes, avisos. +- **Smart Fishing** = “optimizar”: tudo o que é plus, não sobrevivência. + +--- + +## Badges (contrato) + +- **GPS badge:** ON/OFF (verde/cinza). +- **Alerts badge:** só riscos imediatos: + - Barra em risco + - Vento/ondas/marés/correntes acima do limiar (quando existirem) + - “Data on Hold” só se faltar algo crítico para navegação no contexto actual + +--- + +## Checklist “mostrável” (definição de sucesso) + +P0 completo a funcionar e sem falhas silenciosas: + +- [ ] GPS + Go +- [ ] Seamarks visíveis quando ON +- [ ] PNBOIA Loaded/Visible + Go +- [ ] Barra (mesmo “on hold” mas sem quebrar) +- [ ] AIS ON/OFF +- [ ] FMAP essenciais aparecem (porto ou near my location) +- [ ] Preparation: seleccionar porto e ver recursos básicos associados + +--- + +*Lista padrão do pescador — Contrato de UI v1. Revisar em cada release.* diff --git a/docs/ui/MERGE_GATE_E_F_H_EVIDENCE.md b/docs/ui/MERGE_GATE_E_F_H_EVIDENCE.md new file mode 100644 index 000000000..6f4a3f46f --- /dev/null +++ b/docs/ui/MERGE_GATE_E_F_H_EVIDENCE.md @@ -0,0 +1,82 @@ +# UI Docs: Fisher UI Contract v1 (RJ addendum) + Merge Gate (E+F+H) + +## Scope +- Docs only: `docs/ui/**` +- No backend changes +- Goal: freeze RJ defaults + Data-on-Hold policy + explicit Alerts thresholds, and enforce "no silent failure". + +## Contract invariants (must remain true) +- Merge gate: **✅ passes Appendix E + Appendix F + Appendix H** +- Appendix order is **E → F → H → G** in **ES/EN/PT** +- Data on Hold = inline status (no noisy toasts), fail-open with last-good data when possible +- Alerts badge thresholds are explicit for RJ (Maricá/Guanabara) + Barra de São João + +## Evidence (paste real outputs) + +### 1) Appendix order proof (ES/EN/PT) + +``` +docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.md + 365:## Apéndice E — Acceptance Checklist P0 (RJ) (pre-merge) + 420:## Apéndice F — Sources of Truth + "Data on Hold" Policy (v1, RJ) + 529:## Apéndice H — RJ Defaults + Alerts Thresholds (v1) + 570:## Apéndice G — Fisher Dock "collision-free" (RJ v1) + +docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.en.md + 371:## Appendix E — P0 Acceptance Checklist (RJ) (pre-merge) + 426:## Appendix F — Sources of Truth + "Data on Hold" Policy (v1, RJ) + 535:## Appendix H — RJ Defaults + Alerts Thresholds (v1) + 576:## Appendix G — Fisher Dock "collision-free" (RJ v1) + +docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md + 365:## Apêndice E — Checklist de Aceite P0 (RJ) (pré-merge) + 420:## Apêndice F — Sources of Truth + Política "Data on Hold" (v1, RJ) + 529:## Apêndice H — Padrões RJ + Limiares de Alertas (v1) + 570:## Apêndice G — Fisher Dock "collision-free" (RJ v1) +``` + +(Expected to show headings for F, H, G in each file, in order) → **✅ E → F → H → G in all three.** + +### 2) Files changed (only docs/ui) + +``` +docs/ui/README.md +--- +(staged: none) +``` + +(Expected: `docs/ui/...` only. No frontend/backend.) → **✅ Only docs/ui.** + +### 3) Git status sanity + +``` + M docs/ui/README.md +?? docs/ui/FISHER_UI_CONTRACT_v1.en.md +?? docs/ui/FISHER_UI_CONTRACT_v1.md +?? docs/ui/FISHER_UI_CONTRACT_v1.pt.md +?? docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.en.md +?? docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.md +?? docs/ui/FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md +?? docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md +?? docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.md +?? docs/ui/FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md +?? docs/ui/FISHER_UI_CONTRACT_v1_badges.en.md +?? docs/ui/FISHER_UI_CONTRACT_v1_badges.md +?? docs/ui/FISHER_UI_CONTRACT_v1_badges.pt.md +?? docs/ui/FISHER_UI_LIST_v1.en.md +?? docs/ui/FISHER_UI_LIST_v1.md +?? docs/ui/FISHER_UI_LIST_v1.pt.md +?? docs/ui/contract.en.md +?? docs/ui/contract.md +?? docs/ui/contract.pt.md +``` + +(Expected: clean or only intended docs/ui files staged/committed.) → **README modified; RJ addendum + other docs/ui files are Untracked.** + +--- + +## NOTE (important) +If any of these are still listed as **Untracked**, they are not in the PR yet. +They must be added + committed to be part of the review. + +✅ **Merge rule (hard):** Do not merge if Appendix E/F/H text drifts or order breaks. diff --git a/docs/ui/README.md b/docs/ui/README.md index d9a81101a..f0671e250 100644 --- a/docs/ui/README.md +++ b/docs/ui/README.md @@ -14,5 +14,11 @@ - `page_templates.md` - `ux_backlog.md` - `ux_contract.md` +- `contract.md` — UI Contract v1 (Dock vs Modes); [EN](contract.en.md), [PT](contract.pt.md) +- `FISHER_UI_CONTRACT_v1.md` — iURi Fisher UI Contract v1 (Dock, no silent failure, Copy debug); [EN](FISHER_UI_CONTRACT_v1.en.md), [PT](FISHER_UI_CONTRACT_v1.pt.md) +- `FISHER_UI_CONTRACT_v1_badges.md` — Fisher Dock colapsable + FMAP Near + Badges + Alerts; [EN](FISHER_UI_CONTRACT_v1_badges.en.md), [PT](FISHER_UI_CONTRACT_v1_badges.pt.md) +- `FISHER_UI_CONTRACT_v1_alerts_thresholds.md` — Umbrales concretos Alerts v1 + Single source of truth + "si hay duda, no alertar"; [EN](FISHER_UI_CONTRACT_v1_alerts_thresholds.en.md), [PT](FISHER_UI_CONTRACT_v1_alerts_thresholds.pt.md) +- `FISHER_UI_CONTRACT_v1_RJ_addendum.md` — Addendum RJ (Maricá / Guanabara / Barra de São João): defaults RJ, señal Barra siempre visible, AIS ON en Guanabara; [EN](FISHER_UI_CONTRACT_v1_RJ_addendum.en.md), [PT](FISHER_UI_CONTRACT_v1_RJ_addendum.pt.md) +- `FISHER_UI_LIST_v1.md` — Lista estándar del pescador (P0/P1/P2, Dock/Preparation/Forecast/Smart Fishing); [EN](FISHER_UI_LIST_v1.en.md), [PT](FISHER_UI_LIST_v1.pt.md) - `ux_copy_salida_semaforo_timeline.md` - `ux_spec_map_first_semaforo_modes.md` diff --git a/docs/ui/contract.en.md b/docs/ui/contract.en.md new file mode 100644 index 000000000..80589c6e1 --- /dev/null +++ b/docs/ui/contract.en.md @@ -0,0 +1,85 @@ +# UI Contract v1 (Dock vs Modes) + +**Version:** 1 +**Language:** EN (see also: [ES](./contract.md), [PT](./contract.pt.md)) + +--- + +## 0) Principles (non-negotiable) + +- **No fixed Dock that covers the map.** +- **No silent failure:** if something does not load, state must be visible ("loading / ok / error") with a human-readable reason. +- **Debug must not invade UX:** Copy must never open odd panels on the map. "Inspectors" only when `debug=1`. + +--- + +## 1) Snap Dock (Desktop) and Sheet Dock (Mobile) + +### Desktop: Snap Dock + +- Floating dock with **3 positions:** Right / Left / Bottom +- **Controls:** Collapse/Expand; Move (switches between anchors; no free drag) +- **Persistence:** remembers position and collapsed state (per user/device) +- **Coexistence rule:** Dock must never cover global controls (top-right: Layers, Open Tech Center). If on Right, lower its top offset so it does not overlap them. + +### Mobile: Sheet Dock + +- Collapsible bottom-sheet style dock. More conservative defaults (fewer toggles visible, more "Advanced"). + +--- + +## 2) What lives where (Dock vs Preparation/Forecast/Smart Fishing) + +### Dock (Live Map) + +The Dock is **quick operation**, not deep configuration. + +**Includes:** Operational toggles (GPS, Buoys/PNBOIA, AIS off by default, Bars/Zones/Seamarks on by default, FMAP on by default). Quick actions per layer: **Go** (fitBounds), **Copy** (runtime/debug JSON), **Details** only when `debug=1`. + +**Dock does NOT include:** port selection, checklist, forecast cards, smart fishing details. + +### Preparation Mode + +Single source of truth for: Select/Change port, pre-departure checklist, "Essential layers for departure" preset. May show toggles as a guided preset. + +### Forecast Mode + +Shows forecast-related layers and cards; hold state is clear. + +### Smart Fishing + +Separate module with fallback/minimal state if backend does not respond. + +--- + +## 3) Defaults (v1) + +Theme/Light: Light ON (Auto/Night option). Seamarks, Zones, FMAP: ON. AIS, ICE: OFF (Advanced). + +--- + +## 4) FMAP: scoped without port + +**No port:** FMAP ON, near my location (GPS or stateHint). Dock: one row "FMAP (seed): Points / Polygons / Pending", Go/Copy, Details only `debug=1`. No raw point/polygon list in normal UI. + +**With port:** FMAP expands to full set for port; still only counts and actions. + +--- + +## 5) "Copy" contract + +Copy always copies JSON. Copy never opens overlays/inspectors. Extra inspectors/panels only with `debug=1` and explicit "Open inspector" button. + +--- + +## 6) Definition of Done (v1 stable) + +- Dock: does not cover Layers/Tech Center; Snap Right/Left/Bottom and Collapse work and persist. +- FMAP: single row; no port = near my location, with port = full set. +- Seamarks: ON triggers tile requests and counters in Copy debug. +- PNBOIA: not cancelled by pan/zoom; fail-open keeps last good list or fallback. +- Debug: Copy opens nothing; Details only `debug=1`. + +--- + +*Living document; review with each map/Dock release.* diff --git a/docs/ui/contract.md b/docs/ui/contract.md new file mode 100644 index 000000000..37ec5e115 --- /dev/null +++ b/docs/ui/contract.md @@ -0,0 +1,148 @@ +# UI Contract v1 (Dock vs Modes) + +**Versión:** 1 +**Idioma:** ES (versiones: [EN](./contract.en.md), [PT](./contract.pt.md)) + +--- + +## 0) Principios (no negociables) + +- **Nada de Dock fijo que tape el mapa.** +- **No "silent failure":** si algo no carga, debe verse estado ("loading / ok / error") y razón humana. +- **Debug no puede invadir UX:** Copy nunca debe abrir paneles raros en el mapa. "Inspectors" solo con `debug=1`. + +--- + +## 1) Snap Dock (Desktop) y Sheet Dock (Mobile) + +### Desktop: Snap Dock + +- Dock flotante con **3 posiciones:** Right / Left / Bottom +- **Controles:** + - Collapse/Expand + - Move (cambia entre anclajes; sin drag libre) +- **Persistencia:** recuerda posición + colapsado (por usuario/dispositivo) +- **Regla de convivencia:** Dock nunca debe cubrir los controles globales (top-right: Layers, Open Tech Center). Si está en Right, baja su "top offset" para no taparlos. + +### Mobile: Sheet Dock + +- Dock tipo "bottom sheet" colapsable. +- Defaults más conservadores (menos toggles visibles, más "Advanced"). + +--- + +## 2) Qué vive dónde (Dock vs Preparation/Forecast/Smart Fishing) + +### Dock (Live Map) + +El Dock es **operación rápida**, no configuración profunda. + +**Incluye:** + +- **Toggles operativos:** + - GPS (ON/OFF) + - Buoys (PNBOIA) (ON/OFF) + estado (Loaded/Visible + error) + - AIS (OFF por default, aparece en Advanced o como toggle si lo querés visible) + - Bars / Zonas / Señales (Seamarks) (ON por default donde aplique) + - FMAP (ON por default) +- **Acciones rápidas** (por layer, si aplica): + - **Go** (fitBounds) + - **Copy** (copia JSON de runtime/debug) + - **"Details"** (si existe): solo con `debug=1` + +**Dock NO incluye:** selección de puerto, checklist, forecast cards, smart fishing details, etc. + +### Preparation Mode + +"Single source of truth" para: + +- Select/Change port +- checklist pre-salida +- preset de capas "Essential layers for departure" + +Puede mostrar toggles, pero como preset guiado, no como panel técnico. + +### Forecast Mode + +Muestra capas y cards relacionadas a forecast. Si data está en hold, se ve claramente (ya lo hace bien). + +### Smart Fishing + +Vive como módulo aparte (con fallback/minimal state si backend no responde). + +--- + +## 3) Defaults (v1) + +| Capa / Opción | Default | Notas | +|--------------------|-----------|--------------------| +| Theme/Light | Light ON | Con opción Auto/Night | +| Señales/Seamarks | ON | — | +| Zonas | ON | — | +| FMAP | ON | Scoped (ver §4) | +| AIS | OFF | Advanced | +| ICE | OFF | Advanced | + +--- + +## 4) FMAP: scoped sin puerto (para evitar "choclazo") + +### Cuando NO hay puerto seleccionado + +- **FMAP ON**, pero muestra solo **near my location:** + - radio/viewport alrededor de GPS actual + - si no hay GPS: fallback a `stateHint` +- **Dock muestra una sola fila:** + - "FMAP (seed): Points / Polygons / Pending" + - **Go** / **Copy** + - **Details** solo `debug=1` +- **No listar** "point/polygon/point…" en UI normal. + +### Cuando SÍ hay puerto + +- FMAP se expande al set completo del puerto. +- Sigue sin listar crudo; solo counts + acciones. + +--- + +## 5) "Copy" contract (para cortar confusión) + +- **Copy** siempre copia JSON. +- **Copy** jamás abre overlays/inspectors. +- Cualquier inspector o panel extra: solo `debug=1` y con botón explícito **"Open inspector"**. + +--- + +## 6) Definition of Done (blindaje anti-regresión) + +Para considerar **"v1 estable"**: + +### Dock + +- [ ] No tapa Layers / Open Tech Center +- [ ] Snap entre Right/Left/Bottom funciona y persiste +- [ ] Collapse funciona y persiste + +### FMAP + +- [ ] 1 sola fila (no choclazo) +- [ ] Sin puerto: near my location +- [ ] Con puerto: full set del puerto + +### Seamarks + +- [ ] ON: hay requests de tiles (y counters en Copy debug) + +### PNBOIA + +- [ ] No se cancela por pan/zoom +- [ ] Fail-open: si falla fetch, mantiene última lista buena o fallback + +### Debug + +- [ ] Copy no abre nada +- [ ] Details solo `debug=1` + +--- + +*Documento vivo; revisar con cada release de mapa/Dock.* diff --git a/docs/ui/contract.pt.md b/docs/ui/contract.pt.md new file mode 100644 index 000000000..eb39399ff --- /dev/null +++ b/docs/ui/contract.pt.md @@ -0,0 +1,148 @@ +# Contrato de UI v1 (Dock vs Modos) + +**Versão:** 1 +**Idioma:** PT (ver também: [ES](./contract.md), [EN](./contract.en.md)) + +--- + +## 0) Princípios (não negociáveis) + +- **Nada de Dock fixo que tape o mapa.** +- **Sem falha silenciosa:** se algo não carregar, deve aparecer estado ("loading / ok / error") e razão legível. +- **Debug não pode invadir a UX:** Copy nunca deve abrir painéis estranhos no mapa. "Inspectors" só com `debug=1`. + +--- + +## 1) Snap Dock (Desktop) e Sheet Dock (Mobile) + +### Desktop: Snap Dock + +- Dock flutuante com **3 posições:** Direita / Esquerda / Inferior +- **Controles:** + - Recolher/Expandir + - Mover (muda entre ancoragens; sem arraste livre) +- **Persistência:** lembra posição + recolhido (por usuário/dispositivo) +- **Regra de convivência:** Dock nunca deve cobrir os controles globais (topo-direita: Layers, Open Tech Center). Se estiver à direita, desce o "top offset" para não tapá-los. + +### Mobile: Sheet Dock + +- Dock tipo "bottom sheet" recolhível. +- Padrões mais conservadores (menos toggles visíveis, mais "Advanced"). + +--- + +## 2) O que fica onde (Dock vs Preparation/Forecast/Smart Fishing) + +### Dock (Mapa ao Vivo) + +O Dock é **operação rápida**, não configuração profunda. + +**Inclui:** + +- **Toggles operacionais:** + - GPS (ON/OFF) + - Boias (PNBOIA) (ON/OFF) + estado (Loaded/Visible + erro) + - AIS (OFF por padrão, aparece em Advanced ou como toggle se desejado) + - Barras / Zonas / Sinais (Seamarks) (ON por padrão onde aplicar) + - FMAP (ON por padrão) +- **Ações rápidas** (por camada, quando aplicar): + - **Go** (fitBounds) + - **Copy** (copia JSON de runtime/debug) + - **"Details"** (se existir): só com `debug=1` + +**Dock NÃO inclui:** seleção de porto, checklist, cards de previsão, detalhes de smart fishing, etc. + +### Modo Preparation + +Fonte única de verdade para: + +- Selecionar/Trocar porto +- checklist pré-saída +- preset de camadas "Essential layers for departure" + +Pode mostrar toggles como preset guiado, não como painel técnico. + +### Modo Forecast + +Mostra camadas e cards relacionados a previsão. Se os dados estiverem em hold, fica claro (já funciona bem). + +### Smart Fishing + +Vive como módulo à parte (com fallback/estado mínimo se o backend não responder). + +--- + +## 3) Padrões (v1) + +| Camada / Opção | Padrão | Notas | +|-------------------|----------|------------------------| +| Theme/Light | Light ON | Com opção Auto/Night | +| Sinais/Seamarks | ON | — | +| Zonas | ON | — | +| FMAP | ON | Scoped (ver §4) | +| AIS | OFF | Advanced | +| ICE | OFF | Advanced | + +--- + +## 4) FMAP: scoped sem porto (para evitar "choclazo") + +### Quando NÃO há porto selecionado + +- **FMAP ON**, mas mostra só **perto da minha localização:** + - raio/viewport em torno do GPS atual + - sem GPS: fallback para `stateHint` +- **Dock mostra uma única linha:** + - "FMAP (seed): Points / Polygons / Pending" + - **Go** / **Copy** + - **Details** só com `debug=1` +- **Não listar** "point/polygon/point…" na UI normal. + +### Quando há porto selecionado + +- FMAP expande para o conjunto completo do porto. +- Continua sem listar cru; só contagens + ações. + +--- + +## 5) Contrato do "Copy" (para cortar confusão) + +- **Copy** sempre copia JSON. +- **Copy** nunca abre overlays/inspectors. +- Qualquer inspector ou painel extra: só com `debug=1` e com botão explícito **"Open inspector"**. + +--- + +## 6) Definition of Done (blindagem anti-regressão) + +Para considerar **"v1 estável"**: + +### Dock + +- [ ] Não tapa Layers / Open Tech Center +- [ ] Snap entre Direita/Esquerda/Inferior funciona e persiste +- [ ] Recolher funciona e persiste + +### FMAP + +- [ ] 1 só linha (sem choclazo) +- [ ] Sem porto: perto da minha localização +- [ ] Com porto: conjunto completo do porto + +### Seamarks + +- [ ] ON: há requests de tiles (e contadores no Copy debug) + +### PNBOIA + +- [ ] Não é cancelado por pan/zoom +- [ ] Fail-open: se o fetch falhar, mantém última lista boa ou fallback + +### Debug + +- [ ] Copy não abre nada +- [ ] Details só com `debug=1` + +--- + +*Documento vivo; revisar a cada release de mapa/Dock.* diff --git a/frontend/src/components/maps/PnboiaLayerLeaflet.tsx b/frontend/src/components/maps/PnboiaLayerLeaflet.tsx index 95c93bf0f..d6bc9e264 100644 --- a/frontend/src/components/maps/PnboiaLayerLeaflet.tsx +++ b/frontend/src/components/maps/PnboiaLayerLeaflet.tsx @@ -70,6 +70,30 @@ const normalizeBuoys = (rawItems: any[]): BuoyData[] => }) .filter((item): item is BuoyData => item !== null); +function resolvePnboiaUrl(lastFetchUrl: string | null): string | null { + if (!lastFetchUrl) return null; + const raw = String(lastFetchUrl); + if (raw.startsWith("http://") || raw.startsWith("https://")) return raw; + return `${window.location.origin}${raw.startsWith("/") ? raw : `/${raw}`}`; +} + +function inferRequestOutcome(args: { + loading: boolean; + fetchErrorName: string | null; + lastAttempt: { outcome: string | null } | null; + lastHttpStatus: number | null; + lastError: string | null; +}): "ok" | "aborted" | "blocked" | "pending" { + if (args.loading) return "pending"; + const outcome = args.lastAttempt?.outcome || null; + if (outcome === "ok") return "ok"; + if (outcome === "abort" || args.fetchErrorName === "AbortError") return "aborted"; + if (outcome === "error" || outcome === "empty") return "blocked"; + if (args.lastHttpStatus && args.lastHttpStatus >= 200 && args.lastHttpStatus < 400) return "ok"; + if (args.lastError) return "blocked"; + return "pending"; +} + export const PnboiaLayer: React.FC = ({ maxVisible = 50, showLabels = false, @@ -120,11 +144,18 @@ export const PnboiaLayer: React.FC = ({ const [lastFetchUrl, setLastFetchUrl] = useState("/api/v1/pnboia/list"); const [lastOkAtISO, setLastOkAtISO] = useState(null); const [loadingMs, setLoadingMs] = useState(0); + const [abortReason, setAbortReason] = useState<"timeout" | "cleanup" | null>(null); const [retryNonce, setRetryNonce] = useState(0); const [mapViewKey, setMapViewKey] = useState(0); const inFlightRef = useRef(false); const loadingStartedAtRef = useRef(null); const staleCacheRef = useRef<{ ts: number; buoys: BuoyData[] } | null>(null); + const abortReasonRef = useRef<"timeout" | "cleanup" | null>(null); + + const onBuoysLoadedRef = useRef(onBuoysLoaded); + const onLoadingChangeRef = useRef(onLoadingChange); + const onErrorChangeRef = useRef(onErrorChange); + const onDebugInfoChangeRef = useRef(onDebugInfoChange); const paneName = pane || "pnboia"; @@ -149,8 +180,50 @@ export const PnboiaLayer: React.FC = ({ return () => window.removeEventListener("iuri:pnboia-retry", onRetry as EventListener); }, []); - useEffect(() => onLoadingChange?.(loading), [loading, onLoadingChange]); - useEffect(() => onErrorChange?.(lastError), [lastError, onErrorChange]); + useEffect(() => { + onBuoysLoadedRef.current = onBuoysLoaded; + }, [onBuoysLoaded]); + useEffect(() => { + onLoadingChangeRef.current = onLoadingChange; + }, [onLoadingChange]); + useEffect(() => { + onErrorChangeRef.current = onErrorChange; + }, [onErrorChange]); + useEffect(() => { + onDebugInfoChangeRef.current = onDebugInfoChange; + }, [onDebugInfoChange]); + + useEffect(() => onLoadingChangeRef.current?.(loading), [loading]); + useEffect(() => onErrorChangeRef.current?.(lastError), [lastError]); + + useEffect(() => { + loadedBuoysRef.current = loadedBuoys; + }, [loadedBuoys]); + + useEffect(() => { + // Cold start: hydrate last-good from localStorage cache (best-effort). + try { + const raw = localStorage.getItem(CACHE_KEY); + if (!raw) return; + const parsed = JSON.parse(raw) as any; + const ts = Number(parsed?.ts); + const buoys = Array.isArray(parsed?.buoys) ? parsed.buoys : null; + if (!Number.isFinite(ts) || !buoys) return; + const normalized = normalizeBuoys(buoys); + if (normalized.length === 0) return; + const ageMs = Math.max(0, Date.now() - ts); + if (ageMs <= CACHE_TTL_MS) { + cacheTsRef.current = ts; + setLoadedBuoys(normalized); + setSource("cache"); + setLastOkAtISO(new Date(ts).toISOString()); + } else { + staleCacheRef.current = { ts, buoys: normalized }; + } + } catch { + // ignore + } + }, []); useEffect(() => { loadedBuoysRef.current = loadedBuoys; @@ -207,6 +280,8 @@ export const PnboiaLayer: React.FC = ({ setFetchErrorName(null); setLastHttpStatus(null); setLastError(null); + setAbortReason(null); + abortReasonRef.current = null; const nextAttempt = attemptRef.current + 1; attemptRef.current = nextAttempt; @@ -223,7 +298,10 @@ export const PnboiaLayer: React.FC = ({ let cancelled = false; const controller = new AbortController(); - const abortTimer = window.setTimeout(() => controller.abort(), ABORT_MS); + const abortTimer = window.setTimeout(() => { + abortReasonRef.current = "timeout"; + controller.abort(); + }, ABORT_MS); const url = "/api/v1/pnboia/list"; setLastFetchUrl(url); @@ -270,7 +348,7 @@ export const PnboiaLayer: React.FC = ({ } catch { // ignore } - onBuoysLoaded?.(items.length); + onBuoysLoadedRef.current?.(items.length); setLastAttempt((prev) => prev ? { ...prev, endedAtISO: new Date().toISOString(), elapsedMs: now - startedAt, outcome: "ok" } : prev ); @@ -286,6 +364,7 @@ export const PnboiaLayer: React.FC = ({ // Fail-open: keep the last loaded list, only update error. setLastError(message); setFetchErrorName(isAbort ? "AbortError" : errName); + setAbortReason(isAbort ? (abortReasonRef.current ?? "cleanup") : null); // keep current list (fail-open); keep source as-is (live/cache) setLastAttempt((prev) => prev @@ -323,10 +402,13 @@ export const PnboiaLayer: React.FC = ({ return () => { cancelled = true; window.clearTimeout(abortTimer); + if (!controller.signal.aborted) { + abortReasonRef.current = abortReasonRef.current ?? "cleanup"; + } controller.abort(); inFlightRef.current = false; }; - }, [ABORT_MS, onBuoysLoaded, retryNonce, fallbackBuoys]); + }, [retryNonce]); const { visibleCount, nearest, paddedBounds } = useMemo(() => { const bounds = map.getBounds().pad(0.15); @@ -371,6 +453,14 @@ export const PnboiaLayer: React.FC = ({ const status = loading ? "loading" : lastError ? "error" : loadedBuoys.length > 0 ? "ok" : "empty"; const cacheAgeMs = cacheTsRef.current ? Math.max(0, Date.now() - cacheTsRef.current) : null; const lastOkAgeMs = lastOkTs ? Math.max(0, Date.now() - lastOkTs) : null; + const resolvedUrl = resolvePnboiaUrl(lastFetchUrl); + const requestOutcome = inferRequestOutcome({ + loading, + fetchErrorName, + lastAttempt, + lastHttpStatus, + lastError, + }); window.dispatchEvent( new CustomEvent("iuri:pnboia-status", { detail: { @@ -387,10 +477,14 @@ export const PnboiaLayer: React.FC = ({ slow, slowMs: SLOW_MS, abortMs: ABORT_MS, + abortReason, fetchErrorName, source, cacheAgeMs, lastOkAgeMs, + lastFetchUrl, + resolvedUrl, + requestOutcome, attempt, timing: lastAttempt, }, @@ -403,6 +497,7 @@ export const PnboiaLayer: React.FC = ({ fetchErrorName, lastAttempt, lastError, + lastFetchUrl, lastOkTs, loadedBuoys.length, loading, @@ -420,6 +515,14 @@ export const PnboiaLayer: React.FC = ({ const ne = bounds.getNorthEast(); const cacheAgeMs = cacheTsRef.current ? Math.max(0, Date.now() - cacheTsRef.current) : null; const lastOkAgeMs = lastOkTs ? Math.max(0, Date.now() - lastOkTs) : null; + const resolvedUrl = resolvePnboiaUrl(lastFetchUrl); + const requestOutcome = inferRequestOutcome({ + loading, + fetchErrorName, + lastAttempt, + lastHttpStatus, + lastError, + }); window.dispatchEvent( new CustomEvent("iuri:pnboia-runtime", { detail: { @@ -429,6 +532,7 @@ export const PnboiaLayer: React.FC = ({ loadedCount: loadedBuoys.length, visibleCount, lastFetchUrl, + resolvedUrl, httpStatus: lastHttpStatus, lastOkAtISO, zoom: map.getZoom(), @@ -437,10 +541,12 @@ export const PnboiaLayer: React.FC = ({ slow, slowMs: SLOW_MS, abortMs: ABORT_MS, + abortReason, fetchErrorName, source, cacheAgeMs, lastOkAgeMs, + requestOutcome, attempt, timing: lastAttempt, }, @@ -484,8 +590,8 @@ export const PnboiaLayer: React.FC = ({ lastOkTs, lastError, }; - onDebugInfoChange?.(info); - }, [lastError, lastOkTs, loadedBuoys, map, maxVisible, onDebugInfoChange, visibleCount]); + onDebugInfoChangeRef.current?.(info); + }, [lastError, lastOkTs, loadedBuoys, map, maxVisible, visibleCount]); const toRender = loadedBuoys.slice(0, Math.max(0, maxVisible)); diff --git a/frontend/src/components/maps/realtime/FisherDock.tsx b/frontend/src/components/maps/realtime/FisherDock.tsx index b6bc5f8ef..ecba4e5ed 100644 --- a/frontend/src/components/maps/realtime/FisherDock.tsx +++ b/frontend/src/components/maps/realtime/FisherDock.tsx @@ -17,7 +17,9 @@ type PnboiaRuntime = { status: "idle" | "loading" | "ok" | "empty" | "error"; error: string | null; lastFetchUrl: string | null; + resolvedUrl: string | null; httpStatus: number | null; + requestOutcome: "ok" | "aborted" | "blocked" | "pending" | null; lastOkAtISO: string | null; zoom: number | null; bounds: { sw: { lat: number; lon: number }; ne: { lat: number; lon: number } } | null; @@ -25,6 +27,7 @@ type PnboiaRuntime = { slow: boolean; slowMs: number | null; abortMs: number | null; + abortReason: "timeout" | "cleanup" | null; fetchErrorName: string | null; source: "live" | "cache" | "fallback" | null; cacheAgeMs: number | null; @@ -39,6 +42,21 @@ type PnboiaRuntime = { } | null; }; +type FmapRuntime = { + enabled: boolean; + disabled: boolean; + error: string | null; + counts: { points: number; polygons: number; pending: number }; + bounds: { sw: { lat: number; lon: number }; ne: { lat: number; lon: number } } | null; + sample: Array<{ + id: string; + label: string; + category: string | null; + kind: "point" | "polygon"; + hasCoords: boolean; + }>; +}; + const rowBase = "w-full rounded-lg border border-slate-800/60 bg-slate-950/70 px-2 py-2 text-left text-[12px] text-slate-100 shadow-sm hover:bg-slate-950/85"; @@ -74,7 +92,9 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) status: "idle", error: null, lastFetchUrl: null, + resolvedUrl: null, httpStatus: null, + requestOutcome: null, lastOkAtISO: null, zoom: null, bounds: null, @@ -82,6 +102,7 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) slow: false, slowMs: null, abortMs: null, + abortReason: null, fetchErrorName: null, source: null, cacheAgeMs: null, @@ -91,6 +112,29 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) })); const [pnboiaCopyFeedback, setPnboiaCopyFeedback] = useState(null); const [pnboiaCopyFallbackText, setPnboiaCopyFallbackText] = useState(null); + const [signalsTilesRequested, setSignalsTilesRequested] = useState(0); + const [signalsTilesLoaded, setSignalsTilesLoaded] = useState(0); + const [signalsTilesError, setSignalsTilesError] = useState(0); + const [fmapRuntime, setFmapRuntime] = useState(() => { + const points = FMAP_SIGNALS_SEED.filter((s) => s.kind === "point").length; + const polygons = FMAP_SIGNALS_SEED.filter((s) => s.kind === "polygon").length; + const pending = FMAP_SIGNALS_SEED.filter((s) => { + if (s.kind === "point") return !s.point; + return !s.polygon || s.polygon.length < 3; + }).length; + return { + enabled: false, + disabled: false, + error: null, + counts: { points, polygons, pending }, + bounds: null, + sample: [], + }; + }); + const [fmapDetailsOpen, setFmapDetailsOpen] = useState(false); + const [fmapDetailsLimit, setFmapDetailsLimit] = useState(200); + const [fmapCopyFeedback, setFmapCopyFeedback] = useState(null); + const [fmapCopyFallbackText, setFmapCopyFallbackText] = useState(null); const debugMode = useMemo(() => { try { return new URLSearchParams(window.location.search).get("debug") === "1"; @@ -115,7 +159,12 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) : "idle", error, lastFetchUrl: detail?.lastFetchUrl ? String(detail.lastFetchUrl) : null, + resolvedUrl: detail?.resolvedUrl ? String(detail.resolvedUrl) : null, httpStatus: Number.isFinite(Number(detail?.httpStatus)) ? Number(detail.httpStatus) : null, + requestOutcome: + detail?.requestOutcome === "ok" || detail?.requestOutcome === "aborted" || detail?.requestOutcome === "blocked" || detail?.requestOutcome === "pending" + ? detail.requestOutcome + : null, lastOkAtISO: detail?.lastOkAtISO ? String(detail.lastOkAtISO) : null, zoom: Number.isFinite(Number(detail?.zoom)) ? Number(detail.zoom) : null, bounds: @@ -135,6 +184,7 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) slow: Boolean(detail?.slow), slowMs: Number.isFinite(Number(detail?.slowMs)) ? Number(detail.slowMs) : null, abortMs: Number.isFinite(Number(detail?.abortMs)) ? Number(detail.abortMs) : null, + abortReason: detail?.abortReason === "timeout" || detail?.abortReason === "cleanup" ? detail.abortReason : null, fetchErrorName: detail?.fetchErrorName ? String(detail.fetchErrorName) : null, source: detail?.source === "live" || detail?.source === "cache" || detail?.source === "fallback" @@ -162,6 +212,64 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) return () => window.removeEventListener("iuri:pnboia-runtime", onPnboiaRuntime as EventListener); }, []); + useEffect(() => { + const onFmapRuntime = (event: Event) => { + const detail = (event as CustomEvent).detail || {}; + const counts = detail?.counts || {}; + setFmapRuntime({ + enabled: Boolean(detail?.enabled), + disabled: Boolean(detail?.disabled), + error: detail?.error ? String(detail.error) : null, + counts: { + points: Number.isFinite(Number(counts.points)) ? Number(counts.points) : 0, + polygons: Number.isFinite(Number(counts.polygons)) ? Number(counts.polygons) : 0, + pending: Number.isFinite(Number(counts.pending)) ? Number(counts.pending) : 0, + }, + bounds: + detail?.bounds && + detail.bounds.sw && + detail.bounds.ne && + Number.isFinite(Number(detail.bounds.sw.lat)) && + Number.isFinite(Number(detail.bounds.sw.lon)) && + Number.isFinite(Number(detail.bounds.ne.lat)) && + Number.isFinite(Number(detail.bounds.ne.lon)) + ? { + sw: { lat: Number(detail.bounds.sw.lat), lon: Number(detail.bounds.sw.lon) }, + ne: { lat: Number(detail.bounds.ne.lat), lon: Number(detail.bounds.ne.lon) }, + } + : null, + sample: Array.isArray(detail?.sample) + ? detail.sample + .slice(0, 10) + .map((x: any) => ({ + id: String(x?.id ?? ""), + label: String(x?.label ?? ""), + category: x?.category ? String(x.category) : null, + kind: x?.kind === "polygon" ? "polygon" : "point", + hasCoords: Boolean(x?.hasCoords), + })) + .filter((x: any) => x.id && x.label) + : [], + }); + }; + window.addEventListener("iuri:fmap-runtime", onFmapRuntime as EventListener); + return () => window.removeEventListener("iuri:fmap-runtime", onFmapRuntime as EventListener); + }, []); + + useEffect(() => { + const onSignalsTiles = (event: Event) => { + const detail = (event as CustomEvent).detail || {}; + const nextRequested = Number(detail?.tilesRequested ?? 0); + const nextLoaded = Number(detail?.tilesLoaded ?? 0); + const nextError = Number(detail?.tilesError ?? 0); + if (Number.isFinite(nextRequested) && nextRequested >= 0) setSignalsTilesRequested(nextRequested); + if (Number.isFinite(nextLoaded) && nextLoaded >= 0) setSignalsTilesLoaded(nextLoaded); + if (Number.isFinite(nextError) && nextError >= 0) setSignalsTilesError(nextError); + }; + window.addEventListener("iuri:signals-tiles", onSignalsTiles as EventListener); + return () => window.removeEventListener("iuri:signals-tiles", onSignalsTiles as EventListener); + }, []); + const gpsEnabled = Boolean(layers.my_location_leaflet?.enabled || layers.my_location_maplibre?.enabled); const pnboiaEnabled = Boolean(layers.pnboia?.enabled); const aisEnabled = Boolean(layers.ais?.enabled); @@ -201,9 +309,11 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) const version = resp ? await resp.json().catch(() => null) : null; gitCommit = version?.gitCommit ?? (window as any).__IURI_VERSION__?.gitCommit ?? null; const resolvedUrl = - pnboiaRuntime.lastFetchUrl && (pnboiaRuntime.lastFetchUrl.startsWith("http://") || pnboiaRuntime.lastFetchUrl.startsWith("https://")) + pnboiaRuntime.resolvedUrl ?? + (pnboiaRuntime.lastFetchUrl && + (pnboiaRuntime.lastFetchUrl.startsWith("http://") || pnboiaRuntime.lastFetchUrl.startsWith("https://")) ? pnboiaRuntime.lastFetchUrl - : `${window.location.origin}${pnboiaRuntime.lastFetchUrl || "/api/v1/pnboia/list"}`; + : `${window.location.origin}${pnboiaRuntime.lastFetchUrl || "/api/v1/pnboia/list"}`); const payload = { href: window.location.href, version: { gitCommit }, @@ -215,12 +325,14 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) lastFetchUrl: pnboiaRuntime.lastFetchUrl, resolvedUrl, httpStatus: pnboiaRuntime.httpStatus, + requestOutcome: pnboiaRuntime.requestOutcome, lastOkAtISO: pnboiaRuntime.lastOkAtISO, lastOkAgeMs: pnboiaRuntime.lastOkAgeMs, loadingMs: pnboiaRuntime.loadingMs, slow: pnboiaRuntime.slow, slowMs: pnboiaRuntime.slowMs, abortMs: pnboiaRuntime.abortMs, + abortReason: pnboiaRuntime.abortReason, fetchErrorName: pnboiaRuntime.fetchErrorName, cacheAgeMs: pnboiaRuntime.cacheAgeMs, source: pnboiaRuntime.source, @@ -232,6 +344,13 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) bounds: pnboiaRuntime.bounds, }, stateHint, + signals: { + tilesRequested: signalsTilesRequested, + tilesLoaded: signalsTilesLoaded, + tilesError: signalsTilesError, + fmapDisabled: fmapRuntime.disabled, + fmapError: fmapRuntime.error, + }, }; const text = JSON.stringify(payload, null, 2); const ok = await copyText(text); @@ -247,6 +366,51 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) } }; + const handleFmapGo = () => { + window.dispatchEvent(new CustomEvent("iuri:fmap-go")); + }; + + const handleFmapCopyDebug = async () => { + try { + setFmapCopyFallbackText(null); + let gitCommit: string | null = null; + const resp = await fetch("/version.json", { cache: "no-store" }).catch(() => null); + const version = resp ? await resp.json().catch(() => null) : null; + gitCommit = version?.gitCommit ?? (window as any).__IURI_VERSION__?.gitCommit ?? null; + const payload = { + href: window.location.href, + version: { gitCommit }, + fmap: { + enabled: fmapRuntime.enabled, + disabled: fmapRuntime.disabled, + error: fmapRuntime.error, + counts: fmapRuntime.counts, + bounds: fmapRuntime.bounds, + sample: fmapRuntime.sample, + }, + map: { + zoom: pnboiaRuntime.zoom, + bounds: pnboiaRuntime.bounds, + }, + stateHint, + signals: { + tilesRequested: signalsTilesRequested, + tilesLoaded: signalsTilesLoaded, + tilesError: signalsTilesError, + }, + }; + const text = JSON.stringify(payload, null, 2); + const ok = await copyText(text); + if (!ok && debugMode) setFmapCopyFallbackText(text); + if (!ok) throw new Error("copy failed"); + setFmapCopyFeedback("Copied"); + window.setTimeout(() => setFmapCopyFeedback(null), 1500); + } catch { + setFmapCopyFeedback("Copy failed"); + window.setTimeout(() => setFmapCopyFeedback(null), 1500); + } + }; + if (hidden) return null; return ( @@ -349,6 +513,21 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) > Copy + {debugMode && pnboiaRuntime.resolvedUrl ? ( + + ) : null} ) : null} @@ -411,32 +590,104 @@ export function FisherDock({ hidden, layers, onChange, browserPosition }: Props) {seamarksEnabled ? (
-
-
FMAP Señales (seed)
-
{FMAP_SIGNALS_SEED.length}
+
+
+
FMAP Señales (seed)
+
+ Points: {fmapRuntime.counts.points} · Polygons: {fmapRuntime.counts.polygons} · Pending:{" "} + {fmapRuntime.counts.pending} + {fmapRuntime.disabled || fmapRuntime.error ? ( + · (error) + ) : null} +
+
+
+
+ + + {debugMode ? ( + + ) : null} +
+
-
- {FMAP_SIGNALS_SEED.slice(0, 6).map((s) => ( -
-
-
{s.label}
- {s.kind === "point" ? ( -
- {s.point ? `${s.point.lat.toFixed(4)}, ${s.point.lon.toFixed(4)}` : "pending coordinates"} -
- ) : ( -
- {s.polygon ? `${s.polygon.length} pts` : "pending coordinates"} -
- )} + {debugMode && fmapCopyFallbackText ? ( +