Skip to content

feat(markets): expandable terminal price chart + fix(dev): OpenSky proxy path - #5459

Merged
koala73 merged 8 commits into
koala73:mainfrom
Developer1010x:feat/developer1010x-terminal-chart
Jul 25, 2026
Merged

koala73 merged 8 commits into
koala73:mainfrom
Developer1010x:feat/developer1010x-terminal-chart

Conversation

@Developer1010x

Copy link
Copy Markdown
Contributor

Summary

Two independent changes:

  1. feat(markets): an expandable Bloomberg-terminal-style price chart in the Markets panel.
  2. fix(dev): the local OpenSky dev proxy now hits the correct upstream path, so flight tracking works in npm run dev.

1. Terminal price chart (feature)

What: Clicking a market ticker opens a modal with a full terminal-style chart of that ticker's intraday series — horizontal gridlines, a green/red gradient fill keyed to direction, right-axis HI / LAST / LO labels, and a last-price marker. Keyboard accessible (Enter/Space to open, Esc / backdrop to close).

Why it matters: the Markets panel previously only showed a 50×16 sparkline per row; there was no way to see a ticker's movement at a readable size. This adds that without any new data source.

How:

  • New pure util src/utils/terminal-chart.ts — terminalChart(number[], opts) → SVG string, same dependency-free idiom as sparkline.ts, so it's trivially unit-testable. It consumes the existing MarketData.sparkline series; it does not invent OHLC/candlestick data.
  • New src/components/market-chart-modal.ts — singleton overlay following the existing StoryModal pattern (setTrustedHtml / backdrop+Esc close).
  • MarketPanel.ts — stores the rendered rows, marks plottable tickers clickable, opens the modal via one delegated listener.
  • src/styles/main.css — modal + clickable-row styles, using existing theme vars.
  • i18n keys components.markets.chart.{title,close} added to en.json and propagated to all 26 locales via npm run sync:locales.
  • Unit test tests/terminal-chart.test.mjs (wired into the test:data gate) covering the empty-series guard, direction color, HI/LO/LAST labels, custom formatting, and unique gradient ids.

Note: true candlesticks were intentionally not built — no OHLC price-history source exists in the repo, and faking candles from a flat line would be misleading. Candlesticks are a clean follow-up once an OHLC source is added.

2. OpenSky dev-proxy path (dev-only fix)

Issue: in npm run dev, aircraft/ADS-B tracking fails with [Military Flight Tracking] Failed — upstream may be down; /api/opensky returns 404.

Root cause: production routes /api/opensky through the relay (api/opensky.js), which calls OpenSky's states/all endpoint. Dev has no relay, so vite.config.ts proxies straight to https://opensky-network.org/api — but its rewrite stripped the /api/opensky prefix to an empty string, producing a request to the API root (…/api?lamin=…) instead of …/api/states/all?lamin=…. That path 404s, so every region query returns no flights.

Fix: rewrite /api/opensky → /states/all so the dev proxy targets a real OpenSky endpoint. Production is unaffected (it uses the relay, not this proxy).

Verified: curl "http://localhost:5199/api/opensky?lamin=45&lamax=47&lomin=5&lomax=7" → HTTP 200, 34 live aircraft states (was 404). Anonymous OpenSky is rate-limited (~400 req/day), so heavy local use may eventually 429; that's an upstream quota, not a proxy issue.

Duplicates

  • Terminal chart / OpenSky proxy: none found among open PRs.
  • A separate Breaking-Defense feed fix was deliberately excluded from this PR — it is already handled, more completely, by open PR fix: register missing Breaking Defense intel source #5405 ("fix: register missing Breaking Defense intel source").

Verification

  • npm run typecheck — clean
  • npx biome lint (changed files) — clean
  • npm run lint:safe-html — passed
  • npm run sync:locales:check — all 24 locales in sync
  • npx tsx --test tests/terminal-chart.test.mjs — 7 passed
  • Chart rendered in a real browser (Playwright + system Chrome); OpenSky proxy returns live data as above.

…oxy path

Adds a Bloomberg-terminal-style price chart to the Markets panel: click a
ticker to open a modal charting its intraday series (gridlines, green/red
gradient, HI/LAST/LO labels). New pure terminalChart() SVG util + unit test;
consumes the existing MarketData.sparkline, no faked OHLC.

