English · 한국어 · Open the monitor
Lifecycle: Lab — experimental, best-effort, and subject to change or retirement. The
monitorentry in the Mossland ecosystem registry follows MIP-1, ratified on 2026-09-02.
An interactive map of the Mossland ecosystem, with pixel-art detail views for Algora, AO, and Bridge. It brings registry metadata, service health, and governance data into one static web app.
The map separates what exists, what can be observed, and what a service reports. A registry entry alone is not evidence that a service is healthy.
Map is the default view. Registry entries orbit by section: Official, Participation, Developers, Markets, and Ecosystem. Hover over a body for details; click Algora, AO, or Bridge to open its detail view, or other entries to visit their destination. On a touch screen the first tap on a body shows its details and a second tap on the same body opens it; a tap on empty space, or opening the information panel, dismisses them. The monitor's own body marks this page, so clicking it opens nothing. From the keyboard or a screen reader, every body is also a link in the information panel's Ecosystem list, with the same reported health.
| Map form | Meaning |
|---|---|
| Streaming, with a ring | This monitor polls the service's data APIs: Algora, AO, or Bridge. This does not itself mean the service is healthy. |
| Health-checked | A health reading is available, directly or through the fallback aggregator. |
| Listed only | The registry lists the service, but no health reading is available. |
| Link or file | A reference such as an exchange listing, social account, or published file; excluded from service health counts. |
Colour shows ok, degraded, down, or no interpretable measurement, which includes an off-contract status such as running. A down or degraded body also carries the word on its label, so the verdict never rests on colour alone. Archived/deprecated entries stay visible, drawn in a dimmed archived colour instead of a health colour; their measured health, if any, is still shown in the tooltip, in the sidebar, and on the label when it is down or degraded.
The map heading and the sidebar's Ecosystem panel count what the services report: ok, off-contract, degraded, down and unmeasured, one bucket each. Only services are counted, not links or files, and archived services are counted by their reading like any other.
- Every service reporting
off-contract,degradedordownis named: all of them in the Ecosystem panel, which leads with the counts and names, and up to four per verdict in the heading, with+N morefor the rest. Its sidebar row carries that verdict as text. - Between 768px and 1100px, where the panel sits above a short map, the heading keeps only the service counts and the reading time and leaves the tally and names to the panel; a portrait tablet has room above the map, so its heading keeps them too. On a phone 700px tall or less the heading still counts every verdict but leaves the names to the panel; a
downordegradedbody still says so on its own label. - While anything is down or degraded the page title starts with a count, e.g.
(1 down). A hidden tab does not poll (see below), so its title shows the last reading taken before it was hidden and cannot pick up a service that goes down afterwards; it does turn stale on the same clock as everything else.
The sidebar also lists registry entries and lifecycle labels, service counts, API reachability, and governance figures.
- Tabs. The Map / Algora / AO / Bridge tabs work on desktop and mobile; from the keyboard, the Left and Right arrow keys, Home and End move between them. Switching tabs does not change the URL.
- Screen sizes. Below 768px the information panel opens from the menu button and closes with Escape or a tap beside it; while it is open, Tab moves only between it and the menu button. The map heading there shows the data APIs'
LIVE/OFFLINE/CONNECTINGstate that the closed panel would otherwise hide. Between 768px and 1100px the panel sits above the stage. Crossing the 768px breakpoint reloads the app to fit its canvas. - Detail views on a phone. Each sits at the top of the screen, above a card with the text its canvas is too small to show there: the latest debates, ideas and their scores, the signals on the belt, the agent trust average and recent outcomes. The card carries the service's
LIVE/OFFLINE/CONNECTINGbadge. Once AO or Bridge has not answered, its card calls its lists the last received rather than the latest; AO's debates are also called that when only their own read failed and an earlier list was kept. The Algora card only says that Algora did not answer: its belt holds signals from all three services, so Algora's silence does not make them old. - Reduced motion. A reduced-motion preference stops the map's rotation and cursor swirl and leaves out its breathing, particles and ring sweeps; stops the detail belts' treads, bots and blinking agents and moves their items in steps instead of sliding (Algora's signals and Bridge's proposals stage by stage, AO's ideas a fixed distance at a time and onto each score threshold they reach); and removes tab-switch camera transitions. A change to the preference applies while the page is open.
The three services are independent. Their stage labels illustrate each service's workflow; they do not establish an implemented data handoff between services.
| View | Displays |
|---|---|
| Algora · Sense & Detect | A vertical signal belt, nine workflow stages, and agent clusters. The belt consumes a merged queue of signals fetched from all three services, not only Algora. |
| AO · Debate & Plan | Cached AO ideas and scores, topics and context snippets from the three latest debates, and AO's own Ideas / Plans / Projects totals. Idea bubbles illustrate score thresholds of 7 for a plan and 8 for a project. |
| Bridge · Execute & Verify | An L0–L4 workflow, specialist agents, proposal totals from stats, agent trust, and recent outcomes. The monitor does not fetch the full proposal collection. |
See the service overview (in Korean) for responsibilities and the conceptual governance loop. Its cross-service handoff model is a design sketch, not how the services run today.
- Polling, not a push stream. Each read runs again a fixed time after the previous one finishes. Each service's signals and stats, which the
LIVEbadges and sidebar figures come from, refresh after 15 seconds. The detail views' supporting data refreshes after 5 minutes (AO ideas and project total, Bridge outcomes and agent trust) or 10 minutes (the three latest AO debates), and a detail read that fails is retried after 1 minute. Health sweeps refresh after 60 seconds; the registry after 10 minutes once it has loaded. Until its first read succeeds, the registry is retried after 5, 15 and 30 seconds and then every minute, and the map and sidebar say it is unreachable rather than still loading; the health sweep runs again as soon as it loads. Signals, stats, health and registry requests time out after 10 seconds; detail reads after 30. - Polling pauses while the tab is hidden. The first load always completes, even in a tab opened in the background or hidden while it runs; after that, a request already under way finishes, but nothing new starts. When the tab is shown again, every read that fell due in the meantime runs at once, and the rest wait out their remaining time. Signals published while the tab was hidden that have already dropped out of each service's newest 30 (Algora) or 20 (AO, Bridge) are never fetched, so "signals ingested this session" counts only what this tab observed. That count starts at zero and leaves out what each service's first successful read returns — its history, the starting point rather than activity — so it does not depend on how quickly the page loaded, and a service that was unreachable at load does not count its history when it recovers. A tab shown again reads "refreshing", not "stale", while the health sweep that showing it started is under way, unless its reading was already stale when it was hidden.
- API reachability and health are different. A service's card reads
LIVEwhen its signals or stats request succeeds,OFFLINEwhen both fail, andCONNECTINGbefore the first verdict. Its figures come from this poll's stats only: a service that isLIVEon its signals while its stats request fails shows—with "no stats this poll" and the time its stats last arrived, never the last figures as if they were current. The aggregate isLIVEwhen any of the three responds,OFFLINEwhen none responds, andConnecting...before the first verdict. The ecosystem health feed has its own status readings. - Health comes from evidence. The browser reads service
statusUrladdresses from the registry; direct readings override the city health aggregate. A declared status survives an HTTP error response. A 5xx without a non-empty stringstatus(an HTML error page, or JSON without the field) meansdown; network/CORS failures, reads cut off mid-response, and non-5xx responses without a usable verdict do not by themselves prove an outage. Unknown status strings stay untranslated. - Motion has different meanings. Map particles are triggered by newly ingested signals and capped for display; a ring sweep follows each health refresh that landed a reading. A body breathes only while something is watching it: a health-checked body while its reading is current, a streaming body while its data API answers or it carries a current health reading. An archived body never breathes. Galaxy rotation and the cursor swirl are decorative. Belt travel, stage promotion, and Bridge's proposal/proof animation illustrate workflows; they are not execution traces or proof that work completed. Algora's belt carries real signals from all three services, paced out one at a time; AO's bubbles replay its cached ideas, picked at random; Bridge's proposals are drawn on a timer, not read from Bridge.
- Snapshots can be older than the latest poll. Detail caches survive individual request failures, and a wholly unsuccessful health sweep retains the previous snapshot. That snapshot is dated: the map heading shows when the readings were taken (
health checked 14:32:05), and the tooltip shows their age. Once no sweep has landed for about 150 seconds — one interval, one timeout and a margin, so two sweeps in a row came back empty — the readings are marked stale: measured bodies dim and stop pulsing, the heading sayshealth stale since 14:32, the counts readas ofthat time, and the page title says(health stale), in a background tab too, where nothing is refreshing the reading. The colours stay; they are the last reading, not a current one. Every age comes from this monitor's own sweep clock, never from a service'stimestamp. Missing trust scores, an absent success rate, and totals a service did not send as a number, in the sidebar, the AO funnel and the Bridge gauges and outcome line, display—; an unavailable value must not be interpreted as a measured zero. This is an observational viewer, not an uptime or execution audit log.
Use Node.js 22 LTS (22.12+) or Node.js 24 LTS and npm. The development toolchain supports ^22.12.0 || ^24.0.0 || >=26.0.0. The app uses TypeScript, Vite 7, Phaser 3, vanilla DOM/CSS, and Vitest.
npm ci
npm run devVite prints the local URL, normally http://localhost:5173. The registry and health map use public cross-origin endpoints. To populate all governance detail views, run the three APIs locally or adjust the proxy targets in vite.config.ts:
| Browser path | Default development upstream | Data read |
|---|---|---|
/algora-api/* |
http://localhost:3201/api/* |
Signals, stats |
/ao-api/* |
http://localhost:3001/* |
Signals, status, debates, ideas, project total |
/bridge-api/* |
http://localhost:3101/api/* |
Signals, stats, outcomes, agent trust |
API servers are separate projects and are not started by this repository. No frontend API key or .env file is required. Registry and fallback health URLs are defined in ecosystem-client.ts; direct health endpoints must permit browser access with CORS. In production all of them must also stay inside the page's Content-Security-Policy connect-src (see Deploy).
npm run typecheck
npm test
npm run buildGitHub Actions runs these checks on every pull request, whatever its base branch, and on main after each merge, then confirms the build emitted a well-formed dist/health.json (node scripts/check-health-json.mjs). A branch pushed without an open pull request is not tested automatically; open a draft pull request or run the workflow by hand. Use npm run test:watch while working on what the tests cover:
- health-response interpretation, and the ecosystem feed's grading, merging and polling, including the registry's early retry and when health readings turn stale;
- the status counts and page title;
- the service-data poller's connection state, polling schedules (including the pause while hidden), signal deduplication, the per-service stats and signal baselines, and detail-view totals;
- the schedule every poller shares: one run at a time, and a run that throws does not end it;
- the values the sidebar, the map's tooltip and the phone's detail card write into their markup, and the sidebar leaving unchanged panels alone and keeping keyboard focus on a link it rewrites;
- the map's tap selection and where its text is placed, the reduced-motion setting, the tab bar's keys, and focus as the phone's information panel opens and closes;
- what the page says about itself before it is opened: the search and link-preview descriptions and the preview image's alt text in index.html, the manifest's description, and the words in public/og-image.svg, none of which may claim a real-time stream (public/og-image.png is rendered from that SVG by hand, and no test reads the PNG);
- how the origin serves a build (404 for missing files, no directory listing), and the
health.jsoncheck CI runs.
npm run preview # the production build, with the development API proxies
# or
npm run serve # dist/ on port 6300, as the PM2 origin serves itThe two differ in ways that matter when checking a build:
npm run previewreuses the development API proxies in vite.config.ts, so the detail views fill in when the three APIs run locally. It answers every missing file, a missing/assets/*.jsincluded, and/api/healthwithindex.htmland HTTP 200.npm run serverunsservewith the same arguments and public/serve.json as the PM2 origin: missing files return 404 and no directory is listed. It has no API proxies.- Both serve the build's
/health.json. Neither provides/api/healthor production's CORS, caching and security headers, and preview's proxies point at localhost, so full verification needs the reverse-proxy setup below.
Build the reviewed commit and publish dist/. Deployment is manual; GitHub Actions validates the change but does not deploy it. The repository supports static hosting directly or an existing PM2 installation using ecosystem.config.cjs, which runs the installed local serve package on port 6300. Run npm ci before starting it.
The app has no pathname-based routes. Static serving deliberately returns 404 for missing files, including JavaScript assets, instead of substituting index.html, and does not list directory contents. public/serve.json, which the build copies into dist/, turns off serve's default directory index however serve is started on dist/. serve reads it at startup, so restart the process after deploying a build that changes it. Preserve both behaviours when using another host or reverse proxy, and check them locally with npm run serve, not npm run preview.
deploy/nginx.conf.example is a reference configuration for the production routes, not a copy of a running vhost. Adjust domains, certificates, upstream addresses, and disk paths for your host. The deployment must provide:
- The three same-origin API proxies, preserving the path rewrites in the table above.
- An exact
/api/healthroute to the generatedhealth.json. - Revalidation for HTML and health responses, and immutable caching for content-hashed
/assets/files. Phaser is built as its own chunk, so a deploy that changes only app code leaves it cached. - CORS for intended API consumers. The example reflects an origin allowlist, handles
OPTIONS, and varies API responses byOriginandAccept-Encoding. The public health endpoint usesAccess-Control-Allow-Origin: *. - Security headers on every response (
X-Frame-Options,X-Content-Type-Options,Referrer-Policy, HSTS) and aContent-Security-Policyon the page. nginx drops inheritedadd_headerlines in any location that sets its own, so the example includes them in each location. The policy'sconnect-srcallowsmoss.landand its subdomains, which covers the registry and health-aggregate URLs in ecosystem-client.ts and every registrystatusUrl. If any of them moves to another host, add that host first: development and CI send no CSP, so only production would block the request. What a blocked read costs depends on the address. A blocked registry read leaves the map and the Ecosystem panel empty, showing only "Registry unreachable — retrying". A blocked health-aggregate read leaves city unmeasured and every other service without its fallback. A blockedstatusUrlfalls back to city's reading where city probes that service, and otherwise reads as unmeasured. No service is shown as down, and nothing on the page names the policy as the cause.
After deployment, open the map and all three detail tabs at desktop and mobile widths. Check that API responses are JSON, service reachability settles, registry/health data loads, and browser assets load without errors. Verify /api/health returns JSON identifying the commit that was built, that a nonexistent /assets/ file returns 404, and that /assets/ itself lists no files: serve with public/serve.json answers 404, and nginx serving dist/ directly answers 403. Check the security headers with curl -sI https://monitor.moss.land/ | grep -i -e x-frame -e content-security, and that the browser console reports no Content Security Policy violations.
Every production build emits dist/health.json. The reverse proxy exposes it at /api/health:
{
"status": "ok",
"service": "monitor",
"role": "viewer",
"pipeline": "none",
"timestamp": "<build timestamp>",
"buildTime": "<same build timestamp>",
"commit": "<short Git commit, or null outside a Git checkout>"
}This identifies the served frontend build. Its timestamp is the build time, not the last health poll or the freshness of upstream data. status: "ok" does not certify Algora, AO, Bridge, or the wider ecosystem. Neither Vite's development server nor npm run preview / npm run serve provides this route; inspect a local build at /health.json with either of the latter two.
- Mossland ecosystem registry — service discovery and lifecycle metadata.
- mossland-pixelops — a related event-sourced pixel-art operations map.
- MIT License.