Guide Letters
Day one.
How one blank page became a live local-first writing app in its first 24 hours, told through the commits that made it real.
Publié
Twenty-four hours earlier, there had been no Letters. There had only been one article I was avoiding and the suspiciously convenient belief that writing it would be easier if I first built a nicer place to type.
At 11:24 PM on Sunday, August 30, 2026, the first commit landed. The app was called Inkwell. It had one editor, one small library, and automatic browser saves. It was rough, local, and already good enough for the article I still was not writing.
The smallest possible beginning
The first version had made three quiet promises:
- I could open a page without making an account.
- Every change stayed in my browser and saved itself.
- I could come back later and find the draft waiting.
Good enough lasted about five minutes
Then I noticed the route was ugly. Then the toolbar felt busy. Then plain text wanted headings, lists, quotes, tables, and code. The tiny writing page had begun asking to become a real writing app, one completely reasonable improvement at a time.
I kept finishing the app so I could begin the article.
A flawless procrastination system
The first night got out of hand
Before the clock reached one in the morning, the prototype had changed names, tried a visual direction, reverted it, split into a home and an editor, gained readable article addresses, learned Markdown, and picked up the full Editor.js tool set. Then I added a portable backup, because apparently my one-hour experiment now needed disaster recovery.
- Make the page pleasant enough to write in.
- Make every draft save locally without a Save button.
- Make the whole library portable before trusting it with real words.
- Definitely return to the original article.
The fourth step remained impressively untouched.
The milestone that made it real
The best moment arrived the next evening. At 6:48 PM, the library gained a writing timeline and the app went live on Firebase. That mattered more than any individual feature. The thing that had existed only on my laptop now had a URL. I could leave, open it somewhere else, and see whether the idea still made sense outside the space where I had built it.
By the end of the first 24 hours, the repository held 58 commits. Some were real features. Many were tiny negotiations over spacing, colour, and which edge a headline should align to. Together they had turned a bare editor into a full local-first writing page.
| Time | What changed | Why it mattered |
|---|---|---|
| 11:24 PM | The first Inkwell commit | A page could write and save locally |
| 12:25 AM | The full editor tool set | The page could hold more than paragraphs |
| 12:48 AM | Portable backups | The library could leave the browser safely |
| 6:48 PM | Timeline and live deployment | A prototype became a place I could visit |
| 10:18 PM | The theme gallery | The space finally had moods of its own |
What one page had grown into
- A local library with readable article links and automatic saves
- H1–H3 headings, prose, lists, checklists, quotes, tables, code, images, and embeds
- Markdown in and out, plus a backup for the entire library
- A timeline, seven themes, seven typefaces, backdrops, and a writing runway
- The article that started all of this
Make the space change colour
Here was a small reward for reaching the end. Right-click anywhere in Letters, choose Inspect, open the Console tab, paste the code below, and press Enter. The space will drift through a set of animated colour themes until you refresh the page or move to another route.
If the browser showed a paste-safety warning, follow its instruction first. The effect only changed temporary page styles; it did not touch the saved theme or any writing.
(() => { window.lettersThemeShow?.stop() const root = document.documentElement const keys = ['--bg', '--panel', '--panel-raised', '--ink', '--muted', '--line', '--accent', '--accent-ink', '--accent-wash', '--selection'] const original = Object.fromEntries(keys.map(key => [key, root.style.getPropertyValue(key)])) const palettes = [ ['#111827', '#1f2937', '#374151', '#f9fafb', '#9ca3af', '#4b5563', '#fb7185', '#111827', '#4c1d2f', '#fb7185'], ['#172554', '#1e3a8a', '#1d4ed8', '#eff6ff', '#bfdbfe', '#3b82f6', '#fbbf24', '#172554', '#713f12', '#fbbf24'], ['#052e2b', '#134e4a', '#115e59', '#f0fdfa', '#99f6e4', '#2dd4bf', '#f59e0b', '#052e2b', '#78350f', '#f59e0b'], ['#3b0764', '#581c87', '#6b21a8', '#faf5ff', '#e9d5ff', '#a855f7', '#f0abfc', '#3b0764', '#701a75', '#f0abfc'], ] const style = document.head.appendChild(document.createElement('style')) style.textContent = 'body, body *, body *::before, body *::after { transition: background-color 1.8s ease, border-color 1.8s ease, color 1.8s ease, fill 1.8s ease, stroke 1.8s ease, box-shadow 1.8s ease !important; }' let step = 0 const paint = () => palettes[step++ % palettes.length].forEach((color, index) => root.style.setProperty(keys[index], color)) const startRoute = location.href const themeTimer = setInterval(paint, 2200) const routeTimer = setInterval(() => location.href !== startRoute && stop(), 150) function stop() { clearInterval(themeTimer) clearInterval(routeTimer) style.remove() keys.forEach(key => root.style.setProperty(key, original[key])) delete window.lettersThemeShow } window.lettersThemeShow = { stop } paint()})()The lesson after 24 hours
The app had started as a way to avoid a blank page. One day later, it had become a place I wanted to return to. That was the milestone: not that it was finished, but that it had crossed the line from a disposable prototype into something worth continuing.
The original article was still waiting. At least now it had somewhere beautiful to wait.
Letters