Journal — a Bespoke app
One stream for every journaling moment — scratch thoughts, work-log lines,
evening reflections — opened whenever, written in seconds.
(Spec produced by the design-app interview, 2026-08-01.)
This is an unofficial, third-party app for Bespoke. Nobody vetted it but its
author. Read What it does with your stuff
before installing it, the same as any code you run as yourself.
Install
From your Bespoke instance directory, with the platform at v0.13.0 or newer:
go tool bespoke add journal
That pins this module, writes apps/journal/app.toml with a free port, and
recompiles your instance stylesheet. Pin a version you have actually read with
bespoke add journal@v0.1.0.
The directory must be named journal: the slug names this app's database,
process, and subdomain, and the source picks it.
What it does with your stuff
- Stores in its own SQLite database: your entries, their embedding vectors,
and generated summaries.
- Talks to nothing outside your host. Embeddings and summaries go through the
platform's LLM gateway on
127.0.0.1, so where they ultimately go depends on
how you configured that gateway — not on this app.
- Never makes an outbound connection of its own.
- Publishes domain events (
journal.entry_created, journal.entry_deleted,
journal.summary_generated) to platformd's local event service, carrying at
most a ~140-char entry preview — never the full text. Only the weekly
summary raises a notification; entry events are silent and exist for your
own automation rules.
Like every Bespoke app it runs as your user, so nothing above is enforced by
the platform — it is a description you can check against the source
(ADR-0031).
Spec
The behavior this app is built to, unchanged from its original private build.
Records
entries — id, login, body (freeform text), created_at (UTC). Append-only.
summaries — id, login, range_start/range_end, body, created_at. One per
generation; latest wins.
Views
GET / — tabbed capture card on top (Random free-form box · Work
Log: project / task / time, all optional · Evening Reflection:
general / work / family); entries below, newest first, grouped by day
(Today / Yesterday / date). Link to /week.
POST /entries — save a free-form entry, back to /.
POST /entries/work, POST /entries/reflection — structured captures,
formatted to markdown at save (### Work log / ### Evening reflection
with only the provided fields); an all-empty form saves nothing. The
stream stays one undifferentiated list — structure lives in the entry
text, not the schema (non-goal "no kinds" holds).
POST /entries/{id}/delete — accident hatch only (append-only journal).
GET /week — latest saved weekly summary + a "Summarize last 7 days"
button.
POST /week/summarize — generates via llm.Complete (~seconds, page
action), saves, back to /week.
POST /entries/voice — mic button (ui.VoiceButton) records in the
browser; audio is transcribed via audio.Transcribe and saved as a normal
entry.
GET /_card — dashboard card (ADR-0017): today's entry count, latest
entry time and a 120-char snippet. Cheap queries only.
GET/POST /_intents/add-entry — the declared [[intents]] (ADR-0018):
any app can feed selected text into the journal; todo's "Journal it →"
done-banner is the reference caller.
GET /_live (ADR-0022) — every mutation (forms, voice, intent, tools)
calls web.Changed(login); the stream region patches on open pages.
- Chat (
web.EnableChat, ADR-0015/0020/0021) — context is the last 30 days
of entries plus the latest weekly summary; mic input and speak toggle
come with the panel.
- Tools (
web.Tool, ADR-0021) — add_entry, list_entries,
delete_entry; exposed to in-app chat, dashboard chat, and MCP.
Deliberately no update tool — the append-only non-goal binds every
face of the app, not just the forms.
Services
pkg/llm for the weekly summary — on demand, cached in summaries, never
automatic — and behind the chat panel (agentic over the tools above).
pkg/audio for voice capture (first consumer, ADR-0014): WAV recorder →
Whisper-Large-v3-Turbo on Lemonade, validated live end to end
2026-08-01. Requires BESPOKE_LEMONADE_URL on platformd (set in dev
too, e.g. BESPOKE_LEMONADE_URL=http://<app-host>:13305/api/v1 just dev); unset gives clearly-marked stub entries.
Non-goals (confirmed)
- No tags/kinds — one undifferentiated stream.
- No editing — append-only; delete exists for misfires.
- No search, no export, no sharing in v1.
Later
- Writing prompt on empty days (LLM, cached per day).
- Auto-mood tag via
llm.Classify → mood glance view.
- Search, kind filters if the stream ever feels unmanageable.
Developing
views/*_templ.go is committed on purpose — instances build this module
straight out of the read-only Go module cache and never run templ generate
over it. After editing a .templ, run just ui and commit the output.
License
MIT — see LICENSE.