Skip to content

City Filter Function#3983

Open
SipheDilima247 wants to merge 1 commit into
koala73:mainfrom
SipheDilima247:feature/city-filter
Open

City Filter Function#3983
SipheDilima247 wants to merge 1 commit into
koala73:mainfrom
SipheDilima247:feature/city-filter

Conversation

@SipheDilima247
Copy link
Copy Markdown

Summary

Type of change

  • Bug fix
  • New feature
  • New data source / feed
  • New map layer
  • Refactor / code cleanup
  • Documentation
  • CI / Build / Infrastructure

Affected areas

  • Map / Globe
  • News panels / RSS feeds
  • AI Insights / World Brief
  • Market Radar / Crypto
  • Desktop app (Tauri)
  • API endpoints (/api/*)
  • Config / Settings
  • Other:

Checklist

  • Tested on worldmonitor.app variant
  • Tested on tech.worldmonitor.app variant (if applicable)
  • New RSS feed domains added to api/rss-proxy.js allowlist (if adding feeds)
  • No API keys or secrets committed
  • TypeScript compiles without errors (npm run typecheck)

@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

@SipheDilima247 is attempting to deploy a commit to the World Monitor Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the trust:caution Brin: contributor trust score caution label May 31, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 31, 2026

Greptile Summary

This PR adds a city search feature to the CMD+K search modal and introduces a CountryProfileManager with supporting CountrySelector, CountryProfileView, and CountryProfilePanel components for a country-focused intelligence overlay.

  • City filter: city-geometry.ts wraps a static CITY_COORDS map into search items; search-manager.ts registers the source and handles selection by flying the map to the city's coordinates.
  • Country profile system: CountryProfileManager orchestrates a CountrySelector modal and a CountryProfileView overlay that aggregates military, economic, energy, and news data for a selected country; the panel is registered but enabled: false by default.
  • Bugs in new components: The CountrySelector and CountryProfileView both attach listeners to document that are never removed on destroy(), causing handlers to accumulate across open/close cycles. Additionally, CountryProfileManager.selectorContainer is created but never inserted into the DOM, so the selector modal is never rendered.

Confidence Score: 3/5

The city search integration is straightforward and safe to merge; the country profile feature has multiple functional defects that prevent it from working correctly and will degrade DOM event handling over time.

The CountrySelector modal will never be visible because its container is never inserted into the document. Both CountrySelector and CountryProfileView register listeners on document with no cleanup path, so each open/close cycle silently accumulates handlers. These three issues affect the new country profile feature end-to-end. The city search path itself is clean, but it ships in the same PR.

src/app/country-profile-manager.ts, src/components/CountrySelector.ts, and src/components/CountryProfileView.ts all need attention before the country profile feature can work correctly.

Important Files Changed

Filename Overview
src/services/city-geometry.ts New service that wraps CITY_COORDS into search items; straightforward and correct.
src/app/search-manager.ts Adds city search source registration; the initial registration is correct but a redundant second call in updateSearchIndex() re-registers static data on every refresh.
src/components/SearchModal.ts Adds 'city' to SearchResultType union, priority list, and icon map; registerSource correctly replaces rather than appends.
src/app/country-profile-manager.ts New manager with three bugs: selectorContainer never appended to DOM so CountrySelector is always invisible; filterNewsByCountry has dead uppercase check and overly-broad substring matching; relies on window globals for state sharing.
src/components/CountrySelector.ts New component with a capture-phase click listener on document that is never removed in destroy(), causing orphaned handlers to accumulate with each open/close cycle.
src/components/CountryProfileView.ts New modal overlay component; keydown handler stored only as a local variable and never removed, leaking one listener per instantiation.
src/components/CountryProfilePanel.ts New panel that aggregates country intel; straightforward aggregation with proper escaping for user-facing strings.
src/App.ts Wires CountryProfileManager into the app lifecycle; changes are minimal and follow existing patterns.

Sequence Diagram

sequenceDiagram
    participant User
    participant SearchModal
    participant SearchManager
    participant Map
    participant App
    participant CountryProfileManager
    participant CountrySelector
    participant CountryProfileView

    User->>SearchModal: types city name
    SearchModal->>SearchManager: "result selected (type='city')"
    SearchManager->>Map: setView('global')
    SearchManager->>Map: "setCenter(lat, lng, zoom=5) [after 300ms]"

    User->>App: openCountrySelector()
    App->>CountryProfileManager: openCountrySelector()
    CountryProfileManager->>CountrySelector: "new CountrySelector({container: selectorContainer})"
    Note over CountrySelector: selectorContainer not in DOM — invisible
    CountrySelector-->>CountryProfileManager: onCountrySelected(code, name)
    CountryProfileManager->>CountryProfileView: new CountryProfileView(...)
    CountryProfileView->>document: addEventListener('keydown', keyHandler)
    Note over CountryProfileView: keyHandler never removed on destroy()
    CountryProfileView-->>App: renders overlay

    User->>CountryProfileView: closes profile
    CountryProfileView->>CountryProfileManager: onClose()
    CountryProfileManager->>CountryProfileView: destroy()
    Note over document: orphaned keydown listener remains
Loading

Comments Outside Diff (2)

  1. src/components/CountrySelector.ts, line 1314-1318 (link)

    P1 Orphaned document click listener never removed

    The capture-phase click listener is added to document in setupEventListeners() as an anonymous arrow function, so it cannot be removed — destroy() only clears the timeout and removes the container DOM node. After the selector is destroyed, every click anywhere on the page continues to invoke this.onClose?.(). Each open/close cycle stacks another handler, making close callbacks fire multiple times and eventually building up a queue of calls to a stale CountryProfileManager closure.

  2. src/components/CountryProfileView.ts, line 783-789 (link)

    P1 Keydown listener leaks on every CountryProfileView instance

    keyHandler is declared as a local const inside setupEventListeners() and registered on document, but it is never stored as an instance field. Because destroy() calls close(), which only removes the container and invokes onClose, there is no reference available to pass to removeEventListener. Every profile open/close cycle leaves one more orphaned keydown handler on document that calls this.close() indefinitely on an already-torn-down instance.

Reviews (1): Last reviewed commit: "init comit" | Re-trigger Greptile

Comment on lines +26 to +30
/**
* Opens the country selector modal for the user to choose a country
*/
public openCountrySelector(): void {
if (this.countrySelector) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 selectorContainer never appended to the DOM — CountrySelector is invisible

this.selectorContainer is created with document.createElement('div') and assigned an id, but it is never inserted into the document tree. CountrySelector receives it as options.container, skips the fallback document.body.appendChild(this.container), and renders its .country-selector wrapper inside a detached node. Even though .country-selector is position: fixed, it still needs an ancestor in the live document to be painted. openCountrySelector() therefore produces a functional no-op — the modal is never visible to the user.

Comment on lines +114 to +128

return title.includes(countryCode.toLowerCase()) ||
title.includes(countryCode.toUpperCase()) ||
countries.includes(countryCode.toLowerCase());
});

