OpenSpace worldmonitor-reference

Curated architecture index of the WorldMonitor open-source dashboard. Reference for extracting patterns — vanilla TypeScript, Panel class hierarchy, service layer, CSS grid, API edge functions, refresh scheduler, and 60+ data source integrations.

install
source · Clone the upstream repo
git clone https://github.com/HKUDS/OpenSpace
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/HKUDS/OpenSpace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/showcase/skills/worldmonitor-reference" ~/.claude/skills/hkuds-openspace-worldmonitor-reference && rm -rf "$T"
manifest: showcase/skills/worldmonitor-reference/SKILL.md
source content

WorldMonitor Architecture Reference

WorldMonitor is a real-time global intelligence dashboard built with vanilla TypeScript (no framework). This skill provides a curated index of its architecture, organized by the patterns most relevant to building a personal daily monitor.

When to Use

  • You are creating a new dashboard panel and need to follow WorldMonitor conventions
  • You need to understand how a specific pattern works before adapting it
  • You are deciding which WorldMonitor patterns to adopt vs. simplify for a personal project

Project Overview

AspectDetail
LanguageTypeScript (strict), vanilla DOM — no React/Vue/Angular
BundlerVite
DeploymentVercel (60+ Edge Functions) + Railway (WebSocket relay)
DesktopTauri 2 (Rust) with Node.js sidecar
Map enginesglobe.gl + Three.js (3D), deck.gl + MapLibre GL (2D)
CSSCustom properties, dark/light themes, responsive grid
API contractsProtocol Buffers (92 proto files, 22 services)
Data sources435+ RSS feeds, 45 data layers, 30+ live video streams

Directory Structure

worldmonitor/
├── src/
│   ├── main.ts                    # Entry point: Sentry, analytics, App bootstrap
│   ├── App.ts                     # Root application class
│   ├── components/                # All UI panel classes (80+ files)
│   │   ├── Panel.ts               # *** BASE CLASS — most important file ***
│   │   ├── VirtualList.ts         # Virtual scrolling for large lists
│   │   ├── NewsPanel.ts           # News with clustering, NER, summaries
│   │   ├── MarketPanel.ts         # Stock market with sparklines
│   │   ├── SearchModal.ts         # Cmd+K command palette
│   │   ├── InsightsPanel.ts       # AI briefing + metrics
│   │   ├── WorldClockPanel.ts     # Multi-timezone clock
│   │   └── ...                    # 70+ more panels
│   ├── services/                  # Data layer (117 files)
│   │   ├── runtime.ts             # SmartPollLoop, desktop detection, fetch routing
│   │   ├── bootstrap.ts           # Initial data hydration from Redis
│   │   ├── rss.ts                 # RSS feed aggregation
│   │   ├── news/index.ts          # News fetching + processing
│   │   ├── market/index.ts        # Market data (Yahoo Finance, Finnhub)
│   │   ├── i18n.ts                # 21-language internationalization
│   │   ├── threat-classifier.ts   # 3-stage threat classification
│   │   ├── clustering.ts          # News headline clustering
│   │   └── ...                    # 100+ more services
│   ├── config/                    # Static configuration
│   │   ├── variant.ts             # Multi-variant (world/tech/finance/commodity/happy)
│   │   ├── feeds.ts               # 435+ RSS feed definitions
│   │   ├── panels.ts              # Panel enable/disable config
│   │   ├── markets.ts             # Stock symbol lists
│   │   └── ...
│   ├── app/                       # Application-level modules
│   │   ├── app-context.ts         # Shared state (inFlight, isDestroyed)
│   │   ├── refresh-scheduler.ts   # *** REFRESH PATTERN ***
│   │   ├── panel-layout.ts        # Grid layout management
│   │   ├── search-manager.ts      # Command palette logic
│   │   └── event-handlers.ts      # Global keyboard/visibility events
│   ├── utils/                     # Utilities (22 files)
│   │   ├── dom-utils.ts           # h(), fragment(), replaceChildren() — JSX-free helpers
│   │   ├── sanitize.ts            # escapeHtml(), sanitizeUrl() — XSS prevention
│   │   ├── index.ts               # formatTime, formatNumber, etc.
│   │   └── ...
│   ├── styles/                    # CSS
│   │   ├── main.css               # *** THEME VARIABLES + GRID LAYOUT ***
│   │   ├── panels.css             # Panel-specific styles (tabs, rows, badges)
│   │   ├── base-layer.css         # Foundation layer
│   │   └── ...
│   └── generated/                 # Auto-generated from .proto files
│       ├── client/                # TypeScript API clients
│       └── server/                # TypeScript API handlers
├── api/                           # Vercel Edge Functions (60+)
│   ├── _cors.js                   # CORS origin allowlist
│   ├── _rate-limit.js             # Upstash Redis rate limiting
│   ├── _api-key.js                # API key injection
│   ├── bootstrap.js               # Pre-fetch 15 Redis keys for first render
│   ├── rss-proxy.js               # RSS feed proxy with domain allowlist
│   ├── market/v1/[rpc].ts         # Market data endpoints (proto-first)
│   ├── news/v1/[rpc].ts           # News endpoints
│   └── ...                        # 22 service domains
├── server/                        # Railway relay server
├── proto/                         # Protocol Buffer definitions
└── tests/                         # 30 test files, 554 test cases