Also fixes the dev-only OpenSky proxy: rewrite /api/opensky to /states/all so
flight tracking works in npm run dev (was hitting the API root -> 404).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

@Developer1010x 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 Jul 22, 2026
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR delivers two independent changes: an expandable Bloomberg-terminal-style price chart modal for the Markets panel (new market-chart-modal.ts, terminal-chart.ts, and locale keys across 25 locales), and a one-line fix to the Vite dev-proxy rewrite that was sending /api/opensky requests to the OpenSky root endpoint instead of states/all.

  • Market chart feature: pure SVG utility (terminalChart) following the existing sparkline.ts idiom; delegated click/keyboard events on MarketPanel open a singleton modal with HI/LO/LAST axis labels and a gradient fill. One issue: stock.display is passed to t() and directly into an HTML attribute string without escapeHtml(), unlike every other use of the same field in the file.
  • OpenSky proxy fix: rewrite now maps /api/opensky → /states/all so dev-mode flight tracking resolves to a valid endpoint; production relay is unchanged.
  • Locale sync: all 25 non-English files received the two new chart.title/chart.close keys as English fallback strings, consistent with the project's sync-locales workflow.

Confidence Score: 3/5

Safe to merge after fixing the unescaped stock.display in the aria-label attribute string in MarketPanel.ts; the OpenSky proxy fix and locale additions are clean.

The market-panel row template builds an HTML attribute string that interpolates stock.display through the i18n t() function without HTML-escaping it, while the same field is correctly escaped everywhere else in the same file. A ticker whose display value contains a " would produce a malformed attribute in the raw HTML blob handed to setSafeContent.

src/components/MarketPanel.ts line 54 — the aria-label attribute construction

Security Review

  • HTML attribute injection (src/components/MarketPanel.ts line 54): stock.display is interpolated into the aria-label attribute via t() without escapeHtml(). A ticker whose display string contains \" or > would break the attribute context in the raw HTML string passed to setSafeContent/unsafeRawHtml. The same field is correctly escaped in element-content positions elsewhere in the same file. Exploitation requires the market-data API to return a ticker with a crafted display value, which limits realistic risk, but the fix is trivial (escapeHtml(stock.display)).

Important Files Changed

Filename Overview
src/components/MarketPanel.ts Adds clickable rows + delegated event listener for chart modal; stock.display is correctly HTML-escaped in element content but not in the aria-label template-string attribute on line 54
src/components/market-chart-modal.ts New singleton overlay modal following the StoryModal pattern; uses DOM setAttribute for aria-label (safe) and escapeHtml for content; missing focus movement on open reduces keyboard accessibility
src/utils/terminal-chart.ts Pure SVG-generating utility; correct guard for ≥2 finite points, unique gradient IDs, numeric-only interpolation; missing accessible label on the role="img" SVG element
vite.config.ts One-line fix: dev-proxy rewrite for /api/opensky now maps to /states/all instead of an empty path that produced 404s; production relay is unaffected
tests/terminal-chart.test.mjs Good unit tests covering empty-series guard, rising/falling colors, label accuracy, dimensions/formatter, and gradient-ID uniqueness

Sequence Diagram

sequenceDiagram
    participant U as User
    participant MP as MarketPanel
    participant MCM as market-chart-modal
    participant TC as terminalChart()

    U->>MP: click / Enter on market row
    MP->>MP: resolve _markets[idx]
    MP->>MCM: openMarketChartModal(stock)
    MCM->>MCM: closeMarketChartModal() (if open)
    MCM->>TC: terminalChart(stock.sparkline, opts)
    TC-->>MCM: "SVG string (or '' if < 2 points)"
    MCM->>MCM: createElement overlay
    MCM->>MCM: setTrustedHtml(modal content + SVG)
    MCM->>MCM: document.addEventListener keydown(Esc)
    MCM->>MCM: document.body.appendChild(overlay)
    U->>MCM: Esc / backdrop / close button
    MCM->>MCM: closeMarketChartModal()
    MCM->>MCM: overlay.remove(), removeEventListener
Loading

Reviews (1): Last reviewed commit: "feat(markets): expandable terminal price..." | Re-trigger Greptile

Comment thread src/components/MarketPanel.ts Outdated
.map((stock, idx) => {
const clickable = hasPlottableSeries(stock);
const attrs = clickable
? ` class="market-item market-item-clickable" data-market-chart="${idx}" role="button" tabindex="0" aria-label="${t('components.markets.chart.title', { symbol: stock.display })}"`

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 security The aria-label attribute is built via a template string, but stock.display is interpolated through t() without HTML-escaping first. A ticker whose display value contains a " character (e.g. A"B) would produce aria-label="A"B price chart" — a malformed attribute that closes the attribute early. Every other place in this file that uses stock.display in HTML already wraps it in escapeHtml(); this spot is the odd one out.

Suggested change
? ` class="market-item market-item-clickable" data-market-chart="${idx}" role="button" tabindex="0" aria-label="${t('components.markets.chart.title', { symbol: stock.display })}"`
? ` class="market-item market-item-clickable" data-market-chart="${idx}" role="button" tabindex="0" aria-label="${t('components.markets.chart.title', { symbol: escapeHtml(stock.display) })}"`

Comment thread src/components/market-chart-modal.ts Outdated
Comment on lines +75 to +78
modalEl.querySelector('.market-chart-close')?.addEventListener('click', closeMarketChartModal);
document.addEventListener('keydown', escHandler);

document.body.appendChild(modalEl);

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 Modal missing focus movement on open

After document.body.appendChild(modalEl) the focus stays on the market-panel row that was clicked. A keyboard or screen-reader user pressing Tab will navigate through background content rather than being contained within the dialog. The close button is the natural initial focus target; moving focus there (or to modalEl itself with tabindex="-1") at the end of openMarketChartModal would bring the modal into the accessibility tree's natural reading order and enable the Esc handler to work without the user having to manually re-enter the overlay.

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!

Comment thread src/utils/terminal-chart.ts Outdated
const lastDot = `<circle cx="${x(series.length - 1).toFixed(1)}" cy="${y(last).toFixed(1)}" r="2.5" fill="${color}"/>`;

return (
`<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" class="terminal-chart" role="img">` +

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 SVG role="img" without an accessible name

The SVG is declared as an image (role="img") but carries no aria-label attribute and no <title> child element. Screen readers will announce it as "image" with no further description, giving assistive-technology users no indication of what ticker or price range they are looking at. Adding an aria-label option to TerminalChartOptions (e.g. the ticker symbol + "price chart") and writing it onto the <svg> tag would address this with no breaking change.

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!

…name + dialog focus

- MarketPanel: HTML-escape stock.display in the clickable-row aria-label
  (i18next has escapeValue:false, so t() output was unescaped in an attribute).
- terminal-chart: add optional ariaLabel written (attribute-escaped) onto the
  role=img SVG so screen readers announce the ticker, not just 'image'.
- market-chart-modal: pass the ticker label to the chart and move focus to the
  close button on open so keyboard/SR users are contained in the dialog.
- tests: cover ariaLabel rendering + escaping.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@vercel

vercel Bot commented Jul 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jul 25, 2026 4:23pm

Request Review

# Conflicts:
#	AGENTS.md
#	CONTRIBUTING.md
#	docs/generated/stats.json
@koala73
koala73 merged commit 9dbbeb9 into koala73:main Jul 25, 2026
26 checks passed
Developer1010x added a commit to Developer1010x/worldmonitor that referenced this pull request Aug 15, 2026
This repository is a fork of koala73/worldmonitor. Of 4,938 commits, none are
mine. The README described the project with no fork notice, so the repo read as
my own work — which would be misattribution of an AGPL-3.0 project maintained by
Elie Habib and ~40 other contributors.

Add a banner naming upstream as the author, directing users there to run or star
the project, and stating my actual contribution plainly: one merged PR,
koala73#5459 (+892/-39 across 37 files, merged 2026-07-25).

Also notes that this checkout predates that merge and so does not yet contain it.


Claude-Session: https://claude.ai/code/session_012ZLS2PqK7vWnj38jZSxQkD

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
@Developer1010x

Copy link
Copy Markdown
Contributor Author

Thank you will look fwd to contribute

This branch was successfully deployed

1 active deployment
Preview — b311dc89 Deployed Jul 25, 2026 by vercel[bot]
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