Goal
Make a Solid pod served by JSS speak enough of the Mastodon REST API that a standard Mastodon client — Phanpy as the reference target — can sign in to it, read its timeline, post statuses, and interact.
Phanpy is MIT-licensed, browser-based, single-column, lean — an ideal first target. Every other Mastodon client (Elk, Tusky, Ivory, Ice Cubes, Mona, Pinafore, Toot!, Mastodon's own UI…) speaks the same API, so a shim for Phanpy unlocks the whole ecosystem for free.
Why this matters
Scope
The smallest useful target: a single-pod, single-user Phanpy-can-log-in-and-use-its-own-pod prototype. No federation yet.
Phase 1 — Personal client over your own pod (~2 weeks)
Mastodon REST endpoints needed:
- OAuth —
/oauth/authorize, /oauth/token, /api/v1/apps (translate to/from JSS's existing OIDC)
- Identity —
/api/v1/accounts/verify_credentials
- Statuses —
GET /api/v1/statuses/<id>, POST /api/v1/statuses (back the resource as a JSON-LD schema:Note on the pod)
- Timelines —
GET /api/v1/timelines/home, /public (build from the user's outbox container)
- Streaming —
/api/v1/streaming (SSE/WS — JSS already has WebSocket notifications, wrap them in Mastodon's shape)
- Notifications —
GET /api/v1/notifications
- Media —
POST /api/v1/media (JSS already accepts PUT-to-pod for files; wrap in Mastodon response shape)
Phase 2 — Status interactions (~3–4 weeks)
/api/v1/statuses/<id>/{favourite,reblog,reply,bookmark}, polls, edits, deletes. Multi-user on a shared JSS = a small instance.
Phase 3 — Federation (months — the cliff)
/.well-known/webfinger (#164), HTTP signatures, follower fan-out, outbound Create activities, inbound Follow handling, retry queues. This is where the real difficulty is — federation is genuinely hard.
Architectural choice
Two reasonable shapes:
- In JSS — a Mastodon-API module turned on with
--mastodon-api (or always on). Tight integration, single binary.
- External sidecar — a separate Node process that speaks Mastodon API to clients and JSS LDP to the pod. Loose coupling, evolves independently, can be replaced or removed.
Lean: sidecar for Phase 1 (so the shim can iterate quickly without dragging JSS through churn), fold into JSS only once the shape is stable.
Data mapping (rough)
| Mastodon |
JSS / Solid |
| Account |
WebID profile (/profile/card#me) |
| Status |
schema:Note JSON-LD at /public/outbox/<id>.jsonld |
| Outbox |
LDP container /public/outbox/ |
| Inbox |
LDP container /inbox/ (already a Solid convention) |
| Followers |
Resource /private/followers.jsonld |
| Streaming event |
JSS /.notifications WebSocket, reshaped to Mastodon SSE/WS frames |
License
Phanpy is MIT. If we want to embed it (ship as a Solid app inside a bundle), MIT is permissive — no copyleft obligation. If we just want to link to it (links.jsonld-style), license doesn't matter.
Success criteria
A user runs jspod, opens Phanpy, types their pod URL as the "server", clicks Sign In, completes OAuth, sees an empty home timeline, posts a status, sees it appear in real time via the streaming endpoint, refreshes, status persists. No federation needed in Phase 1 — single pod, single user.
Notes
- Done well, this unlocks every Mastodon client at once — that's the leverage. The client surface is the same across Phanpy, Elk, Tusky, Ivory, Ice Cubes, etc.
- The "tiny Mastodon server in your pod" demo is a much sharper story than "yet another decentralized platform."
Goal
Make a Solid pod served by JSS speak enough of the Mastodon REST API that a standard Mastodon client — Phanpy as the reference target — can sign in to it, read its timeline, post statuses, and interact.
Phanpy is MIT-licensed, browser-based, single-column, lean — an ideal first target. Every other Mastodon client (Elk, Tusky, Ivory, Ice Cubes, Mona, Pinafore, Toot!, Mastodon's own UI…) speaks the same API, so a shim for Phanpy unlocks the whole ecosystem for free.
Why this matters
Scope
The smallest useful target: a single-pod, single-user Phanpy-can-log-in-and-use-its-own-pod prototype. No federation yet.
Phase 1 — Personal client over your own pod (~2 weeks)
Mastodon REST endpoints needed:
/oauth/authorize,/oauth/token,/api/v1/apps(translate to/from JSS's existing OIDC)/api/v1/accounts/verify_credentialsGET /api/v1/statuses/<id>,POST /api/v1/statuses(back the resource as a JSON-LDschema:Noteon the pod)GET /api/v1/timelines/home,/public(build from the user's outbox container)/api/v1/streaming(SSE/WS — JSS already has WebSocket notifications, wrap them in Mastodon's shape)GET /api/v1/notificationsPOST /api/v1/media(JSS already accepts PUT-to-pod for files; wrap in Mastodon response shape)Phase 2 — Status interactions (~3–4 weeks)
/api/v1/statuses/<id>/{favourite,reblog,reply,bookmark}, polls, edits, deletes. Multi-user on a shared JSS = a small instance.Phase 3 — Federation (months — the cliff)
/.well-known/webfinger(#164), HTTP signatures, follower fan-out, outboundCreateactivities, inboundFollowhandling, retry queues. This is where the real difficulty is — federation is genuinely hard.Architectural choice
Two reasonable shapes:
--mastodon-api(or always on). Tight integration, single binary.Lean: sidecar for Phase 1 (so the shim can iterate quickly without dragging JSS through churn), fold into JSS only once the shape is stable.
Data mapping (rough)
/profile/card#me)schema:NoteJSON-LD at/public/outbox/<id>.jsonld/public/outbox//inbox/(already a Solid convention)/private/followers.jsonld/.notificationsWebSocket, reshaped to Mastodon SSE/WS framesLicense
Phanpy is MIT. If we want to embed it (ship as a Solid app inside a bundle), MIT is permissive — no copyleft obligation. If we just want to link to it (
links.jsonld-style), license doesn't matter.Success criteria
A user runs
jspod, opens Phanpy, types their pod URL as the "server", clicks Sign In, completes OAuth, sees an empty home timeline, posts a status, sees it appear in real time via the streaming endpoint, refreshes, status persists. No federation needed in Phase 1 — single pod, single user.Notes