Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/architect.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@
* **Pattern:** Extract repeated `embedMessage.edit()` and typing indicator loops into a helper function (e.g. `_animate_update`).
* **Why:** The sequential reveal of complex Discord embeds clutters the main logic loop.
* **Rule:** When multiple sequential embeds updates are tied to typing animations, move the boilerplate into a standalone helper method.

### Command Handler Refactoring
* **Date:** 2026-07-10
* **Pattern:** Extract identical inline command handler logic into a shared helper method (e.g., `submitQuickIssue`).
* **Why:** Monolithic switch statements for Discord interaction routing grow unmanageable when complex logic (like truncating strings, interacting with GitHub APIs, and sending DMs) is duplicated inline.
* **Rule:** If two or more commands share more than 10 lines of identical logic inside the main switch statement, extract the logic into a typed standalone function.
122 changes: 58 additions & 64 deletions packages/bot/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,62 @@ function adaptVoiceChannel(ch: import('discord.js').VoiceChannel): VoiceChannel
// Issue tracking service + reporter notification helper
// ---------------------------------------------------------------------------


/**
* Helper method to handle inline quick issue submissions for bugs and feature requests.
*
* Why: Both `/bug` and `/featurerequest` slash commands duplicate the logic
* to truncate text, resolve the reporter's name, submit the payload via the
* issue tracking service, and handle errors.
* What: Consolidates the common Github issue creation logic into a single method
* that takes the interaction context, issue text, and the target issue type.
*
* @param sender The interaction sender abstraction used to defer and reply.
* @param interaction The original chat input command interaction.
* @param issueType The type of issue being submitted ('bug' | 'feature').
* @param quickText The short text provided by the user.
*/
async function submitQuickIssue(
sender: ReturnType<typeof createInteractionSender>,
interaction: ChatInputCommandInteraction,
issueType: 'bug' | 'feature',
quickText: string,
): Promise<void> {
await sender.defer({ ephemeral: true });
try {
const maxTitle = 60;
const quickTitle = quickText.length > maxTitle
? quickText.slice(0, maxTitle) + '...'
: quickText;
const reporterName = getReporterName(interaction);

const issue = await submitGithubIssueModal({
issueType,
title: quickTitle,
description: quickText,
extraInfo: '',
includeLogs: issueType === 'bug',
reporterName,
reporterId: interaction.user.id,
});
const dmSent = await notifyReporterOfIssue(interaction.user, issue);
const dmHint = dmSent ? '' : '\n(Enable DMs to get notified when this is resolved)';
const issueTypeDisplay = issueType === 'bug' ? 'Bug reported' : 'Feature request created';
await sender.send(`βœ… ${issueTypeDisplay}: ${issue.html_url}${dmHint}`);
} catch (e) {
reportError(e, {
tags: {
handler: 'submitIssue',
kind: issueType,
source: 'quick',
errorType: e instanceof GitHubError ? 'github' : 'unknown',
},
});
const msg = e instanceof Error ? e.message : String(e);
await sender.send(`❌ Failed to create issue: ${msg}`);
}
}

const issueTrackingService = new IssueTrackingService();

async function notifyReporterOfIssue(
Expand Down Expand Up @@ -836,38 +892,7 @@ async function main() {
const quickText = interaction.options.getString('text');

if (quickText) {
await sender.defer({ ephemeral: true });
try {
const maxTitle = 60;
const quickTitle = quickText.length > maxTitle
? quickText.slice(0, maxTitle) + '...'
: quickText;
const reporterName = getReporterName(interaction);

const issue = await submitGithubIssueModal({
issueType: 'bug',
title: quickTitle,
description: quickText,
extraInfo: '',
includeLogs: true,
reporterName,
reporterId: interaction.user.id,
});
const dmSent = await notifyReporterOfIssue(interaction.user, issue);
const dmHint = dmSent ? '' : '\n(Enable DMs to get notified when this is resolved)';
await sender.send(`βœ… Bug reported: ${issue.html_url}${dmHint}`);
} catch (e) {
reportError(e, {
tags: {
handler: 'submitIssue',
kind: 'bug',
source: 'quick',
errorType: e instanceof GitHubError ? 'github' : 'unknown',
},
});
const msg = e instanceof Error ? e.message : String(e);
await sender.send(`❌ Failed to create issue: ${msg}`);
}
await submitQuickIssue(sender, interaction, 'bug', quickText);
break;
}

Expand Down Expand Up @@ -915,38 +940,7 @@ async function main() {
const quickFeatureText = interaction.options.getString('text');

if (quickFeatureText) {
await sender.defer({ ephemeral: true });
try {
const maxTitle = 60;
const quickTitle = quickFeatureText.length > maxTitle
? quickFeatureText.slice(0, maxTitle) + '...'
: quickFeatureText;
const reporterName = getReporterName(interaction);

const issue = await submitGithubIssueModal({
issueType: 'feature',
title: quickTitle,
description: quickFeatureText,
extraInfo: '',
includeLogs: false,
reporterName,
reporterId: interaction.user.id,
});
const dmSent = await notifyReporterOfIssue(interaction.user, issue);
const dmHint = dmSent ? '' : '\n(Enable DMs to get notified when this is resolved)';
await sender.send(`βœ… Feature request created: ${issue.html_url}${dmHint}`);
} catch (e) {
reportError(e, {
tags: {
handler: 'submitIssue',
kind: 'feature',
source: 'quick',
errorType: e instanceof GitHubError ? 'github' : 'unknown',
},
});
const msg = e instanceof Error ? e.message : String(e);
await sender.send(`❌ Failed to create issue: ${msg}`);
}
await submitQuickIssue(sender, interaction, 'feature', quickFeatureText);
break;
}

Expand Down
Loading