diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6aec73f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# AGENTS.md — direction for agents (and humans) working in this repo + +Welcome aboard the airship. This file is the working agreement for AI agents and human contributors. Read it before doing anything else. + +## What this project is + +**Fahrenheit** (internal codename; product name TBD — see `docs/research/2026-07-30-naming.md`) is a personal/professional website builder for the AT Protocol ecosystem ("the Atmosphere"). It aggregates a user's existing atproto data (Bluesky, standard.site publications, sifa.id professional profile, teal.fm scrobbles) into a website at their own domain, with a self-contained management backend. The site's own pages and configuration live as records in the **user's PDS**, under our own lexicon namespace. + +Positioning: **professional presence** — "what Read.cv should have been, but you own the data." + +## Current phase + +**Pre-code.** We are in feasibility/direction. Nothing has been scaffolded; no stack has been chosen (that will be an ADR when it happens). Do not start writing product code without an explicit request from Jacob. + +## Read this first, in this order + +1. `docs/FEASIBILITY.md` — the assessment and verdict +2. `docs/decisions/product-decisions.md` — product/strategy decisions already made (do not relitigate these without new information) +3. `docs/decisions/adr/` — architectural decisions (same rule) +4. `docs/research/` — checkpointed research with sources; **check the checkpoint dates.** The atproto ecosystem moves fast; research older than a few months should be spot-verified before you lean on it. + +## How we record decisions + +- **Technical/architectural decisions → ADRs** in `docs/decisions/adr/`, numbered `NNNN-slug.md`, MADR-lite format (Status / Context / Decision / Consequences). Statuses: Proposed, Accepted, Superseded. +- **Product/strategy decisions → the log** in `docs/decisions/product-decisions.md`. +- If you and Jacob decide something in a session, **write it down before the session ends.** A decision that isn't in this repo didn't happen. +- New research or learnings worth keeping → a dated file in `docs/research/`, with source URLs. Leave a trail the next session can pick up. + +## Hard constraints to respect + +- **Solo, nights-and-weekends capacity.** Scope ruthlessly. Prefer the smallest thing that plants the flag. When proposing work, bias toward "shippable in evenings" over "architecturally complete." +- **User data lives in the user's PDS** (ADR 0004). Don't design features that quietly move canonical data into our database. +- **AGPL-3.0** (ADR 0002). Dependencies and contributions must be compatible. +- **The window is short.** Competitive landscape research (July 2026) says months, not years. Favor shipping the professional-presence wedge over broadening. + +## Voice & whimsy + +The brand is open, warm, and a bit whimsical — atmosphere, wind, flight, airships. The codename is a Final Fantasy X reference and "Wind bless you" is the project greeting; that energy is welcome in docs, commit messages, release notes, and UI copy, in small doses. Whimsy seasons the dish; it is not the dish. Error messages should still tell you what went wrong. + +## Practical notes + +- Default branch: `main`. Session work happens on feature branches; open draft PRs. +- Do not put the model name / AI identifiers in commit messages or code comments beyond the standard co-author footer. +- When research agents are used, checkpoint their findings into `docs/research/` — subagent output that only exists in a chat transcript is lost to future sessions. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md index 711615e..031734d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ # fahrenheit -Wind bless you + +> *Wind bless you.* + +**Fahrenheit** (an internal codename — yes, [that airship](https://finalfantasy.fandom.com/wiki/Fahrenheit_(airship)); the product will get its own name) is a personal/professional website builder for the [AT Protocol](https://atproto.com) ecosystem — the network its community calls **the Atmosphere**. + +Think *"Astro for atproto"*, but more opinionated. Or: the airship you sail the Atmosphere in. + +## What it will be + +Your website, at your domain, built from data you already own: + +- Your professional profile from [sifa.id](https://sifa.id) +- Your long-form writing via [standard.site](https://standard.site) (Leaflet, pckt, Offprint, …) +- Your [Bluesky](https://bsky.app) microblog +- The music you're listening to from [teal.fm](https://teal.fm) + +…plus a self-contained management backend, so you can author and configure everything in one place without visiting each app. Everything the site *is* — its pages, its configuration, its content — lives as records in **your** PDS, not in our database. Point another tool at your repo and your site comes with you. That's the whole point. + +## Status + +🚧 **Pre-code. Feasibility & direction phase.** No product code exists yet; what this repo currently holds is research, decisions, and direction: + +| Where | What | +|---|---| +| [`docs/FEASIBILITY.md`](docs/FEASIBILITY.md) | The full feasibility assessment and verdict | +| [`docs/decisions/`](docs/decisions/) | Product decisions log + ADRs for technical/architectural decisions | +| [`docs/research/`](docs/research/) | Checkpointed research (ecosystem landscape, lexicon feasibility, monetization, naming) for future sessions to build on | +| [`AGENTS.md`](AGENTS.md) | Direction and working agreements for AI agents (and humans) contributing here | + +## Ethos + +- **Open.** AGPL-3.0. The engine is open source; your data lives in your PDS under open lexicons. Lock-in is a bug, not a business model. +- **Owned.** If this project vanished tomorrow, your site's data would still be sitting in your repo, readable by the next tool. +- **A bit whimsical.** Software for personal websites should feel personal. We keep the airship. + +## License + +[AGPL-3.0](LICENSE) diff --git a/docs/FEASIBILITY.md b/docs/FEASIBILITY.md new file mode 100644 index 0000000..a203456 --- /dev/null +++ b/docs/FEASIBILITY.md @@ -0,0 +1,86 @@ +# Feasibility Assessment — Fahrenheit + +**Date:** 2026-07-30 +**Status:** Complete. Verdict below. Underlying research is checkpointed in [`docs/research/`](research/) with sources; decisions extracted from this assessment live in [`docs/decisions/`](decisions/). + +## The idea + +A personal/professional website builder for the AT Protocol ecosystem. "WordPress for atproto" undersells it; "Astro for atproto, but more opinionated" is closer. A user gets a website at their own domain (e.g. `jacobzweifel.com`) that aggregates the atproto data they already produce — sifa.id professional profile, standard.site/Leaflet long-form writing, Bluesky microblog, teal.fm listening history — plus a self-contained management backend for authoring and configuration, so they never need to visit each upstream app. + +## Verdict + +**Build it — as a sustainable indie business, not a venture story.** + +- **Technically feasible, and well-timed.** Every required capability exists as of mid-2026, several only recently. +- **Competitively open at exactly the integration point,** but the window is estimated in months, not years. +- **Monetizable** on the proven indie personal-site model: free subdomain tier, paid custom-domain tier at ~$6–8/mo. Realistic ceiling today is Micro.blog-shaped ($5–50K MRR), because the paying niche is the enthusiast slice of ~4.5M daily-active atproto users. The ceiling rises if Bluesky growth resumes or its roadmap item "third-party app integrations on profiles" ships. + +## Technical feasibility + +The architecture we want — one OAuth grant, then read and write the user's data directly in their PDS — is explicitly supported, not a hack: + +1. **Granular OAuth scopes shipped.** `repo:` scopes give a third-party app create/update/delete on specific collections in namespaces it doesn't own (e.g. `repo:site.standard.document repo:id.sifa.profile.self`), with per-collection user consent. "Permission sets" (scope bundles published as lexicons) improve the consent UX; sifa already publishes them. This is the enabler that makes a self-contained authoring backend newly possible. Caveat: rollout to self-hosted PDS distributions was still completing through late 2025/2026 — test against both bsky.social and self-hosted PDSes; `transition:generic` remains a fallback. +2. **All reads are trivial.** Every source is public JSON in the user's PDS with known NSIDs, readable unauthenticated via `com.atproto.repo.listRecords`/`getRecord` (PDS discovered from the DID document). No scraping, no per-app API keys. Jetstream/firehose exists for live updates (v2+); Constellation (microcosm.blue) can enrich with backlinks but must not be a hard dependency (run by one person, on a Raspberry Pi, gloriously). + +### Source risk ranking (lowest → highest) + +| Source | Lexicons | Risk notes | +|---|---|---| +| Bluesky | `app.bsky.*` | Stable, mature. Republishing your own posts on your own site is the point of the protocol. Hygiene: honor deletions, respect self-labels, follow the [developer guidelines](https://docs.bsky.app/docs/support/developer-guidelines). | +| standard.site | `site.standard.*` | Young (late 2025) but the ecosystem's breakout community lexicon: written by Leaflet, pckt, Offprint, a WordPress plugin, Sequoia, and many indie blogs; Bluesky gives its links rich timeline previews. Best candidate for our blog read *and* write path. | +| Leaflet legacy | `pub.leaflet.*` | Leaflet migrated to standard.site (~Jan 2026) but left old records in place. Rendering rich content means handling the block-content union inside `site.standard.document.content`. Read standard.site first; legacy records as fallback. | +| sifa.id | `id.sifa.*` | Very active (commits this week; SDK, permission sets, MIT lexicons), positioned as a shared standard — but explicitly alpha-badged and single-vendor. Expect schema evolution; validate defensively. | +| teal.fm | `fm.teal.alpha.*` | Readable today with real data in the wild (600k+ plays indexed by third parties), but the first-party app is unlaunched and the `alpha` in the NSID guarantees a breaking namespace rename. Read-only, feature-flagged, not in v1. | + +**Engineering caveats:** pin lexicon versions and validate defensively; support both bsky.social-hosted and self-hosted PDSes in the OAuth client; honor record deletions when caching. + +## Competitive landscape + +**Crowded in components, empty at the integration point.** Bluesky's own blog now teaches ["Build an Atmospheric Website"](https://atproto.com/blog/atmosphere-website) as a named DIY concept — with no flagship product. Nobody ships multi-lexicon aggregation *plus* a unified authoring backend. + +Nearest players (full detail in [`research/2026-07-30-ecosystem-landscape.md`](research/2026-07-30-ecosystem-landscape.md)): + +- **Blento** (blento.app) — bento-grid one-page personal site stored in your PDS; ~200 GitHub stars, active, hosted + self-host. The closest single competitor; the same idea at one-page scale. +- **Leaflet** — custom domains + paid newsletters already; one feature-cycle from "your website." The most dangerous trajectory competitor. +- **EmDash** — Cloudflare's open-source "WordPress successor" with atproto login. Biggest-resourced adjacent player; general CMS, not PDS-native. +- **wisp.place** — static hosting from your PDS (complementary infrastructure, not a competitor). +- **Sifa / Atmos / Linkat** — profile-card, creative-portfolio, and link-in-bio shaped neighbors; none produce a full website. +- A long tail of 0–5-star DIY repos and Astro/Svelte loaders proving demand (pfrazee.com renders his own Leaflet posts by hand). + +**The chosen wedge — professional presence — is the safest opening.** The portfolio/CV slot has been empty since Read.cv (free, beautiful, revenue-less) was acqui-hired by Perplexity and shut down in January 2025. No atproto player is aimed there. + +## Monetization + +Full numbers in [`research/2026-07-30-monetization.md`](research/2026-07-30-monetization.md). The short version: + +- **Inside the Atmosphere, almost nobody charges consumers.** Leaflet, Linkat, Blacksky: free. Bluesky+ (teased at $8/mo, Dec 2024) still unshipped as of mid-2026 — an open lane and a standing platform risk. The only demonstrated recurring revenue is Graze's ad rev-share. Nobody has proven atproto users will pay a subscription; nobody credible has tried. +- **Outside it, the model is proven.** Micro.blog (the closest analog) has sustained $5–10/mo for 8+ years. Bear Blog/Pika at $5–6/mo; Carrd at $19/yr; omg.lol just raised $20/yr → $50/yr and its community accepted it. Above $10/mo you enter Ghost/Squarespace comparison territory. +- **The conversion trigger:** free subdomain → paid custom domain is the most proven upgrade path in this category, and doubly apt on atproto where the domain is also your handle and your verification. Bluesky's Namecheap integration normalized paying for domains in this ecosystem; a domain-reseller margin is a proven secondary line. +- **Anti-models:** free-forever with no plan (Read.cv → shutdown); donations-only (mission projects, not businesses); ads (wrong shape for personal sites). + +**Chosen model (see product decisions log):** free subdomain tier; ~$6–8/mo (~$60–80/yr) with custom domain; domain resale margin secondary. Win the way Micro.blog wins next to free WordPress: polish and convenience, not feature count. + +## Risks + +1. **Window** — Blento and Leaflet are actively shipping toward this space; landscape research estimates months, not years, of open lane. +2. **Bluesky+** — if it finally ships bundling custom-domain/profile-site features, it compresses the paid tier's value. +3. **Free-culture headwind** — the ecosystem expects free; we out-polish, not out-feature. +4. **Alpha lexicons** — sifa and teal.fm schemas will move; the adapter layer (ADR 0006) is the mitigation. +5. **Capacity** — solo, nights-and-weekends against funded/active competitors. Mitigation: ruthless v1 scope (ADR 0008), claim the professional-presence position no one else wants yet. Skyseed grants ($5–25K) exist if runway is wanted without changing the indie character. + +## Recommended v1 (ADR 0008, proposed) + +- atproto OAuth login (granular scopes) +- Read-only aggregation of **three** sources: Bluesky, standard.site, sifa +- **One** opinionated professional-site theme +- **One** authored content type: site pages/config in our own lexicon +- Hosted, free subdomain, paid custom domains + +Deferred to v2+: teal.fm, writing third-party lexicons, the pluggable translation layer, themes-as-plugins, newsletters, static export. + +## Open questions (not yet decided) + +- Product name (research underway — see [`research/2026-07-30-naming.md`](research/2026-07-30-naming.md)); Fahrenheit stays as codename. +- Tech stack (will be an ADR before scaffolding). +- Our lexicon namespace NSID (depends on the product name/domain). +- Hosted infrastructure target and cost model. diff --git a/docs/decisions/README.md b/docs/decisions/README.md new file mode 100644 index 0000000..d39c517 --- /dev/null +++ b/docs/decisions/README.md @@ -0,0 +1,8 @@ +# Decisions + +Every decision gets written down. A decision that isn't in this directory didn't happen. + +- **Product/strategy decisions** → [`product-decisions.md`](product-decisions.md), a running log. +- **Technical/architectural decisions** → [`adr/`](adr/), numbered ADRs in MADR-lite format (Status / Context / Decision / Consequences). + +ADR statuses: **Proposed** (on the table, not yet ratified by Jacob), **Accepted**, **Superseded by NNNN**. Don't edit an accepted ADR's decision; write a new one that supersedes it. diff --git a/docs/decisions/adr/0001-record-architecture-decisions.md b/docs/decisions/adr/0001-record-architecture-decisions.md new file mode 100644 index 0000000..32d6fac --- /dev/null +++ b/docs/decisions/adr/0001-record-architecture-decisions.md @@ -0,0 +1,15 @@ +# ADR 0001: Record architecture decisions as ADRs + +**Status:** Accepted · 2026-07-30 + +## Context + +This project is built solo, part-time, with AI-agent sessions that start from zero context. Decisions made in a chat session evaporate unless written down. + +## Decision + +Technical and architectural decisions are recorded as numbered ADRs in this directory, MADR-lite format (Status / Context / Decision / Consequences). Product/strategy decisions go in `../product-decisions.md` instead. Accepted ADRs are immutable; changes of mind produce a superseding ADR. + +## Consequences + +Small writing overhead per decision; in exchange, any future session (human or agent) can reconstruct why things are the way they are without archaeology. diff --git a/docs/decisions/adr/0002-agpl-3.0-license.md b/docs/decisions/adr/0002-agpl-3.0-license.md new file mode 100644 index 0000000..6a8e9fe --- /dev/null +++ b/docs/decisions/adr/0002-agpl-3.0-license.md @@ -0,0 +1,17 @@ +# ADR 0002: License the engine under AGPL-3.0 + +**Status:** Accepted · 2026-07-30 (license committed at repo creation; rationale recorded here) + +## Context + +The project is an open-source engine plus a paid hosted service (ADR 0005), run as a sustainable indie business (PD-3). The main licensing risk for that shape is a larger operator running our engine as a competing hosted service without contributing back. + +## Decision + +AGPL-3.0 for the engine. Network use counts as distribution, so anyone hosting a modified version must publish their changes. + +## Consequences + +- Protects the indie hosted-service model from cloud freeriding while keeping the engine genuinely open — consistent with the ethos that lock-in is a bug. +- Some companies refuse AGPL dependencies; irrelevant for an end-user product, worth remembering if we ever publish reusable libraries (those could be dual-licensed or MIT'd separately, e.g. lexicon definitions, which should be maximally permissive — see ADR 0004's note). +- All dependencies must be AGPL-compatible. diff --git a/docs/decisions/adr/0003-build-on-atproto.md b/docs/decisions/adr/0003-build-on-atproto.md new file mode 100644 index 0000000..e94ac5e --- /dev/null +++ b/docs/decisions/adr/0003-build-on-atproto.md @@ -0,0 +1,18 @@ +# ADR 0003: Build on AT Protocol; the user's PDS is the source of truth + +**Status:** Accepted · 2026-07-30 + +## Context + +The product aggregates a user's presence from atproto apps (Bluesky, standard.site, sifa.id, teal.fm). We could treat atproto as one integration among many, syncing everything into our own database — or commit to it as the substrate. + +## Decision + +The AT Protocol is the substrate, not an integration. The user's PDS (repo) is the canonical store for their data; our services read from it (directly via `com.atproto.repo.*`, later via Jetstream for liveness) and write to it via OAuth. Our own database holds only derived state: caches, rendered output, indexes, operational metadata — all rebuildable from PDSes. + +## Consequences + +- The ownership story is real, not marketing: another tool can rebuild a user's site from their repo. +- We inherit atproto's operational surface: DID resolution, PDS discovery, both bsky.social-hosted and self-hosted PDSes, record deletion semantics (caches must honor deletes). +- Constellation/microcosm may enrich (backlinks) but must never be a hard dependency. +- We accept coupling to the protocol's maturity curve; mid-2026 research (`../../research/2026-07-30-lexicon-feasibility.md`) says that curve is now on our side. diff --git a/docs/decisions/adr/0004-site-data-in-users-pds-own-lexicon.md b/docs/decisions/adr/0004-site-data-in-users-pds-own-lexicon.md new file mode 100644 index 0000000..a2d827f --- /dev/null +++ b/docs/decisions/adr/0004-site-data-in-users-pds-own-lexicon.md @@ -0,0 +1,20 @@ +# ADR 0004: Site configuration and native content live in the user's PDS, under our own lexicon namespace + +**Status:** Accepted · 2026-07-30 + +## Context + +The site itself has data — layout/theme choices, page content authored in our backend, source bindings ("show my sifa profile here"). It could live in our database (fast, conventional SaaS) or as records in the user's PDS (atproto-native). + +## Decision + +Site configuration and natively-authored content are records in the **user's PDS**, under a lexicon namespace we define and publish openly. Ephemeral/operational state (sessions, build caches, billing) stays in our database. + +The namespace NSID depends on the product's eventual domain (PD-5); lexicons will be published on-network (`com.atproto.lexicon.schema`) and permissively licensed so other tools can read and render a Fahrenheit site. + +## Consequences + +- "You own your site" is credible and testable: delete our service, the site definition survives in your repo. +- More design work up front, and lexicon schema choices are expensive to change once records exist in the wild — schema design deserves review before v1 records ship. +- Blocked on the product name for the final NSID; design can proceed under a placeholder. +- We should publish permission sets for our scopes (sifa's practice) for a clean consent screen. diff --git a/docs/decisions/adr/0005-hybrid-deployment-model.md b/docs/decisions/adr/0005-hybrid-deployment-model.md new file mode 100644 index 0000000..dbf0b66 --- /dev/null +++ b/docs/decisions/adr/0005-hybrid-deployment-model.md @@ -0,0 +1,21 @@ +# ADR 0005: Hybrid deployment — open-source engine, hosted service as the default path + +**Status:** Accepted · 2026-07-30 + +## Context + +Options considered: hosted multi-tenant only; static-export engine only ("pure Astro for atproto"); self-hosted server only; hybrid. The launch audience is non-technical atproto users (PD-1), and the business is a paid hosted service (PD-3/PD-4), but the ethos demands a real exit hatch (README). + +## Decision + +One open-source engine (AGPL) with two first-class consumption modes: + +1. **Hosted multi-tenant service** (ours) — the default, paid path: subdomain free, custom domain paid. Sites stay live/fresh via PDS reads (later Jetstream). +2. **Self-host / static export** — the same engine runnable by anyone; static export is acceptable to land after launch, but the architecture must not preclude it (no hosted-only assumptions baked into the renderer). + +## Consequences + +- The Ghost/Leaflet playbook: credibility with the ecosystem's open-source culture plus a revenue path. +- Hosted mode means we own uptime, support, and onboarding polish from day one — priced into PD-1's choice. +- Renderer must be cleanly separable from the multi-tenant shell; worth enforcing in the eventual stack ADR. +- v1 ships hosted-only (ADR 0008); self-host/export follows. diff --git a/docs/decisions/adr/0006-own-lexicons-plus-adapter-layer.md b/docs/decisions/adr/0006-own-lexicons-plus-adapter-layer.md new file mode 100644 index 0000000..22f4b91 --- /dev/null +++ b/docs/decisions/adr/0006-own-lexicons-plus-adapter-layer.md @@ -0,0 +1,21 @@ +# ADR 0006: Own lexicons at the core; pluggable adapters translate to/from third-party lexicons, chosen per content type + +**Status:** Accepted · 2026-07-30 + +## Context + +When a user authors content in our backend, we can write it as third-party lexicon records (e.g. `site.standard.document` — maximum interop, coupled to schemas we don't control), as our own lexicon only (clean, but invisible to other apps), or some blend. Meanwhile the external schemas we read vary wildly in stability: `app.bsky.*` (stable) → `site.standard.*` (young, multi-vendor) → `id.sifa.*` (alpha, single-vendor) → `fm.teal.alpha.*` (alpha in the NSID itself). + +## Decision + +Our own lexicon namespace is the stable core (site structure, pages, source bindings — ADR 0004). Every external namespace is handled by a **pluggable adapter** (translation layer) that can be versioned, feature-flagged, and updated independently when upstream schemas move. Whether a given content type is *written* in our lexicon, a third-party lexicon, or both is a **per-content-type decision**, made by interop value: + +- **Long-form posts:** write `site.standard.*` (interop is the point there; ecosystem explicitly welcomes third-party writers). Post-v1. +- **Site pages/config:** our lexicon only. +- **Others:** decided as they arrive; users get maximal control over where their content is consumable. + +## Consequences + +- Alpha-schema churn (sifa, teal) is contained to one adapter instead of rippling through the product. +- Adapters are a natural open-source contribution surface, and later the mechanism for "pluggable translation" as a user-facing feature. +- Costs an abstraction layer before the first line of product code; the v1 scope (ADR 0008) keeps it thin — read-only adapters for three sources. diff --git a/docs/decisions/adr/0007-atproto-oauth-granular-scopes.md b/docs/decisions/adr/0007-atproto-oauth-granular-scopes.md new file mode 100644 index 0000000..1cf9c95 --- /dev/null +++ b/docs/decisions/adr/0007-atproto-oauth-granular-scopes.md @@ -0,0 +1,17 @@ +# ADR 0007: Authenticate with atproto OAuth using granular scopes; never app passwords + +**Status:** Accepted · 2026-07-30 + +## Context + +As of mid-2026, atproto OAuth supports granular `repo:` scopes (per-collection create/update/delete, including namespaces the app doesn't own), `rpc:` scopes, `blob:` scopes, and permission-set bundles published as lexicons. The legacy alternatives are app passwords (full-repo access) and the coarse `transition:generic` scope. + +## Decision + +OAuth only, requesting the narrowest scopes the enabled features need (v1 is read-mostly plus writes to our own namespace). Publish permission sets for our scope bundles. Support both bsky.social-hosted and self-hosted PDSes; tolerate `transition:generic` as a fallback where granular scopes haven't rolled out. + +## Consequences + +- The consent screen honestly reflects what we touch — load-bearing for the ownership-first brand, and it's what makes writing other apps' lexicons (ADR 0006) socially acceptable. +- Granular-scope rollout across self-hosted PDS distributions was still completing through 2025–26; needs a test matrix. +- Sources: `../../research/2026-07-30-lexicon-feasibility.md` (§ OAuth), https://atproto.com/guides/permission-sets. diff --git a/docs/decisions/adr/0008-v1-scope.md b/docs/decisions/adr/0008-v1-scope.md new file mode 100644 index 0000000..9940a04 --- /dev/null +++ b/docs/decisions/adr/0008-v1-scope.md @@ -0,0 +1,25 @@ +# ADR 0008: v1 scope — three read sources, one theme, one authored type, hosted only + +**Status:** Proposed · 2026-07-30 (recommended by feasibility assessment; awaiting explicit ratification before scaffolding) + +## Context + +Capacity is solo nights-and-weekends (PD-7); the competitive window is estimated in months (Blento, Leaflet actively shipping adjacent). The full vision — every source, authoring into third-party lexicons, pluggable translation, themes, static export — does not fit that window part-time. The wedge (PD-2) needs a flag planted, not a cathedral. + +## Decision + +v1 is exactly: + +1. atproto OAuth login (ADR 0007) +2. **Read-only** adapters for three sources: Bluesky (`app.bsky.feed.post`, profile), standard.site (`site.standard.document`/`publication`, with `pub.leaflet.*` legacy fallback), sifa (`id.sifa.profile.*`) +3. **One** opinionated professional-presence theme +4. **One** authored content type: site pages + configuration in our own lexicon (ADR 0004) +5. Hosted multi-tenant: free subdomain, paid custom domain (PD-4) + +Explicitly deferred to v2+: teal.fm (schema-fragile), writing third-party lexicons, user-facing translation layer, additional themes/theming API, newsletters, analytics, static export/self-host packaging, Jetstream liveness (v1 may poll/cache). + +## Consequences + +- Shippable in a few part-time months; enough to claim the professional-presence position. +- Saying "no" to interop-writing at launch is the painful cut — it's the long-term differentiator but not the flag-planting requirement; the adapter architecture (ADR 0006) keeps the door open. +- Deferring static export means the ethos rests on the lexicons being open from day one — publish them with v1. diff --git a/docs/decisions/product-decisions.md b/docs/decisions/product-decisions.md new file mode 100644 index 0000000..060614b --- /dev/null +++ b/docs/decisions/product-decisions.md @@ -0,0 +1,13 @@ +# Product decisions log + +Strategy and product decisions, newest last. Technical/architectural decisions live in [`adr/`](adr/). + +| # | Date | Decision | Status | Rationale | +|---|---|---|---|---| +| PD-1 | 2026-07-30 | **Launch audience: non-technical atproto users.** A hosted, pick-a-template product, not a dev framework first. | Accepted | Bigger market than dev-tinkerers; devs can (and visibly do) DIY this. Implies we own onboarding polish, uptime, support from day one. | +| PD-2 | 2026-07-30 | **Wedge: professional presence.** Portfolio/CV-first — sifa profile + standard.site writing + curated Bluesky + (later) teal.fm color. "What Read.cv should have been, but you own the data." | Accepted | Only uncontested position in the landscape: Blento owns bento-scale, Leaflet owns publishing, and the Read.cv-shaped hole has been empty since Jan 2025. Clearest paying buyer. | +| PD-3 | 2026-07-30 | **Business model: sustainable indie business.** Charge early; bootstrap; Micro.blog-shaped ambition ($5–50K MRR), not venture-scale. | Accepted | Paying niche today is thousands-not-millions of enthusiasts; Micro.blog proves 8+ years of sustainability at this shape. Skyseed grants are acceptable runway, not a model change. | +| PD-4 | 2026-07-30 | **Pricing: free subdomain tier; paid custom-domain tier at ~$6–8/mo (~$60–80/yr); domain-reseller margin as secondary revenue.** | Accepted | Most proven conversion trigger in the category (Carrd/Bear/Linktree), doubly apt on atproto where domain = handle = verification. Above $10/mo enters Ghost/Squarespace comparison territory. | +| PD-5 | 2026-07-30 | **"Fahrenheit" is the internal codename, not the product name.** Product name to be chosen; naming research in `../research/2026-07-30-naming.md`. | Accepted | The FFX-airship codename is beloved but may not carry the product. Codename stays for the repo and internal lore. | +| PD-6 | 2026-07-30 | **Brand ethos: open, ownership-first, a bit whimsical.** Atmosphere/wind/airship imagery; "Wind bless you" is the project greeting. Whimsy seasons, never obscures. | Accepted | Fits the Atmosphere's culture and the founder's taste; differentiates from sterile site-builder branding. | +| PD-7 | 2026-07-30 | **Operating constraint: solo, nights-and-weekends.** All scoping decisions assume this capacity. | Accepted | Reality. Drives ADR 0008's ruthless v1 scope and the "ship the wedge before broadening" posture. | diff --git a/docs/research/2026-07-30-ecosystem-landscape.md b/docs/research/2026-07-30-ecosystem-landscape.md new file mode 100644 index 0000000..4df01ff --- /dev/null +++ b/docs/research/2026-07-30-ecosystem-landscape.md @@ -0,0 +1,53 @@ +# Ecosystem landscape: atproto website builders & neighbors + +**Checkpoint date:** 2026-07-30. Compiled from a broad web-research sweep of the atproto app ecosystem. + +## Headline + +**Crowded in components, empty at the integration point.** Bluesky's own team published ["Build an Atmospheric Website"](https://atproto.com/blog/atmosphere-website) — an official how-to for exactly our concept, framed as DIY assemblage. The ecosystem has *named* the thing; nobody ships it as a product. Verdict from the sweep: "the window is open but closing — measured in months, not years." + +## Closest analogues + +- **Blento** — https://blento.app | https://github.com/flo-bit/blento — Bento-grid one-page personal site; all data as records in your PDS; embeds Bluesky/Semble/events/Bandcamp; custom domains; hosted with self-host escape. Svelte/Tailwind/Cloudflare Workers. Created Dec 2025 as a bento.me replacement; ~204 stars, active through late July 2026. **Closest single competitor** — same idea at one-page scale, no multi-page sites, no blog rendering, no cross-app authoring backend. +- **wisp.place** — https://wisp.place — static site *hosting* from your PDS (files in PDS, CDN in front). Complementary infrastructure, not a competitor. Active, self-hostable. +- **EmDash** — https://www.emdashcms.com | https://blog.cloudflare.com/emdash-wordpress/ — Cloudflare's open-source "WordPress successor" (Astro + Workers/D1/R2, sandboxed plugins, MCP server) with atproto login baked in. Launched ~Apr 2026, heavily promoted. Biggest-resourced adjacent player; general CMS with atproto integration, *not* PDS-native, no aggregation. +- **Slices** — https://slices.network — "AppViews in minutes": lexicon import → GraphQL API, OAuth, firehose sync. Dev infrastructure we might use or compete with for dev mindshare. +- **pages.blue** — dead; domain now with an expired-domain broker. + +## Blog platforms rendering atproto lexicons + +- **Leaflet** — https://leaflet.pub — most polished; publications, custom domains, **paid newsletters (Leaflet Pro, 2026)**, co-creator of standard.site. **Most dangerous trajectory competitor** — one feature-cycle from "your website." +- **pckt.blog**, **Offprint** (https://offprint.app, monetization-positioned) — the other standard.site co-creators; hosted, active. +- **WhiteWind** — https://whtwnd.com — the original atproto markdown blog (`com.whtwnd`); maintained but explicitly under-resourced. Self-host frontend: whitebreeze. +- **GreenGale** — https://greengale.app — WhiteWind-compatible, themes/LaTeX; small (22 stars), active. +- **blug** (haileyok, 107 stars) and a long tail of 0–5-star personal "my site reads my PDS" repos — evidence of demand, not competition. + +## standard.site ecosystem (the interop layer we build on) + +- Lexicons `site.standard.publication` / `.document` / graph + basic theme; created ~end 2025 by Leaflet+pckt+Offprint; "most successful community-generated lexicon." Since **May 2026 standard.site links get rich previews in the Bluesky app** ([atproto blog](https://atproto.com/blog/standard-site-bluesky-timeline)). +- Outbound tools (site → atproto): **Sequoia** (https://sequoia.pub, CLI for any markdown SSG), **annot.at**, **WordPress ATmosphere 1.0.0 plugin** (May 2026), astro-standard-site, mastrojs/atproto. +- Inbound tools (atproto → site; our direction): [atproto-standard-site-astro-loader](https://github.com/dylmye/atproto-standard-site-astro-loader) (2 stars), [svelte-standard-site](https://github.com/ewanc26/svelte-standard-site) (4 stars), pfrazee.com hand-rendering his Leaflet posts, coryd.dev, anil.recoil.org. **Our idea's kernel exists only as tiny plugins and personal hand-rolls.** + +## Profile/identity neighbors + +- **Sifa** — https://sifa.id — professional profile from atproto activity, embeddable profile card. Data source *and* partial overlap; produces a card, not a website. +- **Atmos** — https://atmos.cv — creative portfolio network (@you.atmos.cv), anti-scraping stance. Portfolio-adjacent. +- **Linkat** — https://linkat.blue — link-in-bio in your PDS; free, mature, simple. +- **at-me** — record explorer across apps; read-only cousin of our aggregation layer. +- Worth reading: ["ATProto Has a Profile Problem"](https://trezy.com/blog/atproto-profile-lexicon-generics) — articulates the fragmented-identity pain we solve. + +## Embeds & indieweb adjacents + +@astro-community/astro-embed-bluesky, bsky-embed, bsky-comments variants, granary (indieweb translator, 515 stars). + +## Events / signals + +- ATmosphereConf 2026 (Vancouver, Mar 2026, ~350 attendees; publishing was a major track; no full site-builder launched). +- [AT Protocol Spring 2026 roadmap](https://atproto.com/blog/2026-spring-roadmap); auth-scopes work is the enabler for our backend. +- Catalogs to monitor: courier.social, atstore.fyi, bskyinfo.com, alternativeproto.net. + +## So what + +- Empty: the composition (multi-lexicon aggregation + unified authoring backend + full multi-page site). Crowded: every sub-component. +- Threat ranking: Leaflet (trajectory) > Blento (momentum, same shape) > EmDash (resources) > DIY-getting-easier (standard.site tooling). +- Our defensible opening: the professional-presence wedge (see product decisions PD-2) — no one is aimed there, and the Read.cv-shaped hole is real. diff --git a/docs/research/2026-07-30-lexicon-feasibility.md b/docs/research/2026-07-30-lexicon-feasibility.md new file mode 100644 index 0000000..22953fa --- /dev/null +++ b/docs/research/2026-07-30-lexicon-feasibility.md @@ -0,0 +1,46 @@ +# Lexicon & technical feasibility per data source + +**Checkpoint date:** 2026-07-30. Compiled from web research into each source's lexicons, docs, and project health, plus the state of atproto OAuth. + +## sifa.id — professional profile / CV + +- Built by Guido X Jansen via Singi Labs (NL/EU; sibling product: Barazo forum). "Sifa reads and writes professional profile data to your data server." +- **Namespace `id.sifa.*`** (MIT, [singi-labs/sifa-lexicons](https://github.com/singi-labs/sifa-lexicons)): profile — `id.sifa.profile.self` (singleton), `.position`, `.education`, `.skill`, `.certification`, `.project`, `.volunteering`, `.publication`, `.course`, `.honor`, `.language`, `.location`, `.externalAccount`; graph — `.graph.follow`, `.graph.connection`; social — `id.sifa.endorsement`, `.meeting`, org attestations. Ships OAuth **permission sets** and a query method (`id.sifa.getProfileView`). Lexicons published on-network (resolvable via `com.atproto.lexicon.schema`; browsable at lexicon.garden). +- Docs: https://docs.sifa.id · https://sifa.id/about · TypeScript SDK `sifa-sdk` (Zod schemas). +- Health: very active (repo commits July 27–30, 2026; public roadmap). **Red flags:** lexicons explicitly Alpha-badged; single-vendor; small adoption. Validate defensively; expect schema evolution. + +## standard.site — long-form publishing standard + +- Community lexicons by the pckt.blog/Offprint/Leaflet developers; widely called the most successful community-generated lexicon in the Atmosphere. +- **Namespace `site.standard.*`**: `.publication` (a site/blog), `.document` (article: required `site`/`title`/`publishedAt`; `content` is an **open union** — markdown, rich blocks, extensible; `textContent` plaintext fallback), `.graph.subscription`, `.graph.recommend`, `.theme.basic`. +- Docs: https://standard.site/docs (modular by design — "implement only what your platform requires"). +- Adoption: Leaflet, pckt, Offprint, WordPress plugin, Sequoia CLI, Astro/Eleventy/Svelte integrations, many indie blogs. Bluesky gives standard.site links rich previews in-app ([announcement](https://atproto.com/blog/standard-site-bluesky-timeline)). Third-party writes are explicitly the point. +- **Red flags:** young (materialized late 2025/early 2026); minor implementation inconsistencies acknowledged (e.g. trailing slash on `site`); informal multi-vendor governance. Overall the safest read/write target. + +## Leaflet (leaflet.pub) + +- Native namespace `pub.leaflet.*`; typed client `@atcute/leaflet` on npm. **Migrated to standard.site ~Jan 2026**; legacy `pub.leaflet.*` records left in place deliberately. +- Fully open; Leaflet encourages third-party rendering ([lab notes](https://lab.leaflet.pub), "Integrating your Atmosphere account content in your web projects"); pfrazee.com does it. +- **Work item:** rendering rich content means handling Leaflet's block union inside `site.standard.document.content`. Strategy: read standard.site first, legacy records as fallback. + +## teal.fm — music scrobbling + +- Repo: https://github.com/teal-fm/teal (mirrored on tangled.org). **Namespace `fm.teal.alpha.*`** — `feed` (notably `fm.teal.alpha.feed.play`, the scrobble), `actor`, `stats`. **The `alpha` is in the NSID itself** → a stable release implies a breaking namespace migration. +- Status: first-party app still "coming soon" as of mid-2026, but the lexicon is a de-facto standard — multi-scrobbler and plyr.fm write it; an independent viewer has indexed 600k+ plays. Last lexicon commit ~Feb 2026. +- **Treatment:** read-only, feature-flagged, deferred from v1 (ADR 0008). + +## Bluesky + +- `app.bsky.feed.post`, `app.bsky.actor.profile`: stable, public, readable unauthenticated from any PDS or `public.api.bsky.app`. +- Republishing a user's own posts on their own site is unambiguously fine (protocol's purpose; ToS grants display rights across AT Protocol Developer Applications). Obligations ([developer guidelines](https://docs.bsky.app/docs/support/developer-guidelines)): reasonable security, published contact, respond to reports; honor deletions (no indefinitely stale caches); respect self-labels like `!no-unauthenticated` if republishing beyond the user's own content. + +## Cross-cutting: OAuth & reading patterns + +- **atproto OAuth is live and mature in mid-2026.** Granular scopes: `repo:` (per-collection CUD, *including namespaces the app doesn't own*, with per-collection consent), `rpc:` scopes, `blob:*/*`. Docs: https://atproto.com/guides/permission-sets · https://atproto.com/blog/oauth-improvements. +- **Permission sets** = scope bundles published as lexicons (sifa already publishes them). Rollout: bsky.social done; self-hosted PDS distributions completing through late 2025/2026 ([progress](https://github.com/bluesky-social/atproto/discussions/4118)); `transition:generic` remains as fallback. **Test against both PDS types.** +- **Lexicon community:** https://lexicon.community — grassroots working group for `community.lexicon.*` schemas; active calls; `awesome-lexicons` index. Check it before inventing any schema. +- **Reading tiers:** (1) direct PDS reads (`com.atproto.repo.listRecords`/`getRecord`, PDS from DID doc) — sufficient for v1; (2) **Constellation** (https://constellation.microcosm.blue) — global backlink index, ~12B links, run by one person on a Raspberry Pi: enrichment only, never a hard dependency, self-hostable; (3) Jetstream/firehose for liveness — v2+. + +## So what — feasibility verdict + +**Feasible and well-timed.** One OAuth grant → read sifa/standard.site/teal/Bluesky from the user's PDS, write our records back. Risk ranking (low→high): Bluesky → standard.site → Leaflet legacy → sifa → teal.fm. Engineering caveats: pin/validate lexicons defensively; support both PDS types; honor deletions in caches; don't hard-depend on Constellation. diff --git a/docs/research/2026-07-30-monetization.md b/docs/research/2026-07-30-monetization.md new file mode 100644 index 0000000..ce114e0 --- /dev/null +++ b/docs/research/2026-07-30-monetization.md @@ -0,0 +1,42 @@ +# Monetization precedents & pricing comps + +**Checkpoint date:** 2026-07-30. Compiled from web research into atproto-ecosystem business models and indie personal-site pricing. + +## Inside the Atmosphere: who actually takes money (almost nobody) + +- **Bluesky:** ~43.5M registered / ~4.5M DAU (spring 2026, plateaued); **$100M Series B** (2025, disclosed Mar 2026, Bain Capital Crypto) on top of $15M Series A. **Bluesky+** teased Dec 2024 at $8/mo–$72/yr (badges, icons, longer video) — **still unshipped mid-2026**; Jan 2026 roadmap doesn't mention monetization but does promise "third-party app integrations on profiles" (a tailwind for us). Only shipped consumer revenue: the **Namecheap custom-domain reseller** integration (since Jul 2023) — domain-as-identity is normalized and paid for in this ecosystem. ([Series B](https://bsky.social/about/blog/03-19-2026-series-b) · [roadmap](https://bsky.social/about/blog/01-26-2026-whats-next-at-bluesky) · [Namecheap](https://bsky.social/about/blog/7-05-2023-namecheap)) +- **Graze** (feed builder): $1M pre-seed (Apr 2025, Betaworks/Salesforce Ventures/Skyseed); monetizes via feed ad marketplace taking a **30% cut** + sponsored posts. The only demonstrated recurring revenue in the ecosystem. ([TechCrunch](https://techcrunch.com/2025/04/16/bluesky-feed-builder-graze-raises-1m-rolls-out-ads/)) +- **Tangled** (git on atproto): $4.5M raise (Mar 2026, Accel/Founders Fund et al.), no paid product yet. **Skylight/Spark** (video): angel-funded (Mark Cuban), pre-revenue. **Streamplace**: Livepeer crypto-treasury-funded, deliberately not a revenue business. +- **PDS hosting: nobody charges.** Blacksky (donations/mutual aid), Northsky (grant + co-op, ~280 users), community hosting list mostly volunteer. +- **Leaflet**: custom domains + publications, **no paid site tier found** as of mid-2026 (newsletters via Leaflet Pro emerging). **Linkat**: free. +- **Skyseed**: ~$1.5M ecosystem fund (Dec 2024, Peter Wang); ~$100K equity checks, **$5–25K grants** — viable runway for us without changing the indie model. ([skyseed.fund](https://skyseed.fund)) +- **Pattern:** money in the ecosystem = venture/grants/crypto/donations. Consumer subscriptions are unproven — and untried by anyone credible. Open lane + culture risk ("should be free"). + +## Outside: what individuals pay for a personal web presence + +| Product | Price | Note | +|---|---|---| +| **Micro.blog** | $1/mo (subdomain) · $5/mo · $10/mo premium | Closest analog; bootstrapped, sustained 8+ years ([pricing](https://micro.one/about/pricing)) | +| **omg.lol** | $20/yr → **$50/yr from Aug 1, 2026** | Community accepted a 2.5× raise — indie-web tolerance is higher than assumed ([announcement](https://omglol.news/2026/06/25/omg-lol-membership-price-increases-august-1)) | +| **Bear Blog** | Free · $5/mo / $49/yr / $189 lifetime | | +| **Pika** | $6/mo | | +| **Carrd** | Free · $9–49/yr ($19/yr popular tier) | | +| **Linktree** | Free · $8/$15/$35/mo | Raised prices Nov 2025 | +| **Ghost(Pro)** | $15+/mo | For people making money from the site | +| **Squarespace** | $16+/mo | The comparison to avoid | +| **Read.cv** | Free, no model | **Shut down** — acqui-hired by Perplexity Jan 2025. The cautionary tale *and* our open wedge ([TechCrunch](https://techcrunch.com/2025/01/17/perplexity-acquires-read-cv-a-social-media-platform-for-professionals/)) | + +Cluster: **$5–10/mo hosted blogging; $19–50/yr identity pages. Above $10/mo = Ghost/Squarespace territory.** + +## Market signal + +- Website-builder market ~$3.6–6.4B (2026), ~10–16% CAGR; personal sites ≈ 22% of it. Indie-web tailwind real but niche (omg.lol raise, Bear/Pika growth, POSSE discourse). +- Atproto addressable enthusiast base (custom-domain handles, Leaflet blogs, multi-app users): realistically **tens-to-hundreds of thousands**, not millions. DAU plateau (~4.5M) caps the venture case; fine for indie. + +## So what — model chosen (PD-3/PD-4) + +1. Free subdomain tier → **paid custom-domain tier ~$6–8/mo (~$60–80/yr)** — the category's most proven conversion trigger, extra-apt where domain = handle. +2. Domain reseller margin (Namecheap/Porkbun, ~$2–10/domain/yr) secondary. +3. Skyseed grant as optional runway. +4. Anti-models: free-forever-no-plan (Read.cv), donations-only (Blacksky is a mission, not a business), ads (wrong shape). +5. Risks: Bluesky+ eventually bundling domain/profile-site features; free-culture expectations → win on polish and convenience (the Micro.blog-next-to-free-WordPress play). diff --git a/docs/research/README.md b/docs/research/README.md new file mode 100644 index 0000000..4695e92 --- /dev/null +++ b/docs/research/README.md @@ -0,0 +1,12 @@ +# Research checkpoints + +Findings from research sessions, preserved so future sessions (human or agent) don't start from zero. **Check the dates** — the Atmosphere moves fast; spot-verify anything older than a few months before leaning on it. + +| File | Date | Topic | +|---|---|---| +| [`2026-07-30-ecosystem-landscape.md`](2026-07-30-ecosystem-landscape.md) | 2026-07-30 | Who else is building in/near this space; competitive assessment | +| [`2026-07-30-lexicon-feasibility.md`](2026-07-30-lexicon-feasibility.md) | 2026-07-30 | Technical feasibility per data source; OAuth/scopes state | +| [`2026-07-30-monetization.md`](2026-07-30-monetization.md) | 2026-07-30 | Monetization precedents in and out of the ecosystem; pricing comps | +| [`2026-07-30-naming.md`](2026-07-30-naming.md) | 2026-07-30 | Product name candidates and collision/domain checks | + +Conventions: one dated file per topic, source URLs inline, and an honest "so what" at the end of each. When you re-research a topic, add a new dated file rather than editing the old one, and update this index.