| read_when |
|
|---|
The repo is a Go module plus a pnpm workspace. The Go binary embeds the built SPA, so a full local build runs both toolchains.
- Go (matching
go.mod). - pnpm 11 (auto-managed via
corepack). - TypeScript runs via stable TypeScript 7 native
tscfrom@typescript/native— installed through pnpm. - Lint/format use
oxlintandoxfmt— installed through pnpm.
pnpm install
pnpm build # builds SPA + SDK and copies dist into apps/api
go run ./apps/api/cmd/clickclack serve
open http://localhost:8080The dev fallback creates Local Captain as the first user, a ClickClack
workspace, and a general channel, so the SPA loads into a working state on
first hit.
# terminal 1
pnpm dev:api # go run ... serve --dev-bootstrap=true
# terminal 2
pnpm dev:web # vite dev server with API proxyThe Vite dev server proxies /api and /api/realtime/ws to localhost:8080.
| Command | What it does |
|---|---|
pnpm build |
Builds the Svelte app and the SDK, then embeds apps/web/dist into apps/api/internal/webassets/dist. |
pnpm build:web |
Builds and normalizes the Svelte app without touching embedded Go assets. |
pnpm build:sdk |
Builds the TypeScript SDK. |
pnpm build:desktop |
Bundles the Electron main process, preloads, and settings renderer. |
pnpm check |
Full local gate: pnpm test, root/workspace tsc, oxlint, and format checks. |
pnpm coverage |
Go tests with coverage; fails under 85% line coverage. |
pnpm dev:api |
go run ./apps/api/cmd/clickclack serve --dev-bootstrap=true. |
pnpm dev:web |
vite dev for the SPA. |
pnpm dev:desktop |
Builds and starts the Electron client against its configured server. |
pnpm fmt |
gofmt + oxfmt over Go and TS/Svelte. |
pnpm fmt:check |
CI-compatible formatting check with gofmt -l and oxfmt --check. |
pnpm lint |
oxlint over web, SDK, examples, and tests. |
goreleaser release --snapshot --clean |
Local release smoke test for all configured OS/arch targets. |
pnpm typecheck |
tsc --noEmit -p tsconfig.json for root Playwright config/tests. |
pnpm test |
Builds the web app and SDK, then runs Go tests against those fresh web assets in a temp copy without rewriting tracked embedded assets. |
pnpm test:e2e |
Playwright suite in tests/e2e. |
pnpm test:desktop |
Tests desktop URL, deep-link, notification, settings, and badge contracts. |
pnpm build uses CLICKCLACK_WEB_VERSION=dev by default. That keeps repeated
local builds deterministic while still allowing real source changes to update
content-hashed assets. Release and Docker builds should set
CLICKCLACK_WEB_VERSION to the commit or tag being shipped.
apps/
api/ # Go backend, single-binary entrypoint
cmd/clickclack/ # CLI main
internal/
auth/ # placeholder
config/ # flag/env/file resolution
httpapi/ # chi router, handlers, auth resolution
realtime/ # in-process pub/sub hub
store/ # store interface + types
sqlite/ # SQLite implementation, migrations, backup, export
postgres/ # Postgres implementation, migrations, export
webassets/ # go:embed for the built SPA
desktop/ # Electron shell, platform assets, settings, packaging
web/ # Svelte 5 SPA
packages/
protocol/ # OpenAPI spec, source of truth for the wire shape
sdk-ts/ # TypeScript SDK (generated types + friendly wrapper)
examples/
bot-ts/ # SDK usage example
infra/
migrations/sqlite # mirror of embedded SQLite migrations for tooling
migrations/postgres # mirror of embedded Postgres migrations for tooling
tests/
e2e/ # Playwright tests
docs/ # this directory
- Update
packages/protocol/openapi.yamlfirst when the wire shape changes. It is the contract. - Add the store method on
apps/api/internal/store/types.goand implement it in bothapps/api/internal/store/sqliteandapps/api/internal/store/postgreswhen the feature touches durable state. - Wire the handler in
apps/api/internal/httpapi. - Update the SDK in
packages/sdk-ts/src/index.tsso TS clients have a typed surface. - Update or add a
docs/features/<thing>.md. - Run
pnpm checkandpnpm coverage.
apps/api/internal/...is the bulk of the test suite. Coverage gate is 85%.tests/e2e/chat.spec.tsexercises the SPA end-to-end via Playwright.- The SDK has no test target yet — the bot example is the smoke test.
- IDs are sortable ULID-style with semantic prefixes (
usr_,wsp_,chn_,msg_,evt_,upl_,idn_). - Keep transactions short. Outbox events are inserted in the same tx as the durable write that produced them.
- Keep SQL behind the store interface. Dialect-specific SQL belongs in the SQLite/Postgres store packages, not in HTTP handlers.
- Use
sqlcfor typed SQL. Edit schema/query files, then runpnpm generate:sqlc; do not hand-maintain generatedstoredbcode. - TypeScript: no Svelte imports in
packages/sdk-ts. The SDK must stay framework-neutral.