social components
inlay.at
atproto
components
sdui
1# proto
2
3A prototype Inlay host built with [Hono](https://hono.dev/) and [htmx](https://htmx.org/). It resolves Inlay component trees into HTML and streams the result to the browser.
4
5**Live demo:** https://inlay-proto.up.railway.app/
6
7## Running
8
9```bash
10npm install # from the repo root
11npm run dev # starts on http://localhost:3001
12```
13
14Expects a Redis instance for caching. Set `REDIS_URL` in `.env` or `.env.local` at the repo root (defaults to `redis://localhost:6379`). Records and DID resolution go through [Slingshot](https://slingshot.microcosm.blue).
15
16## How it works
17
18The main route is `/at/:did/:collection/:rkey?componentUri=...`. It fetches the component record, creates an element (`$(componentType, { uri })`), and calls [`@inlay/render`](../packages/@inlay/render) in a loop until everything bottoms out in primitives. Each primitive maps to a custom HTML element (`org-atsui-stack`, `org-atsui-text`, etc.) styled by two CSS files.
19
20The response is streamed with Hono's `renderToReadableStream`. Slow XRPC calls don't block the whole page — `at.inlay.Loading` components use `<Suspense>` to show a fallback while the rest of the page streams.
21
22## Primitives
23
24This host implements the [Atsui](https://pdsls.dev/at://did:plc:e4fjueijznwqm2yxvt7q4mba/com.atproto.lexicon.schema) primitives. Each primitive is a function in `src/primitives.tsx` that takes `{ ctx, props }` and returns Hono JSX. A `componentMap` at the bottom registers them by NSID.
25
26## Caching
27
28Records and XRPC responses are cached in Redis. Cache keys are tagged using the same scheme as `@inlay/cache` — `tagRecord` for AT URIs, `tagLink` for backlink patterns. The existing [invalidator](../invalidator/) (a Jetstream firehose listener) can flush entries when records change.
29
30TTLs follow the `life` values from component responses: `seconds` (30s), `minutes` (5m), `hours` (1h), `max` (24h).
31
32## Infinite scroll
33
34`org.atsui.List` renders the first page server-side, then uses htmx to load more pages as the user scrolls. The `/htmx/list` endpoint handles pagination — it takes a cursor, fetches the next page from the component's XRPC query, renders the items, and returns an HTML fragment with a new sentinel `div` for the next page.
35
36## Link prefetching
37
38An `IntersectionObserver` watches for `<a>` tags pointing to `/at/...` routes. When one scrolls into view, it injects a `<link rel="prefetch">` so the browser fetches the page before the user clicks.