Core Pattern: Panel Base Class

File:

src/components/Panel.ts
(~1000 lines)

The Panel class is the foundation for ALL UI components (80+ panels inherit from it).

Constructor Options

interface PanelOptions {
  id: string;          // Unique panel identifier (used in DOM data-panel attr)
  title: string;       // Display title in header
  showCount?: boolean; // Show item count badge
  className?: string;  // Additional CSS class (e.g., 'panel-wide')
  trackActivity?: boolean; // Enable activity tracking
  infoTooltip?: string;    // Hover tooltip text
  premium?: 'locked' | 'enhanced'; // Pro feature gating
}

DOM Structure

div.panel[data-panel="{id}"]
├── div.panel-header
│   ├── div.panel-header-left
│   │   ├── span.panel-title
│   │   └── span.panel-new-badge (optional)
│   ├── span.panel-data-badge (optional, "LIVE"/"CACHED")
│   └── span.panel-count (optional)
├── div.panel-content
│   └── (loading spinner | error state | rendered content)
└── div.panel-resize-handle

Key Methods

MethodPurpose
getElement()
Returns the outer
div.panel
showLoading(msg?)
Display spinner + message
showError(msg, onRetry?)
Display error with optional retry button
setContent(html)
Set content innerHTML (debounced 150ms)
setContentDirect(html)
Set content immediately (no debounce)
setCount(n)
Update the count badge
setStatusBadge(label, type)
Show "LIVE"/"CACHED" badge
show() / hide()
Toggle visibility
destroy()
Remove element, abort controller, cleanup listeners

Resize System

Panels support both row span (1-4 rows) and column span (1-3 cols) resizing via drag handles. Sizes are persisted in

localStorage
with keys
worldmonitor-panel-spans
and
worldmonitor-panel-col-spans
.

Core Pattern: Data Service Layer

Directory:

src/services/
(117 files)

SmartPollLoop

File:

src/services/runtime.ts

Instead of raw

setInterval
, WorldMonitor uses
startSmartPollLoop()
:

const loop = startSmartPollLoop(async () => {
  // fetch data, return boolean for success/failure
}, intervalMs);
loop.stop(); // cleanup

Features:

  • Exponential backoff on failure (1x → 2x → 4x, capped at 8x)
  • Hidden-tab throttle (pauses when
    document.hidden
    )
  • Circuit breaker integration (stops after N consecutive failures)
  • Adaptive refresh (can adjust interval based on data freshness)

Service Module Convention

Each service is a module (not a class) exporting async functions:

// src/services/market/index.ts
export async function fetchMarketQuotes(symbols: string[]): Promise<MarketQuote[]> { ... }
export async function fetchMarketNews(): Promise<MarketNews[]> { ... }

Services call

/api/...
proxy endpoints, never external APIs directly.

Core Pattern: CSS Theme System

File:

src/styles/main.css
(~17,000 lines)

CSS Custom Properties (Dark Theme)

:root {
  /* Backgrounds */
  --bg: #0a0a0a;
  --bg-secondary: #111;
  --surface: #141414;
  --surface-hover: #1e1e1e;

  /* Borders */
  --border: #2a2a2a;
  --border-strong: #444;
  --border-subtle: #1a1a1a;

  /* Text */
  --text: #e8e8e8;
  --text-secondary: #ccc;
  --text-dim: #888;
  --text-muted: #666;
  --accent: #fff;

  /* Semantic Colors */
  --semantic-critical: #ff4444;
  --semantic-high: #ff8800;
  --semantic-elevated: #ffaa00;
  --semantic-normal: #44aa44;
  --semantic-info: #3b82f6;

  /* Status */
  --green: #44ff88;
  --red: #ff4444;
  --yellow: #ffaa00;

  /* Font Stack */
  --font-mono: 'SF Mono', 'Monaco', 'Cascadia Code', 'Fira Code', monospace;
  --font-body: var(--font-mono);
}

