You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(network): search refinements — zoom on hit, address panel, location/staking on detail panels (#102)
- Node hash/name search zooms to the matched node + 1-hop neighbors
- 0x address search opens a dedicated panel with copyable address, link
count, staking positions (CCNs + ALEPH per position), wallet link
- Address-driven spotlight: dim everything outside the wallet's footprint;
highlightedIds now matches id (staker), owner, and reward
- CCN/CRN detail panels show a Location row (flag + country name);
country attribution decoupled from the geo layer toggle
- Search: controlled component (q lifted to page), 280px to match the
detail card, 28×28 info-icon button, gap-0.5 to the input;
close-panel + close-address clear the search input
- Fix: --network-country OKLCH chroma was out of sRGB gamut at hue 200°
and silently dropped by Lightning CSS — country nodes rendered black;
lowered chroma to 0.13 (Decision #77)
Copy file name to clipboardExpand all lines: docs/DECISIONS.md
+24Lines changed: 24 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,6 +18,30 @@ Each entry includes:
18
18
19
19
---
20
20
21
+
## Decision #79 - 2026-05-11
22
+
**Context:** Country attribution on CCN/CRN nodes used to live inside the `if (layers.has("geo"))` block of `buildGraph`, so `n.country` was only populated when the geo layer was on. After adding a "Location" row to the CCN/CRN detail panels (showing `🇫🇷 France` etc.), the row was missing whenever Geo was off (the default).
23
+
**Decision:** Split the geo block into two passes in `network-graph-model.ts`: (1) country *attribution* (set `n.country` on located CCN/CRN, build the `represented` set) always runs; (2) geo *layer artifacts* (the country hub nodes + geo edges) only run when `layers.has("geo")`. Detail panels read `node.country` regardless of layer state.
24
+
**Rationale:** Country is a property of the node, not of a presentation layer. Coupling it to the layer toggle was a quirk of the original geo-layer commit (#74), not a design intent. The split is mechanical, doesn't change geo-layer visuals, and unlocks the Location row without forcing users to enable Geo to see where a node lives. The cost is one extra pass over `nodes` when Geo is off — negligible at ~500 nodes.
25
+
**Alternatives considered:** Look up country directly in the panel from `node-locations.json` (rejected — duplicates the existing graph-model lookup; two sources of truth for the same data). Force the geo layer on by default (rejected — the layer affects the entire layout; turning it on just to show a country label is overkill). Add a separate `geo-attribute` non-visible layer that only sets `country` (rejected — adds a layer concept the user can never toggle, confusing).
26
+
27
+
---
28
+
29
+
## Decision #78 - 2026-05-11
30
+
**Context:** Address deep-link (`?address=0x...`) previously highlighted matching nodes with a pulsing primary-color ring and auto-fit the camera to them, but otherwise nothing changed — no detail panel, no spotlight dim. After making `?selected=` open a node panel, search-by-address still felt like nothing happened: pulse rings were easy to miss against a busy graph and the user got no panel feedback. Separately, `highlightedIds` was computed only from `owner === address`, so a wallet that staked (id-match) or received rewards (reward-match) but didn't own any node showed zero highlights.
31
+
**Decision:** Treat `?address=` as a first-class selection signal. (1) Open a dedicated `NetworkSearchAddressPanel` (right side, 280px, same chrome as the node panel) when address is set and no node is selected — showing the copyable address, link count, an "Open wallet view →" link, and a staking section listing every CCN where the address appears in `c.stakers` with per-position and total ALEPH. (2) Expand `highlightedIds` to match `id` (staker), `owner`, or `reward` so the on-graph pulse reflects the wallet's full footprint. (3) Extend the selection spotlight: when no node is selected but `highlightedIds.size > 0`, `relevantIds = highlightedIds ∪ 1-hop neighbors` and the existing dim path applies to nodes, labels, *and* edges (non-incident edges fade). Address-panel close clears `?address=`*and* the search input, matching the panel-close clears-search pattern.
32
+
**Rationale:** The pulse-ring-only design assumed power users would notice the dim signal — in practice users searched an address and saw "nothing happen." Promoting the address to a real panel + spotlight reuses the same visual idiom users already know from node selection (dim everything else, panel on the right), so address search becomes discoverable without inventing a new pattern. Expanding `highlightedIds` to id/owner/reward matches the wallet's actual footprint, not just its ownership — a staker who doesn't own anything still gets visual presence. The edge-dim extension was a missing piece: previously only `selectedId` drove the `faded` flag, so address search left edges fully lit while nodes dimmed — visually inconsistent.
33
+
**Alternatives considered:** Auto-select the first matching node instead of opening an address panel (rejected — the panel context would be "this node" rather than "this wallet"; misleading when the address owns multiple nodes). Treat the address as a virtual GraphNode and add `kind: "address"` (rejected — ripples through `RADIUS`, color tables, panel switch statements for a UI-only synthetic). Use a thicker pulse ring instead of dimming (rejected — pulse alone didn't read; the dim is what makes the spotlight feel intentional). Keep `highlightedIds` owner-only (rejected — a real-world staker without ownership is invisible, contradicts the new panel's "Linked to N nodes" copy).
34
+
35
+
---
36
+
37
+
## Decision #77 - 2026-05-11
38
+
**Context:** Country nodes on `/network` rendered as flat black circles instead of cyan-teal. The CSS token `--network-country` declared in `globals.css` resolved to an empty string in the browser; SVG `fill="var(--network-country)"` then fell back to the SVG default (`black`). Sibling tokens (`--network-edge-owner`, `--network-edge-reward`) in the same `:root` block compiled fine. The compiled `_next/static/chunks/src_app_globals_*.css` literally did not contain `--network-country` — the property was being stripped at build time.
39
+
**Decision:** Drop the chroma value on `--network-country` from `0.16` (light) / `0.14` (dark) to `0.13` for both, keeping hue `200°` and lightness `0.70` / `0.78`. The token now compiles through Lightning CSS as a valid `lab()` value with a hex fallback.
40
+
**Rationale:** Lightning CSS (Tailwind v4's CSS engine) silently drops `oklch()` values that are out of sRGB gamut at the requested hue, instead of clamping. At hue ~200° (cyan), the in-gamut chroma ceiling is around `0.13` at these lightnesses — `0.16`/`0.14` overshot and got dropped silently. The other tokens used `230°` (blue), which has more headroom and stayed in gamut. The visual difference between chroma `0.13` and `0.14`/`0.16` is small enough to not warrant a different hue or a hex fallback, and keeping OKLCH preserves the codebase convention.
41
+
**Alternatives considered:** Switch the token to a hex value (rejected — breaks the OKLCH convention used by the rest of `globals.css`; would also lose automatic light/dark inversion since hex doesn't communicate intent). Move to a different hue with more headroom (rejected — the cyan/teal hue was the entire point of the choice in Decision #74; would lose the distinct identity vs CCN purple, CRN green, staker amber). Add a hex fallback alongside the OKLCH (rejected — works but doesn't address the root cause of *why* the OKLCH dropped, and would mask the issue if anyone later swapped values back to out-of-gamut). Verified with browser-resolved computed styles after each chroma value.
42
+
43
+
---
44
+
21
45
## Decision #76 - 2026-05-11
22
46
**Context:** Decision #74's initial design treated geo edges as force-only — `network-graph.tsx` early-returned on `e.type === "geo"` so they never rendered, on the theory that proximity to the country dot would already convey "in this country." Real-world testing with Geo + Structural both on showed the cross-country CCN→CRN structural arrows visually muddled the geo grouping — each country dot was surrounded by its cluster but no edges connected them, so the eye couldn't tell which CCN belonged to which country at a glance.
23
47
**Decision:** Render geo edges as a thin, country-tinted dashed tether — `STROKE.geo = var(--network-country)`, `DASH.geo = "1 2"`, `OPACITY.geo = 0.35`, `strokeWidth=0.5`, no arrowhead. The country becomes a visible hub-and-spoke hub. Wire country into the selection-incident-color path (`incidentColor` returns `var(--network-country)` when a country is selected) so picking a country brightens its tethers to `0.9` opacity via the existing `highlightColor` branch in `NetworkEdge`. Add a "Country tether" line swatch to the legend (also conditional on the geo layer).
Copy file name to clipboardExpand all lines: src/changelog.ts
+31-1Lines changed: 31 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -11,9 +11,39 @@ export type VersionEntry = {
11
11
changes: ChangeEntry[];
12
12
};
13
13
14
-
exportconstCURRENT_VERSION="0.12.0";
14
+
exportconstCURRENT_VERSION="0.13.0";
15
15
16
16
exportconstCHANGELOG: VersionEntry[]=[
17
+
{
18
+
version: "0.13.0",
19
+
date: "2026-05-11",
20
+
changes: [
21
+
{
22
+
type: "feature",
23
+
text: "Network graph search: pressing Enter on a node hash or name now zooms in on the matching node and its 1-hop neighborhood — no more squinting at the dot that just got selected. Country searches still focus the country's subgraph; address searches use their own fit path.",
24
+
},
25
+
{
26
+
type: "feature",
27
+
text: "Network graph address search: a 0x address now opens a dedicated panel (right side) listing the copyable wallet, link count, and a Staking section showing every CCN the address stakes on with per-position and total ALEPH. The spotlight dims everything outside the wallet's footprint — nodes where the address is the staker, owner, or reward target stay full opacity, the rest fade. Pulse rings still mark the matches.",
28
+
},
29
+
{
30
+
type: "feature",
31
+
text: "Network graph CCN/CRN cards: new Location row with flag emoji and country name (e.g. 🇫🇷 France) — visible regardless of whether the Geo layer is on, since country attribution is now independent of the layer toggle.",
32
+
},
33
+
{
34
+
type: "ui",
35
+
text: "Network graph search field: same width (280px) as the detail cards so the column reads as one stack, and the info-icon button shrunk to a 28×28 target tight against the input (was a chunky pill-shaped lozenge with extra padding).",
36
+
},
37
+
{
38
+
type: "ui",
39
+
text: "Network graph panels: closing the node detail panel (× button) now also clears the search input — previously you had to clear the field separately.",
40
+
},
41
+
{
42
+
type: "fix",
43
+
text: "Network graph country nodes rendered as flat black circles because the cyan CSS token's OKLCH chroma was out of sRGB gamut at hue 200° and got silently dropped by Lightning CSS. Lowered chroma to a safe value; country nodes now render in their intended cyan-teal.",
0 commit comments