Browse the Web Tiles published on the AT Protocol.

README.md

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/3meis42djm22d is /@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) and POST /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. Without TAP_URL it polls each repo's PDS directly instead, which is how npm run dev runs.
  • 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/:cid proxies).
  • 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 a resources map and a / entry is not a tile. A missing createdAt is 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.