- JavaScript 92%
- CSS 7%
- Docker <1%
appmosphe.re #
A map of the Web Tiles published on the AT
Protocol. Tiles live in their authors' repos as ing.dasl.masl records;
appmosphe.re follows the network for them and shows them with their
metadata: name, description, icons and screenshots, sizing, resources, CIDs.
- The front page lists the ten latest tiles, with search (name, description, categories, author), browsing (all tiles, all authors, categories), and a form to ask for a repo to be indexed.
- Every tile has a page at its short ID, which is the
at://URI with the collection dropped and the DID swapped for the handle:at://robin.berjon.com/ing.dasl.masl/3meis42djm22dis/@robin.berjon.com/3meis42djm22d. The DID form always works too. - Every author has a page with their Bluesky profile and their tiles.
- A JSON API mirrors the pages (
/api/tiles,/api/search,/api/tiles/:actor/:rkey,/api/authors,/api/authors/:actor,/api/stats) andPOST /api/index {"actor": "…"}files an index wish. The API is open to any origin (CORS*).
Styling is supram, with the
teal tint standing in for the warm white on chrome in the light scheme
(public/style.css).
How it works #
relay ──listReposByCollection──▶ discovery ─┐
├─▶ Tap ──/channel──▶ index (SQLite + FTS5) ──▶ pages, JSON
index wishes ──POST /api/index──────────────┘ ▲
Bluesky public API ┘ (profiles, daily)
- Tap (
tap/): a dedicated Tap in signal collection mode (TAP_SIGNAL_COLLECTION=ing.dasl.masl) tracks every repo on the network with a tile, verifies and backfills it, and streams only that collection. Repos can also be added on demand. - Ingest (
src/ingest.js): consumes the Tap channel (@atproto/tap), asks the relay hourly which repos carry the collection, and takes index wishes. WithoutTAP_URLit polls each repo's PDS directly instead, which is hownpm run devruns. - Index (
src/db.js):node:sqlite, one row per tile with the record kept whole, FTS5 over name/description/categories/author, an allow-list of the image blobs a tile declares (the only blobs/img/:did/:cidproxies). - Records (
src/tiles.js): both shapes seen in the wild are indexed, the lexicon's{ cid, tile, createdAt }record and a bare MASL bundle; anything without aresourcesmap and a/entry is not a tile. A missingcreatedAtis derived from the TID.
Running #
npm install
npm run dev # poll mode, data in ./data, http://localhost:3000
npm test
Environment: PORT, DATA_DIR, PUBLIC_URL, TAP_URL, TAP_ADMIN_PASSWORD,
RELAY_URL, PLC_URL, APPVIEW_URL, TILE_VIEWER, DISCOVERY_MINUTES,
POLL_MINUTES, PROFILE_TTL_MINUTES, MAX_BLOB_BYTES (see src/config.js).
Deploying #
Two sm services on the supramundane
server: appmosphere (this directory; node, /data for the index and blob
cache) and appmosphere-tap (tap/; docker). Each has an uncommitted .env
on the server with the shared TAP_ADMIN_PASSWORD.
npm run deploy # sm deploy here, then in tap/
DNS: appmosphe.re and tap.appmosphe.re → the supramundane front.