A world map, and clicking a country to select it - #82
Merged
Conversation
ui.worldMap({ onSelect: country => console.log(country?.name) });
The map is the canvas doing what it already does. Once it draws in the
caller's own coordinates, longitude and latitude are just another pair of
axes, so the world is 258 polylines in degrees and needs no projection of its
own beyond choosing which window on the globe to show.
The half worth building is the other direction. A click arrives as a terminal
cell, and a country is a polygon, so answering "what did they click" means
turning the cell back into degrees and ray-casting it against the outlines.
Doing it with bounding boxes would be far cheaper and consistently wrong:
Russia's box covers most of the northern hemisphere and Chile's covers
Argentina. The lookup is exact, and open water comes back as nothing rather
than as whichever box happened to be first.
The inverse has to agree with the drawing or every click lands somewhere the
user did not point at -- and nothing about the map would look wrong while it
did. So `degreesAt` takes the cell centre through the same 2x4 Braille
denominators the canvas projects with, and a test round-trips all 2600 cells
of a 100x26 map back to themselves.
The data is Natural Earth 1:110m Admin 0, which is public domain, simplified
by Douglas-Peucker to one degree. A world map across eighty columns is about
160 Braille pixels wide, so a pixel is two degrees and one degree of tolerance
is already finer than the terminal can show; it keeps 171 of the 177 countries
in 1982 points. The six it drops are island states smaller than a pixel, which
could be neither seen nor clicked. `scripts/generate-world.ts` fetches and
regenerates it, so the 36KB of coordinates is derived rather than pasted.
Rings are drawn closed, so a coastline has no seam where the ring began.
TypeScript only. The shape and the lookup port cleanly, but the dataset would
be a seventh copy in every port, and that is a call about repo weight rather
than about the feature -- so it is not in the widget gallery either, which
promises a runnable example in all eleven languages.
`bun examples/world.ts` for the clickable version: hover, click, z to zoom to
the selection, r to go back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
The generator now emits the dataset for every port from the one Natural Earth source, so the six copies cannot drift: regenerating rewrites all of them or none of them. Roughly 230KB in total, 32-44KB per language, all derived. C++ gets the coordinates as a flat pool with index tables rather than nested initialiser lists. The nested form is the same data and minutes of compile time, and C++ is the one language where that bites; the pool is turned into the shape the rest of the code wants once, on first use. Agreement is checked rather than assumed. Every port ships a `world-probe` that prints what the lookup answers for thirteen places -- nine capitals, four patches of open ocean -- plus four cell clicks and three raw projections, and all five ports diff clean against the TypeScript reference. That catches what the render fixtures cannot: a country lookup can be wrong in a way that still draws a perfectly good-looking map. Three fixtures pin the drawing itself: the whole globe, a window zoomed on Japan, and two highlighted countries. 93 widget scenes now, up from 90. Verified: 353 TS tests under bun and node; 93 widget scenes matching in Rust, Go, Python, Zig and C++; five world probes diffing clean against TypeScript; 11/11 ctest; every gallery; the site and the benchmark. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
ralyodio
added a commit
that referenced
this pull request
Sep 9, 2026
…#83) Two things the demo promised and did not do. `c` toggled collapsed borders and only half the panels moved. Every screen already computed `panelGap(state)`, which is zero while collapsed, and every screen used it for the seams inside its columns -- but the row that splits the screen into left and right was written `gap: 1` in six of them, and the library only merges a seam where two bordered siblings actually touch. So the vertical stacks closed up and the one seam running down the middle of the screen never did, on every tab after the dashboard. themes.ts never imported panelGap at all, and the three sub-panels inside the dashboard's System panel were the same oversight one level down. Rendering all eleven screens both ways and counting seams that stayed open now gives zero on all of them, where six screens had an open seam before. The world map has been in the library since #82 but was only ever reachable from examples/world.ts, so `bunx @profullstack/hqtui-demo` showed no map anywhere. It is a screen now, on `w`: hover to read the country under the cursor, click to select, z to zoom to the selection and r to go back. The country list and the map are two views of one selection rather than two that have to be kept in step -- the list's pane index *is* the selection, so the arrow keys move the highlight on the map for free and a click on the map only has to move the list. Digits run out at ten screens, so the eleventh takes a letter and the tab labels read their key from SCREEN_KEYS rather than recomputing `(i + 1) % 10`, which would have printed a second "1" and shadowed the dashboard. Claude-Session: https://claude.ai/code/session_01VhrgsHr1C5BYxXDpN1mahf Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
bun examples/world.tsfor the clickable version — hover, click,zto zoom to the selection,rto go back.Drawing it is nearly free
Once the canvas draws in the caller's own coordinates (#80), longitude and latitude are just another pair of axes. The world is 258 polylines in degrees and needs no projection of its own beyond choosing which window on the globe to show.
Clicking it is the part worth building
A click arrives as a terminal cell; a country is a polygon. Answering "what did they click" means turning the cell back into degrees and ray-casting it against the outlines.
Bounding boxes would be far cheaper and consistently wrong: Russia's box covers most of the northern hemisphere and Chile's covers Argentina. The lookup is exact, and open water comes back as nothing rather than as whichever box happened to be first.
The inverse has to agree with the drawing, or every click lands somewhere the user did not point at — and nothing about the map would look wrong while it did. So
degreesAttakes the cell centre through the same 2×4 Braille denominators the canvas projects with, and a test round-trips all 2,600 cells of a 100×26 map back to themselves.The data
Natural Earth 1:110m Admin 0 countries, which is public domain, simplified by Douglas-Peucker to one degree. A world map across eighty columns is about 160 Braille pixels wide, so one pixel is two degrees — one degree of tolerance is already finer than the terminal can show. That keeps 171 of the 177 countries in 1,982 points; the six it drops are island states smaller than a pixel, which could be neither seen nor clicked.
scripts/generate-world.tsemits the dataset for every port from that one source, so the six copies cannot drift: regenerating rewrites all of them or none. Roughly 230KB in total, 32–44KB per language, all derived rather than pasted.C++ gets the coordinates as a flat pool with index tables rather than nested initialiser lists — the nested form is the same data and minutes of compile time, and C++ is the one language where that bites.
Rings are drawn closed, so a coastline has no seam where the ring began.
Agreement is checked, not assumed
Every port ships a
world-probethat prints what the lookup answers for thirteen places — nine capitals, four patches of open ocean — plus four cell clicks and three raw projections. All five native ports diff clean against the TypeScript reference.That catches what the render fixtures cannot: a country lookup can be wrong in a way that still draws a perfectly good-looking map.
Three fixtures pin the drawing itself — the whole globe, a window zoomed on Japan, and two highlighted countries.
Verified
bun testandnode --test, typecheck cleannext build, the benchmarkNot in the website widget gallery: that promises a runnable example in all eleven languages, and the four bridge languages reach the library through a narrower core.
🤖 Generated with Claude Code
https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy