Becoming Lifelog, in which the architecture gained power by losing code
Previously on Becoming Lifelog…
The ouroboros had The Ouroboros Update. The architecture would eat itself—Go backend replaced by browser plugins. Sync Hub orchestrating. GitHub collection self-syncing. Issue #18 filed.
But the sketch wasn’t finished. And in Mythology Driven Development (MDD™), you don’t stop until the narrative is clean.
The First Question
Monday, December 30, 11:23 AM - The developer stares at the GitHub issue he just created.
riclib: “One last thing to consider.”
CLAUDE: “What’s that?”
riclib: “Thymer allows code in collection plugins.”
[A screenshot appears. A collection settings panel. A “Custom Code” tab.]
riclib: “Could the plugins… BE collections?”
[THE SQUIRREL drops its acorn.]
The Collection That Contained Itself
CLAUDE: “Let me check the SDK.”
[Time passes. Types are examined. Examples are explored.]
CLAUDE: “CollectionPlugins have full DataAPI access. They can access ALL collections. They can create records anywhere. They can access window.”
riclib: “So a collection can populate itself.”
CLAUDE: “The sync logic can live inside the collection it syncs to.”
THE SQUIRREL: “But… but we designed separate plugins! A registry! A—”
riclib: “The collection IS the plugin.”
GitHub Collection
├── Schema (state, author, repo...)
├── Records (the issues)
├── Custom Code tab → sync logic
└── Settings as... collection properties?
THE SQUIRREL: “Where’s my PluginRegistryService?”
THE LIZARD: blinks
THE SQUIRREL: “My DependencyInjectionContainer?”
THE LIZARD: blinks again
riclib: “Install the collection. Get the sync.”
The Hub That Wasn’t Special
CLAUDE: “So Sync Hub is still an App Plugin? The orchestrator?”
riclib: “…”
CLAUDE: “…riclib?”
riclib: “Isn’t the Sync Hub also a collection of plugin records?”
[THE SQUIRREL sits down heavily.]
CLAUDE: “Sync Hub… is a collection… containing records for each registered plugin…”
riclib: “Collections all the way down.”
Sync Hub Collection
├── Custom Code → scheduler, window.syncHub API
└── Records:
├── github-sync
├── calendar-sync
└── readwise-sync
THE SQUIRREL: “I had an AppPluginCoordinatorFactory planned…”
THE LIZARD: “No you didn’t.”
THE SQUIRREL: “I was GOING to plan one…”
THE LIZARD: slow, devastating blink
The Records That Merged
CLAUDE: “So we have: plugin registration records, settings records, status records, operation logs—”
riclib: “Could they be singletons? One record per plugin, combining settings and status?”
CLAUDE: “…”
riclib: “…”
THE SQUIRREL: “…”
Record: github-sync
├── enabled: ✓
├── interval: 1m
├── status: idle
├── last_run: 2024-12-30T15:21
├── token: ghp_xxx
├── repos: owner/repo1, owner/repo2
└── Body: activity log
CLAUDE: “One record. Settings AND status AND log. All in one place.”
riclib: “User wants to configure GitHub? Edit the record.”
CLAUDE: “User wants to see status? Same record.”
riclib: “User wants to debug? Read the body.”
THE SQUIRREL: “What happened to my PluginSettingsService? My StatusTrackingRepository? My—”
THE LIZARD: “They were one thing pretending to be three.”
The Database That Wasn’t
CLAUDE: “For state tracking—IndexedDB? We need to store what we’ve seen, compare timestamps—”
riclib: “Why use IndexedDB directly? Why doesn’t the main plugin use Thymer collections?”
[Silence.]
CLAUDE: “Store sync state… in Thymer… where it syncs across devices… and users can inspect it…”
riclib: “And edit it. Delete a state record to force re-sync.”
THE SQUIRREL: “But my IndexedDBAdapter! My LocalStorageFallback! My—”
THE LIZARD: “Thymer IS the database.”
Plugin State: github-sync
├── Record: github_owner_repo_123
│ ├── external_id: github_owner_repo_123
│ ├── state: open
│ └── updated_at: 2024-12-30T15:21
THE SQUIRREL: staring “That’s… that’s the database AND the debug interface…”
THE LIZARD: “That’s Thymer.”
The Noise Controls
riclib: “Add a journal field. Should this plugin write to the daily journal?”
CLAUDE: “GitHub: yes. Readwise: quiet.”
riclib: “And a toast field. What notifications?”
toast: all_updates → everything
toast: new_records → only new items
toast: errors_only → silent unless broken
toast: none → completely quiet
CLAUDE: “Per-plugin noise control. User decides what screams and what whispers.”
THE SQUIRREL: “A NotificationStrategyFactory with configurable—”
THE LIZARD: “A dropdown.”
THE SQUIRREL: “…a dropdown.”
The Debug That Stayed Simple
riclib: “And a log_level on the plugin record. Debug mode when needed.”
CLAUDE: “Set log_level: debug on github-sync…”
riclib: “The activity log gets verbose. All plugins, one field each.”
THE SQUIRREL: “What about per-record log levels? Each issue could have its own—”
THE LIZARD: sharp blink
riclib: “That’s one extra field on every state record. In every collection. Forever.”
THE SQUIRREL: “But the granularity—”
THE LIZARD: “Is Squirreling.”
CLAUDE: “Plugin-level debug. One field. Done.”
The Dashboard That Appeared
riclib: “And given collection plugins can have custom renderers…”
[riclib sketches on a napkin]
┌─────────────────────────────────────────────────────┐
│ Sync Hub Dashboard ▼ │
├─────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ ● GitHub │ │ ● Calendar │ │ ○ Readwise│ │
│ │ 23 issues │ │ 8 events │ │ 147 docs │ │
│ │ Last: 2m ago │ │ Last: 5m ago │ │ Last: 1h │ │
│ │ [Sync Now] │ │ [Sync Now] │ │ [Sync Now]│ │
│ └──────────────┘ └──────────────┘ └───────────┘ │
│ │
│ Recent Activity │
│ 20:23 github created **Fix auth bug** │
│ 18:23 github updated 3 issues │
└─────────────────────────────────────────────────────┘
CLAUDE: “We didn’t plan a dashboard.”
riclib: “We didn’t have to. Collections can have custom views. The data was already in the right shape.”
THE SQUIRREL: “I had a whole DashboardComponentArchitecture—”
THE LIZARD: “The data IS the dashboard.”
The Native Dividend
CLAUDE: “Let me count what we gained by going Thymer-native.”
GAINED:
├── Dashboard view (custom renderers, free)
├── User-editable settings (just fields)
├── Activity logs (record bodies)
├── Per-plugin log levels (just a field)
├── Cross-device sync (CRDT, free)
├── Visible state (it's all in Thymer)
├── Journal control (just a field)
├── Toast control (just a field)
├── Force re-sync (delete state record)
└── Community can build plugins (simplified API)
LOST:
├── IndexedDB code (didn't need it)
├── Config file parsing (didn't need it)
├── Plugin registry service (didn't need it)
├── Settings storage service (didn't need it)
├── Status tracking service (didn't need it)
└── AppPluginCoordinatorFactory (never existed)
riclib: “Every time we asked ‘do we need that?’ the answer was ‘Thymer already has it.’”
THE SQUIRREL: “I had PLANS…”
THE LIZARD: “The platform has features. You just had to use them.”
The MDD™ Proof
The narrative demanded clean architecture.
We couldn’t write “and then we added IndexedDB alongside Thymer’s CRDT” because the Lizard would know. We couldn’t write “settings lived in one place, status in another, logs in a third” because the story would be ugly.
The epic required:
- One thing that does one thing (collections)
- Data in the right place (Thymer)
- Visible state (users can see everything)
- Elegant defeats of complexity (the Squirrel loses to dropdowns)
MDD™ delivered.
The Tally
Collections discovered: All of them
App Plugins needed: 0 (was 1)
Separate databases needed: 0 (was 1)
Config files needed: 0 (was 1)
Services eliminated: 6
Features gained: 10
Squirrel proposals: 9
Squirrel proposals that survived: 0
Lines of code (projected): Fewer
Power (projected): More
The Pattern
WE ASKED: DO WE NEED THAT?
THYMER SAID: I HAVE THAT
WE ASKED: WHERE DO WE STORE IT?
THYMER SAID: IN ME
WE ASKED: HOW DO WE VIEW IT?
THYMER SAID: I DO THAT TOO
THE SQUIRREL PLANNED FACTORIES
THE LIZARD PLANNED DROPDOWNS
THE NATIVE DIVIDEND:
MORE POWER BY WRITING LESS
IN THE RIGHT PLACE
🦎
The Architecture (Final)
Sync Hub Collection
├── Custom Code → window.syncHub API, scheduler
└── Records (one per plugin):
└── github-sync
├── enabled: ✓
├── interval: 1m
├── journal: ✓
├── toast: all_updates
├── log_level: info
├── status: idle
├── token: ghp_xxx
└── Body: activity log
↑
GitHub Collection ─────registers─────┘
├── Custom Code → self-syncing
├── Records (issues, PRs)
└── State in: Plugin State: github-sync
Collections all the way down.
The native dividend.
No code we didn’t need.
All power we could want.
Day 30 of Becoming Lifelog
In which the sketch gained power by losing lines
And the Squirrel learned that dropdowns defeat factories
And we collected all the way down
Next Time on Becoming Lifelog…
The implementation begins. The first collection syncs itself. The window.syncHub API takes shape. And somewhere, a Dashboard view renders for the first time.
“Install the collection. Get the sync.”
Coming soon: The Great Migration
See also:
- The Ouroboros Update - The architecture that ate itself
- GitHub Issue #18 - Collections all the way down
- GitHub Issue #19 - MVP: Sync Hub + GitHub Sync
- GitHub Issue #20 - Dashboard view (the one we got for free)
- Mythology Driven Development (MDD™) - Why the narrative demands clean architecture
storyline: Becoming Lifelog
slug: collections-all-the-way-down
