Architecture Overview
ClickClack ships as one Go binary serving a Svelte SPA, JSON API, websocket endpoint, embedded SQLite migrations, local SQLite data, and local upload files.
Durable state lives in SQLite. WebSockets are an update pipe only; clients recover missed durable events through GET /api/realtime/events?after_cursor=... or by reconnecting the websocket with a cursor.
#Layers
apps/api/cmd/clickclack— CLI and single-binary entrypoint. Seeapps/api/internal/httpapi— chi router, auth resolution, REST/WSapps/api/internal/store— backend-facing store contract and domain typesapps/api/internal/store/sqlite— SQLite implementation, embedded SQLapps/api/internal/realtime— in-process workspace event hub (Hub).apps/api/internal/config— flag/env/file resolution.apps/api/internal/webassets—go:embedfor the built SPA.apps/web— Svelte 5 SPA, API-only client behavior.packages/protocol— OpenAPI contract, source of truth.packages/sdk-ts— generated OpenAPI types plus framework-neutral
handlers, SPA serving.
(Store interface in types.go).
migrations, search/FTS, backup, JSON export.
TypeScript wrapper.
#Storage rules
- SQLite uses
modernc.org/sqliteand WAL. - Foreign keys on;
busy_timeout=5000. - Single writer discipline:
db.SetMaxOpenConns(1). - Transactions stay short. Outbox
eventsrows are inserted in the same - IDs are sortable ULID text with semantic prefixes (see
- Postgres lives behind the same store layer as SQLite. API handlers call store
commit as the durable write that produced them, so subscribers can't see a message that isn't in the DB.
methods and do not embed dialect-specific SQL.
#Cross-cutting docs
- ../api/overview.md — REST + WS surface.
- ../data-model.md — tables, IDs, invariants.
- ../features/realtime.md — durable vs ephemeral
- ../features/auth.md — auth resolution and
events, cursor recovery.
precedence.