What Skylite is: a kid-safe, installable PWA that gives a child a curated, non-algorithmic window into Bluesky/ATProto — a “walled garden whose door was always hers.” Identity is rooted in a DID the child ultimately controls, so the account (and its whole history/graph) is portable and the guardian relationship is a handoff, not a lock-in.
Provenance: extracted from a brainstorming transcript on 2026-07-10. Separated out of the arecipe idea-cataloguing session because Skylite is a distinct project. Claims flagged verify were called out in the source as needing confirmation against current ATProto/PDS/iOS docs before building on them. Nothing here is verified engineering — it’s a captured idea set.
Scope: this doc holds the identity / ATProto / PWA-hardening engineering. The product concept + branding (read-only pitch, Sky-Channels, Scrapbook, Sky-Shield moderation, co-viewing, the name/visual/tagline) lives in
CONCEPT.md, distilled from a separate same-day Gemini dialogue (raw). SeeCONCEPT.md§3 for where its features meet this doc — notably the guardian toggle = the pause screen / kill switch below (§4), and the “Sky-Shield” AI moderation layer is in tension with the no-server spine of §2/§5. The raw transcript behind this doc is not yet filed — seePROVENANCE.md.
When the child is old enough, you don’t loosen flags — you migrate the account to a PDS she controls and give her the keys. Because identity is rooted in the DID (not the PDS), her whole history, graph, and posts come with her. Roughly one command’s worth of ceremony:
goat account migrate:
create account on new PDS → import repo as CAR → move blobs
→ sign PLC operation to repoint identity → activate
did:plc now; hold a backup rotation key offlineThe identity decision that makes both the handoff and any recovery possible — the concrete form of the bus-factor worry.
did:plc is recoverable and supports key rotation. did:web ties identity
to a domain; moving domains creates an entirely new DID and loses the social
graph. → choose did:plc.bsky.social
one, so the namespace is yours and neutral.Instead of an algorithmic timeline: the accounts on an inclusion list, each
pulled with getAuthorFeed, merged newest-first, client-side, no server.
One tap reaches the trusted adult out-of-band (messaging handoff or email) — not platform reporting, not activity monitoring. Distress routes to a person; she chooses to reach out. The honest inverse of surveillance.
Shows her what’s in her repo and states plainly: it’s public under the hood, portable, and pausable by a guardian. Teaching the real shape of the network > a comforting fiction. Same anti-decoy stance already landed on.
The OAuth browser session lives in the same storage iOS willingly evicts. If reads are gated behind auth on a closed PDS, eviction doesn’t clear a cache — it locks her out until re-auth. Treat “logged in” as temporary; make re-auth a single calm step; let the passkey front door double as the re-login affordance. This is where eviction + closed PDS + passkey all meet — handle it deliberately.
PWAs update silently on next open (no App Store review) — a gift, but iOS
service-worker caching notoriously strands stale builds. Use skipWaiting +
clients.claim, cache-bust assets, show a visible version stamp. Skylite is a
safety tool: a lingering old build means your patches — and even the client-flag
pause screen / kill switch — might not actually be live.
Her iPad shoots HEIC by default; most browsers can’t decode HEIC to canvas, but
Safari can. Because Skylite is WebKit-only, you get Safari’s native HEIC
decoder for free — no ~2MB WASM converter (heic2any etc. are heavy).
draw image → scale down → canvas.toBlob('image/jpeg')
⇒ converts format + shrinks blob (saves storage) + strips EXIF geodata, one pass
accept="image/jpeg" makes iOS
auto-convert the pick; another says that transcoding is unreliable (including
image/heic or multi-select changes behavior). The canvas re-encode is the
robust move. Verify on her exact iOS version — decode/transcode behavior
shifts release to release.The passkey gate covers launch, but a kid sets the iPad down mid-use. Add a
visibilitychange/blur handler that locks the garden when Skylite backgrounds;
re-gate on return; optional inactivity timeout. Closes the “left on the couch” /
“handed to a friend” gaps.
Cache last-fetched posts + blob images in the SW so flaky wifi / a car ride still shows her garden (“showing saved, you’re offline” vs. a blank screen).
iOS shows a blank white cold-start screen without apple-touch-startup-image
splash images. Set those + maskable/apple-touch icons, theme-color,
status-bar style, and a manifest (name “Skylite”, short name). The difference
between launching like an app and launching like a bookmark — matters for
something she’s meant to trust and reach for.
Two auth layers that do not substitute for each other — only one can revive a dead session.
┌─ OAuth session ──────────────┐ ┌─ Local biometric / passkey ──┐
│ a BADGE the PDS issues to │ │ the LOCK on Skylite's own │
│ Skylite → read/write her repo │ │ front door — "right kid │
│ (access token + refresh token)│ │ opened the app" │
└───────────────────────────────┘ └───────────────────────────────┘
Softeners that don’t require building custom PDS auth:
login_hint pre-fills her handle so she isn’t typing an identifier. Small, free.Bigger levers:
Rather than both kids on your one PDS: each family runs a PDS and they allowlist only each other, no public relay. More sovereignty; fits cooperative-governance leanings — but real ops for the other parent. Confirm the exact peering wiring (two hosts subscribing to each other vs. a tiny private relay) before calling it a supported mode rather than hand-rolled.
goat account migrate +
PLC op) against current docs.