SIGN IN SIGN UP
koala73 / worldmonitor UNCLAIMED

Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface

0 0 157 TypeScript

feat(followed-countries): consumer wiring PR B — CII pin + filter chip + deep-dive notify (#3629)

* feat(cii): pin followed countries to top with stable sort (U6)

Followed countries appear at the top of the CIIPanel ranking,
non-followed maintain their original relative order. Panel re-
renders reactively on WM_FOLLOWED_COUNTRIES_CHANGED.

- Partition logic extracted to _cii-panel-partition.ts (pure helper,
  testable without Panel.ts's import.meta.glob deps).
- Stable sort via Array.prototype.filter twice (preserves order by
  construction, avoids the sort-before-positional-index trap).
- Two section labels ('FOLLOWING' / 'ALL') render only when both
  groups are non-empty; zero-followed and all-followed cases match
  today's UX byte-identically.
- subscribe() from the service (NOT manual window.addEventListener);
  unsubscribe in destroy() to prevent listener leaks across re-mount.
- rerenderRows() reuses cached this.scores — no recomputation on
  watchlist change.
- 16 new tests covering: zero-followed, all-followed, partial,
  followed-but-absent-in-scores, watchlist-mutation reactivity,
  and section-label rendering predicate.

Plan: U6
PR: #3621-stack (PR B builds on PR A foundation)

* feat(panels): 'Followed only' filter chip on Disease + Displacement (U7)

Toggleable chip on country-scoped multi-row panels that hides
non-followed entries client-side. Default off. Persisted per-panel-
instance in localStorage so each panel's choice survives reload but
doesn't bleed across unrelated panels.

- Reusable factory in src/utils/followed-only-chip.ts mirroring
  follow-button's { html, attach, isActive } shape. Storage key:
  wm-followed-only-filter-${panelId}.
- Disabled when watchlist empty (tooltip prompts user to follow
  countries first); re-renders disabled state on
  WM_FOLLOWED_COUNTRIES_CHANGED.
- Hidden entirely when VITE_FOLLOW_COUNTRIES_ENABLED is off.
- Empty-state message when filter yields zero rows: 'No items in
  your followed countries. Add countries by tapping the star, or
  turn off this filter.'

Target panels (multi-country lists with per-row ISO-2 codes):
- DiseaseOutbreaksPanel
- DisplacementPanel (filter applies inside both origins + hosts tabs)

Skipped CountryDeepDivePanel (single-country, tautological).
Skipped news / climate / breaking-news panels (no per-row country
code in row data).

24 new tests covering: chip helper unit behavior (default off,
persistence, per-panel scoping, disabled tooltip, re-render on
watchlist change, feature-flag-off branch, onChange callback) +
panel integration (5/5 rows; 5→2 with 2 followed; empty watchlist
disables chip but persisted-active state preserved; rows without
code are filtered when chip on; re-filter on watchlist change).

Plan: U7
PR: #3621-stack (PR B builds on PR A foundation)

* feat(deep-dive): 'Notify me about this country' sub-action under follow star (U8 degraded)

Mounts a small inline link next to the FollowButton on the Country Deep
Dive header. Visible only while the user is following the country;
clicking opens the notifications settings tab.

Degraded path: the alertRules.countries schema PR has NOT merged, so
this PR ships only the link wiring -- no form pre-fill. The future PR
swaps the body of openNotificationsForCountry to pre-fill the create
form via routing param. Injection point is intentionally a single
helper so the change is local. See plan U8 R9 + TODO comment inside
src/utils/notify-country-link.ts.

- New src/utils/notify-country-link.ts factory: { html, attach } -> teardown
  shape, mirrors FollowButton + FollowedOnlyChip; subscribes to
  WM_FOLLOWED_COUNTRIES_CHANGED so visibility tracks per-country state.
- CountryDeepDivePanel mounts a sibling host beside cdp-follow-btn-host
  and tears it down alongside the FollowButton.
- Event-handlers wires WM_OPEN_NOTIFICATIONS_FOR_COUNTRY to
  unifiedSettings.open('notifications') (degraded -- detail.country
  ignored until schema PR enables pre-fill).
- main.css adds .cdp-notify-link understated dotted-underline style.
- 12 new tests in tests/country-deep-dive-notify-sub-action.test.mjs
  cover visibility / reactivity / click->helper / click->real event /
  feature-flag-off no-op / teardown idempotency.

* fix(followed-countries): three review fixes — flag gate, listener teardown, chip placement

PR #3629 (PR B of followed-countries) review fixes:

Finding 1 (P1) — CIIPanel.ts:174 partitioned by getFollowed() regardless of
the VITE_FOLLOW_COUNTRIES_ENABLED flag. `getFollowed()` reads localStorage
in anonymous mode whether the flag is on or off (only mutations are
short-circuited), so flag-off users still saw partitioning + FOLLOWING /
ALL section labels even though FollowButton was hidden. Gate the partition
input with isFollowFeatureEnabled() — when off, treat followed list as []
so partitionByFollowed becomes a no-op. Single source-of-truth gate in
buildList() covers both render paths (refresh / rerenderRows /
renderFromCached all flow through it).

Finding 2 (P2) — event-handlers.ts:1123 added the
WM_OPEN_NOTIFICATIONS_FOR_COUNTRY listener with an inline anonymous
function in setupUnifiedSettings. Same-document reinit (HMR / test
harnesses / multiple App instances) accumulated listeners that retained
stale AppContext closures — every dispatched event fired all of them
against stale state. Stored the handler on
boundNotifyForCountryHandler and removed it in destroy(), matching the
existing bound-handler pattern (boundFocalPointsReadyHandler,
boundThemeChangedHandler, etc).

Finding 3 (P2) — DiseaseOutbreaksPanel.ts:80 and DisplacementPanel.ts:59
appended the "Followed only" chip host to this.header AFTER the Panel
base class had already appended .panel-close-btn (Panel.ts:748). Result:
close button no longer rightmost, breaking user expectation that X is
the last header control. Use insertBefore against the existing
.panel-close-btn so the chip lands directly before close; falls back to
appendChild defensively if the close button isn't present.

Tests: +9 (cii-panel-pin-to-top: flag-off identity passthrough;
country-deep-dive-notify-sub-action: listener install/destroy lifecycle
+ HMR shape; country-panels-followed-only-filter: chip placement before
close on both panel shapes + bug-shape regression guard).

typecheck + typecheck:api clean. test:data 7977 / 0 fail (was 7968).

* fix(followed-countries): handle followed-only edge cases

* fix(followed-countries): harden cap-drop toast

* ci: re-trigger workflows after stack rebase

* fix(followed-countries): clear cap-drop toast timer
E
Elie Habib committed
37de85244609fa64c1bc99b9cf017db31df5a020
Parent: ba79c49
Committed by GitHub <noreply@github.com> on 5/26/2026, 1:18:44 PM