Skip to content

Feature/nuclear historical profiles#3859

Open
noishey wants to merge 4 commits into
koala73:mainfrom
noishey:feature/nuclear-historical-profiles
Open

Feature/nuclear historical profiles#3859
noishey wants to merge 4 commits into
koala73:mainfrom
noishey:feature/nuclear-historical-profiles

Conversation

@noishey
Copy link
Copy Markdown

@noishey noishey commented May 22, 2026

Summary

This PR introduces interactive historical profile timelines for nuclear facility layers on both the 2D Flat Map and the 3D Globe views.

Key Accomplishments:

  • Interactive Timeline Accordions: Implemented collapsible <details> components inside flat map Leaflet popups (MapPopup.ts) and 3D globe canvas hover tooltips (GlobeMap.ts) to reveal site timelines on-demand.
  • Premium Micro-Animations: Engineered smooth CSS transition rules (transition: transform 0.2s ease-in-out;) for summary headers and rotation arrows inside the accordion component.
  • Manhattan Project Seed Data: Populated realistic timeline milestones, operational dates, active treaties (e.g., NPT, CTBT), and IAEA compliance status for historic complexes (los_alamos and oak_ridge) in src/config/geo.ts.
  • Auto-Dismiss Timer Race Condition Fix: Integrated dynamic state management into GlobeMap's hover system. Expanding the historical accordion automatically cancels the auto-hide timer (clearTimeout(this.tooltipHideTimer)). Tooltips will now stay open indefinitely for easy reading and re-arm standard timers upon collapse or mouse-out.
  • Strict Type Safety: Fully narrowed marker unions to eliminate loose typecasting, ensuring 100% compliance with strict TypeScript compiler checks.

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 22, 2026

@noishey 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 22, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR adds interactive historical-profile accordions to nuclear facility tooltips on both the 2D Leaflet map (MapPopup.ts) and the 3D globe (GlobeMap.ts), and seeds geo.ts with Manhattan Project timeline data for Los Alamos and Oak Ridge.

  • New historicalProfile type field (src/types/index.ts, src/config/geo.ts): fully optional sub-object added to NuclearFacility; two facilities populated with established dates, treaties, IAEA status, and timeline notes.
  • Accordion UI (MapPopup.ts, GlobeMap.ts): <details>/<summary> HTML rendered inside both popup and tooltip contexts; CSS arrow rotation transition added in main.css; both paths escape all user-controlled strings through escapeHtml/esc.
  • Timer management in GlobeMap (GlobeMap.ts): accordion toggle event extends auto-hide to 5 s and freezes the timer when open; mouseleave guard skips starting a hide timer while the accordion is expanded; one minor gap where the mouseleave handler does not cancel a previously armed timer before setting a new one.

Confidence Score: 4/5

Safe to merge; the accordion and timer changes are additive and isolated to nuclear site tooltips, with no impact on other map layers.

The feature is well-contained and both rendering paths use proper HTML escaping. The one rough edge is in GlobeMap's mouseleave handler, which can leave an orphaned 3500 ms timer running after the accordion close-branch has already armed one — because this.tooltipHideTimer is overwritten without first canceling the old handle. In practice hideTooltip() is idempotent, so the stale callback fires harmlessly, but a sufficiently fast hover sequence could cause it to dismiss a newly opened tooltip for a different marker.

src/components/GlobeMap.ts — specifically the mouseleave handler and the ordering of the auto-hide timer relative to the toggle listener setup.

Important Files Changed

Filename Overview
src/components/GlobeMap.ts Adds historicalProfile rendering into the 3D globe tooltip and accordion toggle/timer logic; mouseleave handler can orphan a stale timer when the accordion close branch has already armed one
src/components/MapPopup.ts Adds historical-profile accordion to the Leaflet nuclear facility popup; straightforward HTML template addition with proper escaping, no timer logic
src/config/geo.ts Adds historicalProfile seed data for los_alamos and oak_ridge; static config addition conforming to the new NuclearFacility type shape
src/styles/main.css Adds CSS for the accordion summary layout, disclosure-triangle suppression, and arrow rotation transition; rules are well-scoped and do not conflict with existing styles
src/types/index.ts Adds optional historicalProfile sub-object to NuclearFacility; all fields are optional, maintaining backward compatibility with existing facility entries

Reviews (1): Last reviewed commit: "fix: resolve globe type regression, smoo..." | Re-trigger Greptile

Comment on lines 1697 to 1701
el.addEventListener('mouseleave', () => {
const details = el.querySelector('.historical-profile-accordion') as HTMLDetailsElement | null;
if (details && details.open) return;
this.tooltipHideTimer = setTimeout(() => this.hideTooltip(), 2000);
});
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 Orphaned timer on mouseleave after accordion close

The mouseleave handler assigns this.tooltipHideTimer without first clearing the existing value. When the accordion's toggle close-branch has already armed a 3500 ms timer and the user then moves the mouse out of the tooltip (accordion already closed), the mouseleave handler overwrites this.tooltipHideTimer with a fresh 2000 ms timer — leaving the old 3500 ms timer running and untracked. Both timers eventually fire hideTooltip(), which is idempotent, but the orphaned timer means any subsequent clearTimeout(this.tooltipHideTimer) call (e.g. from mouseenter) only cancels the 2000 ms one; the earlier 3500 ms timer can still dismiss an unrelated tooltip that opened in the meantime.

Suggested change
el.addEventListener('mouseleave', () => {
const details = el.querySelector('.historical-profile-accordion') as HTMLDetailsElement | null;
if (details && details.open) return;
this.tooltipHideTimer = setTimeout(() => this.hideTooltip(), 2000);
});
el.addEventListener('mouseleave', () => {
const details = el.querySelector('.historical-profile-accordion') as HTMLDetailsElement | null;
if (details && details.open) return;
if (this.tooltipHideTimer) { clearTimeout(this.tooltipHideTimer); this.tooltipHideTimer = null; }
this.tooltipHideTimer = setTimeout(() => this.hideTooltip(), 2000);
});

Comment thread src/components/GlobeMap.ts Outdated
Comment on lines +1722 to +1725
const details = el.querySelector('.historical-profile-accordion');
if (details) {
details.addEventListener('toggle', (e) => {
const target = e.target as HTMLDetailsElement;
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 Using the generic overload querySelector<HTMLDetailsElement> returns a precise HTMLDetailsElement | null type, removing the need for the runtime cast inside the listener and making the target.open access inherently type-safe.

Suggested change
const details = el.querySelector('.historical-profile-accordion');
if (details) {
details.addEventListener('toggle', (e) => {
const target = e.target as HTMLDetailsElement;
const details = el.querySelector<HTMLDetailsElement>('.historical-profile-accordion');
if (details) {
details.addEventListener('toggle', (e) => {
const target = e.target as HTMLDetailsElement;

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.

1 participant