- Added typography and design system styles to globals.css. - Removed deprecated home.css and landing.css files. - Introduced DiscordSignInButton component for Discord authentication. - Created Card and CardGrid components for structured content display. - Implemented ContainerShowcase to demonstrate card usage and layout. - Added DesignSystemTabs for navigation between design system sections. - Established typography.css for consistent text styling across components. - Added tests for typography styles to ensure compliance with design standards.
235 lines
11 KiB
Markdown
235 lines
11 KiB
Markdown
# Minabot
|
|
|
|
Bun + Hono habit-tracking REST API with Drizzle ORM and local SQLite. The existing
|
|
React Router client currently contains only the design system, ready for a fresh page redesign.
|
|
|
|
See [the REST API reference](docs/API.md) for habits, task recurrence, dated progress,
|
|
combined calendars, historical corrections, daily resets, and optional count carryover.
|
|
|
|
```sh
|
|
bun install
|
|
bun dev
|
|
```
|
|
|
|
Open http://127.0.0.1:3000. Set `PORT` to use a different port.
|
|
|
|
- Client page: `/design-system`. Root and all other client URLs redirect there.
|
|
- Hono endpoint: `GET /api/health`.
|
|
- Unknown API routes return JSON with a 404 status.
|
|
|
|
### Design system preview
|
|
|
|
The library uses seven tabs: Foundations, Components, Layout, Calendars, Playground,
|
|
Editing, and References. Foundations is the default. Each tab has a URL hash;
|
|
existing section links still work. Arrow keys, Home, and End navigate the tabs.
|
|
Switching tabs preserves unsaved demo state; browser back/forward restores the section.
|
|
|
|
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.
|
|
|
|
The previous Landing, Home, About, and Settings pages have been removed. The
|
|
backend API, authentication infrastructure, database, and reusable components remain
|
|
available for the redesigned pages. The design system does not load account data
|
|
or call the API.
|
|
|
|
`styles/globals.css` is the only stylesheet
|
|
entry point; component styles are in Tailwind's components layer so utilities can
|
|
override them predictably. Tailwind Preflight remains omitted.
|
|
|
|
`styles/typography.css` defines reusable Tailwind classes: `type-display` (72/81),
|
|
`type-section` (48/54), `type-title` (32/40), `type-lead` (20/30), `type-body`
|
|
(16/24), `type-small` and `type-label` (14/21), and `type-control` (24/30, symbols).
|
|
Values are font-size/line-height in pixels at the default 16px root, implemented
|
|
in rem. At 640px and below, display steps to 48/54 and section to 36/45; body and
|
|
supporting text never shrink. Use these classes directly or with `@apply`, not
|
|
bespoke font sizes or viewport-based type. See `/design-system#foundations` for
|
|
live specimens. Reuse `SectionHeading`, `Button`, `ButtonLink`, and the shared
|
|
`DiscordSignInButton` for headings and calls to action.
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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
|
|
|
|
1. Copy `.env.example` to `.env` for shared defaults, and copy
|
|
`.env.development.example` / `.env.production.example` to `.env.development` /
|
|
`.env.production` if those files do not exist. These local files are Git-ignored.
|
|
2. Create an application in the [Discord Developer Portal](https://discord.com/developers/applications).
|
|
Under OAuth2, copy the client ID and client secret into `DISCORD_CLIENT_ID`
|
|
and `DISCORD_CLIENT_SECRET` in `.env`.
|
|
3. Set `AUTH_COOKIE_SECRET` to a random secret of at least 32 characters, generated
|
|
with `openssl rand -hex 32`. Keep it server-side along with the client secret.
|
|
4. Set `APP_ORIGIN` in `.env.development` to the development browser origin,
|
|
initially `http://127.0.0.1:3000`, and set the same variable in `.env.production`
|
|
to the production origin, such as `https://habits.example.com`. Register **both**
|
|
origins with `/api/auth/discord/callback` appended as redirect URIs in Discord.
|
|
Use the matching hostname throughout each sign-in; `localhost` and `127.0.0.1`
|
|
have different cookie storage.
|
|
5. Run `bun dev`, click **Sign in with Discord**, and authorize the `identify` scope.
|
|
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](https://bun.sh/docs/runtime/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/me` updates 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```ts
|
|
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.
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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.
|