Feature Spec: /live — Personal Microblog Feed
Spec for a /live route — a personal microblog feed powered by a write API and SQLite, postable from anywhere including a phone.
The Idea
A /live route that works like a CNN live news feed crossed with a personal Twitter wall — except it's on my own site, I own everything, and there's no algorithm or platform risk.
The feed is a reverse-chronological stream of short blurbs. Each entry is timestamped. Low friction to post, permanently mine, searchable once search is built.
The key requirement: I should be able to post from my phone, not just from my IDE. That's what separates this from the file-based content system and makes it actually useful as a microblog.
Why Not Just Use Twitter
- I don't like Twitter
- Platform risk — content can disappear, accounts get banned, APIs get paywalled
- Character limits are arbitrary
- No integration with my own search, metadata, or content system
- The content isn't in git, isn't mine
The cross-posting angle (post here → also post to Bluesky/Twitter) is interesting later. But the source of truth should be my own site.
Why This Needs SQLite (Not Flat Files)
The file-based content system works great for content I write in my IDE. But blurbs need zero friction — I'm on my phone, I have a thought, it should be live in 10 seconds.
Flat files require:
- Open IDE
- Create file in right location
- Write frontmatter
- Commit
- Push
- Wait for deploy
That's 5 steps too many. A writable database enables a proper write API. Post from anywhere.
This feature is a forcing function for the SQLite migration — it's the first thing that genuinely can't be done well with flat files.
Data Model
Simple. No title required — these are blurbs.
CREATE TABLE live_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT NOT NULL,
tags TEXT, -- JSON array stored as string
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT
);Write API
// src/app/api/live/route.ts
export async function POST(request: Request) {
const auth = request.headers.get('Authorization');
if (auth !== `Bearer ${process.env.LIVE_API_SECRET}`) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const { body, tags } = await request.json();
if (!body?.trim()) {
return Response.json({ error: 'Body required' }, { status: 400 });
}
const entry = db.prepare(
`INSERT INTO live_entries (body, tags) VALUES (?, ?) RETURNING *`
).get(body.trim(), tags ? JSON.stringify(tags) : null);
return Response.json(entry, { status: 201 });
}LIVE_API_SECRET is a long random string stored in Vercel environment variables. Never in the repo.
Auth Strategy
Simple bearer token for personal use. No OAuth, no sessions, no complexity.
Generate a secret:
openssl rand -base64 32Add to Vercel env vars as LIVE_API_SECRET. Use in every request:
Authorization: Bearer Posting From Anywhere
Mobile web form at /admin/live (best option for phone):
- Password-gated page, works great in mobile Safari
- Textarea + submit, that's it
- Add it to your home screen as a PWA bookmark for one-tap access
- No app, no Shortcuts, no bugs — just a webpage
Twilio SMS webhook:
- Buy a number, point the SMS webhook at
/api/live/sms - Any text you send to that number gets posted as a blurb
- More setup but most frictionless once configured
Admin form at /admin/live (same as above, also accessible from desktop):
- Works from any browser on any device
Copilot/Cline skill (for IDE posting):
- Trigger words:
blurb,tweet,live - Skill calls the write API directly
- Good for when you're already in the IDE and want to post something technical
/live Page
// src/app/live/page.tsx
export default async function LivePage() {
const entries = db.prepare(
`SELECT * FROM live_entries ORDER BY created_at DESC`
).all();
return (
<Container size="sm">
<Heading level={1}>Live</Heading>
{entries.map(entry => (
<LiveEntry key={entry.id} entry={entry} />
))}
</Container>
);
}Each entry renders the body (markdown-enabled), timestamp, and optional tags. Simple card or just paragraphs with a timestamp — no need to over-design it.
Agent Skill (Future)
Once the API is live, a Copilot/Cline skill can call it directly.
Trigger words: blurb, tweet, live post
The skill:
- Captures the message text from the conversation
- Optionally extracts tags from context
- POSTs to
/api/livewith auth header - Confirms it posted
This way posting a blurb while working is: "blurb: just figured out that SQLite FTS5 snippet() returns highlighted fragments out of the box" → done.
Implementation Order
- SQLite migration (prerequisite — need a writable DB)
live_entriestablePOST /api/liveroute with bearer auth/livepage rendering entries- iOS Shortcut for phone posting
- Copilot skill for IDE posting
- (Optional) Twilio SMS webhook
- (Optional) Cross-post to Bluesky
Related Posts
- Feature Spec: Full-Text Hybrid Search with SQLite FTS5Replacing Fuse.js title-only search with server-side full-text search using SQLite FTS5 — paragraph-level results with highlighted snippet previews linking directly to headings.
- Why I'm Finally Moving to SQLite (And Why I Waited Until Now)After over 60 articles in one month, my file-based content system is breaking down. Here's why I'm migrating to SQLite, why now, and why I didn't do this sooner.