Skip to content

refactor: leaf-helper consolidation — writeAuditAction + extractModalField (v3.2.2)#8

Merged
NindroidA merged 4 commits into
mainfrom
v3.3.0-consolidation
Jun 3, 2026
Merged

refactor: leaf-helper consolidation — writeAuditAction + extractModalField (v3.2.2)#8
NindroidA merged 4 commits into
mainfrom
v3.3.0-consolidation

Conversation

@NindroidA

@NindroidA NindroidA commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 2 of the consolidation roadmap — two reusable helpers extracted, with one latent-bug fix. No behavior change otherwise.

writeAuditAction (api/handlers)

Folds the repeated const triggeredBy = optionalString(body,'triggeredBy'); await writeAuditLog(...) into writeAuditAction(guildId, body, action, details?). 16 uniform sites migrated; 7 non-uniform sites (approve/deny ?? requireString, bait ?? 'dashboard'/multi-use, announcement sentBy/createdBy) left as-is. (Prereq for the Phase 3 API config-CRUD bundle.)

extractModalField (utils/interactions)

The field-type-tolerant modal reader (text .value and select .values[]) — promoted from a botSetup-local helper to the shared barrel. systemFlows repointed (12 sites) + 8 inline getField(id)?.value reads migrated.

🐛 Fixed

  • Announcement role/channel select reads used fields.getField(id)?.value, which silently returns undefined for select components. Now read via extractModalField (.values[]). Checkbox-boolean reads + full-array multi-selects were intentionally left (would break under String() coercion / are array reads).

Verification

1284 tests pass; bun run build + biome clean. Reviewed: each migration confirmed single-use before folding; correctness over coverage.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved modal field reading for select components that previously returned undefined values
  • Chores

    • Version bumped to 3.2.2
    • Internal utility refactoring for audit logging and modal field extraction

NindroidA and others added 3 commits June 2, 2026 23:56
…sites

The dashboard-API handlers repeated `const triggeredBy = optionalString(body,
'triggeredBy'); await writeAuditLog(guildId, action, triggeredBy, details)`.
New `writeAuditAction(guildId, body, action, details?)` folds the triggeredBy
extraction in. Migrated the 16 single-use sites; left the 7 non-uniform ones
(approve/deny `?? requireString`, bait `?? 'dashboard'` / multi-use triggeredBy,
announcement `sentBy`/`createdBy`) untouched. Updated the mock.module(auditHelper)
test fakes to add writeAuditAction. (Phase 3 API config-CRUD will build on this.)

1284 tests pass; build + biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ct footgun

systemFlows' local getModalFieldValue (the only modal reader tolerant of both
`.value` text inputs and `.values[]` selects) is promoted to
utils/interactions/modalHelper.ts as `extractModalField` and exported from the
barrel. systemFlows repointed (12 sites). Migrated 8 inline `getField(id)?.value`
reads — including 2 announcement role/channel SELECT reads that were silently
returning undefined (the actual footgun). Left checkbox-boolean reads (String
coercion would break them) and full-array multi-select reads untouched.
(Phase 4 bot-setup edit modals will use this.)

1284 tests pass; build + biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@NindroidA, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 39 minutes and 53 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 51c6a4a4-7ad3-40d4-a28c-46ec653cbab6

📥 Commits

Reviewing files that changed from the base of the PR and between d2a7c56 and 90ffcc0.

📒 Files selected for processing (8)
  • src/commands/handlers/baitChannel/settings.ts
  • src/commands/handlers/botSetup/systemFlows.ts
  • src/commands/handlers/ticket/typeEdit.ts
  • src/commands/handlers/ticket/workflowSettings.ts
  • src/utils/api/handlers/auditHelper.ts
  • src/utils/interactions/index.ts
  • src/utils/interactions/modalHelper.ts
  • tests/unit/utils/interactions/modalHelper.test.ts
📝 Walkthrough

Walkthrough

This PR introduces two new utility helpers to reduce boilerplate in dashboard API handlers and command handlers, then migrates existing code to use them. Version is bumped to 3.2.2 with detailed changelog documentation of the additions and fixes.

Changes

Modal Field & Audit Helpers for v3.2.2

