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.

### Guard Clauses in roleUi.ts
* **Date:** 2026-06-26
* **Pattern:** Use early return guard clauses for toggling state rather than deep nesting (e.g. `if (state.has) { ... return; } else { ... }`).
* **Why:** Deep nesting inside Discord UI interactions makes code harder to parse.
* **Rule:** If an `if` condition represents a complete action that finishes the handler's work (like untoggling a role), early.
38 changes: 25 additions & 13 deletions packages/bot/src/core/roleUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ export interface ViewData {

// -- Role button callback logic (pure, testable) --

/**
* Handles toggling a specific role button in a role selection view.
* Mutates the provided state and button views directly.
*
* @param state - The current role selection state (selected roles set).
* @param viewButtons - The buttons for the current active view layer.
* @param roleName - The unique ID of the role being toggled.
* @param isMainSpec - Whether the view is operating in "Main Spec" mode
* (where only one selection is allowed at a time).
*/
export function handleRoleButtonClick(
state: RoleSelectionState,
viewButtons: ButtonData[],
Expand All @@ -83,25 +93,27 @@ export function handleRoleButtonClick(
if (state.selectedRoles.has(roleName)) {
state.selectedRoles.delete(roleName);
btn.style = 'secondary';
} else {
if (isMainSpec) {
for (const item of viewButtons) {
if (isRoleButton(item) && item.roleName !== roleName) {
state.selectedRoles.delete(item.roleName);
item.style = 'secondary';
}
}
}
state.selectedRoles.add(roleName);
btn.style = 'primary';
return;
}

// Deselect NoneButton sibling if present
if (isMainSpec) {
for (const item of viewButtons) {
if (isNoneButton(item)) {
if (isRoleButton(item) && item.roleName !== roleName) {
state.selectedRoles.delete(item.roleName);
item.style = 'secondary';
}
}
}

state.selectedRoles.add(roleName);
btn.style = 'primary';

// Deselect NoneButton sibling if present
for (const item of viewButtons) {
if (isNoneButton(item)) {
item.style = 'secondary';
}
}
}

export function handleNoneButtonClick(
Expand Down
Loading