- Created home.css with comprehensive styles for the home page layout, including typography, buttons, and responsive design adjustments. - Created landing.css to style the landing page, focusing on typography, layout, and responsive behavior for various screen sizes.
Minabot
Bun + Hono habit-tracking REST API with Drizzle ORM and local SQLite. The existing React Router client has a public landing page and a connected signed-in habit dashboard.
See the REST API reference for habits, task recurrence, dated progress, combined calendars, historical corrections, daily resets, and optional count carryover.
bun install
bun dev
Open http://127.0.0.1:3000. Set PORT to use a different port.
- Client pages:
/,/about,/settings. - Hono endpoint:
GET /api/health. - Unknown API routes return JSON with a 404 status.
Design system preview
Open /design-system#editing for interactive habit settings, task recurrence,
and dated progress corrections. Habit colors include Earth, Coast, Dusk, Forest,
Citrus, Blossom, Jewel, and Slate palettes plus a native picker and hex input. Editors support save/cancel,
archive/restore, and an in-session correction history. The backfill calendar
uses frozen historical requirements, independent of edits to today's settings.
These are unsaved, local UI examples with a fixed September 4, 2026 demo clock;
they do not call the habit API or change account data. Reload or Reset editor
clears the preview. Habit colors preview immediately across the editor and the
daily/detail charts; Save keeps the color in the demo and Cancel restores it.
Other settings and progress remain independent of the daily tracking playground.
Signed-out visitors at / see the landing page, with interactive habit calendars
reused from the design system, a how-it-works introduction, and Discord sign-in.
The demo is local, unsaved state and never writes to the habit API. Account loading
and retryable errors are resolved before choosing the public or signed-in home.
Signing out updates the shared account state and reveals the landing page immediately.
Discord integration currently covers identity only, not bot commands or reminders.
Signed-in users at / get a personalized home with their Discord avatar, tracking
date/timezone, daily completion summary, manual/count/task check-ins, a remaining
filter, and per-habit calendar history in the design system's two-column chart grid.
Each habit has its own heading, schedule, color key, and compact calendar. Daily
check-ins use compact rows with expandable tasks. New accounts get editable starter ideas and
a create-habit dialog supporting all three methods, all schedule types, and the shared
palette/custom color picker. The habit and its color are saved atomically. Progress
is saved through the habit API; history uses server-provided progress colors and dated units,
never the landing page's demo data. Visible dashboards refresh every minute and on
returning to the tab. Loading, retry, expired-session, no-schedule, and all-complete
states are handled separately. Each habit has Edit and Delete controls: edit its name,
count target/unit, schedule, and color, or confirm removal from the dashboard using
the existing archive API, which retains recorded history. Editing keeps the tracking
method and existing tasks intact. Unscheduled dates use neutral dots; upcoming dates
share the zero-progress fill while retaining their distinct inspection labels.
Archive management, task-definition editing, historical corrections, and saved
combined-chart management remain outside this home view.
For isolated browser QA, run bun scripts/dashboard-preview.ts and open the printed
preview login URL. It serves the real UI and API against an in-memory test database,
uses a separate test cookie, and discards its data on exit.
It is a local test harness, not a production sign-in flow.
Secondary pages retain a heading, native navigation buttons, and account controls. Tailwind utilities and shadcn configuration are available; Tailwind Preflight is omitted to preserve native browser styling.
bun run typecheck
bun run build
bun start
The build includes the backend and frontend in dist/. bun start serves that
production build. Client routes support direct loading and browser history.
Database
The backend uses Drizzle ORM with Bun's built-in SQLite driver. The database is
created at data/minabot.sqlite, with WAL mode and foreign keys enabled. Database
files are ignored by Git. Development and production use the same file.
Optionally copy .env.example to .env and set DATABASE_PATH to another local
file. Environment-specific database paths can be set in .env.development and
.env.production. Relative paths are resolved from the project root by the package scripts.
Define tables in src/db/schema.ts, then generate and apply migrations:
bun run db:generate --name describe_change
bun run db:migrate
Commit the generated drizzle/ files alongside schema changes. bun dev and
bun start apply pending migrations before starting the server. The initial
migration creates the users and sessions tables.
Import db from src/db/index.ts in backend code to query the database.
GET /api/health checks the database connection before returning success.
Discord sign-in
- Copy
.env.exampleto.envfor shared defaults, and copy.env.development.example/.env.production.exampleto.env.development/.env.productionif those files do not exist. These local files are Git-ignored. - Create an application in the Discord Developer Portal.
Under OAuth2, copy the client ID and client secret into
DISCORD_CLIENT_IDandDISCORD_CLIENT_SECRETin.env. - Set
AUTH_COOKIE_SECRETto a random secret of at least 32 characters, generated withopenssl rand -hex 32. Keep it server-side along with the client secret. - Set
APP_ORIGINin.env.developmentto the development browser origin, initiallyhttp://127.0.0.1:3000, and set the same variable in.env.productionto the production origin, such ashttps://habits.example.com. Register both origins with/api/auth/discord/callbackappended as redirect URIs in Discord. Use the matching hostname throughout each sign-in;localhostand127.0.0.1have different cookie storage. - Run
bun dev, click Sign in with Discord, and authorize theidentifyscope. No bot token or email scope is needed. After returning, View my profile opens/api/me. Sign out revokes the current browser's session.
Without the OAuth settings, public pages and /api/health remain usable, /api/me
returns 401, and sign-in displays a configuration message. Restart after editing
any env file. Use HTTPS for non-local deployments; HTTP is only allowed on loopback
origins. When using a reverse proxy, preserve the public request host and serve
both React and /api from the selected origin.
The package scripts explicitly select the environment before migrations or server startup:
| Command | Environment file | Mode |
|---|---|---|
bun dev |
.env.development |
development |
bun start |
.env.production |
production |
bun run db:generate / bun run db:migrate |
.env.development |
development |
bun run db:migrate:production |
.env.production |
production |
Each command loads optional .env shared defaults, then the selected file.
Variables exported by the shell or deployment platform take precedence over files.
The selected APP_ORIGIN controls OAuth callbacks, cookie security, and logout
Origin validation. Production requires an explicit APP_ORIGIN; development can
fall back to http://127.0.0.1:$PORT (port 3000 if unset). To test bun start
locally, set APP_ORIGIN=http://127.0.0.1:3000 in .env.production.
Automatic env-file loading is disabled in bunfig.toml so the outer bun start
process cannot preload development settings into production. Package scripts load
the files explicitly using Bun's --env-file option; .env.local files are not
loaded by these commands. Build, test, and typecheck do not load application env
files. For custom Bun commands, pass the desired --env-file flags explicitly.
See Bun environment variables.
Use APP_ORIGIN in each file; APP_ORIGIN_DEV and APP_ORIGIN_PROD are no longer
used. Credentials can stay in .env or be overridden separately in each file.
For deployments sharing a machine, set distinct DATABASE_PATH values to keep
production habit data separate from development data.
Schema and session behavior
users: app UUID, unique Discord ID stored as text, username, nullable display name and avatar hash, IANA timezone, and creation/update timestamps.sessions: SHA-256 token hash, user foreign key with cascading deletion, creation timestamp, and expiry timestamp. User and expiry indexes support session management and cleanup.- Timestamps are Unix milliseconds. Each session expires 30 days after sign-in; activity does not extend it. A new sign-in replaces the current browser's session. Other browsers retain independent sessions. Expired sessions are removed during sign-in or when presented to an authenticated route.
- Cookies are HttpOnly, SameSite=Lax, and host-only. HTTPS enables Secure and
__Host-cookie names. OAuth state uses a signed cookie with a ten-minute lifetime and is cleared on callback. Token exchange and profile retrieval happen server-side; Discord access/refresh tokens are never persisted or sent to React. - React detects the browser timezone during initial registration. The server
validates it using
Intl.DateTimeFormat, falling back to UTC. Later sign-ins update the Discord profile without changing the saved timezone. A timezone settings UI is deferred;PATCH /api/meupdates the timezone. Habit timestamps remain UTC and daily boundaries follow the saved timezone with preserved historical deadlines. See the API reference for travel and DST behavior.
API
| Method | Route | Behavior |
|---|---|---|
| GET | /api/health |
Public database health check |
| GET | /api/auth/discord?timezone=Europe%2FBelgrade |
Start sign-in |
| GET | /api/auth/discord/callback |
Validate OAuth response and create a session |
| POST | /api/auth/logout |
Revoke current session; requires matching Origin |
| GET | /api/me |
Public user fields for the current session, or 401 |
/api/me returns the object directly:
{
"id": "app-generated-uuid",
"discordId": "123456789012345678",
"username": "mina",
"displayName": "Mina",
"avatarUrl": null,
"timezone": "Europe/Belgrade"
}
Only the fields declared in src/shared/user.ts are returned. Missing/invalid or
expired sessions receive 401 {"error":"Unauthorized"}. Authentication endpoints
and /api/me use Cache-Control: no-store.
In src/api.ts, protect another route with the same middleware:
app.get("/api/example", auth.requireAuth, c => c.json({ userId: c.get("user").id }));
app.post("/api/example", auth.requireSameOrigin, auth.requireAuth, c => {
// Validate input and enforce ownership using c.get("user").id.
return c.json({ ok: true });
});
Use requireSameOrigin for all cookie-authenticated mutations. Same-origin browser
fetches send the cookie automatically; tokens should not be placed in localStorage.
bun test
bun run typecheck
bun run build
Tests use an isolated in-memory SQLite database with the real migrations and a
stubbed Discord HTTP client. They cover registration, profile refresh, timezone
preservation, cookie security, invalid/expired state, provider failures, session
expiry/rotation, logout CSRF checks, and the exact /api/me response. A real Discord
consent flow additionally requires your application credentials and browser login.
Habit API verification
bun run test:coverage
bun run typecheck
bun run test:smoke
The smoke command builds and starts the production server against a disposable SQLite database, exercises authenticated HTTP requests, then restarts it to verify persistence. It does not use a live Discord provider or modify application data. Habit tests cover every route, all PRD acceptance behaviors, ownership and Origin checks, historical corrections, expiry, timezone boundaries, daily resets, optional count carryover, Nivo data adaptation, and migration upgrades. Semantic commits separate the database foundation, REST implementation, carryover, and verification.