This document defines the preferred day-to-day development workflow for this repo.
It is intentionally practical: the goal is to reduce ambiguity while the system is still being designed and built.
Related docs:
- api.md — canonical client/server API surface
- architecture.md — system boundaries and layering
- plugin-guide.md — plugin interface and sync behavior
- schema.md — persistence model
When a behavior crosses a boundary, we should document and test it at that boundary.
Examples:
- HTTP behavior should be documented and exercised through HTTP requests
- Plugin behavior should be documented through plugin contracts and tests
- DB behavior should be documented through schema and query expectations
This keeps the design honest and helps prevent the docs from drifting away from actual usage.
Since this project is building a documented internal HTTP API, we should use httpyac for both:
- API documentation-in-use
- manual and automated API testing
Reference:
We want one place where API work can be:
- readable by humans
- executable by developers
- easy to version in git
- close to the canonical route and payload definitions in
docs/api.md
httpyac fits well because it lets us keep request examples as text files in the repo,
execute them locally, organize environments, and reuse them during development.
docs/api.mddefines the canonical API contracthttpyacrequest files define the executable examples and test flows
Put differently:
- if someone asks, “what is the route shape?”, the answer should live in
docs/api.md - if someone asks, “how do I actually call it and verify it works?”, the answer should live in
httpyacfiles
When implementation starts, add an API requests directory like:
http/
environments/
local.env.json
test.env.json
session.http
plugins.http
timeline.http
insights.http
share-profile.http
Suggested responsibilities:
http/session.http— login/logout/session checkshttp/plugins.http— connect/import/disconnect/sync plugin flowshttp/timeline.http— timeline listing and filtershttp/insights.http— summary and aggregate querieshttp/share-profile.http— preview and share profile config flowshttp/environments/*.env.json— base URLs, auth/session helpers, local variables
If the API grows, split further by feature area.
For every new API endpoint or route change:
-
Design the route in
docs/api.mdfirst- route path
- method
- request shape
- response shape
- error cases
-
Add or update a
httpyacrequest- happy path example
- at least one failure/validation example when relevant
-
Implement the server behavior
- handler
- validation
- core call
- serialization
-
Run the
httpyacrequests against the local server- confirm response shape matches docs
- confirm status codes and error envelopes
-
Update docs if implementation reveals a better API shape
- prefer improving the API over preserving a confusing route
- keep
docs/api.mdandhttp/examples in sync
A route is not fully done until all of the following are true:
- documented in
docs/api.md - exercised in
httpyac - implemented in server code
- aligned with auth/permissions expectations
- returns stable JSON envelopes and status codes
Each .http file should group related operations and read top-to-bottom as a workflow.
Example for plugins.http:
- list plugins
- get one plugin
- start connect flow
- import file
- sync plugin
- disconnect plugin
Use section comments that make intent obvious.
Example:
### List plugins
GET {{baseUrl}}/api/v1/plugins
### Sync Spotify
POST {{baseUrl}}/api/v1/plugins/spotify/syncUse environment files for values like:
baseUrl- local test user identifiers if needed
- CSRF token helpers if needed
- session/cookie state when supported by the setup
Do not hardcode secrets into committed request files.
For important routes, prefer covering:
- happy path
- unauthorized request
- validation error
- not found
- rate limited or conflict state when relevant
Illustrative example only:
@baseUrl = http://localhost:8080
### Session
GET {{baseUrl}}/api/v1/session
### List plugins
GET {{baseUrl}}/api/v1/plugins
### Get Spotify plugin state
GET {{baseUrl}}/api/v1/plugins/spotify
### Start Spotify connect flow
POST {{baseUrl}}/api/v1/plugins/spotify/connect
Content-Type: application/json
{}
### Sync Spotify
POST {{baseUrl}}/api/v1/plugins/spotify/sync
Content-Type: application/json
{}
### Timeline
GET {{baseUrl}}/api/v1/timeline?limit=20&offset=0
### Share profile preview
GET {{baseUrl}}/api/v1/share-profile/previewWhen changing an existing route:
- update
docs/api.md - update the matching
httpyacrequest file - update implementation
- verify old examples are removed so stale routes do not linger in docs
This matters because route drift creates high cognitive load: developers stop knowing which doc is real, which example is current, and which behavior clients should trust.
When a change affects boundaries between client, server, core, plugins, or storage:
- update
docs/architecture.md - update
docs/api.mdif the client/server contract changes - update
httpyacfiles if the HTTP surface changes
When adding or changing a plugin:
- update
docs/plugin-guide.mdif the shared contract changes - update the plugin-specific doc under
docs/plugins/ - add or update
httpyacflows for user-visible plugin operations
When changing persisted fields that affect API responses:
- update
docs/schema.md - update
docs/api.mdresponse examples if needed - update relevant
httpyacexamples
This project uses mise for all tool and task management.
mise installThis installs Go, templ, sqlc, golang-migrate, air, and httpyac — all pinned in mise.toml.
mise run dev # Start dev server with live reload (air)
mise run templ # Generate templ files
mise run db-migrate # Run database migrations
mise run db-rollback # Rollback last migration
mise run sqlc # Generate type-safe SQL from queries
mise run setup # Full setup (migrate + templ + sqlc)
mise run build # Build production binary
mise run api-test # Run httpyac requests against local serverDefined in mise.toml under [env]:
DATABASE_URL— PostgreSQL connection stringPORT— HTTP server port (default3000)CSRF_KEY— 32-byte CSRF secret
Override locally with mise.local.toml (gitignored).
API request files live in http/ with environment configs in http/environments/:
http/
environments/
local.env.json # baseUrl = http://localhost:3000
health.http # Health check
session.http # Login/logout/session (future)
plugins.http # Plugin operations (future)
timeline.http # Timeline queries (future)
insights.http # Insight aggregates (future)
Run requests:
# All requests
mise run api-test
# Single file
httpyac send http/health.http -e local
# Single request by name
httpyac send http/health.http --name "Health check" -e localIf we add a route and cannot easily express it as a clean httpyac request,
that is a signal to re-check the API design.
Good routes should be easy to:
- describe in markdown
- call from a request file
- understand without reading server internals