# Discord interface Signed HTTP interactions enter `/api/discord/interactions`. The raw request body is verified with the application's Ed25519 public key and a five-minute timestamp window. `DISCORD_PUBLIC_KEY` enables the endpoint; `DISCORD_CLIENT_ID` identifies the application. Commands are global with contexts `[0, 1]` (server and bot DM). `HabitService` remains the shared domain implementation. Verified Discord user IDs resolve the same account as website OAuth; new accounts start in UTC. Server responses are ephemeral. Buttons/forms carry opaque persisted owner-bound state, expire after 15 minutes, and mutation controls are consumed after successful use. Interaction IDs retain replay receipts for 24 hours. Neither table stores tokens. `daily.ts` implements the `/today` workflow. `types.ts`, `ui.ts`, and `state.ts` provide the shared feature building blocks. Feature tasks extend command registration and route dispatch while keeping their behavior in separate modules. Tests use real Ed25519 signatures and disposable migrated SQLite databases. The foundation tests cover server/DM privacy, replay after restart, stale progress, cross-account controls, and matching website state. All feature modules are now composed in the shared API. End-to-end user interaction in Discord remains a manual acceptance check after deployment and registration. `/help` lists the current command definitions automatically, explains check-in controls and privacy, and links to the website. It works before account setup and does not create an account or synchronize habits. Re-run the registration script with `--apply` after deployment to expose newly added commands in Discord. Reminder delivery accepts `commandsAvailable`; production enables it when `DISCORD_PUBLIC_KEY` is a 64-digit hexadecimal key and `DISCORD_CLIENT_ID` is set. The existing bot token still controls delivery. Configured reminders include an owner-bound `today` control with `mode: 'reminder'`: it opens a separate private flow, leaving the reminder unchanged, and expires at the sending account's next local midnight (including DST). Delivery claims, nonce, opt-out, and retry rules are unchanged. The dashboard preview explicitly disables buttons because it has no signed interaction endpoint configured. Global command registration and the Discord interaction endpoint must be configured separately before production use. ## Preferences `preferences.ts` implements global `/settings` and `/reminders`. Reminder menus paginate ten named schedules at a time; entries with matching name and schedule are edited together as multiple comma-separated 24-hour times. Forms accept weekday names (or `every day`) or one ISO calendar date. Delivery is toggled for all schedules. Removing the last schedule disables delivery and stores an empty list; the shared contract permits empty lists only while disabled. Invalid forms provide a prefilled retry button. UTC accounts receive a timezone setup hint. `ReminderService` is shared with the website routes. Every save advances its `updatedAt` monotonically; Discord controls compare the saved preference snapshot and the user's timezone/update version before editing. `HabitService.setTimezone` also advances the user timestamp monotonically, including repeated writes in the same millisecond. This rejects old Discord forms after edits from either interface, including change-and-revert sequences. Website PUT retains its existing request contract. Delivery receipts do not invalidate preference controls. Integration: retain the `preferences` command dispatch and `preferencesAction` component dispatch in `routes.ts`, the two definitions in `commands.ts`, and the preference fields in `ControlData`. `CommandConfig.remindersAvailable` supplies availability; `createApi` derives it from the existing sharing bot token, matching the website and reminder worker. Direct route fixtures default it to false. No new environment variables, migrations, outbound requests, or remote command registration are needed by this module. Global command registration remains an integration step. Successful mutations invoke the supplied live-card dirty hook. `progress.ts` implements `/progress`: six-habit text pages, focused private previews, explicit destination confirmation for snapshots, and live-image start/status/pause/resume. API composition injects the same `sharing.send` and `live.start/pause/state` operations used by website routes. Keep these shared instances when reconciling dispatch; do not construct a second live worker. Rendering and network actions return ephemeral type-5 acknowledgements and edit the original interaction response once. `DeferredDispatch.complete` and `schedule` are injectable for tests. Interaction tokens live only in memory, never receipts or controls. A process interruption after acknowledgement can leave the private response unfinished; restarting never replays the public send. Run `/progress` again to inspect status. Public snapshot and live deliveries retain their existing persistent quotas, nonce and uncertain-delivery guards. `progress-image.ts` renders the focused 30-day snapshot at 1200×860 with bundled Mina Sans / Instrument Serif, shared theme colors, and existing habit shades. The 30 cells retain fixed readable sizes; symbols are Canvas paths. Text summaries and buttons accompany the PNG. To produce review images without sending messages: `PROGRESS_IMAGE_REVIEW_DIR=/tmp/minabot-progress-review bun test src/discord/progress-image.test.ts`. ## Integrated commands All commands use global registration with server and bot-DM contexts. `/help` reads this same registration metadata, so its command list stays in sync. | Command | Function | | --- | --- | | `/help` | Command guide and website link | | `/today` | Daily manual/count/task check-ins | | `/habits` | Create, edit, schedule, archive, restore, permanently delete | | `/tasks` | Add, rename, schedule, delete routine tasks | | `/history` | Inspect and correct dated progress, including archived habits | | `/reminders` | Named schedules, multiple times, quiet hours, enable/disable | | `/settings` | Timezone and reminder delivery status | | `/progress` | Private graphics, confirmed snapshot sharing, live-image controls | After the deployment controller installs a release, register its definitions: ```sh cd /opt/minabot sudo docker compose exec -T app bun scripts/register-discord-commands.ts --apply ``` Registration upserts individual global commands and preserves unrelated commands. The public key and interactions URL are one-time setup; adding commands requires rerunning registration after their handlers have deployed. ## Additional command graphics `/today` includes **View day card**, summarizing all due habits in pages of six. The selected daily check-in and `/history` editor include **View check-in card**, showing the selected date's requirements, task completion, carryover where applicable, and a 14-day progress strip ending on that date. `/reminders` includes **View schedule card**, showing each time slot, weekday/date schedule, quiet-hour conflicts, and delivery status. Schedule cards retain disabled/unavailable states. Management forms and `/help` remain text-based because their controls already communicate the relevant choices. These are private snapshots: **Refresh card**, **Toggle theme**, and pagination request a fresh owned snapshot. Regular check-ins keep their existing immediate text responses. Graphic actions acknowledge before rendering and attach a PNG to a new private reply; they never publish to the sharing channel or write progress. Each image has a text equivalent and attachment description. A render failure retains the text and navigation. No new command registration or configuration is needed after deploying this change. Generate local visual review artifacts with `COMMAND_IMAGE_REVIEW_DIR=/tmp/minabot-command-review bun test src/discord/command-graphics.test.ts`.