feat: implement branded discord embeds and versioning

This commit is contained in:
syntaxbullet
2026-01-14 16:10:23 +01:00
parent 6e57ab07e4
commit 4af2690bab
13 changed files with 291 additions and 9 deletions

View File

@@ -0,0 +1,51 @@
---
name: create-ticket
description: Create a ticket for a task that needs to be worked on.
---
# Skill: create-ticket
## Purpose
Decompose high-level objectives into "Atomic Tickets" to maximize development velocity and minimize cognitive overhead.
## Execution Rules
1. **Directory Check:** Scan `.agent/work/tickets/`. Determine sequence number `NNN`.
2. **Naming:** `.agent/work/tickets/NNN-brief-description.md`.
3. **The Atomic/Velocity Test:** - Max 3 files modified.
- Max 80 lines of logic.
- Must be verifiable via a single command or test suite.
- If it exceeds these, **split it.**
4. **Context Injection:** Include relevant code snippets or interface definitions directly in the ticket to prevent "context hunting."
5. **No Breaking Changes:** If a ticket changes a shared interface, it must include the refactor for consumers or be split into a "Transition" ticket.
## Ticket Template
### Context & Goal
[Why this matters + the specific problem it solves.]
### Dependencies
- [e.g., Ticket #NNN]
### Affected Files
- `path/to/file_A.ext`: [Specific change description]
- `path/to/file_B.ext`: [Specific change description]
### Technical Constraints & Strategy
- [e.g., Implementation: Use the existing X wrapper instead of a new fetch call.]
- [e.g., Constraint: Maintain backward compatibility with Y.]
### Definition of Done (Binary)
- [ ] Criterion 1 (e.g., `npm test` passes for `X.test.ts`)
- [ ] Criterion 2 (e.g., UI component renders without hydration errors)
- [ ] Criterion 3 (e.g., API response matches the schema in `types.ts`)
### New Test Files
- `path/to/test_A.ext`: [What is being tested]

View File

@@ -0,0 +1,54 @@
---
name: code-review
description: A "Default-to-Fail" audit of codebase changes. Observation only; no file modifications.
---
# Skill: code-review
## Purpose
Protect the codebase from "feature creep," technical debt, and weak validation. This skill assumes the latest changes are flawed until they pass a rigorous audit.
## Execution Rules
1. **Read-Only Protocol:** This is a diagnostic skill. **Under no circumstances should any files be modified.** Provide feedback only.
2. **Default-to-Fail:** Assume the code is broken or insufficient. The burden of proof lies on the code and its tests.
3. **The Atomic Veto:** - Check the diff. If it exceeds 3 files or 80 lines of logic, **Veto immediately.**
- Reason: "Change exceeds atomic threshold; high risk of cognitive load."
4. **Strictness Audit (Tests):**
- **Veto** if assertions are fuzzy (e.g., `toBeTruthy()`).
- **Veto** if there is no "Red Path" (failure case) test.
- **Veto** if the test is "loose" (e.g., doesn't check specific property values).
5. **Direct Feedback:** No sycophancy. Use "Blockers" for issues and "Verdict: APPROVE" only when the code is bulletproof.
## Review Template
### Verdict: [FAIL / APPROVE]
**Primary Blocker:** [One sentence identifying the biggest reason for rejection.]
---
### 1. Atomic Constraint Check
- **Files Changed:** [Count] / 3
- **Logic Lines:** [Count] / 80
- **Status:** [PASS / FAIL (Veto if FAIL)]
### 2. Test Strictness Audit
- **Assertion Quality:** [List specific lines with fuzzy matchers. Demand strict equality.]
- **Failure Coverage:** [Does a test exist for the 'Error/Empty' state? If no, FAIL.]
- **Logic Sync:** [Does the test actually exercise the logic added, or just side effects?]
### 3. Logic & Resilience
- **Unchecked States:** [Identify unhandled nulls, undefineds, or missing error catches.]
- **Efficiency:** [Is there a faster path or a redundant operation?]
### 4. Direct Actionables
_Note: The reviewer does not apply these. The user/agent must create a ticket or apply fixes manually._
1. [Specific fix for Blocker 1]
2. [Specific fix for Blocker 2]

View File

@@ -0,0 +1,30 @@
### Context & Goal
Currently, every command manually performs checks like user existence or maintenance mode, or these are hardcoded into the `CommandHandler`. Standardizing these requirements in the command definition itself makes the code cleaner and more declarative.
### Dependencies
- None
### Affected Files
- `shared/lib/types.ts`: Update `Command` interface to include a optional `requirements` object.
- `bot/lib/handlers/CommandHandler.ts`: Update to read and enforce these requirements.
- `bot/commands/economy/balance.ts`: Refactor to use the new requirements (example).
### Technical Constraints & Strategy
- Implementation: Use a standardized `requirements` object in the `Command` interface.
- Requirements could include: `userExists: boolean`, `permissions: string[]`, `devOnly: boolean`.
- Ensure `CommandHandler` provides clear error messages to the user when a requirement fails.
### Definition of Done (Binary)
- [ ] `Command` interface updated in `types.ts`.
- [ ] `CommandHandler.ts` enforces requirements before executing command.
- [ ] At least one command (e.g., `balance`) is refactored to use the new system.
- [ ] Clear error embeds are shown to the user when requirements aren't met.
### New Test Files
- None (Verification via manual testing of command execution).

View File

@@ -0,0 +1,31 @@
### Context & Goal
The bot currently relies on `console.error` which is hard to track and lacks context. A centralized error logging service will allow for better debugging, persistent error logs, and future integration with services like Sentry or Discord webhooks for alerts.
### Dependencies
- None
### Affected Files
- `shared/lib/logger.ts`: New file for the unified logger service.
- `bot/lib/handlers/CommandHandler.ts`: Update to use the new logger for command errors.
- `web/src/server.ts`: Update to use the new logger for API and WebSocket errors.
### Technical Constraints & Strategy
- Implementation: Create a `Logger` class/object in `shared/lib`.
- Support log levels: `info`, `warn`, `error`, `debug`.
- Errors should capture: timestamp, source (bot/web), error message, and stack trace if available.
- For now, logging to console and a local log file (e.g., `logs/error.log`) is sufficient.
### Definition of Done (Binary)
- [ ] `Logger` service implemented in `shared/lib/logger.ts`.
- [ ] Command errors are logged via the new service.
- [ ] Web server errors are logged via the new service.
- [ ] Log output is formatted consistently.
### New Test Files
- `shared/lib/logger.test.ts`: Verify logger output and file writing.

View File

@@ -0,0 +1,32 @@
### Context & Goal
The current `web/src/server.ts` is a monolithic file with a very long `fetch` handler. This makes it difficult to read and maintain. Modularizing the logic into separate route handlers and middleware-like functions will improve code quality and scalability.
### Dependencies
- None
### Affected Files
- `web/src/server.ts`: Refactor to use modular handlers.
- `web/src/routes/api.ts`: New file for API route definitions.
- `web/src/routes/static.ts`: New file for static file serving logic.
- `web/src/routes/websocket.ts`: New file for WebSocket event handling.
### Technical Constraints & Strategy
- Implementation: Move different responsibilities (API, Static, WS) into separate files.
- The main `serve` configuration should just call these modules.
- Ensure the SPA fallback logic remains intact.
### Definition of Done (Binary)
- [ ] `web/src/server.ts` length reduced by at least 50%.
- [ ] API routes moved to dedicated module.
- [ ] Static file serving moved to dedicated module.
- [ ] WebSocket logic moved to dedicated module.
- [ ] Dashboard still loads and functions correctly.
### New Test Files
- None (Verification via manual testing of the dashboard).

View File

@@ -0,0 +1,28 @@
### Context & Goal
Enhance the user experience by standardizing the look and feel of Discord embeds. Adding consistent branding like a custom footer (with version info) and using the bot's accent color will make the bot feel more professional.
### Dependencies
- None
### Affected Files
- `bot/lib/embeds.ts`: Update standard embed creators.
- `shared/lib/constants.ts`: Add branding-related constants (colors, footer text).
### Technical Constraints & Strategy
- Implementation: Update `createBaseEmbed` and other helpers to automatically include footers and standard colors.
- Use info from `package.json` for versioning in the footer.
- Ensure the changes don't break existing layouts where custom colors might be needed.
### Definition of Done (Binary)
- [x] All standard embeds now include a consistent footer.
- [x] Embeds use a predefined brand color by default.
- [x] Version number is automatically pulled for the footer.
### New Test Files
- None.

View File

@@ -0,0 +1,29 @@
### Context & Goal
The `exam` command currently contains a lot of business logic, including reward calculations, timer management, and complex database transactions. Moving this logic to a dedicated `ExamService` will improve testability, maintainability, and keep the command file focused on user interaction.
### Dependencies
- None
### Affected Files
- `shared/modules/economy/exam.service.ts`: New file for the exam logic.
- `bot/commands/economy/exam.ts`: Refactor to use the new service.
### Technical Constraints & Strategy
- Implementation: Create an `ExamService` that handles `getExamStatus`, `takeExam`, and `registerForExam`.
- The command should only handle user input and formatting the response embeds based on the service's result.
- Ensure the Drizzle transactions are correctly handled within the service.
### Definition of Done (Binary)
- [ ] `ExamService` implemented with methods for all exam-related operations.
- [ ] `bot/commands/economy/exam.ts` refactored to use the service.
- [ ] Logic is covered by unit tests in a new test file.
- [ ] Manual verification shows the exam command still works as expected.
### New Test Files
- `shared/modules/economy/exam.service.test.ts`: Unit tests for reward calculations and state transitions.

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
.env .env
node_modules node_modules
docker-compose.override.yml docker-compose.override.yml
.agent
shared/db-logs shared/db-logs
shared/db/data shared/db/data
shared/db/loga shared/db/loga

View File

@@ -1,4 +1,15 @@
import { Colors, type ColorResolvable, EmbedBuilder } from "discord.js"; import { Colors, type ColorResolvable, EmbedBuilder } from "discord.js";
import { BRANDING } from "@shared/lib/constants";
import pkg from "../../package.json";
/**
* Applies standard branding to an embed.
*/
function applyBranding(embed: EmbedBuilder): EmbedBuilder {
return embed.setFooter({
text: `${BRANDING.FOOTER_TEXT} v${pkg.version}`
});
}
/** /**
* Creates a standardized error embed. * Creates a standardized error embed.
@@ -7,11 +18,13 @@ import { Colors, type ColorResolvable, EmbedBuilder } from "discord.js";
* @returns An EmbedBuilder instance configured as an error. * @returns An EmbedBuilder instance configured as an error.
*/ */
export function createErrorEmbed(message: string, title: string = "Error"): EmbedBuilder { export function createErrorEmbed(message: string, title: string = "Error"): EmbedBuilder {
return new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`${title}`) .setTitle(`${title}`)
.setDescription(message) .setDescription(message)
.setColor(Colors.Red) .setColor(Colors.Red)
.setTimestamp(); .setTimestamp();
return applyBranding(embed);
} }
/** /**
@@ -21,11 +34,13 @@ export function createErrorEmbed(message: string, title: string = "Error"): Embe
* @returns An EmbedBuilder instance configured as a warning. * @returns An EmbedBuilder instance configured as a warning.
*/ */
export function createWarningEmbed(message: string, title: string = "Warning"): EmbedBuilder { export function createWarningEmbed(message: string, title: string = "Warning"): EmbedBuilder {
return new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`⚠️ ${title}`) .setTitle(`⚠️ ${title}`)
.setDescription(message) .setDescription(message)
.setColor(Colors.Yellow) .setColor(Colors.Yellow)
.setTimestamp(); .setTimestamp();
return applyBranding(embed);
} }
/** /**
@@ -35,11 +50,13 @@ export function createWarningEmbed(message: string, title: string = "Warning"):
* @returns An EmbedBuilder instance configured as a success. * @returns An EmbedBuilder instance configured as a success.
*/ */
export function createSuccessEmbed(message: string, title: string = "Success"): EmbedBuilder { export function createSuccessEmbed(message: string, title: string = "Success"): EmbedBuilder {
return new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`${title}`) .setTitle(`${title}`)
.setDescription(message) .setDescription(message)
.setColor(Colors.Green) .setColor(Colors.Green)
.setTimestamp(); .setTimestamp();
return applyBranding(embed);
} }
/** /**
@@ -49,11 +66,13 @@ export function createSuccessEmbed(message: string, title: string = "Success"):
* @returns An EmbedBuilder instance configured as info. * @returns An EmbedBuilder instance configured as info.
*/ */
export function createInfoEmbed(message: string, title: string = "Info"): EmbedBuilder { export function createInfoEmbed(message: string, title: string = "Info"): EmbedBuilder {
return new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(` ${title}`) .setTitle(` ${title}`)
.setDescription(message) .setDescription(message)
.setColor(Colors.Blue) .setColor(Colors.Blue)
.setTimestamp(); .setTimestamp();
return applyBranding(embed);
} }
/** /**
@@ -65,11 +84,12 @@ export function createInfoEmbed(message: string, title: string = "Info"): EmbedB
*/ */
export function createBaseEmbed(title?: string, description?: string, color?: ColorResolvable): EmbedBuilder { export function createBaseEmbed(title?: string, description?: string, color?: ColorResolvable): EmbedBuilder {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTimestamp(); .setTimestamp()
.setColor(color ?? BRANDING.COLOR);
if (title) embed.setTitle(title); if (title) embed.setTitle(title);
if (description) embed.setDescription(description); if (description) embed.setDescription(description);
if (color) embed.setColor(color);
return embed; return applyBranding(embed);
} }

View File

@@ -9,12 +9,12 @@
"discord.js": "^14.25.1", "discord.js": "^14.25.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.44.7", "drizzle-orm": "^0.44.7",
"postgres": "^3.4.7",
"zod": "^4.1.13", "zod": "^4.1.13",
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"drizzle-kit": "^0.31.7", "drizzle-kit": "^0.31.7",
"postgres": "^3.4.7",
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5", "typescript": "^5",

View File

@@ -1,5 +1,6 @@
{ {
"name": "app", "name": "app",
"version": "1.0.0",
"module": "bot/index.ts", "module": "bot/index.ts",
"type": "module", "type": "module",
"private": true, "private": true,

View File

@@ -85,3 +85,9 @@ export enum TriviaCategory {
ANIMALS = 27, ANIMALS = 27,
ANIME_MANGA = 31, ANIME_MANGA = 31,
} }
export const BRANDING = {
COLOR: 0x00d4ff as const,
FOOTER_TEXT: 'AuroraBot' as const,
};

View File

@@ -13,6 +13,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"resolveJsonModule": true,
"noEmit": true, "noEmit": true,
// Best practices // Best practices
"strict": true, "strict": true,