// Store filtered news for use in panels
(window as any).__COUNTRY_PROFILE_NEWS = countryRelevantNews;
}

/**
* Loads country-specific panel data (uses CountryDeepDivePanel)
*/
private loadCountryPanelData(countryCode: string, countryName: string): void {
if (!this.countryProfileView) return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Country-code substring matching produces broad false positives

title.includes(countryCode.toLowerCase()) checks for the 2-letter ISO code as a substring in the already-lowercased title string. Codes like "US""us", "IN""in", "IR""ir", or "DE""de" appear in thousands of common English words (discuss, include, inside, index, decide, etc.), so virtually every news item would match. The second check title.includes(countryCode.toUpperCase()) is also dead code because title was already converted to lowercase and will never contain uppercase characters.

Comment thread src/app/search-manager.ts
Comment on lines +715 to 718
this.ctx.searchModal.registerSource('city', getCitySearchItems());
}

private buildCountrySearchItems(): { id: string; title: string; subtitle: string; data: { code: string; name: string } }[] {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant registerSource('city', ...) call in updateSearchIndex()

City coordinates are a static import from CITY_COORDS and never change at runtime. The initial registration at setup time already covers it. Re-calling getCitySearchItems() and re-registering on every dynamic index update is unnecessary work. The call should be removed from updateSearchIndex().

Suggested change
this.ctx.searchModal.registerSource('city', getCitySearchItems());
}
private buildCountrySearchItems(): { id: string; title: string; subtitle: string; data: { code: string; name: string } }[] {
}
private buildCountrySearchItems(): { id: string; title: string; subtitle: string; data: { code: string; name: string } }[] {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@koala73
Copy link
Copy Markdown
Owner

koala73 commented Jun 1, 2026

Did you see all the comments greptile wrote on your PR @SipheDilima247

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trust:caution Brin: contributor trust score caution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants