c27aebd25cdd4b60d6a503bc6e2836b8cc59c6ef
ailog
ATProto-based blog platform built on at-browser.
Concept
Data lives in ATProto, not on this server.
This is not a traditional blog generator. It's a viewer (client) for ATProto records.
Traditional blog:
Server DB ← article data ← user
ATProto blog:
User's PDS ← article data (ai.syui.log.post)
↓
at-browser (this site) → displays records
Architecture
┌─────────────────────────────────────────┐
│ at-browser │
│ (ATProto record viewer/editor) │
├─────────────────────────────────────────┤
│ │
│ / → admin (config.json) │
│ /@alice → user page │
│ /@bob.bsky → user page │
│ │
└─────────────────────────────────────────┘
Roles
| Role | Path | Data Source |
|---|---|---|
| admin | / (root) |
local + remote |
| user | /@handle |
remote only |
Admin (Site Owner)
- Defined in
config.json - Has root (
/) access - Can reference local files (static assets, custom styles)
- Can reference remote (ATProto records)
User (Any ATProto User)
- Accessed via
/@handlepath - Remote only (ATProto records from their PDS)
- No registration required
- Anyone with an ATProto account can be displayed
Features
1. at-browser (Core)
- Search by handle/DID
- Browse PDS collections
- Navigate ATProto records
2. ai.syui.log.post View
- Markdown rendering
- Syntax highlighting
- Blog-style display
3. OAuth
- Login with ATProto
- Post to ai.syui.log.post collection
Use Cases
Personal Blog
// config.json
{
"did": "did:plc:xxxxx",
"handle": "syui.syui.ai"
}
- Deploy to
syui.ai - Root shows your profile + posts
- You are the admin (local + remote)
- Others can view via
/@handle
Blog Service
// config.json
{
"admin": "service.example.com",
"handle": null
}
- Deploy to
blog.example.com - Root shows landing/search
- All users via
/@handle(remote only) - Platform for any ATProto user
Data Flow
┌──────────────┐ ┌──────────────┐
│ User's PDS │────→│ at-browser │
│ (ATProto) │←────│ (this site) │
└──────────────┘ └──────────────┘
↑ │
│ ↓
ai.syui.log.post ┌──────────┐
collection │ Display │
│ - Profile│
│ - Posts │
└──────────┘
Local = Remote (Same Format)
Critical design principle: local files use the exact same format as ATProto API responses.
This allows the same code to handle both data sources.
Remote (ATProto API)
curl "https://syu.is/xrpc/com.atproto.repo.listRecords?repo=did:plc:xxx&collection=ai.syui.log.post"
{
"records": [
{
"uri": "at://did:plc:xxx/ai.syui.log.post/3xxx",
"cid": "bafyrei...",
"value": {
"title": "Hello World",
"content": "# Hello\n\nThis is my post.",
"createdAt": "2025-01-01T00:00:00Z"
}
}
]
}
Local (Static File)
content/
└── did:plc:xxx/
├── describe.json # describeRepo (special)
├── app.bsky.actor.profile/
│ └── self.json # {collection}/{rkey}.json
└── ai.syui.log.post/
└── 3xxx.json # {collection}/{rkey}.json
// content/did:plc:xxx/ai.syui.log.post/3xxx.json
{
"uri": "at://did:plc:xxx/ai.syui.log.post/3xxx",
"cid": "bafyrei...",
"value": {
"title": "Hello World",
"content": "# Hello\n\nThis is my post.",
"createdAt": "2025-01-01T00:00:00Z"
}
}
ATProto API Reference
| API | Path | Description |
|---|---|---|
| getRecord | /xrpc/com.atproto.repo.getRecord |
Get single record |
| listRecords | /xrpc/com.atproto.repo.listRecords |
List records in collection |
| describeRepo | /xrpc/com.atproto.repo.describeRepo |
Get repo info + collections list |
See: com.atproto.repo.describeRepo
Resolution Strategy
at-browser
│
├── admin (config.json user)
│ ├── 1. Check local: /content/{did}/{collection}/{rkey}.json
│ └── 2. Fallback to remote: PDS API
│
└── user (/@handle)
└── remote only: PDS API
Why Same Format?
- One codebase: No branching logic for local vs remote
- Easy testing: Copy API response to local file
- Offline support: Admin can work with local files
- Migration: Local → Remote (just POST to PDS)
Config
config.json
{
"did": "did:plc:xxxxx",
"handle": "syui.syui.ai",
"pds": "syu.is",
"collection": "ai.syui.log.post"
}
Tech Stack
- Frontend: Vite + TypeScript
- ATProto: @atproto/api
- OAuth: @atproto/oauth-client-browser
- Markdown: marked + highlight.js
Collection Schema
ai.syui.log.post
{
"title": "Post Title",
"content": "Markdown content...",
"createdAt": "2025-01-01T00:00:00Z"
}
License
MIT
Description
Languages
Rust
43.3%
TypeScript
41.4%
CSS
14.4%
HTML
0.9%