Grid Layout

.panels-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  grid-auto-flow: row dense;
  grid-auto-rows: minmax(200px, 380px);
  gap: 4px;
  padding: 4px;
}

Panel Base Styles

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px;
  background: var(--overlay-subtle);
  border-bottom: 1px solid var(--border);
}

.panel-title {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.panel-content {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
  font-size: 12px;
}

Core Pattern: API Proxy Layer

Directory:

api/
(60+ Vercel Edge Functions)

CORS Helper (
api/_cors.js
)

Origin allowlist pattern — only approved origins can call API endpoints:

const ALLOWED_ORIGIN_PATTERNS = [
  /^https:\/\/(.*\.)?worldmonitor\.app$/,
  /^https?:\/\/localhost(:\d+)?$/,
];

export function getCorsHeaders(req, methods = 'GET, OPTIONS') {
  const origin = req.headers.get('origin') || '';
  const allowOrigin = isAllowedOrigin(origin) ? origin : 'https://worldmonitor.app';
  return {
    'Access-Control-Allow-Origin': allowOrigin,
    'Access-Control-Allow-Methods': methods,
    'Access-Control-Allow-Headers': 'Content-Type',
  };
}

Edge Function Pattern

Each endpoint file follows this structure:

  1. CORS preflight handling
  2. Input validation
  3. API key from
    process.env
  4. Upstream fetch with error wrapping
  5. Cache-Control
    headers for CDN caching
  6. Structured JSON response

Core Pattern: Refresh Scheduler

File:

src/app/refresh-scheduler.ts

interface RefreshRegistration {
  name: string;
  fn: () => Promise<boolean | void>;
  intervalMs: number;
  condition?: () => boolean;   // Skip refresh if false
}

class RefreshScheduler {
  scheduleRefresh(name, fn, intervalMs, condition?): void
  flushStaleRefreshes(hiddenMs): void   // Staggered 150ms between each
  destroy(): void
}

Key behaviors:

  • Each panel registers with a unique name and interval
  • Visibility-aware: pauses when tab hidden, flushes stale on return
  • In-flight guard:
    ctx.inFlight
    set prevents duplicate refreshes
  • Stagger on resume: 150ms gap between each stale refresh to avoid API burst

Core Pattern: DOM Utilities

File:

src/utils/dom-utils.ts

WorldMonitor uses a lightweight JSX-free helper

h()
:

function h(tag: string, propsOrChild?, ...children): HTMLElement
function fragment(...children): DocumentFragment
function replaceChildren(el: Element, ...children): void
function rawHtml(html: string): DocumentFragment
function safeHtml(html: string): string  // XSS-safe innerHTML

Example usage:

const row = h('div', { className: 'stock-row' },
  h('span', { className: 'symbol' }, 'AAPL'),
  h('span', { className: 'price' }, '$178.72'),
);

Adaptation Notes for Personal Monitor

When adapting WorldMonitor patterns for my-daily-monitor:

WorldMonitor FeaturePersonal Monitor Adaptation
80+ panel classes15-20 panels (email, feishu, office, social, etc.)
Proto-first APISimple REST endpoints (no proto needed)
Vercel Edge FunctionsVite dev server plugin or simple Express routes
SmartPollLoopSimplified RefreshScheduler with setInterval
VirtualListNot needed until panel has 100+ items
Multi-variant (5 sites)Single variant only
21 languages (i18n)Chinese + English only
Tauri desktop appWeb-only (may add later)
Redis cachingIn-memory cache only

Key Files to Study

When extracting a specific pattern, start with these files:

PatternRead FirstThen Read
Panel architecture
src/components/Panel.ts
Any concrete panel (e.g.,
NewsPanel.ts
)
Service layer
src/services/runtime.ts
src/services/market/index.ts
CSS theme
src/styles/main.css
(first 60 lines)
src/styles/panels.css
API proxy
api/_cors.js
Any
api/*/v1/[rpc].ts
Refresh
src/app/refresh-scheduler.ts
src/app/event-handlers.ts
Grid layout
src/styles/main.css
(search
.panels-grid
)
src/app/panel-layout.ts
DOM helpers
src/utils/dom-utils.ts
src/utils/sanitize.ts
Entry point
src/main.ts
src/App.ts