refactor(ui): reorganize Nearby Nodes (one list, two sources, unified menu)

Replace the many scrollable filter/sort categories and inconsistent popups
with a single list / detail / action-menu path over two sources (stored
contacts and live discover scan). Filter (type) and sort are independent
axes: LEFT/RIGHT cycle the type filter, the action menu's Sort row is
adjusted in-place with LEFT/RIGHT (not Enter). The active filter is shown
in the SCAN title and empty-list messages distinguish "no contacts" from
"filtered out". Filter/sort persist across re-entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-15 09:22:26 +02:00
parent 38e9273ec7
commit 019f739b8b
3 changed files with 575 additions and 435 deletions

View File

@@ -0,0 +1,210 @@
# Nearby Nodes — analiza i propozycja uporządkowania
> Branch: `refactor/nearby-nodes`
> Plik źródłowy: [NearbyScreen.h](../../examples/companion_radio/ui-new/NearbyScreen.h)
> Status: **propozycja** (do akceptacji przed implementacją)
---
## 1. Jak narzędzie jest zbudowane dzisiaj
Ekran łączy w sobie **trzy osobne tryby** o własnych listach, widokach
szczegółów i popupach:
```
NearbyScreen
├── LIST (kontakty zapisane w mesh) ← tryb domyślny
│ ├── filtr cyklowany LEFT/RIGHT (7 stanów)
│ ├── DETAIL (Enter) → Lat/Lon/Dist/Type/Seen
│ │ └── _opts popup (Hold Enter): Navigate / Ping / Save waypoint
│ │ └── _ping_menu popup
│ ├── NAV view (pełnoekranowa nawigacja)
│ └── _ctx_menu popup (Hold Enter): Discover / Navigate / Save waypoint
└── DISCOVER (skan na żywo NODE_DISCOVER_REQ) ← osobny pod-ekran
├── lista wyników (karty 2-liniowe)
│ Hold Enter = ponowny skan (brak menu!)
└── DETAIL (Enter) → pubkey / RSSI / SNR / status
└── _ping_menu popup (Hold Enter = od razu ping, bez Options)
```
Stan ekranu trzyma **15+ pól** (`_detail`, `_nav`, `_discover_mode`,
`_ddetail`, `_pinging`, `_filter`, dwa komplety `_sel/_scroll`, trzy
bufory wyników ping…) i **trzy** instancje `PopupMenu` (`_ctx_menu`,
`_opts`, `_ping_menu`).
---
## 2. Co konkretnie jest nieuporządkowane
### 2.1 Jeden cykl filtra miesza trzy różne osie
`LEFT/RIGHT` przewija 7 stanów:
```
Fav · ALL · Comp · Rpt · Room · Snsr · TIME
```
Ale to nie jest jedna oś — to **trzy** wciśnięte w jeden liniowy cykl:
| Stan | Czym naprawdę jest |
| --------------------------- | ---------------------------------------------------- |
| `Fav` | filtr **ulubionych** (flaga `ci.flags & 1`) |
| `ALL/Comp/Rpt/Room/Snsr` | filtr **po typie** węzła |
| `TIME` | **sortowanie** (po `lastmod` zamiast po dystansie) |
Konsekwencje:
- użytkownik „scrolluje po kategoriach” na ślepo — nie widzi wszystkich
naraz, musi cyklować, by trafić w to, czego szuka;
- **kombinacje są niemożliwe**: nie da się zobaczyć „ulubionych
repeaterów posortowanych po dystansie" ani „repeaterów posortowanych
po czasie” — model wymusza dokładnie jeden stan z siedmiu;
- `TIME` jako „filtr" jest mylące — zmienia kolejność, nie zawartość;
- etykieta w nagłówku (`NEARBY[TIME]`) nie mówi, że to sort.
### 2.2 Te same akcje, różne menu w zależności od miejsca
| Akcja | Lista (`_ctx_menu`) | Detail kontaktu (`_opts`) | Detail discover |
| -------------- | :-----------------: | :-----------------------: | :-------------: |
| Navigate | ✓ | ✓ | — |
| Save waypoint | ✓ | ✓ | — |
| Ping | — | ✓ | ✓ |
| Discover | ✓ | — | — |
Ten sam węzeł oferuje inny zestaw akcji w zależności od tego, gdzie na
niego patrzysz. Ping jest osiągalny tylko ze szczegółów, Discover tylko
z listy.
### 2.3 „Hold Enter” znaczy co innego na każdym ekranie
| Ekran | Hold Enter (`KEY_CONTEXT_MENU`) |
| ----------------- | ------------------------------------ |
| Lista nearby | otwiera menu kontekstowe |
| Detail kontaktu | otwiera menu Options |
| Lista discover | **ponowny skan** (żadnego menu) |
| Detail discover | **od razu Ping** (pomija Options) |
Brak spójnego modelu „przytrzymaj = menu akcji”.
### 2.4 `_ping_menu` to bespoke widget
W przeciwieństwie do reszty popupów, ping-menu:
- ma wiersze tylko-do-odczytu (RTT / SNR), więc połyka `UP/DOWN`;
- przebudowuje się w trakcie (`rebuildPingMenu`) gdy przychodzą wyniki;
- zostaje otwarte po `SELECTED` (reszta popupów się zamyka);
- ma własny `handlePingMenuInput` z trybem `allow_enter_to_open`.
To działa, ale jest to czwarty, niestandardowy wzorzec interakcji w
jednym narzędziu.
### 2.5 Duplikacja list/detail/ping
`renderDiscover`/`handleInputDiscover`/`renderDiscoverDetail` to niemal
równoległa kopia logiki listy i szczegółów nearby (osobne `_dsel`,
`_dscroll`, `_d_visible`, własne rysowanie kart). Discover i Nearby
robią to samo — pokazują listę węzłów z możliwością wejścia w szczegóły
i pingowania — ale dwoma osobnymi ścieżkami kodu.
---
## 3. Propozycja uporządkowania
Trzy zasady przewodnie: **(a) jedno menu akcji wszędzie**, **(b) filtr
i sortowanie jako osobne, jawne osie**, **(c) jeden wzorzec listy**.
### 3.1 Rozdziel filtr od sortowania
Zamiast jednego cyklu 7-stanowego — dwie niezależne osie wybierane z
menu (nie przez ślepe cyklowanie):
**Filtr** (oś „co pokazujemy") i **sort** (oś „w jakiej kolejności") są
od siebie niezależne i **łączą się dowolnie**:
```
FILTR (jedna oś — typ węzła, z Ulubionymi) SORT (przełącznik)
• Wszystkie • Dystans (domyślnie)
• Ulubione • Ostatnio słyszane
• Companion
• Repeater
• Room
• Sensor
```
**Sterowanie (zatwierdzone):**
- `LEFT/RIGHT` = szybki cykl **tylko po filtrze-typie** (jedna spójna oś,
znany gest; bez „TIME" zanieczyszczającego cykl);
- **Sort** = przełącznik w menu akcji (`Dystans ↔ Ostatnio słyszane`),
trzymany niezależnie od filtra;
- **Filter…** dostępny też w menu akcji (odkrywalność — cała lista
widoczna naraz, nie tylko cyklowanie).
Dzięki temu „ulubione repeatery po dystansie" staje się możliwe, a w
nagłówku widać oba wymiary, np. `NEARBY · Rpt · ↓dist`.
### 3.2 Jedno spójne menu akcji (Hold Enter wszędzie)
Jeden `PopupMenu` (zamiast `_ctx_menu` + `_opts`), pozycje zależne od
kontekstu, ale **kolejność i nazwy stałe**:
```
Hold Enter → Options
• Navigate (gdy węzeł ma GPS)
• Ping (gdy znamy pubkey)
• Save waypoint (gdy węzeł ma GPS)
──────────────
• Filter… (podmenu z 3.1)
• Sort… (toggle z 3.1)
• Discover scan (uruchamia skan na żywo = przełącza źródło)
```
To samo menu na liście i w szczegółach, w obu źródłach (Zapisane /
Skan). Pozycje niedostępne są pomijane (jak już teraz robi `has_node`),
ale nigdy nie zmieniają kolejności ani nazw. „Hold Enter = menu akcji" —
bez wyjątków. Ping zawsze przez Options (znika „od razu Ping" z detalu
Discover); rescan to pozycja menu, nie ukryty Hold Enter.
### 3.3 Jedna lista, dwa źródła (Zapisane / Skan na żywo)
**Zatwierdzone:** zamiast osobnego pod-ekranu Discover — **jeden
komponent** listy/szczegółów/menu/ping napędzany przełącznikiem
**źródła**:
- ta sama nawigacja, to samo menu akcji, ten sam wzorzec szczegółów i
pingu (znika duplikacja `renderDiscover*`, niespójne Hold Enter i
połowa pól stanu);
- różni się tylko **prawa kolumna** wiersza i pola w detalu:
- źródło **Zapisane** → dystans / azymut / lastmod (jak dziś),
- źródło **Skan na żywo** → RSSI / SNR (dane z `DiscoverResult`).
To nie jest dosłowne zlanie dwóch list w jedną tablicę (dane mają różny
kształt — kontakt ma GPS, wynik skanu ma sygnał), tylko **jedna ścieżka
interakcji nad dwoma źródłami**. Wybór źródła = „Discover scan" w menu
(uruchamia `NODE_DISCOVER_REQ` i przełącza widok na wyniki) oraz powrót
do Zapisanych przez Cancel.
---
## 4. Proponowany zakres (etapami)
| Etap | Zmiana | Ryzyko |
| ---- | ------------------------------------------------------------------------ | :----: |
| 1 | Rozdziel sort od filtra: `TIME` znika z cyklu; `LEFT/RIGHT` cyklują tylko typ+Fav; sort jako stan + toggle | niskie |
| 2 | Scal `_ctx_menu` i `_opts` w jedno menu akcji o stałej kolejności; dodaj Filter…/Sort… | niskie |
| 3 | Ujednolić Hold Enter w Discover (menu zamiast bezpośredniego rescan/ping; Ping zawsze przez Options) | średnie |
| 4 | Scal listę Discover z listą Nearby w jeden komponent z przełącznikiem źródła | wyższe |
Kolejność implementacji: 1 → 2 → 3 → 4. Etapy 12 dają największą
poprawę „uporządkowania" przy najmniejszym ryzyku; 34 domykają
spójność (jedna lista, jedno menu wszędzie).
---
## 5. Decyzje (zatwierdzone 2026-06-14)
1. **Filtr i sort — pełne rozdzielenie osi.** `LEFT/RIGHT` = szybki cykl
filtra-typu; Sort jako toggle w menu; Filter… też w menu dla
odkrywalności. (Nie chowamy wszystkiego do menu — zachowujemy szybki
gest.)
2. **Discover — jedna lista, dwa źródła.** Wspólny komponent
interakcji; różni się tylko prawa kolumna/pola detalu (dystans vs
sygnał).

View File

@@ -18,12 +18,14 @@ The Tools screen is a hub for GPS trail recording, nearby node browsing, rington
| :-----------------------: | :-----------------------: |
| ![](./tls_scr_2_oled.png) | ![](./tls_scr_2_eink.png) |
Browse nodes that have recently advertised on the mesh. Filter by category with **LEFT/RIGHT**:
Browse nodes that have recently advertised on the mesh. **Filter** (which nodes) and **sort** (in what order) are independent axes and combine freely.
Filter by category with **LEFT/RIGHT** (one coherent axis — type only):
| Filter | Shows |
| ------ | ------------------------------ |
| All | All known nodes |
| Fav | Upstream-starred contacts only |
| ALL | All known nodes |
| Comp | Companion (chat) nodes |
| Rpt | Repeaters |
| Room | Room servers |
@@ -31,9 +33,20 @@ Browse nodes that have recently advertised on the mesh. Filter by category with
Select a node to see its coordinates, distance, bearing with cardinal direction, type, and last-heard time.
From the list view, **Hold Enter** opens the context menu, where **Discover nearby** sends a live `NODE_DISCOVER_REQ` scan.
**Hold Enter** opens the same **Options** menu everywhere (list and detail), with a fixed set of actions — only the unavailable ones are omitted:
From either node detail view, **Hold Enter** opens the Ping popup:
| Action | Available when |
| --------------- | --------------------------------------- |
| Navigate | selected node has GPS |
| Ping | a public key is known for the node |
| Save waypoint | selected node has GPS |
| Filter… | always (full filter list in one popup) |
| Sort: Dist/Recent | always (toggles distance ↔ last-heard) |
| Discover scan | always (live `NODE_DISCOVER_REQ` scan) |
The sort toggle switches the list (and the right-hand column) between **distance** and **last-heard**, independent of the active filter.
Selecting **Ping** opens the Ping popup:
| OLED | E-Ink |
| :----------------------------: | :----------------------------: |
@@ -46,21 +59,17 @@ Use **Enter** on the popups `Ping` row to send a direct mesh ping to that nod
---
### Active Discovery
### Active Discovery (live scan)
| OLED | E-Ink |
| :-----------------------: | :-----------------------: |
| ![](./tls_scr_3_oled.png) | ![](./tls_scr_3_eink.png) |
Sends a `NODE_DISCOVER_REQ` ping. Repeaters, sensors and room servers within zero-hop range respond immediately with name, type and signal data.
**Options → Discover scan** sends a `NODE_DISCOVER_REQ`. Repeaters, sensors and room servers within zero-hop range respond immediately with name, type and signal data. This is not a separate screen — it is the **same list switched to a live-scan source**: the right-hand column shows **RSSI** instead of distance, and node detail shows the public key, signal data and contact status.
Results show as 2-line cards: node name + type, then RSSI / SNR / remote SNR.
Because it is the same list, all the same keys apply — **UP/DOWN** to navigate, **Enter** for detail, **Hold Enter** for the Options menu (where **Rescan** repeats the scan and **Ping** works exactly as on stored nodes).
- **UP/DOWN** — navigate results
- **Enter** — open full-screen detail (public key, signal data, contact status)
- **Hold Enter** — rescan
- **Hold Enter** in full-screen detail — open the Ping popup for the selected node
- **Cancel / Back** — return to nearby list
- **Cancel / Back** — return to the stored-nodes list
---