Layer / File(s) Summary
Define modal and audit helpers
src/utils/interactions/modalHelper.ts, src/utils/interactions/index.ts, src/utils/api/handlers/auditHelper.ts
New extractModalField(fields, customId) safely extracts modal field values, handling both text input .value and select component .values[] patterns. New writeAuditAction(guildId, body, action, details?) wraps writeAuditLog and extracts triggeredBy from the request body, eliminating repeated boilerplate across handlers.
Migrate command handlers to extractModalField
src/commands/handlers/announcement/setup.ts, src/commands/handlers/baitChannel/settings.ts, src/commands/handlers/botSetup/index.ts, src/commands/handlers/botSetup/systemFlows.ts, src/commands/handlers/ticket/typeEdit.ts
Replace direct Discord.js getField(...).value and getField(...).values?.[0] patterns with extractModalField calls in announcement setup, bait channel settings, bot setup language/system flows, and ticket type editor.
Migrate API handlers to writeAuditAction
src/utils/api/handlers/announcementHandlers.ts, src/utils/api/handlers/applicationHandlers.ts, src/utils/api/handlers/configHandlers.ts, src/utils/api/handlers/memoryHandlers.ts, src/utils/api/handlers/permissionHandlers.ts, src/utils/api/handlers/reactionRoleHandlers.ts, src/utils/api/handlers/rulesHandlers.ts, src/utils/api/handlers/setupHandlers.ts, src/utils/api/handlers/ticketHandlers.ts
Replace writeAuditLog calls that manually extracted triggeredBy from request bodies with unified writeAuditAction(guildId, body, actionKey, details?) calls across all dashboard API route handlers.
Update tests for writeAuditAction
tests/unit/utils/api/applicationHandlers.test.ts, tests/unit/utils/api/setupHandlers.test.ts, tests/unit/utils/api/ticketHandlers.test.ts
Add fakeWriteAuditAction mocks and update test assertions to verify writeAuditAction is called with the new signature (guildId, body with triggeredBy, action key, and optional details) instead of the prior writeAuditLog pattern.
Version bump and release documentation
package.json, CLAUDE.md, CHANGELOG.md
Bump version to 3.2.2 in package metadata and update Quick Reference. Add comprehensive changelog entry documenting the new writeAuditAction and extractModalField utilities and the fix for modal select-field reads that previously returned undefined.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: refactoring audit logging and modal field extraction helpers, with version bump to 3.2.2.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3.3.0-consolidation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/handlers/botSetup/systemFlows.ts`:
- Line 134: extractModalField returns string | undefined, so the checkbox read
for enabled is wrong: change how enabled is derived (the variable where
currently: const enabled = extractModalField(submit.fields,
'setup_staff_enable') ?? true) to coerce the modal field value to a boolean
consistently. Use the same pattern as other handlers: fetch the raw field via
extractModalField or submit.fields.getField('setup_staff_enable') and wrap with
Boolean(...) or compare to the string 'true' to ensure unchecked ("false" or
undefined) yields false; update any downstream logic that relies on enabled
accordingly.

In `@src/utils/api/handlers/auditHelper.ts`:
- Around line 66-74: writeAuditAction currently calls optionalString(body,
'triggeredBy') which can throw ApiError.badRequest for non-string values and
breaks the "best-effort" audit contract; change writeAuditAction to extract
triggeredBy tolerantly (e.g., check if body['triggeredBy'] is a string and use
it, otherwise set triggeredBy = undefined) before calling writeAuditLog so that
malformed triggeredBy values do not throw and the audit path remains
non-throwing; keep the function signature and ensure writeAuditLog is invoked
with undefined for invalid types instead of propagating optionalString's error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a1e40a6-5bcb-47e5-adb5-eb19cfaffbae

📥 Commits

Reviewing files that changed from the base of the PR and between 30a9672 and d2a7c56.

📒 Files selected for processing (23)
  • CHANGELOG.md
  • CLAUDE.md
  • package.json
  • src/commands/handlers/announcement/setup.ts
  • src/commands/handlers/baitChannel/settings.ts
  • src/commands/handlers/botSetup/index.ts
  • src/commands/handlers/botSetup/systemFlows.ts
  • src/commands/handlers/ticket/typeEdit.ts
  • src/utils/api/handlers/announcementHandlers.ts
  • src/utils/api/handlers/applicationHandlers.ts
  • src/utils/api/handlers/auditHelper.ts
  • src/utils/api/handlers/configHandlers.ts
  • src/utils/api/handlers/memoryHandlers.ts
  • src/utils/api/handlers/permissionHandlers.ts
  • src/utils/api/handlers/reactionRoleHandlers.ts
  • src/utils/api/handlers/rulesHandlers.ts
  • src/utils/api/handlers/setupHandlers.ts
  • src/utils/api/handlers/ticketHandlers.ts
  • src/utils/interactions/index.ts
  • src/utils/interactions/modalHelper.ts
  • tests/unit/utils/api/applicationHandlers.test.ts
  • tests/unit/utils/api/setupHandlers.test.ts
  • tests/unit/utils/api/ticketHandlers.test.ts

Comment thread src/commands/handlers/botSetup/systemFlows.ts Outdated
Comment thread src/utils/api/handlers/auditHelper.ts
- extractModalField now returns undefined for boolean (checkbox) values rather
  than stringifying them — a checkbox's `.value` is a boolean, and `String(false)`
  is the truthy "false". New `extractModalBoolean(fields, id, default?)` companion
  reads checkboxes correctly. Fixed the flagged systemFlows `setup_staff_enable`
  read (unchecking it was silently ignored) + unified the 7 other ad-hoc
  `getField(id)?.value as boolean` reads (drops the `as any` casts).
- writeAuditAction extracts triggeredBy TOLERANTLY (was via optionalString,
  which throws a 400 on a non-string). Audit logging is best-effort and runs
  after side effects, so it must never abort an already-completed request.
- +10 unit tests for extractModalField / extractModalBoolean (incl. the
  checkbox-footgun regression).

1294 tests pass; build + biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NindroidA

Copy link
Copy Markdown
Owner Author

Thanks @coderabbitai — both addressed in 90ffcc0:

  • Checkbox type (Critical): extractModalField now returns undefined for a boolean .value instead of stringifying it, and a new extractModalBoolean(fields, id, default?) companion reads checkboxes correctly. Fixed the flagged setup_staff_enable read (unchecking it was silently ignored — pre-existing, surfaced by this refactor) and unified the 7 other getField(id)?.value as boolean reads onto it (dropping the as any casts). +10 regression tests including the checkbox footgun.
  • writeAuditAction throw (Minor): now extracts triggeredBy tolerantly (a non-string value → treated as absent) instead of via optionalString, so the best-effort audit path — which runs after the action's side effects — never aborts an already-completed request with a 400.

1294 tests pass; build + biome clean.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@NindroidA Great work on both fixes — the extractModalBoolean companion is a clean solution for the checkbox footgun, and the tolerant triggeredBy extraction in writeAuditAction is the right call for a best-effort audit path that mustn't surface a 400 after side-effects have already committed.

(ʘ‿ʘ)ノ🐇


✏️ Learnings added
Learnt from: NindroidA
URL: https://github.com/NindroidA/cogworks-bot/pull/8

Timestamp: 2026-06-03T07:03:10.156Z
Learning: In `src/utils/interactions/modalHelper.ts` (and re-exported from `src/utils/interactions/index.ts`): `extractModalField(fields, customId)` returns `undefined` for boolean `.value` (checkbox components) — it does NOT stringify booleans. A separate `extractModalBoolean(fields, id, default?)` companion helper exists for reading checkbox/boolean modal fields correctly, replacing the previous `getField(id)?.value as boolean` / `as any` pattern.

Learnt from: NindroidA
URL: https://github.com/NindroidA/cogworks-bot/pull/8

Timestamp: 2026-06-03T07:03:10.156Z
Learning: In `src/utils/api/handlers/auditHelper.ts`: `writeAuditAction(guildId, body, action, details?)` extracts `triggeredBy` tolerantly — a non-string value is treated as absent — rather than via `optionalString`. This prevents a 400 error from aborting an already-completed request when `writeAuditAction` is called after side-effects have committed.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: NindroidA/cogworks-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-03T04:23:31.135Z
Learning: Applies to **/*.{ts,tsx} : Do not hardcode strings; use the `lang` module. Do not query without `guildId` filter. Do not delete DB records before Discord objects. Do not replace forum tags. Do not use deprecated `ephemeral: true`; use `flags: [MessageFlags.Ephemeral]`. Do not use `requireAdmin()` with truthy check; use `.allowed` property.

Learnt from: CR
Repo: NindroidA/cogworks-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-03T04:23:31.135Z
Learning: Applies to src/{utils/baitChannel,events}/**/*.{ts,tsx} : Bait Channel audit: Use `auditReason.ts` for structured audit reason. Use `retryQueue.ts` for 5s/30s/5min backoff + dead-letter. Handle `GuildAuditLogEntryCreate` event: patch `BaitChannelLog.discordAuditLogId`, `actionConfirmedAt`, `unbannedAt`, `unbannedBy`. Handle mod supersedes and unban via audit log entry.

Learnt from: CR
Repo: NindroidA/cogworks-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-03T04:23:31.135Z
Learning: Applies to src/utils/baitChannel/**/*.{ts,tsx} : Bait Channel v3.2.0: Use `BaitChannelManager.handleMessage` for message processing. Use `banExecutor.ts` (REST executor + idempotency) instead of `member.ban()` directly. Use `raidModeManager.ts` for sticky lockdown. Use `contentBurstDetector.ts` for cross-channel content burst detection. Requires `GatewayIntentBits.GuildModeration` intent.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@NindroidA NindroidA merged commit 4832ae2 into main Jun 3, 2026
2 checks passed
@NindroidA NindroidA deleted the v3.3.0-consolidation branch June 3, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant