refactor(ai): rewrite triage prompts and add channel context#248
refactor(ai): rewrite triage prompts and add channel context#248AnExiledDev wants to merge 22 commits intomainfrom
Conversation
|
This PR was not deployed automatically as @AnExiledDev does not have access to the Railway project. In order to get automatic PR deploys, please add @AnExiledDev to your workspace on Railway. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds channel name/topic metadata to triage buffers and context, updates prompt files (removes anti-abuse, tightens triage prompts, adjusts search guardrails), extends triage tests for channel-context and escaping, refactors the web dashboard config editor into modular section components, introduces config-normalization and immutable config-updates utilities plus many new section UIs and tests, and adds a docs-maintenance GitHub Actions workflow. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/prompts/triage-classify-system.md (1)
15-25:⚠️ Potential issue | 🟠 MajorSchema contract is stricter than parser recovery currently supports.
The prompt now requires
recommendedActionandviolatedRule, but truncated-response recovery insrc/modules/triage-parse.jsreconstructs onlyclassification,reasoning, andtargetMessageIds. That creates schema drift and can yield incomplete moderate decisions. Please align parser normalization/validation with this schema (including explicit null/default handling).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/prompts/triage-classify-system.md` around lines 15 - 25, The parser's truncated-response recovery in src/modules/triage-parse.js only reconstructs classification, reasoning, and targetMessageIds, causing schema drift because the prompt requires recommendedAction and violatedRule; update the recovery/normalization logic (the function that reconstructs parsed triage results / normalize/validate triage output) to always include recommendedAction and violatedRule fields: set both to null for classifications other than "moderate", and when classification === "moderate" ensure recommendedAction is one of the allowed actions (warn|timeout|kick|ban|delete) and violatedRule is a non-empty string or null fallback; also add a final validation step to enforce the required schema shape before returning the parsed object.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/maintain-docs.md:
- Line 65: Change the lowercase "markdown" to the proper noun "Markdown" in the
sentence "Ensure markdown formatting is correct" so it reads "Ensure Markdown
formatting is correct"; update that exact string in the workflow documentation
to capitalize Markdown.
- Line 56: Replace the hardcoded branch name string
"docs/maintain-docs-2026-03-04" with a dynamic placeholder or template variable
so each workflow run produces a current branch name; update the occurrence of
that literal in the workflow to reference a generated date/template (e.g., an
env or workflow expression like a DATE variable or run-id) and ensure the
workflow step that creates the branch sets that variable before using it.
- Line 1: The file begins with YAML frontmatter ('---') which triggers a
markdownlint "missing top-level heading" warning; to suppress it add a
markdownlint disable comment at the very top of the file (above the '---'), e.g.
insert <!-- markdownlint-disable-file MD025 --> (or include MD003/MD025 as
needed) so the linter ignores the missing top-level heading for this file.
- Around line 1-18: The workflow currently hardcodes the branch name
"docs/maintain-docs-2026-03-04" which will break future runs; update the job
that creates/pushes the branch to compute the date at runtime and use a dynamic
branch name (e.g., "docs/maintain-docs-<today>") instead of the static literal.
Locate the branch reference in the maintain-docs workflow (look for the literal
docs/maintain-docs-2026-03-04) and replace it with a runtime-generated value
using the shell date command or a workflow expression (for example derive
YYYY-MM-DD via date -u +%F or an actions/step output) so each run uses the
current date. Ensure the step that sets the branch and the push/create-branch
action consume this computed value consistently.
In `@src/prompts/triage-classify.md`:
- Around line 56-57: Update the `@mention` handling rule so that messages
containing `<@{{botUserId}}>` are no longer forced to `respond` unconditionally;
instead, evaluate moderation first and allow classification to be `moderate`
when content violates safety/policy, while still treating `ignore` as disallowed
for direct bot mentions. Concretely, modify the classification flow referenced
by the two rules ("@mention handling" and the "Volvox by name" guidance) to run
the moderation check before finalizing a `respond` decision for any message that
mentions the bot, and only prevent `ignore` as an outcome (i.e., map mention →
{moderate|respond}, not `ignore`). Ensure existing logic that favors responding
when "Volvox" appears remains contextual and also respects the pre-check
moderation step.
In `@src/prompts/triage-respond.md`:
- Around line 20-23: Clarify and unify the contradictory guidance under
"Response structure": change the rules to state that you should produce one
response per distinct topic, and map that response to the earliest
targetMessageId among messages that share that topic (i.e., if multiple
targetMessageId values discuss the same topic, collapse them into a single
response and assign the earliest targetMessageId as its identifier); update the
lines mentioning "one response per targetMessageId" and the collapsing rule so
they consistently read that responses are one per topic and are anchored to the
earliest targetMessageId when multiple IDs reference the same topic.
In `@tests/modules/triage-prompt.test.js`:
- Around line 168-278: The channel metadata (channelName and channelTopic) is
interpolated raw into the <channel-context> block in buildConversationText;
update buildConversationText to pass channelName and channelTopic through
escapePromptDelimiters() (the same sanitizer used for message content) before
rendering the Channel: and Topic: lines, and add unit tests that assert escaped
output when channelName/channelTopic contain special chars like '<', '>', '&' or
the sequence '</channel-context>' to prevent XML/prompt injection.
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 395-396: Remove the unnecessary Biome suppression comment before
the useEffect in config-editor.tsx: delete the line "// biome-ignore
lint/correctness/useExhaustiveDependencies: guildId IS necessary - effect must
re-run when guild changes" so the effect using useEffect(...) with guildId as a
dependency will be lint-checked normally; leave the useEffect and its dependency
array (including guildId) intact.
In `@web/src/components/dashboard/config-sections/AiAutoModSection.tsx`:
- Around line 92-99: The onChange handler for the threshold input uses
Number(e.target.value) which turns an empty string into 0 and causes unintended
writes; update the handler in AiAutoModSection.tsx (the onChange that calls
percentToDecimal and onFieldChange) to guard parsing: read e.target.value into a
variable, if it's empty or parseFloat yields NaN then do not call onFieldChange
(or pass undefined/null if your state expects that), otherwise convert the
parsed numeric value with percentToDecimal and call
onFieldChange({...thresholds, [cat]: v}); ensure you reference the same
variables/functions: e.target.value, percentToDecimal, onFieldChange,
thresholds, and cat.
In `@web/src/components/dashboard/config-sections/EngagementSection.tsx`:
- Around line 69-79: The remove button is icon-only and lacks an accessible
name; update the Button in EngagementSection (the Button with onClick that calls
onActivityBadgesChange and uses badges/filter) to provide a clear accessible
label (e.g., add aria-label or include visually hidden text) such as
aria-label={`Remove badge ${i + 1}`} or `Remove ${badge.name}` so assistive tech
can distinguish each row; ensure the label is unique/per-row and keep the
existing onClick, disabled, and variant props unchanged.
In `@web/src/components/dashboard/config-sections/ReputationSection.tsx`:
- Around line 129-137: The onBlur handler for thresholdsRaw currently parses and
sorts numbers then calls onFieldChange('levelThresholds', sorted) but does not
remove duplicates; update the logic in that handler (the block that computes
nums and sorted) to deduplicate parsed numeric thresholds before sorting (e.g.,
via a Set or filtering by first index) so you pass a sorted, unique array to
onFieldChange('levelThresholds'), preventing duplicate level boundaries.
- Around line 55-121: The two-column layout in ReputationSection is not
mobile-responsive: update the container for the inputs (the div using className
"grid grid-cols-2 gap-4") to use a single column on small screens and switch to
two columns on larger screens (e.g., "grid grid-cols-1 sm:grid-cols-2 gap-4" or
"grid grid-cols-1 md:grid-cols-2 gap-4"), and ensure child labels/inputs use
full width (w-full) if needed; locate this change in the ReputationSection
component where the XP and Announce Channel inputs are rendered and replace the
grid class accordingly.
In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Around line 14-16: Extract the duplicated inputClasses constant into a shared
module (e.g., create and export it from a new "@/lib/ui-constants.ts"), then
remove the local inputClasses declaration in StarboardSection.tsx and in
TriageSection.tsx and replace them with an import of the shared symbol (import {
inputClasses } from '@/lib/ui-constants'). Ensure the exported name matches
exactly (inputClasses) and update any references in both files to use the
imported constant so styling remains consistent and DRY.
In `@web/src/components/dashboard/config-sections/TicketsSection.tsx`:
- Around line 62-63: The grid in TicketsSection currently uses a fixed
two-column class on the div with className "grid grid-cols-2 gap-4"; change it
to be mobile-first by using a single-column default and switch to two columns at
a breakpoint (e.g., replace "grid grid-cols-2 gap-4" with "grid grid-cols-1
md:grid-cols-2 gap-4") so the form stacks on small screens and becomes
two-column on medium+ screens.
In `@web/src/components/dashboard/config-sections/WelcomeSection.tsx`:
- Around line 145-165: The code assumes WelcomeRoleOption.id always exists when
rendering and deleting roleMenuOptions (using opt.id for React key and in the
delete filter), so options with undefined id can be removed incorrectly; update
rendering to use a stable fallback key (e.g., use the loop index i when opt.id
is missing) and change the delete handler in the Button onClick to remove by
index when opt.id is undefined (rather than filtering by id equality) — update
the code paths around roleMenuOptions, opt.id, the JSX key, and the onClick
delete logic that calls onRoleMenuChange('options', ...) to ensure deletion
targets the exact item (use index-based removal for undefined ids, id-based
removal for defined ids).
In `@web/src/lib/config-normalization.ts`:
- Around line 79-80: The decimalToPercent function can return values outside
0–100 whereas percentToDecimal clamps input; update decimalToPercent to clamp
its computed percent to the 0–100 range (e.g., compute Math.round(decimal * 100)
and then clamp with Math.max(0, Math.min(100, value)) or a shared clamp helper)
so UI percentages cannot be out-of-range; modify the decimalToPercent
implementation accordingly and keep its name unchanged.
In `@web/src/lib/config-updates.ts`:
- Around line 120-123: The code creates arr by spreading cursor[lastKey] without
verifying it's an array, which will throw if the resolved value is non-array;
update the logic that defines arr (and the two other similar spots) to first
check Array.isArray(cursor[lastKey]) and only spread into arr when true,
otherwise set arr = [] (or handle as appropriate), and adjust the subsequent
index validation (the Number.isInteger(index) && index >= 0 && index <
arr.length) to operate on the guarded arr; ensure you apply the same
Array.isArray guard at the other occurrences that build arr from
cursor[lastKey].
- Around line 18-21: Narrow the generic for section keys so only object-like
config sections are allowed: replace K extends keyof GuildConfig in
updateSectionEnabled with a constrained type that only includes keys whose
GuildConfig[K] extends Record<string, unknown> (e.g., create a helper
ObjectKeys<T> = { [P in keyof T]: T[P] extends Record<string, unknown> ? P :
never }[keyof T] and use K extends ObjectKeys<GuildConfig>); apply the same
constraint to the other update helpers that accept a section key (the other
functions in this file using the K generic) so scalar keys cannot be passed and
the object-merge helpers cannot corrupt config shape.
In `@web/src/types/config.ts`:
- Line 153: The property alertChannelId was made optional but lost the ability
to represent explicit null from the API; update the type declaration for
alertChannelId in types/config.ts to allow null as a valid value (e.g., change
alertChannelId?: string to include null) so the TypeScript contract matches the
API nullable payload (reference the alertChannelId property in that file and
make it accept string | null while remaining optional).
In `@web/tests/components/dashboard/config-editor-autosave.test.tsx`:
- Around line 226-251: Tests for updateSectionEnabled, updateSectionField, and
updateNestedField don't assert immutability; add assertions that the original
inputs (baseConfig and configWithNested) remain unchanged and that nested
objects are not mutated. After calling updateSectionEnabled/updateSectionField,
assert baseConfig.ai?.enabled and baseConfig.ai?.systemPrompt retain their
original values and, where applicable, that result.ai !== baseConfig.ai
(different object). For updateNestedField, assert
configWithNested.moderation.rateLimit.maxMessages is still the original value
and that result.moderation !== configWithNested.moderation and
result.moderation.rateLimit !== configWithNested.moderation.rateLimit to ensure
no in-place mutation.
---
Outside diff comments:
In `@src/prompts/triage-classify-system.md`:
- Around line 15-25: The parser's truncated-response recovery in
src/modules/triage-parse.js only reconstructs classification, reasoning, and
targetMessageIds, causing schema drift because the prompt requires
recommendedAction and violatedRule; update the recovery/normalization logic (the
function that reconstructs parsed triage results / normalize/validate triage
output) to always include recommendedAction and violatedRule fields: set both to
null for classifications other than "moderate", and when classification ===
"moderate" ensure recommendedAction is one of the allowed actions
(warn|timeout|kick|ban|delete) and violatedRule is a non-empty string or null
fallback; also add a final validation step to enforce the required schema shape
before returning the parsed object.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 12053889-46cb-4743-9ad9-2beeeabf1103
📒 Files selected for processing (37)
.github/workflows/maintain-docs.mdsrc/modules/triage-buffer.jssrc/modules/triage-prompt.jssrc/modules/triage-respond.jssrc/modules/triage.jssrc/prompts/anti-abuse.mdsrc/prompts/community-rules.mdsrc/prompts/search-guardrails.mdsrc/prompts/triage-classify-system.mdsrc/prompts/triage-classify.mdsrc/prompts/triage-respond-system.mdsrc/prompts/triage-respond.mdtests/modules/triage-prompt.test.jstests/modules/triage.test.jsweb/next-env.d.tsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/lib/config-normalization.tsweb/src/lib/config-updates.tsweb/src/types/config.tsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/lib/config-normalization.test.tsweb/tests/lib/config-updates.test.ts
💤 Files with no reviewable changes (1)
- src/prompts/anti-abuse.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Docker Build Validation
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
web/src/components/dashboard/config-sections/ReputationSection.tsxsrc/modules/triage-buffer.jsweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/lib/config-normalization.tsweb/src/components/dashboard/config-sections/GitHubSection.tsxsrc/modules/triage-respond.jsweb/src/components/dashboard/config-sections/TriageSection.tsxsrc/modules/triage-prompt.jsweb/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/EngagementSection.tsxtests/modules/triage-prompt.test.jsweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxtests/modules/triage.test.jsweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/next-env.d.tsweb/src/types/config.tsweb/src/lib/config-updates.tsweb/tests/lib/config-normalization.test.tsweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/index.tssrc/modules/triage.jsweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-editor.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/lib/config-normalization.tsweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/next-env.d.tsweb/src/types/config.tsweb/src/lib/config-updates.tsweb/tests/lib/config-normalization.test.tsweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-editor.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsx
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/modules/triage-buffer.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.jstests/modules/triage-prompt.test.jstests/modules/triage.test.jssrc/modules/triage.js
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*methods
Use safe Discord message methods:safeReply(),safeSend(),safeEditReply()instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries
Files:
src/modules/triage-buffer.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.jssrc/modules/triage.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_CONFIG_KEYS
Files:
src/modules/triage-buffer.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.jssrc/modules/triage.js
tests/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Maintain 80% code coverage threshold minimum, never lower this threshold
Files:
tests/modules/triage-prompt.test.jstests/modules/triage.test.js
🧠 Learnings (8)
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/utils/reputationCache.js : Cache leaderboard, rank, and user reputation data with auto-invalidation on config changes
Applied to files:
web/src/components/dashboard/config-sections/ReputationSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Applied to files:
web/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/next-env.d.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/modules/**/*.{js,mjs} : Create new modules for features with corresponding config sections in `config.json` and entries in `SAFE_CONFIG_KEYS`
Applied to files:
web/src/lib/config-normalization.tsweb/tests/lib/config-updates.test.tsweb/src/lib/config-updates.tsweb/src/components/dashboard/config-sections/index.ts
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves
Applied to files:
web/tests/lib/config-updates.test.tsweb/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-01T06:03:04.239Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-01T06:03:04.239Z
Learning: For full project context, architecture, and coding guidelines, refer to AGENTS.md
Applied to files:
.github/workflows/maintain-docs.md
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/routes/**/*.{js,mjs} : Create API routes with proper authentication middleware, mount in `src/api/server.js`, and document in OpenAPI spec
Applied to files:
web/next-env.d.ts
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to tests/**/*.{js,mjs,jsx,ts,tsx} : Maintain 80% code coverage threshold minimum, never lower this threshold
Applied to files:
web/tests/lib/config-normalization.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/utils/discordCache.js : Cache Discord API responses for channels, roles, and members with auto-invalidation on config changes
Applied to files:
web/src/components/dashboard/config-editor.tsx
🪛 LanguageTool
.github/workflows/maintain-docs.md
[uncategorized] ~65-~65: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...ass 6. Quality Checks: - Ensure markdown formatting is correct - Verify all l...
(MARKDOWN_NNP)
src/prompts/triage-respond.md
[uncategorized] ~26-~26: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...nd under 2000 characters. - Use Discord markdown when helpful. - Mention users using the...
(MARKDOWN_NNP)
🪛 markdownlint-cli2 (0.21.0)
.github/workflows/maintain-docs.md
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🔇 Additional comments (28)
web/next-env.d.ts (1)
3-3: Looks correct — route typings reference points to the standard Next.js output path.This change is consistent with generated Next.js typed-route references and does not introduce functional risk.
As per coding guidelines, this remains aligned with the Next.js App Router web setup expectations.
src/modules/triage-buffer.js (1)
25-26: Good metadata extension for channel-aware triage.The added
channelNameandchannelTopicfields are clear and consistent with the new prompt-context flow.src/prompts/triage-respond-system.md (1)
15-55: Prompt rewrite is coherent and operationally safer.The classification-aware tone rules plus anti-abuse guardrails are clear and actionable.
src/prompts/community-rules.md (1)
2-14: Clear and practical rule framing.The rewritten list is concise and easier for moderation-time reference.
web/src/components/dashboard/config-sections/ChallengesSection.tsx (1)
44-84: Solid section extraction and field wiring.The component is cleanly structured, responsive, and consistent with the sectionized dashboard pattern.
web/src/components/dashboard/config-sections/AiSection.tsx (1)
61-84: Blocked-channel control is a strong functional addition.Nice integration of
ChannelSelectorwith graceful fallback when no guild is selected.web/src/components/dashboard/config-sections/GitHubSection.tsx (1)
23-70: Looks good — section wiring is clean and defensive.The defaults, guarded number parsing, and
savinggating are consistent and safe.web/src/components/dashboard/config-sections/PermissionsSection.tsx (1)
25-109: LGTM for this section.Role selection constraints and bot-owner parsing flow are implemented coherently.
web/src/components/dashboard/config-sections/ModerationSection.tsx (1)
139-337: Nice expansion of moderation controls.The new sub-sections are wired coherently and keep updates scoped through dedicated handlers.
src/modules/triage-respond.js (1)
71-80: Good additive context enrichment.Including
channelName/channelTopichere is low-risk and keeps fetch/error behavior unchanged.src/modules/triage.js (1)
660-661: LGTM!Channel metadata is correctly captured from the Discord message object with appropriate null fallbacks. This aligns well with the corresponding changes in
triage-prompt.jsandtriage-respond.jsfor building the<channel-context>block.src/modules/triage-prompt.js (1)
52-63: LGTM — channel-context extraction is well-structured.The implementation correctly:
- Prioritizes buffer entries over context (buffer first in spread)
- Only renders the block when channelName is present
- Conditionally includes Topic when available
Note:
channelNameis not passed throughescapePromptDelimiters(), but Discord channel names have strict character restrictions (alphanumeric, hyphens, underscores) that exclude<and>, so this is safe.tests/modules/triage.test.js (3)
105-106: LGTM!Importing
channelBuffersenables direct inspection of buffer state, which is appropriate for verifying that channel metadata is correctly stored in buffer entries.
172-176: LGTM!The
makeMessagehelper correctly propagateschannelNameandchannelTopicfrom extras to the mock channel object, with sensible defaults ('test-channel'andnull).
409-420: LGTM!Good test coverage for the new channel metadata fields. The test correctly verifies that
channelNameandchannelTopicare preserved in buffer entries afteraccumulateMessage.web/tests/lib/config-normalization.test.ts (1)
1-188: LGTM — comprehensive test coverage for config normalization utilities.The tests thoroughly cover:
- Edge cases (empty strings, whitespace, NaN)
- Boundary conditions (min/max clamping, negative values)
- Normal operations (parsing, formatting, conversions)
Good coverage of the utility functions that support the config editor components.
web/tests/lib/config-updates.test.ts (1)
1-178: LGTM — solid test coverage for immutable config update utilities.The tests effectively verify:
- Immutability guarantees (original config not mutated)
- Section/field creation when missing
- Array operations (update, remove, append) with edge cases
- Preservation of sibling fields during updates
Good use of a shared
baseConfigfixture with nested structures to exercise the utilities.web/src/components/dashboard/config-sections/TriageSection.tsx (2)
15-17: Good DRY pattern for input styling.Extracting
inputClassesas a constant avoids repetition and ensures consistent styling across all inputs in the section.
201-221: LGTM — new controls align with TriageConfig interface.The
statusReactionstoggle andmoderationLogChannelinput correctly bind to fields defined in theTriageConfiginterface. The input normalization (trim() || null) appropriately converts empty strings tonull.src/prompts/search-guardrails.md (1)
1-25: LGTM — well-balanced search guardrails.The updated guidance effectively:
- Defines clear criteria for when searches are appropriate
- Maintains the 1-2 search limit
- Adds abuse detection with a firm but friendly response
- Preserves the JSON output structure requirement
The shift from overly restrictive to context-aware guidance should improve response quality while still preventing misuse.
web/src/components/dashboard/config-sections/StarboardSection.tsx (2)
24-132: Overall implementation looks good.The component correctly handles:
- Numeric input parsing with
parseNumberInput- Emoji field with "Any" quick-set functionality
- Comma-separated list parsing for ignored channels
- Consistent disabled state during saves
8-12: StarboardSection intentionally uses onFieldChange for the enabled toggle, unlike TriageSection.TriageSection and MemorySection have both
onEnabledChangeandonFieldChangeprops and useonEnabledChangefor the toggle (line 45 in TriageSection). StarboardSection only hasonFieldChangeand uses it for the enabled toggle (line 35). The parent component (config-editor.tsx, line 648–652) does not passonEnabledChangeto StarboardSection, which suggests this is intentional. GitHubSection follows the same pattern as StarboardSection. If this difference is intentional, no changes are needed; if all section components should follow the same pattern, addonEnabledChangeto the props interface and update the parent component accordingly.web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx (1)
42-75: Clean section extraction and toggle wiring look good.This component is cohesive, readable, and correctly binds per-feature enabled flags with safe fallbacks.
web/src/components/dashboard/config-sections/MemorySection.tsx (1)
47-70: Memory controls are implemented cleanly with good input guarding.The section uses clear defaults and safe numeric parsing, which keeps state updates predictable.
web/src/components/dashboard/config-editor.tsx (3)
439-464: Good abstraction for section mutations.
createSectionUpdatermeaningfully reduces duplicated update boilerplate and keeps section wiring consistent.
74-75:aiAutoModsection is correctly added toSAFE_CONFIG_KEYS.Verified that
aiAutoModis present in the backend writable config keys (SAFE_CONFIG_KEYS) atsrc/api/utils/configAllowlist.js. The new section can be persisted via PATCH calls without API rejection.
347-355: Parallel section PATCHes are safe from write clobbering.The backend config API uses PostgreSQL transactions with row-level locking (
FOR UPDATE) to serialize concurrent writes to the same section. Each section is stored as an independent row, so parallel PATCH requests targeting different sections proceed concurrently without conflict, while requests targeting the same section are serialized by the database row lock. This approach prevents the read-modify-write race condition you were concerned about.web/src/components/dashboard/config-sections/index.ts (1)
1-14: Barrel exports look clean and consistent.This is a good consolidation point for section components and improves maintainability of dashboard imports.
|
Note Docstrings generation - SUCCESS |
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (5)
web/src/lib/config-normalization.ts (1)
79-80:⚠️ Potential issue | 🟡 MinorClamp and sanitize
decimalToPercent.Line 80 can return out-of-range values (or
NaN), unlikepercentToDecimal.Proposed fix
export function decimalToPercent(decimal: number): number { - return Math.round(decimal * 100); + if (Number.isNaN(decimal)) return 0; + const clamped = Math.min(1, Math.max(0, decimal)); + return Math.round(clamped * 100); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-normalization.ts` around lines 79 - 80, decimalToPercent currently returns Math.round(decimal * 100) which can produce NaN or values outside 0–100; update the decimalToPercent function to first coerce/sanitize the input (e.g., ensure Number.isFinite(decimal)), compute Math.round(decimal * 100), then clamp the result to the inclusive range 0 to 100 before returning (mirror the validation approach used in percentToDecimal) so callers always get a finite percent between 0 and 100.web/src/components/dashboard/config-sections/WelcomeSection.tsx (1)
159-160:⚠️ Potential issue | 🟠 MajorHandle role-option identity safely when
idis missing.Line 160 uses
opt.idas the React key and Line 178 deletes byidonly. Missing IDs can collide keys and remove multiple rows unintentionally. Use index fallback for keying/removal whenidis absent.Proposed fix
- {roleMenuOptions.map((opt, i) => ( - <div key={opt.id} className="flex flex-col gap-2 rounded-md border p-2"> + {roleMenuOptions.map((opt, i) => ( + <div key={opt.id ?? `role-option-${i}`} className="flex flex-col gap-2 rounded-md border p-2"> @@ - onClick={() => { - const opts = roleMenuOptions.filter((o) => o.id !== opt.id); - onRoleMenuChange('options', opts); - }} + onClick={() => { + const opts = roleMenuOptions.filter((o, idx) => + opt.id ? o.id !== opt.id : idx !== i, + ); + onRoleMenuChange('options', opts); + }}Also applies to: 177-180
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/WelcomeSection.tsx` around lines 159 - 160, The map over roleMenuOptions uses opt.id as the React key and the delete path removes items by id, which breaks when id is missing; adjust the mapping to use key={opt.id ?? i} (i is the map index) and update the delete handler (the function invoked on the delete button—e.g., removeRoleOption / onDeleteRoleOption) to accept an index fallback: if opt.id exists delete by id, otherwise delete by the provided index. Also update the delete button call site in the roleMenuOptions.map to pass the index when calling the delete handler so removal is deterministic when ids are absent.web/src/components/dashboard/config-sections/TicketsSection.tsx (1)
69-70:⚠️ Potential issue | 🟠 MajorUse a mobile-first grid and responsive span for transcript channel.
Line 69 should default to one column on small screens, and Line 129 should only span two columns on medium+.
Proposed fix
- <div className="grid grid-cols-2 gap-4"> + <div className="grid grid-cols-1 gap-4 md:grid-cols-2"> @@ - <label className="space-y-2 col-span-2"> + <label className="space-y-2 md:col-span-2">As per coding guidelines
web/**/*.{ts,tsx}: Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design.Also applies to: 129-129
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/TicketsSection.tsx` around lines 69 - 70, The TicketsSection grid is not mobile-first: change the container's Tailwind classes on the div with className "grid grid-cols-2 gap-4" to default to one column and switch to two on medium screens (use "grid grid-cols-1 md:grid-cols-2 gap-4"), and update the transcript channel control (the element currently spanning two columns) to only span on medium+ screens by replacing its span class (e.g., "col-span-2") with "md:col-span-2" so it stacks on small screens and spans two columns on md and up.web/src/lib/config-updates.ts (2)
122-123:⚠️ Potential issue | 🟠 MajorGuard
cursor[lastKey]before array spread in path-based updates.At Line 122, Line 173, and Line 227, spread assumes
cursor[lastKey]is iterable. If a bad path resolves to a non-array object, this throws and breaks config editing.Proposed fix
- const arr = [...((cursor[lastKey] as T[]) || [])]; + const current = cursor[lastKey]; + if (current !== undefined && !Array.isArray(current)) return config; + const arr = [...((current as T[] | undefined) ?? [])]; @@ - const arr = [...((cursor[lastKey] as unknown[]) || [])]; + const current = cursor[lastKey]; + if (current !== undefined && !Array.isArray(current)) return config; + const arr = [...((current as unknown[] | undefined) ?? [])]; @@ - const arr = [...((cursor[lastKey] as T[]) || []), item]; + const current = cursor[lastKey]; + if (current !== undefined && !Array.isArray(current)) return config; + const arr = [...((current as T[] | undefined) ?? []), item];Also applies to: 173-174, 227-227
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-updates.ts` around lines 122 - 123, The spread expression assumes cursor[lastKey] is iterable and can throw if it's a non-array; guard it by checking Array.isArray(cursor[lastKey]) and only spread when true, otherwise use an empty array (e.g., const arr = Array.isArray(cursor[lastKey]) ? [...cursor[lastKey]] : []). Apply the same fix to the other occurrences that use the same pattern (the other spots where arr is created from cursor[lastKey]) so all path-based updates safely handle non-array values.
18-21:⚠️ Potential issue | 🟠 MajorConstrain
sectionto object-like config sections.At Line 18, Line 41, Line 68, Line 102, Line 154, and Line 208,
sectionis typed askeyof GuildConfig, which still allows scalar keys (for exampleguildId) into object-merge helpers and can silently corrupt shape.Proposed fix
+type ObjectSectionKey = { + [K in keyof GuildConfig]: + GuildConfig[K] extends Record<string, unknown> | undefined ? K : never; +}[keyof GuildConfig]; + -export function updateSectionEnabled<K extends keyof GuildConfig>( +export function updateSectionEnabled<K extends ObjectSectionKey>( config: GuildConfig, section: K, enabled: boolean, ): GuildConfig { @@ -export function updateSectionField<K extends keyof GuildConfig>( +export function updateSectionField<K extends ObjectSectionKey>( config: GuildConfig, section: K, field: string, value: unknown, ): GuildConfig { @@ -export function updateNestedField<K extends keyof GuildConfig>( +export function updateNestedField<K extends ObjectSectionKey>( config: GuildConfig, section: K, nestedKey: string, field: string, value: unknown, ): GuildConfig { @@ -export function updateArrayItem<T>( +export function updateArrayItem<T, K extends ObjectSectionKey>( config: GuildConfig, - section: keyof GuildConfig, + section: K, path: string[], index: number, item: T, ): GuildConfig { @@ -export function removeArrayItem( +export function removeArrayItem<K extends ObjectSectionKey>( config: GuildConfig, - section: keyof GuildConfig, + section: K, path: string[], index: number, ): GuildConfig { @@ -export function appendArrayItem<T>( +export function appendArrayItem<T, K extends ObjectSectionKey>( config: GuildConfig, - section: keyof GuildConfig, + section: K, path: string[], item: T, ): GuildConfig {Also applies to: 41-44, 68-71, 102-103, 154-155, 208-209
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-updates.ts` around lines 18 - 21, Constrain the generic section key to only object-like sections to avoid merging scalars: add a utility type like "type ObjectSectionKeys = { [P in keyof GuildConfig]: GuildConfig[P] extends object ? P : never }[keyof GuildConfig]" and change signatures from "<K extends keyof GuildConfig>" to "<K extends ObjectSectionKeys>" for updateSectionEnabled and the other helper functions in this file that accept a section parameter so the compiler enforces that GuildConfig[K] is an object before performing object-merge operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/modules/triage-prompt.js`:
- Around line 55-57: The prompt assembly inserts unescaped channel metadata
(channelEntry.channelName and channelEntry.channelTopic) into the prompt, which
can break prompt structure; update the code in src/modules/triage-prompt.js
where text is built (the lines using Channel: #${channelEntry.channelName} and
Topic: ${channelEntry.channelTopic}) to pass both values through
escapePromptDelimiters() before interpolation (e.g., use
escapePromptDelimiters(channelEntry.channelName) and
escapePromptDelimiters(channelEntry.channelTopic)) so any prompt delimiters are
neutralized.
In `@src/modules/triage-respond.js`:
- Around line 54-57: The JSDoc for the function that takes channelId,
bufferSnapshot, client and limit is missing documentation for the client
parameter; update that JSDoc block (the same block documenting channelId,
bufferSnapshot and limit) to add a `@param` entry for client (e.g., `@param`
{Object} client - API client instance used to fetch channel history / make
requests), ensuring the name matches the function signature so tooling and
maintainers see the full contract for the function that accepts channelId,
bufferSnapshot, client and limit.
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Line 75: The label wrapping the timezone input in the ChallengesSection
component currently uses an unconditional "col-span-2" which creates an extra
column on mobile; update the label's className to use a responsive span (e.g.,
replace "col-span-2" with "md:col-span-2") so the span only applies at medium+
breakpoints where the grid has two columns.
In `@web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx`:
- Around line 67-75: The checked prop is using a broad type assertion for
draftConfig entries; define a stricter type for feature entries (e.g., an
interface FeatureConfig { enabled?: boolean } and/or a mapped type on
GuildConfig for feature keys) and update draftConfig's type so the cast can be
removed. Locate ToggleSwitch usage in CommunityFeaturesSection, remove the "as {
enabled?: boolean } | undefined" assertion, and ensure onToggleChange and any
feature key types (key variable / feature key union) are updated to match the
new FeatureConfig/GuildConfig mapping so TypeScript can infer
(draftConfig[key]?.enabled) without casts.
In `@web/src/components/dashboard/config-sections/GitHubSection.tsx`:
- Line 43: The grid in GitHubSection.tsx currently forces two columns with the
class "grid grid-cols-2 gap-4"; change it to a mobile-first responsive grid by
defaulting to one column and switching to two at the md breakpoint (use the
pattern from WelcomeSection/StarboardSection/ChallengesSection), i.e., replace
the hard-coded two-column class with the responsive classes that set grid-cols-1
and md:grid-cols-2 while keeping gap-4 so the layout is single-column on mobile
and two-column on medium+ screens.
In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Around line 112-126: The Ignored Channels input currently normalizes on every
keystroke (in StarboardSection) which breaks typing; introduce a local piece of
state (e.g., ignoredChannelsRaw) initialized from
draftConfig.starboard?.ignoredChannels.join(', ') and kept in sync via
useEffect, update the input's value and onChange to modify ignoredChannelsRaw
only, and onBlur parse ignoredChannelsRaw with the existing
parseCommaSeparatedList (from '@/lib/config-normalization') and call
onFieldChange('ignoredChannels', parsedArray); ensure you still handle updates
to draftConfig by updating ignoredChannelsRaw when
draftConfig.starboard?.ignoredChannels changes.
In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Around line 223-224: The frontend writes null for empty moderationLogChannel
(onFieldChange('moderationLogChannel', ... || null)) but the TS type declares
moderationLogChannel as string; update the type of moderationLogChannel in
types/config (the exported config type/interface) to string | null to match
backend nullable schema, or if you prefer to keep it as string, change the input
handler in TriageSection (where draftConfig.triage?.moderationLogChannel is
used) to never assign null (remove the || null and use an empty string). Ensure
both the type and the value assignment for moderationLogChannel (and any
consumers) are consistent.
---
Duplicate comments:
In `@web/src/components/dashboard/config-sections/TicketsSection.tsx`:
- Around line 69-70: The TicketsSection grid is not mobile-first: change the
container's Tailwind classes on the div with className "grid grid-cols-2 gap-4"
to default to one column and switch to two on medium screens (use "grid
grid-cols-1 md:grid-cols-2 gap-4"), and update the transcript channel control
(the element currently spanning two columns) to only span on medium+ screens by
replacing its span class (e.g., "col-span-2") with "md:col-span-2" so it stacks
on small screens and spans two columns on md and up.
In `@web/src/components/dashboard/config-sections/WelcomeSection.tsx`:
- Around line 159-160: The map over roleMenuOptions uses opt.id as the React key
and the delete path removes items by id, which breaks when id is missing; adjust
the mapping to use key={opt.id ?? i} (i is the map index) and update the delete
handler (the function invoked on the delete button—e.g., removeRoleOption /
onDeleteRoleOption) to accept an index fallback: if opt.id exists delete by id,
otherwise delete by the provided index. Also update the delete button call site
in the roleMenuOptions.map to pass the index when calling the delete handler so
removal is deterministic when ids are absent.
In `@web/src/lib/config-normalization.ts`:
- Around line 79-80: decimalToPercent currently returns Math.round(decimal *
100) which can produce NaN or values outside 0–100; update the decimalToPercent
function to first coerce/sanitize the input (e.g., ensure
Number.isFinite(decimal)), compute Math.round(decimal * 100), then clamp the
result to the inclusive range 0 to 100 before returning (mirror the validation
approach used in percentToDecimal) so callers always get a finite percent
between 0 and 100.
In `@web/src/lib/config-updates.ts`:
- Around line 122-123: The spread expression assumes cursor[lastKey] is iterable
and can throw if it's a non-array; guard it by checking
Array.isArray(cursor[lastKey]) and only spread when true, otherwise use an empty
array (e.g., const arr = Array.isArray(cursor[lastKey]) ? [...cursor[lastKey]] :
[]). Apply the same fix to the other occurrences that use the same pattern (the
other spots where arr is created from cursor[lastKey]) so all path-based updates
safely handle non-array values.
- Around line 18-21: Constrain the generic section key to only object-like
sections to avoid merging scalars: add a utility type like "type
ObjectSectionKeys = { [P in keyof GuildConfig]: GuildConfig[P] extends object ?
P : never }[keyof GuildConfig]" and change signatures from "<K extends keyof
GuildConfig>" to "<K extends ObjectSectionKeys>" for updateSectionEnabled and
the other helper functions in this file that accept a section parameter so the
compiler enforces that GuildConfig[K] is an object before performing
object-merge operations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: acc2ee14-33d0-4be6-8082-b08f28c7c90d
📒 Files selected for processing (14)
src/modules/triage-prompt.jssrc/modules/triage-respond.jsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/lib/config-normalization.tsweb/src/lib/config-updates.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/modules/triage-respond.jssrc/modules/triage-prompt.js
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
src/modules/triage-respond.jsweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/lib/config-normalization.tsweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/lib/config-updates.tssrc/modules/triage-prompt.jsweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsx
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*methods
Use safe Discord message methods:safeReply(),safeSend(),safeEditReply()instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries
Files:
src/modules/triage-respond.jssrc/modules/triage-prompt.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_CONFIG_KEYS
Files:
src/modules/triage-respond.jssrc/modules/triage-prompt.js
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/lib/config-normalization.tsweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsx
🧠 Learnings (5)
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Applied to files:
web/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/modules/**/*.{js,mjs} : Create new modules for features with corresponding config sections in `config.json` and entries in `SAFE_CONFIG_KEYS`
Applied to files:
web/src/lib/config-updates.tsweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves
Applied to files:
web/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/**/*.{js,mjs,jsx,ts,tsx} : Use safe Discord message methods: `safeReply()`, `safeSend()`, `safeEditReply()` instead of direct Discord.js methods
Applied to files:
src/modules/triage-prompt.js
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/utils/discordCache.js : Cache Discord API responses for channels, roles, and members with auto-invalidation on config changes
Applied to files:
web/src/components/dashboard/config-editor.tsx
🔇 Additional comments (5)
web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx (1)
1-81: LGTM!The component is well-structured with:
- Correct
'use client'directive for Next.js App Router- Clean separation of feature definitions via
COMMUNITY_FEATURESarray withas const- Proper TypeScript interface for props
- Comprehensive JSDoc documentation
- Consistent code style (single quotes, semicolons, 2-space indentation)
- Accessible toggle switches with labels
src/modules/triage-respond.js (1)
82-83: Good channel metadata propagation with null fallbacks.Lines 82-83 correctly include
channelName/channelTopicand gracefully default tonullwhen unavailable.src/modules/triage-prompt.js (1)
109-118: Prompt interpolation inputs look consistent with template placeholders.The responder prompt variable set is coherent after anti-abuse consolidation, and required sections (
systemPrompt,communityRules,conversationText,classification,reasoning,targetMessageIds,memoryContext,searchGuardrails) are still populated.web/src/components/dashboard/config-sections/TriageSection.tsx (1)
94-176: Nice improvement usingparseNumberInputfor numeric fields.The handlers now consistently normalize/clamp numeric text input before state updates, which reduces invalid config writes.
web/src/components/dashboard/config-editor.tsx (1)
455-480: Good consolidation of section update plumbing.
createSectionUpdaterat Line 456-Line 467 removes repeated mutation boilerplate and keeps section writes funneled through shared immutable helpers.
web/src/components/dashboard/config-sections/ChallengesSection.tsx
Outdated
Show resolved
Hide resolved
web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx
Show resolved
Hide resolved
web/src/components/dashboard/config-sections/StarboardSection.tsx
Outdated
Show resolved
Hide resolved
Signed-off-by: Bill Chirico <bill@chirico.dev>
* refactor(web): replace channel/role ID inputs with selectors * fix(web): align alertChannelId type with usage * fix(ci,web): handle missing Railway token and normalize cleared channel values * feat(bot): support API-only mode for Railway preview environments * fix: address PR #242 review issues and Railway preview behavior * Delete .github/workflows/railway-preview.yml Signed-off-by: Bill Chirico <bill@chirico.dev> * chore(pr242): remove railway preview changes from selector refactor * chore(pr242): remove railway workflow deletion from PR --------- Signed-off-by: Bill Chirico <bill@chirico.dev> Co-authored-by: Bill <bill@example.com> Co-authored-by: Bill Chirico <bill@volvox.gg>
…re (#244) * refactor(config): add normalization utilities for config editor * refactor(config): add config update utilities with tests * refactor(config): extract section components from config-editor * refactor(config): simplify config-editor.tsx to orchestration layer * fix: address PR #244 review comments (#245) * fix(sections): responsive grid layouts * fix(config-editor): clear undo snapshot on guild change * fix(config-updates): correct buildPath for deep nesting * fix(sections): use onBlur for comma-separated inputs --------- Co-authored-by: Bill <bill@example.com> * fix: address remaining PR #244 review comments - AiAutoModSection: render even when aiAutoMod is missing - EngagementSection: use stable keys for badge rows - StarboardSection: fix default Any button active state - TriageSection: normalize moderationLogChannel before patch - WelcomeSection: read dmSteps from event target in onBlur - config-updates: validate array index bounds before mutations --------- Co-authored-by: Bill <bill@example.com>
Rewrite all triage prompts (classifier + responder) for clearer structure, better tone guidance, and channel-aware decisions. - Add channelName/channelTopic to buffer entries and context messages - Render <channel-context> block in conversation text - Rewrite classifier prompts: evaluation order, Volvox name nuance, restraint posture, channel-context awareness - Rewrite responder prompts: classification-context section, structured response-rules, baked-in anti-abuse - Rewrite search-guardrails and community-rules for clarity - Delete anti-abuse.md, fold content into system prompt - Remove antiAbuse template variable from buildRespondPrompt() - Add tests for channel-context rendering and buffer metadata
Docstrings generation was requested by @BillChirico. The following files were modified: * `src/modules/triage-prompt.js` * `src/modules/triage-respond.js` * `web/src/components/dashboard/config-editor.tsx` * `web/src/components/dashboard/config-sections/ChallengesSection.tsx` * `web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx` * `web/src/components/dashboard/config-sections/GitHubSection.tsx` * `web/src/components/dashboard/config-sections/MemorySection.tsx` * `web/src/components/dashboard/config-sections/PermissionsSection.tsx` * `web/src/components/dashboard/config-sections/StarboardSection.tsx` * `web/src/components/dashboard/config-sections/TicketsSection.tsx` * `web/src/components/dashboard/config-sections/TriageSection.tsx` * `web/src/components/dashboard/config-sections/WelcomeSection.tsx` * `web/src/lib/config-normalization.ts` * `web/src/lib/config-updates.ts` These files were kept as they were: * `src/modules/triage.js` * `web/src/components/dashboard/config-sections/AiAutoModSection.tsx` * `web/src/components/dashboard/config-sections/AiSection.tsx` * `web/src/components/dashboard/config-sections/EngagementSection.tsx` * `web/src/components/dashboard/config-sections/ModerationSection.tsx` * `web/src/components/dashboard/config-sections/ReputationSection.tsx` These files were ignored: * `tests/modules/triage-prompt.test.js` * `tests/modules/triage.test.js` * `web/tests/components/dashboard/config-editor-autosave.test.tsx` * `web/tests/lib/config-normalization.test.ts` * `web/tests/lib/config-updates.test.ts` These file types are not supported: * `.github/workflows/maintain-docs.md` * `src/prompts/community-rules.md` * `src/prompts/search-guardrails.md` * `src/prompts/triage-classify-system.md` * `src/prompts/triage-classify.md` * `src/prompts/triage-respond-system.md` * `src/prompts/triage-respond.md`
…y response structure - Escape channelName and channelTopic with escapePromptDelimiters() to prevent prompt injection via malicious channel names or topics - triage-classify: make @mention a tiebreaker not an override — moderate still takes precedence for harmful/abusive/scam content even when bot is @mentioned - triage-respond: rewrite contradictory response-count rules to be unambiguous (one response per targetMessageId; collapse multiple targets into one if needed) - triage-respond.js: add missing @param {Client} client JSDoc and fix return type to Promise<Array<Object>> - tests: add channel metadata escaping tests (channelName and channelTopic with tag-like chars must be escaped in the prompt output) Fixes items 1, 2, 11, 23 from PR review.
…capitalize Markdown - Add '# Maintain Docs' heading at top (markdownlint MD041) - Replace hardcoded 'docs/maintain-docs-2026-03-04' branch with template placeholder 'docs/maintain-docs-YYYY-MM-DD' plus explanatory comment (prevents daily cron branch collisions) - Capitalize 'Markdown' in quality checks step Fixes items 3, 16, 17 from PR review.
…uildConfigSectionKey, array guards, decimalToPercent clamp - config.ts: alertChannelId?: string | null (API contract includes null) - config.ts: moderationLogChannel: string | null (component writes null when cleared) - config-updates.ts: introduce GuildConfigSectionKey type to restrict section helpers to object-valued keys only (excludes scalar 'guildId') - config-updates.ts: add Array.isArray guards before spreads in updateArrayItem, removeArrayItem, appendArrayItem to prevent throwing on non-array values - config-normalization.ts: clamp decimalToPercent to [0, 100] matching percentToDecimal Fixes items 9, 10, 15, 8, 21 from PR review.
…r GuildConfigSectionKey - Create web/src/components/dashboard/config-sections/shared.ts with shared inputClasses constant (was duplicated in StarboardSection and TriageSection) - StarboardSection: import from shared.ts, add local state for ignoredChannels to buffer raw comma-separated input and only parse on blur (fixes mid-type commit) - TriageSection: import inputClasses from shared.ts - config-editor: import and use GuildConfigSectionKey in createSectionUpdater, restore intentional biome-ignore with clearer comment (guildId triggers guild-change reset) Fixes items 20, 14, 22 from PR review.
…bel, stricter types
- AiAutoModSection: guard threshold onChange against empty input (Number('') = 0
was silently writing 0% config on field clear)
- ReputationSection: grid-cols-2 → grid-cols-1 sm:grid-cols-2 (mobile responsive)
- ReputationSection: deduplicate level thresholds after sort to avoid ambiguous
level boundaries (filter same adjacent values)
- TicketsSection: grid-cols-2 → grid-cols-1 sm:grid-cols-2 (mobile responsive)
- WelcomeSection: fix role-option key (id ?? 'role-option-${i}') and delete logic
(index-based filter when id is undefined, avoids deleting all id-less options)
- ChallengesSection: add htmlFor/id to timezone label/input (accessibility)
- GitHubSection: grid-cols-2 → grid-cols-1 sm:grid-cols-2 (mobile responsive)
- EngagementSection: add aria-label to remove badge button
- CommunityFeaturesSection: use ToggleSectionConfig type instead of
as { enabled?: boolean } | undefined
Fixes items 4, 5, 6, 7, 12, 13, 18, 19, 25 from PR review.
Verify that source config objects are not mutated by updateSectionEnabled, updateSectionField, and updateNestedField after each update call. Fixes item 24 from PR review.
…ection, TriageSection, and tests
010e460 to
71f8f3b
Compare
|
Claude finished @BillChirico's task in 4m 1s —— View job Code Review — PR #248
|
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/components/dashboard/config-editor.tsx (1)
344-365:⚠️ Potential issue | 🟠 MajorA failed section can already be partially persisted.
sendSection()still emits one PATCH per field. If a later PATCH in the same section fails, earlier ones may already be committed server-side, but the downstream bookkeeping treats that section as wholly failed and leavessavedConfiguntouched for it. That desynchronizes the editor from the backend and makes retry/discard operate on stale state. Please batch each section into a single request, or reconcile the successfully applied prefix before continuing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-editor.tsx` around lines 344 - 365, sendSection currently issues one PATCH per field causing partial commits; change it to send the entire sectionPatches array in a single PATCH request (replace the per-patch loop in sendSection with one fetch to `/api/guilds/${encodeURIComponent(guildId)}/config` whose body is JSON.stringify(sectionPatches)), keep headers, cache, and signal, and preserve the 401 handling (call saveAbortController.abort(), redirect, throw). After a successful response update the downstream bookkeeping (savedConfig) for that section using the new values returned or by applying sectionPatches atomically so the editor state matches the backend; on non-ok responses parse the error body as before and throw. Ensure sendSection, guildId, saveAbortController, and savedConfig are the referenced symbols updated.
♻️ Duplicate comments (6)
web/src/lib/config-updates.ts (2)
241-243:⚠️ Potential issue | 🟠 MajorDo not overwrite an existing non-array in
appendArrayItem().
appendArrayItem()currently treats any non-array value as[], so a bad path can silently replace an object or string with a new array.updateArrayItem()andremoveArrayItem()already bail out on non-arrays; this helper should preserve that behavior and only synthesize[]when the target is missing.Suggested fix
const lastKey = path[path.length - 1]; - const arr = [...(Array.isArray(cursor[lastKey]) ? (cursor[lastKey] as T[]) : []), item]; + const current = cursor[lastKey]; + if (current !== undefined && !Array.isArray(current)) { + return config; + } + const arr = [...((current as T[] | undefined) ?? []), item];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-updates.ts` around lines 241 - 243, The appendArrayItem() helper currently spreads a non-array into [] and overwrites existing non-array values; change it to first inspect cursor[lastKey]: if cursor[lastKey] === undefined then treat it as [] and append the item, but if cursor[lastKey] exists and !Array.isArray(cursor[lastKey]) then bail out (like updateArrayItem() and removeArrayItem()) rather than creating an array; update the logic around lastKey/arr to only synthesize an array for missing keys and preserve non-array values by returning/aborting when a non-array is encountered.
107-110:⚠️ Potential issue | 🟠 MajorNarrow the array helpers to
GuildConfigSectionKeytoo.These helpers still accept
keyof GuildConfig, so callers can pass'guildId'. Once that happens, the spread/rebuild logic turns a scalar into an object/array and corrupts the config shape instead of safely no-oping.Suggested fix
-export function updateArrayItem<T>( +export function updateArrayItem<T, K extends GuildConfigSectionKey>( config: GuildConfig, - section: keyof GuildConfig, + section: K, path: string[], index: number, item: T, ): GuildConfig {-export function removeArrayItem( +export function removeArrayItem<K extends GuildConfigSectionKey>( config: GuildConfig, - section: keyof GuildConfig, + section: K, path: string[], index: number, ): GuildConfig {-export function appendArrayItem<T>( +export function appendArrayItem<T, K extends GuildConfigSectionKey>( config: GuildConfig, - section: keyof GuildConfig, + section: K, path: string[], item: T, ): GuildConfig {Also applies to: 163-166, 221-224
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-updates.ts` around lines 107 - 110, The array/object helper functions accept too-broad keys (keyof GuildConfig) allowing scalar keys like "guildId" which corrupts the config; narrow their section parameter type to the specific section key union (GuildConfigSectionKey) for all helpers (e.g., updateArrayItem, updateObjectItem, and the other update helpers referenced) so callers can only pass valid section keys and the spread/rebuild logic won't convert scalars into objects/arrays; update the function signatures to use GuildConfigSectionKey for the section parameter and update any related overloads/usages to match.src/prompts/triage-respond.md (1)
20-22:⚠️ Potential issue | 🟠 MajorKeep the responder anchored to a single
targetMessageId.These bullets still conflict, and Line 22 now explicitly allows
multiple targetMessageIdseven though the sender logic consumes onetargetMessageIdper response. That can push the model into an output shape the runtime cannot reliably reply against.Suggested wording fix
Response structure: -- Emit one `response` action per unique `targetMessageId`. -- If multiple targets need the same response, you may collapse them into one `response` with multiple `targetMessageIds`, using the earliest `targetMessageId` as the primary. +- Emit one `response` action per distinct response topic. +- If multiple targets need the same response, collapse them into one `response` anchored to the earliest `targetMessageId` in that topic cluster.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/prompts/triage-respond.md` around lines 20 - 22, The bullets conflict: remove the allowance for collapsing multiple targetMessageIds into a single `response` and make the contract explicit that each `response` action must be anchored to exactly one `targetMessageId`; if multiple targets require the same content emit separate `response` actions (one per `targetMessageId`) rather than a single `response` with multiple `targetMessageIds`, keeping references to the `response` action and `targetMessageId` symbols to locate the change.web/src/components/dashboard/config-sections/TriageSection.tsx (1)
81-113:⚠️ Potential issue | 🟠 MajorMake the numeric settings grids collapse on small screens.
All three groups are fixed at two columns, so the inputs get cramped on phones. Use a one-column base layout and only switch to two columns at a larger breakpoint.
📱 Proposed fix
- <div className="grid grid-cols-2 gap-4"> + <div className="grid grid-cols-1 gap-4 md:grid-cols-2"> ... - <div className="grid grid-cols-2 gap-4"> + <div className="grid grid-cols-1 gap-4 md:grid-cols-2"> ... - <div className="grid grid-cols-2 gap-4"> + <div className="grid grid-cols-1 gap-4 md:grid-cols-2">As per coding guidelines,
web/**/*.{ts,tsx}: Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design.Also applies to: 115-145, 147-177
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/TriageSection.tsx` around lines 81 - 113, The numeric input groups in TriageSection (the div with className "grid grid-cols-2 gap-4") use a fixed two-column layout which breaks on small screens; change the grid classes to a responsive layout (e.g., use a one-column base and switch to two columns at a small/medium breakpoint like "grid grid-cols-1 sm:grid-cols-2 gap-4") for the container wrapping the classify/respond inputs and apply the same responsive class change to the other two similar grids referenced in the diff (the blocks around lines 115-145 and 147-177) so the inputs collapse to one column on phones and become two columns only on larger viewports.web/src/components/dashboard/config-sections/ChallengesSection.tsx (1)
75-75:⚠️ Potential issue | 🟠 MajorUse a responsive span for the timezone row.
col-span-2creates an implicit second column while the base grid isgrid-cols-1, so this row can misalign on mobile. Make the span apply only once the layout actually has two columns.📱 Proposed fix
- <label htmlFor="challenge-timezone" className="space-y-2 col-span-2"> + <label htmlFor="challenge-timezone" className="space-y-2 md:col-span-2">As per coding guidelines,
web/**/*.{ts,tsx}: Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx` at line 75, The label element with htmlFor="challenge-timezone" currently uses a fixed "col-span-2" which breaks mobile alignment; change that class to a responsive variant that only spans two columns at the breakpoint where the grid becomes multi-column (e.g., replace "col-span-2" with "md:col-span-2" or "sm:col-span-2" to match the grid's breakpoint), updating the className on the label in ChallengesSection.tsx accordingly.web/src/components/dashboard/config-sections/StarboardSection.tsx (1)
3-3:⚠️ Potential issue | 🟠 MajorSync
ignoredChannelsRawwith draft updates.This local state only captures the initial prop value. After async config load, discard/reset, or any external draft replacement, the input can keep stale text and write it back on the next blur.
Proposed fix
-import { useState } from 'react'; +import { useEffect, useState } from 'react'; @@ export function StarboardSection({ draftConfig, saving, onFieldChange }: StarboardSectionProps) { - // Local state buffers the raw comma-separated string to avoid mid-type parses - const [ignoredChannelsRaw, setIgnoredChannelsRaw] = useState( - (draftConfig.starboard?.ignoredChannels ?? []).join(', '), - ); + const ignoredChannelsDisplay = (draftConfig.starboard?.ignoredChannels ?? []).join(', '); + const [ignoredChannelsRaw, setIgnoredChannelsRaw] = useState(ignoredChannelsDisplay); + + useEffect(() => { + setIgnoredChannelsRaw(ignoredChannelsDisplay); + }, [ignoredChannelsDisplay]);Also applies to: 24-28
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/StarboardSection.tsx` at line 3, The local state ignoredChannelsRaw in StarboardSection is initialized from props but never updated when the prop/draft changes, causing stale input to be saved on blur; update ignoredChannelsRaw whenever the corresponding prop/draft value changes by adding an effect that listens to the prop (e.g., ignoredChannels or the draft object) and setsIgnoredChannelsRaw accordingly, and apply the same pattern to the other local states at the same block (lines referenced around 24-28) so all controlled inputs mirror external config/draft updates (refer to the StarboardSection component and the ignoredChannelsRaw state variable).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/maintain-docs.md:
- Around line 3-20: Move the file from .github/workflows/maintain-docs.md to
.github/agents/maintain-docs.agent.md and update the YAML frontmatter: add a
description field (description: "Keep AGENTS.md documentation updated with
recent architecture and pattern changes"), change tools from an object to a list
(tools: [github, edit]), keep the existing engine: copilot and existing
on/schedule/permissions blocks, and ensure the file uses proper YAML frontmatter
delimiters (---) so GitHub Copilot can discover this agent configuration.
- Around line 57-61: Update the branch naming pattern used in the "Create Pull
Request" step so it begins with the required Copilot prefix: replace the
existing `docs/maintain-docs-YYYY-MM-DD` pattern with a Copilot-safe name such
as `copilot/docs-maintain-docs-YYYY-MM-DD` (or use a dynamic expression like
`copilot/docs-maintain-docs-${{ github.run_id }}`) wherever the branch string is
defined to ensure the Copilot coding agent can push and create the PR.
In `@src/prompts/community-rules.md`:
- Line 15: The moderation ladder text contains an invalid action "mute" which is
injected via {{communityRules}} into the classifier prompt and makes the
classifier's recommendedAction invalid; update the ladder string to use the
allowed action "timeout" (e.g., change "Consequences: warning → mute → ban." to
"Consequences: warning → timeout → ban.") so the classifier schema (warn,
timeout, kick, ban, delete) matches the prompt and prevents invalid
recommendedAction outputs.
In `@src/prompts/triage-classify.md`:
- Line 26: Update the example text that identifies violated rules so it matches
the current rule names: find the example string "Rule 4: No spam/shilling" in
the triage-classify prompt content and replace it with the current wording "Rule
4: No spam or drive-by promotion" (ensure any other example occurrences of
renamed rules are updated similarly) so moderation outputs reference the
canonical rule names.
In `@web/src/components/dashboard/config-sections/AiAutoModSection.tsx`:
- Around line 14-16: Replace the duplicated inputClasses constant in
AiAutoModSection.tsx by importing the centralized inputClasses from the shared
module: remove the local inputClasses definition and add an import for
inputClasses from the shared file (where the shared inputClasses constant is
declared) so AiAutoModSection.tsx uses the single source of truth for input
styling.
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Around line 63-73: Replace the free-text postTime input with a proper time
control or validate the value before updating draftConfig: either change the
<input id="post-time-hh-mm"> to type="time" and keep value bound to
draftConfig.challenges?.postTime (fallback to "09:00"), or, if keeping text
input, validate the onChange value with a HH:MM check (regex plus numeric range
check for 00-23 hours and 00-59 minutes) and only call onFieldChange('postTime',
value) when valid (optionally show inline error state while saving is false).
Ensure you reference the existing input id "post-time-hh-mm", the
draftConfig.challenges?.postTime value, the onFieldChange handler, and respect
the disabled={saving} flag.
In `@web/src/components/dashboard/config-sections/ModerationSection.tsx`:
- Around line 246-259: The blocked-domains input only writes to draftConfig in
the onBlur handler, risking lost edits if the user saves immediately; update the
onChange handler for the input that currently calls setBlockedDomainsRaw to also
parse blockedDomainsRaw into an array (split by ',', trim, filter(Boolean)) and
call onLinkFilterChange('blockedDomains', parsedArray) so draftConfig is kept in
sync as the user types; retain the onBlur handler but use it only to normalize
the display value (e.g., join the parsed array back into a cleaned string) so
setBlockedDomainsRaw remains normalized for presentation.
In `@web/src/components/dashboard/config-sections/TicketsSection.tsx`:
- Around line 129-139: The label for the Transcript Channel ID uses a hardcoded
"col-span-2" which forces a two-column span on mobile; change that class to
"sm:col-span-2" on the label containing the input (the element that renders
draftConfig.tickets?.transcriptChannel and calls
onFieldChange('transcriptChannel', ...)) so the field only spans two columns at
sm+ breakpoints and remains single-column on mobile, preserving responsive grid
behavior; keep other attributes like disabled={saving},
className={inputClasses}, and the placeholder unchanged.
In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Around line 215-225: TriageSection currently uses a free-text input for
draftConfig.triage?.moderationLogChannel which allows invalid values; replace
that input with the same ChannelSelector component used in AiSection (or, if
keeping the input, validate the value before calling onFieldChange) so only
valid Discord channel IDs (snowflakes) are saved. Locate the input with id
"moderation-log-channel" and either swap it for ChannelSelector wired to
onFieldChange('moderationLogChannel', value) or add a pre-submit/ onChange check
that verifies the value matches the snowflake regex and rejects/clears invalid
entries instead of passing them to onFieldChange. Ensure disabled={saving},
className={inputClasses} and the placeholder/label semantics are preserved when
swapping components.
In `@web/src/lib/config-normalization.ts`:
- Around line 79-80: Add a NaN guard to decimalToPercent so it mirrors
percentToDecimal: at the start of export function decimalToPercent(decimal:
number) check Number.isNaN(decimal) (or isNaN(Number(decimal))) and return 0 for
malformed input, then proceed to Math.round(decimal * 100) and clamp with
Math.min/Math.max as before; this prevents NaN from propagating into percent
inputs while keeping the existing clamping logic.
---
Outside diff comments:
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 344-365: sendSection currently issues one PATCH per field causing
partial commits; change it to send the entire sectionPatches array in a single
PATCH request (replace the per-patch loop in sendSection with one fetch to
`/api/guilds/${encodeURIComponent(guildId)}/config` whose body is
JSON.stringify(sectionPatches)), keep headers, cache, and signal, and preserve
the 401 handling (call saveAbortController.abort(), redirect, throw). After a
successful response update the downstream bookkeeping (savedConfig) for that
section using the new values returned or by applying sectionPatches atomically
so the editor state matches the backend; on non-ok responses parse the error
body as before and throw. Ensure sendSection, guildId, saveAbortController, and
savedConfig are the referenced symbols updated.
---
Duplicate comments:
In `@src/prompts/triage-respond.md`:
- Around line 20-22: The bullets conflict: remove the allowance for collapsing
multiple targetMessageIds into a single `response` and make the contract
explicit that each `response` action must be anchored to exactly one
`targetMessageId`; if multiple targets require the same content emit separate
`response` actions (one per `targetMessageId`) rather than a single `response`
with multiple `targetMessageIds`, keeping references to the `response` action
and `targetMessageId` symbols to locate the change.
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Line 75: The label element with htmlFor="challenge-timezone" currently uses a
fixed "col-span-2" which breaks mobile alignment; change that class to a
responsive variant that only spans two columns at the breakpoint where the grid
becomes multi-column (e.g., replace "col-span-2" with "md:col-span-2" or
"sm:col-span-2" to match the grid's breakpoint), updating the className on the
label in ChallengesSection.tsx accordingly.
In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Line 3: The local state ignoredChannelsRaw in StarboardSection is initialized
from props but never updated when the prop/draft changes, causing stale input to
be saved on blur; update ignoredChannelsRaw whenever the corresponding
prop/draft value changes by adding an effect that listens to the prop (e.g.,
ignoredChannels or the draft object) and setsIgnoredChannelsRaw accordingly, and
apply the same pattern to the other local states at the same block (lines
referenced around 24-28) so all controlled inputs mirror external config/draft
updates (refer to the StarboardSection component and the ignoredChannelsRaw
state variable).
In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Around line 81-113: The numeric input groups in TriageSection (the div with
className "grid grid-cols-2 gap-4") use a fixed two-column layout which breaks
on small screens; change the grid classes to a responsive layout (e.g., use a
one-column base and switch to two columns at a small/medium breakpoint like
"grid grid-cols-1 sm:grid-cols-2 gap-4") for the container wrapping the
classify/respond inputs and apply the same responsive class change to the other
two similar grids referenced in the diff (the blocks around lines 115-145 and
147-177) so the inputs collapse to one column on phones and become two columns
only on larger viewports.
In `@web/src/lib/config-updates.ts`:
- Around line 241-243: The appendArrayItem() helper currently spreads a
non-array into [] and overwrites existing non-array values; change it to first
inspect cursor[lastKey]: if cursor[lastKey] === undefined then treat it as []
and append the item, but if cursor[lastKey] exists and
!Array.isArray(cursor[lastKey]) then bail out (like updateArrayItem() and
removeArrayItem()) rather than creating an array; update the logic around
lastKey/arr to only synthesize an array for missing keys and preserve non-array
values by returning/aborting when a non-array is encountered.
- Around line 107-110: The array/object helper functions accept too-broad keys
(keyof GuildConfig) allowing scalar keys like "guildId" which corrupts the
config; narrow their section parameter type to the specific section key union
(GuildConfigSectionKey) for all helpers (e.g., updateArrayItem,
updateObjectItem, and the other update helpers referenced) so callers can only
pass valid section keys and the spread/rebuild logic won't convert scalars into
objects/arrays; update the function signatures to use GuildConfigSectionKey for
the section parameter and update any related overloads/usages to match.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2813f7ac-4a0d-4119-93a9-1c2b3afa4851
📒 Files selected for processing (38)
.github/workflows/maintain-docs.mdsrc/modules/triage-buffer.jssrc/modules/triage-prompt.jssrc/modules/triage-respond.jssrc/modules/triage.jssrc/prompts/anti-abuse.mdsrc/prompts/community-rules.mdsrc/prompts/search-guardrails.mdsrc/prompts/triage-classify-system.mdsrc/prompts/triage-classify.mdsrc/prompts/triage-respond-system.mdsrc/prompts/triage-respond.mdtests/modules/triage-prompt.test.jstests/modules/triage.test.jsweb/next-env.d.tsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/shared.tsweb/src/lib/config-normalization.tsweb/src/lib/config-updates.tsweb/src/types/config.tsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/tests/lib/config-normalization.test.tsweb/tests/lib/config-updates.test.ts
💤 Files with no reviewable changes (1)
- src/prompts/anti-abuse.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
web/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/shared.tssrc/modules/triage.jsweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/next-env.d.tsweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxtests/modules/triage-prompt.test.jsweb/src/components/dashboard/config-sections/AiSection.tsxsrc/modules/triage-buffer.jsweb/src/components/dashboard/config-sections/ReputationSection.tsxtests/modules/triage.test.jsweb/src/components/dashboard/config-sections/TicketsSection.tsxsrc/modules/triage-respond.jsweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/tests/lib/config-normalization.test.tsweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxsrc/modules/triage-prompt.jsweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/lib/config-updates.tsweb/src/lib/config-normalization.tsweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/types/config.tsweb/src/components/dashboard/config-editor.tsx
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/shared.tsweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/next-env.d.tsweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/tests/lib/config-normalization.test.tsweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/lib/config-updates.tsweb/src/lib/config-normalization.tsweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/types/config.tsweb/src/components/dashboard/config-editor.tsx
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/modules/triage.jstests/modules/triage-prompt.test.jssrc/modules/triage-buffer.jstests/modules/triage.test.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.js
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*methods
Use safe Discord message methods:safeReply(),safeSend(),safeEditReply()instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries
Files:
src/modules/triage.jssrc/modules/triage-buffer.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_CONFIG_KEYS
Files:
src/modules/triage.jssrc/modules/triage-buffer.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.js
{.env*,README.md,src/**/!(*.test).{js,ts}}
📄 CodeRabbit inference engine (CLAUDE.md)
Remove
GUILD_IDfrom shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag--guild-id <guild_id>
Files:
src/modules/triage.jssrc/modules/triage-buffer.jssrc/modules/triage-respond.jssrc/modules/triage-prompt.js
tests/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Maintain 80% code coverage threshold minimum, never lower this threshold
Files:
tests/modules/triage-prompt.test.jstests/modules/triage.test.js
🧠 Learnings (13)
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.test.{js,jsx,ts,tsx} : Config editor tests must cover manual-save workspace behavior (not autosave assumptions), category switching, search functionality, and dirty badges
Applied to files:
web/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves
Applied to files:
web/tests/lib/config-updates.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/modules/**/*.{js,mjs} : Create new modules for features with corresponding config sections in `config.json` and entries in `SAFE_CONFIG_KEYS`
Applied to files:
web/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/lib/config-updates.tsweb/src/lib/config-normalization.ts
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor save contract must maintain: global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/tests/lib/config-updates.test.tsweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to tests/**/*.{js,mjs,jsx,ts,tsx} : Maintain 80% code coverage threshold minimum, never lower this threshold
Applied to files:
web/tests/lib/config-updates.test.tsweb/tests/components/dashboard/config-editor-autosave.test.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Refactor config feature presentation to use reusable `SettingsFeatureCard` pattern with structure: header + master toggle + Basic/Advanced blocks
Applied to files:
web/src/components/dashboard/config-sections/shared.tsweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Web dashboard config editor must use category workspace navigation with categories: `AI & Automation`, `Onboarding & Growth`, `Moderation & Safety`, `Community Tools`, `Support & Integrations` located in `web/src/components/dashboard/config-workspace/`
Applied to files:
web/src/components/dashboard/config-sections/shared.tsweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/AiSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/PermissionsSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor must implement metadata-driven config search with cross-category quick jump, focus/scroll targeting, and auto-open advanced sections when search hits advanced controls
Applied to files:
web/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/MemorySection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/index.tsweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Applied to files:
web/next-env.d.tsweb/src/components/dashboard/config-sections/ReputationSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/tests/components/dashboard/config-editor-autosave.test.tsxweb/src/components/dashboard/config-sections/WelcomeSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/routes/**/*.{js,mjs} : Create API routes with proper authentication middleware, mount in `src/api/server.js`, and document in OpenAPI spec
Applied to files:
web/next-env.d.ts
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/utils/reputationCache.js : Cache leaderboard, rank, and user reputation data with auto-invalidation on config changes
Applied to files:
web/src/components/dashboard/config-sections/ReputationSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/**/*.{js,mjs,jsx,ts,tsx} : Use safe Discord message methods: `safeReply()`, `safeSend()`, `safeEditReply()` instead of direct Discord.js methods
Applied to files:
src/modules/triage-prompt.js
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to {.env*,README.md,src/**/!(*.test).{js,ts}} : Remove `GUILD_ID` from shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag `--guild-id <guild_id>`
Applied to files:
web/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsx
🪛 LanguageTool
src/prompts/triage-respond.md
[uncategorized] ~26-~26: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...nd under 2000 characters. - Use Discord markdown when helpful. - Mention users using the...
(MARKDOWN_NNP)
.github/workflows/maintain-docs.md
[uncategorized] ~15-~15: The official name of this software platform is spelled with a capital “H”.
Context: ...requests: true issues: true tools: github: edit: engine: copilot --- # Mai...
(GITHUB)
🪛 markdownlint-cli2 (0.21.0)
src/prompts/triage-classify.md
[warning] 6-6: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
[warning] 6-6: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
🔇 Additional comments (17)
web/next-env.d.ts (1)
3-6: No action required. The path.next/types/routes.d.tsis the standard auto-generated location in Next.js 16 for typed routes. This file should always be regenerated by Next.js tooling, not manually edited.src/modules/triage-buffer.js (1)
25-26: LGTM!The new
channelNameandchannelTopicfields are properly documented with nullable types (string|null), aligning with the PR objective to add channel metadata to buffer entries. This integrates well with the downstream channel-context rendering intriage-prompt.js.tests/modules/triage.test.js (2)
172-176: LGTM!The updated
makeMessagehelper properly structures the channel object withid,name, andtopicfields, mirroring Discord.js message structure. Usingextras.channelNameandextras.channelTopicwith sensible defaults allows flexible test configuration.
409-420: LGTM!Good test coverage for the new channel metadata feature. The test correctly verifies that
channelNameandchannelTopicare properly propagated from the message to the buffer entry.src/modules/triage-respond.js (2)
45-58: LGTM!The JSDoc is now complete with the
clientparameter properly documented, addressing the previous review feedback. The return type documentation clearly specifies all fields including the newchannelNameandchannelTopicproperties.
83-84: LGTM!Using nullish coalescing (
?? null) correctly handles cases wherechannel.nameorchannel.topicmay be undefined (e.g., DM channels). This aligns with theBufferEntrytypedef intriage-buffer.js.web/src/components/dashboard/config-sections/AiAutoModSection.tsx (2)
92-101: LGTM!The threshold handler now correctly guards against empty input (
if (val === '') return;) and validates withNumber.isNaNbefore callingonFieldChange. This properly addresses the concern about unintentionally writing0when the field is temporarily cleared.
47-52: No action required —onChangeis the correct prop forToggleSwitch.The
ToggleSwitchcomponent's interface explicitly definesonChangeas the expected prop (notonCheckedChange). The component internally adapts this to the underlyingSwitchcomponent'sonCheckedChange. The code in AiAutoModSection.tsx is correct as written.> Likely an incorrect or invalid review comment.src/prompts/triage-respond-system.md (2)
15-26: LGTM!The new
<classification-context>block provides clear, concise tone guidance for each classification type. The structure (definition + adjustment) makes it easy for the model to understand how to modulate responses appropriately.
36-43: LGTM!Good updates to constraints: the 2-6 sentence guideline helps maintain conciseness, and the credential rotation warning (line 42) is an important security safeguard to prevent accidental credential exposure in responses.
src/prompts/search-guardrails.md (1)
1-25: LGTM!The search guardrails are well-structured with clear criteria for when to search, sensible limits (1-2 searches), and appropriate abuse detection. The JSON response directive (lines 23-24) aligns with the structured output expectations in the triage-respond prompt.
web/tests/lib/config-normalization.test.ts (1)
1-188: LGTM!Comprehensive test suite with good coverage of edge cases including empty strings, whitespace handling, NaN, clamping, and rounding behavior. The tests are well-organized with clear describe blocks per function.
web/tests/lib/config-updates.test.ts (2)
52-56: Good immutability test.This test correctly verifies that
updateSectionEnableddoes not mutate the original config object, which is essential for React state management and predictable behavior.
1-178: LGTM!Comprehensive test suite covering all update utility functions with good edge case coverage including missing sections/arrays, immutability verification, and graceful handling of empty arrays. The tests align well with the immutable update patterns needed for React state management.
src/modules/triage-prompt.js (1)
50-60: Nice hardening on<channel-context>rendering.Escaping
channelNameandchannelTopicbefore interpolation closes the delimiter-injection hole without changing the prompt structure.tests/modules/triage-prompt.test.js (1)
528-567: Good coverage for channel-metadata escaping.These cases pin the new
<channel-context>escaping behavior and protect the structural closing tag from regressions.web/src/components/dashboard/config-editor.tsx (1)
66-89:aiAutoModis properly registered in all config registries.The new section is correctly added to
SAFE_CONFIG_KEYSinsrc/api/utils/configAllowlist.js, which gates API writes. It is also included in the client type definitions (ConfigSectionKey) and config workspace configuration, so the frontend and backend are synchronized.
web/src/components/dashboard/config-sections/AiAutoModSection.tsx
Outdated
Show resolved
Hide resolved
web/src/components/dashboard/config-sections/TicketsSection.tsx
Outdated
Show resolved
Hide resolved
- Change permissions from true → write (true is not a valid GitHub Actions permission value; write is needed to create branches and open PRs) - Change branch prefix from docs/ to copilot/ (required for GitHub Copilot coding agent branches per GitHub's workflow spec) - Update the example in the comment to use the copilot/ prefix
- community-rules.md: change 'mute' to 'timeout' — classifier only supports warn/timeout/kick/ban/delete; mute is not a valid action - triage-classify.md: update stale example 'Rule 4: No spam/shilling' to match current rule text 'Rule 4: No spam or drive-by promotion'
…bSection, ChallengesSection Remove locally-defined inputClasses constants and import from the shared module (config-sections/shared.ts) so styling stays consistent and is maintained in one place.
…pt label id, community feature key type - ChallengesSection: change postTime input to type='time' so the browser constrains input to valid HH:MM clock values - ModerationSection: update blockedDomains draftConfig on onChange as well as onBlur so Ctrl+S doesn't miss in-progress edits - TicketsSection: add htmlFor/id to transcript channel label+input for accessibility (label was not programmatically associated with input) - CommunityFeaturesSection: replace 'key as keyof GuildConfig' with the narrower CommunityFeatureKey type derived from COMMUNITY_FEATURES['key']
- Add useEffect to sync ignoredChannelsRaw when draftConfig changes externally (guild switch or discard). Without this, the raw text buffer would show stale values after a discard or guild switch because React only uses useState's initial value argument once. - Fix channelId onChange to trim and null-coerce empty string (consistent with all other channel ID inputs in the dashboard)
…riageSection - Replace plain text input with ChannelSelector (maxSelections=1, filter=text) so users pick from real channel names instead of typing raw IDs - Add guildId prop to TriageSectionProps to power the channel fetch - Update config-editor.tsx to pass guildId down to TriageSection - Convert between string|null (config) and string[] (ChannelSelector) at the boundary
…Default config-updates.ts: - Change updateArrayItem, removeArrayItem, appendArrayItem from section: keyof GuildConfig to section: GuildConfigSectionKey to prevent scalar fields (guildId) being passed as section keys; matches the type used by the other update helpers in the same file config-editor.tsx: - Only call e.preventDefault() when we're actually handling the save (hasChanges && !hasValidationErrors). Previously, Ctrl+S always swallowed the browser event even when there were validation errors, giving the user no feedback and blocking the native save dialog
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (6)
.github/workflows/maintain-docs.md (1)
3-20:⚠️ Potential issue | 🔴 CriticalFix file location, extension, and YAML frontmatter schema.
This file mixes GitHub Actions workflow syntax with Copilot agent configuration, creating a hybrid that won't function as either. Critical issues:
File location & extension: Copilot agents must be in
.github/agents/with.agent.mdextension. Move this file to.github/agents/maintain-docs.agent.md.YAML schema conflict: The frontmatter includes GitHub Actions fields (
on,schedule,workflow_dispatch,permissions) which are not valid in Copilot agent configs. Remove these fields.Missing required field: Copilot agents require a
descriptionfield.Malformed
tools: Thetoolskey should be a list[github, edit], not an object with nested keys.🔧 Corrected frontmatter structure
-on: - schedule: - - cron: '0 9 * * *' # 4 AM EST = 9 AM UTC - workflow_dispatch: {} - -permissions: - contents: write - pull-requests: write - issues: write - -tools: - github: - edit: - +description: "Keep AGENTS.md documentation updated with recent architecture and pattern changes" +tools: [github, edit] engine: copilotAdditionally, move the file from
.github/workflows/maintain-docs.mdto.github/agents/maintain-docs.agent.md.Note: If you intended to trigger this agent on a schedule, that's handled separately through GitHub's scheduled agent runs or manual invocation—not via
on:fields in the agent config itself.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/maintain-docs.md around lines 3 - 20, The file maintain-docs mixes GitHub Actions frontmatter with a Copilot agent config; remove the GitHub Actions keys (on, schedule, workflow_dispatch, permissions) from the YAML, add the required agent field description, change tools to a list form (tools: [github, edit]) and relocate/rename the file to the Copilot agent location/extension (i.e., move from the workflows file to the agents directory and use the .agent.md extension as the reviewer requested).web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx (1)
8-12: 🧹 Nitpick | 🔵 TrivialTighten the feature key typing and remove the assertion.
The broad
stringcallback contract leaks into the render path and forces theToggleSectionConfigcast here. NarrowingonToggleChangetoCommunityFeatureKeylets this read stay type-safe without bypassing the schema.♻️ Proposed cleanup
interface CommunityFeaturesSectionProps { draftConfig: GuildConfig; saving: boolean; - onToggleChange: (key: string, enabled: boolean) => void; + onToggleChange: (key: CommunityFeatureKey, enabled: boolean) => void; }<ToggleSwitch - checked={ - (draftConfig[key as CommunityFeatureKey] as ToggleSectionConfig | undefined) - ?.enabled ?? false - } + checked={draftConfig[key]?.enabled ?? false} onChange={(v) => onToggleChange(key, v)} disabled={saving} label={label}Also applies to: 71-76
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx` around lines 8 - 12, The onToggleChange callback in CommunityFeaturesSectionProps is too broad (string) and forces a type assertion in the render; change the prop signature to use the specific CommunityFeatureKey type instead of string and update any callers to pass that narrower key type, then remove the cast in the render where ToggleSectionConfig is used. Specifically update CommunityFeaturesSectionProps.onToggleChange to (key: CommunityFeatureKey, enabled: boolean) => void, adjust call sites that provide the key, and delete the assertion around ToggleSectionConfig in the component so the schema types flow correctly.web/src/components/dashboard/config-sections/ChallengesSection.tsx (1)
71-85:⚠️ Potential issue | 🟡 MinorMake the timezone row span only on 2-column layouts.
col-span-2on top ofgrid-cols-1creates an implicit extra column on mobile. This should bemd:col-span-2so the row only spans when the grid actually has two columns.As per coding guidelines, `web/**/*.{ts,tsx}`: Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design.📱 Proposed fix
- <label htmlFor="challenge-timezone" className="space-y-2 col-span-2"> + <label htmlFor='challenge-timezone' className='space-y-2 md:col-span-2'>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx` around lines 71 - 85, The label wrapper for the timezone field currently forces two-column spanning unconditionally via "col-span-2" which creates an extra implicit column on mobile; update the label's class to use responsive span so it only spans on medium+ layouts (change "col-span-2" to "md:col-span-2" on the element containing id="challenge-timezone" / the label that wraps the input tied to draftConfig.challenges?.timezone and onFieldChange) and ensure styling remains consistent with inputClasses and the disabled={saving} behavior.web/src/components/dashboard/config-sections/TicketsSection.tsx (1)
129-129:⚠️ Potential issue | 🟠 MajorMake the transcript field span responsive.
col-span-2is applied even when the parent grid is single-column, which creates an implicit extra track on mobile. Change it tosm:col-span-2so the field only spans once the grid actually has two columns.As per coding guidelines: `web/**/*.{ts,tsx}`: Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design.Suggested fix
- <label htmlFor="transcript-channel-id" className="space-y-2 col-span-2"> + <label htmlFor="transcript-channel-id" className="space-y-2 sm:col-span-2">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/TicketsSection.tsx` at line 129, In TicketsSection (the <label htmlFor="transcript-channel-id"> element) the className currently forces col-span-2 on all sizes; change the grid span token from "col-span-2" to "sm:col-span-2" (i.e., update the className on the label for transcript-channel-id) so the field only spans two columns on small+ screens and stays single-column on mobile.src/prompts/triage-classify.md (1)
26-26:⚠️ Potential issue | 🟡 MinorUpdate the Rule 1 example to the renamed rule.
Line 26 still says
"Rule 1: Respect", butsrc/prompts/community-rules.mdnow defines"Rule 1: Respect others". Keeping stale example names here will make moderation outputs drift from the canonical rule list.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/prompts/triage-classify.md` at line 26, Update the example rule name in src/prompts/triage-classify.md so it matches the canonical rule text: replace the stale string "Rule 1: Respect" with the current canonical "Rule 1: Respect others" wherever that example appears (the line beginning "Identify which community rule was violated...") to keep examples consistent with src/prompts/community-rules.md.web/src/components/dashboard/config-sections/ModerationSection.tsx (1)
57-59:⚠️ Potential issue | 🟠 MajorThe blocked-domains buffer still collapses while the user is typing.
Because Lines 250-258 update
linkFilter.blockedDomainson every keystroke, the effect on Lines 57-59 immediately rewritesblockedDomainsRawfrom the normalized array and strips intermediate commas/spaces (foo,snaps back tofoo). Gate that sync while the input is focused, or move normalization toonBlurand reserve the effect for external resets like guild switches/discard.Also applies to: 250-268
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/ModerationSection.tsx` around lines 57 - 59, The effect that syncs setBlockedDomainsRaw from blockedDomainsDisplay is overwriting the user's in-progress edits because linkFilter.blockedDomains is being normalized on every keystroke; change the logic so normalization happens on input blur (e.g., move normalization from the onChange handler that updates linkFilter.blockedDomains to an onBlur handler) or guard the useEffect sync so it only runs when the input is not focused (check input focus state) — specifically update the useEffect that sets blockedDomainsRaw and the handlers that write to linkFilter.blockedDomains (the input onChange and onBlur) so intermediate commas/spaces are preserved while focused and normalization / external resets still update blockedDomainsRaw when the input loses focus or when the guild/external reset occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/dashboard/config-sections/AiAutoModSection.tsx`:
- Around line 89-99: The onChange handler for the threshold input currently
accepts any numeric string and converts it via percentToDecimal, so values like
150 or -5 become invalid decimals; clamp the parsed number to the 0–100 range
before calling percentToDecimal and passing to onFieldChange (keep the
empty-string guard and Number.isNaN check), i.e., parse the input into parsed,
compute clamped = Math.min(100, Math.max(0, parsed)), then call
onFieldChange('thresholds', { ...thresholds, [cat]: percentToDecimal(clamped)
}).
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Around line 73-84: The timezone input (id "challenge-timezone") currently
accepts any free text via value={draftConfig.challenges?.timezone} and
onChange={(e) => onFieldChange('timezone', e.target.value)}; add validation so
only known IANA tz values are accepted before updating the config: implement a
validator (e.g., using Intl.DateTimeFormat().resolvedOptions() checks or a
curated list of IANA timezones) in the ChallengesSection component and call
onFieldChange only when the value passes validation, otherwise set a local
validation error state and show a small error message under the input and
disable the overall save (or prevent propagation) while invalid; ensure the
validation hook/utility is reusable and referenced by the input change handler
and any save/submit logic so invalid values like "America/Chcago" are rejected.
- Around line 34-84: Update JSX string literals in ChallengesSection.tsx to use
single quotes instead of double quotes to match repo conventions; replace
double-quoted attributes and text values used in JSX elements (e.g., id, type,
className, placeholder, label text and ToggleSwitch label prop) so that strings
like "challenge-channel-id", "time", "text-xs text-muted-foreground",
"America/New_York", "Channel ID for daily challenges", "Post Time", "Timezone",
and "Challenges" are single-quoted; ensure the onChange handlers and JS
expressions remain unchanged and run the formatter/linter after the edits.
In `@web/src/components/dashboard/config-sections/GitHubSection.tsx`:
- Around line 59-63: The poll interval input shows 5 even when
feed.pollIntervalMinutes is undefined, causing the UI to display a value not
present in draftConfig; in GitHubSection, change the controlled input to render
an empty string when feed.pollIntervalMinutes is undefined (e.g.,
value={feed.pollIntervalMinutes ?? ''}) and update the onChange handler (used
with parseNumberInput and onFieldChange) to call
onFieldChange('pollIntervalMinutes', undefined) when the input is cleared (empty
string), otherwise pass the parsed number; alternatively, if you prefer
initializing the draft, set feed.pollIntervalMinutes = 5 when creating the
draftConfig so the input and state always match.
In `@web/src/components/dashboard/config-sections/ModerationSection.tsx`:
- Around line 151-229: The rate-limit input grids in ModerationSection.tsx use
fixed "grid grid-cols-2" and "grid grid-cols-3" which keep multiple columns on
small screens; change those containers (the divs with className "grid
grid-cols-2 gap-4" and "grid grid-cols-3 gap-4") to use a single-column default
and add responsive breakpoints (e.g., grid-cols-1 then sm: or md: variants) so
inputs like "max-messages", "window-seconds", "mute-after-triggers",
"mute-window-s", and "mute-duration-s" stack on mobile; update the Tailwind
className strings accordingly to preserve existing layout at larger sizes while
making them one column on small screens.
In `@web/src/components/dashboard/config-sections/StarboardSection.tsx`:
- Around line 56-57: The change is setting channelId to null when cleared which
violates StarboardConfig.channelId (should be a string); update the onChange
handler in StarboardSection.tsx (the line calling onFieldChange('channelId',
...)) to pass an empty string instead of null (use e.target.value.trim() || ''),
ensuring draftConfig.starboard and updateSectionField always receive a string
for channelId.
- Around line 124-133: The current handler only updates the persisted field
onBlur causing saves while the input is focused to miss the latest
ignoredChannelsRaw; modify the onChange for the input (where ignoredChannelsRaw
and setIgnoredChannelsRaw are used) to also parse the comma-separated string and
call onFieldChange('ignoredChannels', parsed) on every change, and keep the
onBlur handler only for normalizing the displayed string by setting
setIgnoredChannelsRaw(parsed.join(', ')); ensure you still call
setIgnoredChannelsRaw(e.target.value) on change so the textbox updates
immediately.
In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Around line 85-182: The three container divs currently use a fixed two-column
class ("grid grid-cols-2 gap-4") which keeps fields cramped on small screens;
change each to a mobile-first responsive class such as "grid grid-cols-1
sm:grid-cols-2 gap-4" so the layout stacks to one column on phones and splits to
two columns at the small breakpoint—update the divs containing the
classify/respond inputs, the default-interval/timeout inputs, and the
context-messages/max-buffer-size inputs accordingly.
In `@web/src/lib/config-updates.ts`:
- Around line 130-137: The early-return treats missing arrays as non-iterable
and prevents index-0 initialization in updateArrayItem; change the guard so it
only bails when cursor[lastKey] is neither undefined nor an Array (i.e., allow
undefined to proceed), and when building arr use an empty array as the starting
value for initialization (e.g., treat cursor[lastKey] ?? []), while keeping the
existing index validation for out-of-range indices so that index === 0 can
create the array but other invalid indices still return early; update references
to cursor[lastKey], arr, and index accordingly.
---
Duplicate comments:
In @.github/workflows/maintain-docs.md:
- Around line 3-20: The file maintain-docs mixes GitHub Actions frontmatter with
a Copilot agent config; remove the GitHub Actions keys (on, schedule,
workflow_dispatch, permissions) from the YAML, add the required agent field
description, change tools to a list form (tools: [github, edit]) and
relocate/rename the file to the Copilot agent location/extension (i.e., move
from the workflows file to the agents directory and use the .agent.md extension
as the reviewer requested).
In `@src/prompts/triage-classify.md`:
- Line 26: Update the example rule name in src/prompts/triage-classify.md so it
matches the canonical rule text: replace the stale string "Rule 1: Respect" with
the current canonical "Rule 1: Respect others" wherever that example appears
(the line beginning "Identify which community rule was violated...") to keep
examples consistent with src/prompts/community-rules.md.
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Around line 71-85: The label wrapper for the timezone field currently forces
two-column spanning unconditionally via "col-span-2" which creates an extra
implicit column on mobile; update the label's class to use responsive span so it
only spans on medium+ layouts (change "col-span-2" to "md:col-span-2" on the
element containing id="challenge-timezone" / the label that wraps the input tied
to draftConfig.challenges?.timezone and onFieldChange) and ensure styling
remains consistent with inputClasses and the disabled={saving} behavior.
In `@web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx`:
- Around line 8-12: The onToggleChange callback in CommunityFeaturesSectionProps
is too broad (string) and forces a type assertion in the render; change the prop
signature to use the specific CommunityFeatureKey type instead of string and
update any callers to pass that narrower key type, then remove the cast in the
render where ToggleSectionConfig is used. Specifically update
CommunityFeaturesSectionProps.onToggleChange to (key: CommunityFeatureKey,
enabled: boolean) => void, adjust call sites that provide the key, and delete
the assertion around ToggleSectionConfig in the component so the schema types
flow correctly.
In `@web/src/components/dashboard/config-sections/ModerationSection.tsx`:
- Around line 57-59: The effect that syncs setBlockedDomainsRaw from
blockedDomainsDisplay is overwriting the user's in-progress edits because
linkFilter.blockedDomains is being normalized on every keystroke; change the
logic so normalization happens on input blur (e.g., move normalization from the
onChange handler that updates linkFilter.blockedDomains to an onBlur handler) or
guard the useEffect sync so it only runs when the input is not focused (check
input focus state) — specifically update the useEffect that sets
blockedDomainsRaw and the handlers that write to linkFilter.blockedDomains (the
input onChange and onBlur) so intermediate commas/spaces are preserved while
focused and normalization / external resets still update blockedDomainsRaw when
the input loses focus or when the guild/external reset occurs.
In `@web/src/components/dashboard/config-sections/TicketsSection.tsx`:
- Line 129: In TicketsSection (the <label htmlFor="transcript-channel-id">
element) the className currently forces col-span-2 on all sizes; change the grid
span token from "col-span-2" to "sm:col-span-2" (i.e., update the className on
the label for transcript-channel-id) so the field only spans two columns on
small+ screens and stays single-column on mobile.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a3901d23-eba1-4831-bb17-2f83f0595b09
📒 Files selected for processing (15)
.github/workflows/maintain-docs.mdsrc/prompts/community-rules.mdsrc/prompts/triage-classify.mdsrc/utils/dbMaintenance.jsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/lib/config-updates.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Greptile Review
- GitHub Check: claude-review
- GitHub Check: Docker Build Validation
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
web/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsxsrc/utils/dbMaintenance.jsweb/src/components/dashboard/config-sections/TriageSection.tsx
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/TriageSection.tsx
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/utils/dbMaintenance.js
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*methods
Use safe Discord message methods:safeReply(),safeSend(),safeEditReply()instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries
Files:
src/utils/dbMaintenance.js
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/tests/components/dashboard/**/*.test.{ts,tsx} : Web dashboard config editor tests should cover category switching, search functionality, dirty badges, and explicit manual-save workspace behavior instead of autosave assumptions
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/tests/components/dashboard/**/*.test.{ts,tsx} : Web dashboard config editor tests should cover category switching, search functionality, dirty badges, and explicit manual-save workspace behavior instead of autosave assumptions
Applied to files:
web/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/TriageSection.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/routes/dashboard/**/*.{ts,tsx} : Server-rendered dashboard entry pages should include static metadata for SEO and consistency with client-side title updates
Applied to files:
web/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with metadata-driven config search and reusable `SettingsFeatureCard` component pattern
Applied to files:
web/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/CommunityFeaturesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/TriageSection.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard shell layout component should mount `DashboardTitleSync` to sync `document.title` on pathname changes for client-rendered pages
Applied to files:
web/src/components/dashboard/performance-dashboard.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to web/**/*.{ts,tsx} : Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Applied to files:
web/src/components/dashboard/config-sections/TicketsSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves
Applied to files:
web/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/modules/**/*.{js,mjs} : Create new modules for features with corresponding config sections in `config.json` and entries in `SAFE_CONFIG_KEYS`
Applied to files:
web/src/lib/config-updates.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to {.env.example,README.md} : Remove `GUILD_ID` from shared environment variables in deployment configuration; preserve guild-scoped deploy support only via CLI flag `--guild-id` for development
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to src/commands/**/*.js : Command registration startup and reload logic should not read `process.env.GUILD_ID` directly; rely on CLI flags for guild-scoped deployments
Applied to files:
web/src/components/dashboard/config-editor.tsx
🪛 LanguageTool
.github/workflows/maintain-docs.md
[uncategorized] ~15-~15: The official name of this software platform is spelled with a capital “H”.
Context: ...quests: write issues: write tools: github: edit: engine: copilot --- # Mai...
(GITHUB)
🪛 markdownlint-cli2 (0.21.0)
src/prompts/community-rules.md
[warning] 6-6: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
[warning] 6-6: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
.github/workflows/maintain-docs.md
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🔇 Additional comments (10)
src/utils/dbMaintenance.js (1)
13-13: Import change looks good.This stays within the file’s existing ESM/module structure and does not introduce any behavioral risk. As per coding guidelines, "
**/*.{js,cjs,mjs}: Use ESM only withimport/exportsyntax, never CommonJS except in migration files (.cjs)`."web/src/components/dashboard/performance-dashboard.tsx (1)
363-363: Tooltip formatter simplification looks good.These inline formatter updates keep the behavior intact while making the chart JSX a bit easier to scan.
Also applies to: 403-403
.github/workflows/maintain-docs.md (4)
1-1: Markdownlint warning already addressed in previous review.The missing top-level heading warning (MD041) is expected for files starting with YAML frontmatter and has been discussed in prior comments.
15-15: LanguageTool capitalization warning is a false positive here.While "GitHub" should be capitalized in prose, in YAML configuration contexts
github(lowercase) is typically the correct tool identifier. This specific line will be removed when fixing the frontmatter schema.
58-62: Good use ofcopilot/branch prefix.The branch naming pattern correctly uses the required
copilot/prefix for GitHub Copilot coding agent branches, and the inline comment clearly explains the date substitution requirement and branch collision prevention. Well documented!
71-71: "Markdown" capitalization is already correct.The code currently shows "Markdown" properly capitalized (line 71). This issue appears to have been resolved since the prior review.
web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx (1)
14-36: Nice use of metadata-driven rendering.Keeping the feature labels/descriptions in
COMMUNITY_FEATURESmakes the section easy to extend and keeps the toggle markup consistent.Also applies to: 65-81
web/src/components/dashboard/config-sections/TriageSection.tsx (1)
223-236: Good move switchingmoderationLogChannelback toChannelSelector.Passing
guildId, constraining tofilter="text", and limiting selection to one channel removes the invalid free-form ID path while keeping the stored value simple.src/prompts/community-rules.md (1)
2-15: Looks aligned with the classifier schema.The rewritten rules are clearer, and the moderation ladder now matches the allowed action set.
web/src/components/dashboard/config-editor.tsx (1)
467-492: Nice consolidation of section updaters.This removes a lot of repetitive nested merge code and makes the section wiring much easier to audit.
web/src/components/dashboard/config-sections/ChallengesSection.tsx
Outdated
Show resolved
Hide resolved
web/src/components/dashboard/config-sections/ChallengesSection.tsx
Outdated
Show resolved
Hide resolved
web/src/components/dashboard/config-sections/ModerationSection.tsx
Outdated
Show resolved
Hide resolved
web/src/components/dashboard/config-sections/StarboardSection.tsx
Outdated
Show resolved
Hide resolved
- AiAutoModSection: clamp threshold to [0,100] before percentToDecimal - ChallengesSection: IANA timezone validation + single quotes - EngagementSection: stable badge keys (id ?? name ?? index) - GitHubSection: sync pollIntervalMinutes display and write-back - ModerationSection: mobile-responsive rate-limit grids (sm:grid-cols-*) - StarboardSection: channelId empty='' not null; ignoredChannels flushes on change - TriageSection: mobile-responsive numeric grids - config-updates.ts: updateArrayItem initializes missing array instead of no-op
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (5)
web/src/components/dashboard/config-sections/GitHubSection.tsx (1)
52-66: 🧹 Nitpick | 🔵 TrivialPoll interval input still has UI/state mismatch.
The input displays
5whenpollIntervalMinutesisundefinedin state, but that default value isn't written to the draft until the user interacts. This creates a disconnect where the UI shows a value that isn't actually in state.Additionally, line 58 uses
draftConfig.github?.feed?.pollIntervalMinutesinstead offeed.pollIntervalMinutesfor consistency with howchannelIdandenabledare accessed via thefeedvariable on line 25.♻️ Proposed fix for UI/state alignment and consistency
<label htmlFor="poll-interval-minutes" className="space-y-2"> <span className="text-sm font-medium">Poll Interval (minutes)</span> <input id="poll-interval-minutes" type="number" min={1} - value={draftConfig.github?.feed?.pollIntervalMinutes ?? 5} + value={feed.pollIntervalMinutes ?? ''} + placeholder="5" onChange={(e) => { - const val = Math.max(1, parseInt(e.target.value, 10) || 5); - onFieldChange('pollIntervalMinutes', val); + const parsed = parseInt(e.target.value, 10); + onFieldChange('pollIntervalMinutes', Number.isNaN(parsed) ? undefined : Math.max(1, parsed)); }} disabled={saving} className={inputClasses} /> </label>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/GitHubSection.tsx` around lines 52 - 66, The input shows a UI default (5) but doesn't write it into state, causing a mismatch; change the input to consistently read from feed.pollIntervalMinutes (use feed.pollIntervalMinutes ?? 5 instead of draftConfig.github?.feed?.pollIntervalMinutes) and ensure the draft is initialized when missing by calling onFieldChange('pollIntervalMinutes', 5) (e.g., in a small useEffect that runs when feed exists and feed.pollIntervalMinutes is undefined) so the displayed value and draft state stay aligned; keep the existing onChange logic to parse and set values.web/src/components/dashboard/config-sections/ChallengesSection.tsx (2)
90-98:⚠️ Potential issue | 🟠 MajorDo not write invalid timezone values into
draftConfig.Line 92 forwards
tzbefore validation, so typos likeAmerica/Chcagostill land inchallenges.timezoneand can be saved. The field needs a local input buffer and should only commit upstream once the value is valid.🛠️ Suggested approach
-import { useState } from 'react'; +import { useEffect, useState } from 'react'; ... - const currentTimezone = draftConfig.challenges?.timezone ?? 'America/New_York'; + const [timezoneInput, setTimezoneInput] = useState( + draftConfig.challenges?.timezone ?? 'America/New_York', + ); const [timezoneError, setTimezoneError] = useState<string | null>(null); + + useEffect(() => { + setTimezoneInput(draftConfig.challenges?.timezone ?? 'America/New_York'); + }, [draftConfig.challenges?.timezone]); ... - value={currentTimezone} + value={timezoneInput} onChange={(e) => { const tz = e.target.value; - onFieldChange('timezone', tz); + setTimezoneInput(tz); if (tz && !isValidTimezone(tz)) { setTimezoneError(`"${tz}" is not a valid IANA timezone`); - } else { - setTimezoneError(null); + return; } + setTimezoneError(null); + onFieldChange('timezone', tz); }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx` around lines 90 - 98, The current onChange handler forwards tz to onFieldChange before validation, allowing invalid values into draftConfig; change this by introducing a local state buffer (e.g., timezoneInput) used in the input's onChange to update that buffer and call setTimezoneError using isValidTimezone(tz), and only call onFieldChange('timezone', tz) when the value is valid (and/or empty if you want to clear the upstream field). Update the onChange logic (and any submit/blur handler if present) to commit the buffered timezone to draftConfig via onFieldChange only after validation succeeds, leaving invalid entries in the local buffer and showing setTimezoneError until corrected.
84-84:⚠️ Potential issue | 🟡 MinorUse a responsive span for the timezone row.
Line 84 applies
col-span-2even when the grid isgrid-cols-1, which creates an implicit extra column on small screens. Limit the span to the two-column breakpoint.🧩 Suggested fix
- <label htmlFor="challenge-timezone" className="space-y-2 col-span-2"> + <label htmlFor="challenge-timezone" className="space-y-2 md:col-span-2">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx` at line 84, The label element in ChallengesSection.tsx using className "space-y-2 col-span-2" forces a two-column span even on single-column layouts; change the span class to a responsive variant (e.g., replace "col-span-2" with "md:col-span-2") on the label with htmlFor="challenge-timezone" so it only spans two columns at the intended breakpoint while keeping "space-y-2".web/src/lib/config-updates.ts (2)
242-243:⚠️ Potential issue | 🟠 MajorDon't overwrite non-array fields in
appendArrayItem.If the resolved path already holds an object or scalar, this silently replaces it with a new array. That hides bad paths and corrupts the config shape instead of failing safely.
🛡️ Proposed fix
const lastKey = path[path.length - 1]; - const arr = [...(Array.isArray(cursor[lastKey]) ? (cursor[lastKey] as T[]) : []), item]; + const current = cursor[lastKey]; + if (current !== undefined && !Array.isArray(current)) return config; + const arr = [...((current as T[] | undefined) ?? []), item];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-updates.ts` around lines 242 - 243, In appendArrayItem, avoid silently replacing non-array values at the target path: when resolving the final key (lastKey) and reading cursor[lastKey], if it is undefined create a new array with the item, if it is already an array append the item, but if it exists and is not an array (object/scalar) throw an error (or return a failing result) instead of constructing arr and overwriting; update the logic around lastKey/cursor[lastKey] to perform this type check and fail-fast so bad paths do not corrupt the config shape.
130-141:⚠️ Potential issue | 🟠 MajorKeep
updateArrayItemreplace-only.
index === arr.lengthis currently accepted for any existing array, so off-by-one callers append a new element instead of being rejected. Only the[]+index === 0initialization case should be allowed here; everything else should stay replace-only.🐛 Proposed fix
- // Initialize as empty array if missing, then update/append the item + // Initialize as empty array if missing so index 0 can create the first item const arr = Array.isArray(cursor[lastKey]) ? [...(cursor[lastKey] as T[])] : []; - // Validate index bounds (allow index === arr.length to append into freshly initialized array) - if (!Number.isInteger(index) || index < 0 || index > arr.length) { + const isInitialInsert = arr.length === 0 && index === 0; + if (!Number.isInteger(index) || index < 0 || (!isInitialInsert && index >= arr.length)) { return config; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/lib/config-updates.ts` around lines 130 - 141, The updateArrayItem logic currently allows index === arr.length for existing arrays, which lets off-by-one callers append instead of being rejected; change the validation so appending (index === arr.length) is only permitted when the target was missing and you're initializing a new array (i.e., when cursor[lastKey] === undefined), otherwise require replacements only (index < arr.length). In practice, record whether the original existed (e.g., const wasMissing = cursor[lastKey] === undefined), build arr as now, then change the bounds check in updateArrayItem to reject index === arr.length unless wasMissing and index === 0, and otherwise ensure index < arr.length; keep the other guards (non-integer, negative) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@TASK2.md`:
- Around line 1-41: Delete the transient PR-local checklist block (the “Remove
this PR-local checklist before merge.” note and any machine-specific paths or
review-state lines) from TASK2.md so no local environment or ephemeral review
notes remain in the committed file; if the whole file is only transient scratch
work, remove TASK2.md entirely or replace it with a short, permanent task list
without local paths or private notes.
In `@TASK3.md`:
- Around line 1-85: Remove the ad-hoc TASK3.md from the PR: delete the file from
the repo (or run git rm --cached TASK3.md if you want to keep it locally) and
amend the commit so it is not part of the branch; to prevent recurrence add
TASK3.md to a local ignore (e.g., .git/info/exclude) or your global gitignore
rather than committing it to the project. Ensure no other planning docs like
TASK2.md are staged before finalizing the commit.
In `@web/src/components/dashboard/config-sections/EngagementSection.tsx`:
- Around line 41-42: The current badge list uses badge.label as the React key in
the badges.map rendering (in EngagementSection's badges.map and the similar map
at lines ~86-88), but badge.label is editable and can be duplicated which causes
remounts and lost focus; change the key to use the index from the map callback
(e.g., key={i} or key={`badge-row-${i}` where i is the map index) for both
places (the badges.map rendering and the other badge-row mapping at ~86-88) so
rows remain stable while editing until persistent IDs exist.
- Around line 43-68: The badge inputs in EngagementSection lack accessible
names; update the two Input elements (the days numeric input and the label text
input) to provide unique accessible names per row—either add aria-label
attributes that include the row index (e.g., aria-label={`Days for activity
badge ${i}`} and aria-label={`Badge label ${i}`}) or generate unique ids and
associate <label htmlFor={id}> for each input; ensure the change is applied
where badges are updated (the onChange handlers/newBadges logic and
disabled={saving} remain unchanged) so each input is announced with a clear,
unique name by screen readers.
In `@web/src/components/dashboard/config-sections/ModerationSection.tsx`:
- Around line 26-28: Replace the duplicated local inputClasses string in
ModerationSection.tsx with the centralized constant from the shared module:
remove the local const inputClasses and import the shared inputClasses (or the
exported name for shared input styling) from
web/src/components/dashboard/config-sections/shared.ts, then use that imported
symbol wherever the local variable was referenced so all sections share the same
styling token.
In `@web/src/components/dashboard/config-sections/TriageSection.tsx`:
- Around line 88-177: The numeric inputs are controlled as numbers so
intermediate edits get lost; change each input (e.g., ids "classify-budget",
"respond-budget", "default-interval-ms", "timeout-ms", "context-messages",
"max-buffer-size") to maintain a local string state (e.g., useState per field
initialized from draftConfig.triage.* converted to string), update that string
in onChange (do not call parseNumberInput there), and onBlur parse with
parseNumberInput and then call onFieldChange (or clear/commit as appropriate);
ensure the input value reads from the local string state and syncs back to
draftConfig when the component receives new props or on save.
---
Duplicate comments:
In `@web/src/components/dashboard/config-sections/ChallengesSection.tsx`:
- Around line 90-98: The current onChange handler forwards tz to onFieldChange
before validation, allowing invalid values into draftConfig; change this by
introducing a local state buffer (e.g., timezoneInput) used in the input's
onChange to update that buffer and call setTimezoneError using
isValidTimezone(tz), and only call onFieldChange('timezone', tz) when the value
is valid (and/or empty if you want to clear the upstream field). Update the
onChange logic (and any submit/blur handler if present) to commit the buffered
timezone to draftConfig via onFieldChange only after validation succeeds,
leaving invalid entries in the local buffer and showing setTimezoneError until
corrected.
- Line 84: The label element in ChallengesSection.tsx using className "space-y-2
col-span-2" forces a two-column span even on single-column layouts; change the
span class to a responsive variant (e.g., replace "col-span-2" with
"md:col-span-2") on the label with htmlFor="challenge-timezone" so it only spans
two columns at the intended breakpoint while keeping "space-y-2".
In `@web/src/components/dashboard/config-sections/GitHubSection.tsx`:
- Around line 52-66: The input shows a UI default (5) but doesn't write it into
state, causing a mismatch; change the input to consistently read from
feed.pollIntervalMinutes (use feed.pollIntervalMinutes ?? 5 instead of
draftConfig.github?.feed?.pollIntervalMinutes) and ensure the draft is
initialized when missing by calling onFieldChange('pollIntervalMinutes', 5)
(e.g., in a small useEffect that runs when feed exists and
feed.pollIntervalMinutes is undefined) so the displayed value and draft state
stay aligned; keep the existing onChange logic to parse and set values.
In `@web/src/lib/config-updates.ts`:
- Around line 242-243: In appendArrayItem, avoid silently replacing non-array
values at the target path: when resolving the final key (lastKey) and reading
cursor[lastKey], if it is undefined create a new array with the item, if it is
already an array append the item, but if it exists and is not an array
(object/scalar) throw an error (or return a failing result) instead of
constructing arr and overwriting; update the logic around
lastKey/cursor[lastKey] to perform this type check and fail-fast so bad paths do
not corrupt the config shape.
- Around line 130-141: The updateArrayItem logic currently allows index ===
arr.length for existing arrays, which lets off-by-one callers append instead of
being rejected; change the validation so appending (index === arr.length) is
only permitted when the target was missing and you're initializing a new array
(i.e., when cursor[lastKey] === undefined), otherwise require replacements only
(index < arr.length). In practice, record whether the original existed (e.g.,
const wasMissing = cursor[lastKey] === undefined), build arr as now, then change
the bounds check in updateArrayItem to reject index === arr.length unless
wasMissing and index === 0, and otherwise ensure index < arr.length; keep the
other guards (non-integer, negative) intact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 05052d5e-3925-4fac-83e9-71ebeebdf6c0
📒 Files selected for processing (10)
TASK2.mdTASK3.mdweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsxweb/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/lib/config-updates.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,tsx,jsx}: Use ESM only — Useimport/export, no CommonJS
Use single quotes — No double quotes except in JSON
Semicolons are always required
Files:
web/src/components/dashboard/config-sections/GitHubSection.tsxweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsx
**/*.{js,ts,tsx,jsx,json,md}
📄 CodeRabbit inference engine (AGENTS.md)
Use 2-space indent — Biome enforced
Files:
web/src/components/dashboard/config-sections/GitHubSection.tsxTASK3.mdweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/lib/config-updates.tsweb/src/components/dashboard/config-sections/StarboardSection.tsxTASK2.mdweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/tests/components/dashboard/**/*.test.{ts,tsx} : Web dashboard config editor tests should cover category switching, search functionality, dirty badges, and explicit manual-save workspace behavior instead of autosave assumptions
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{ts,tsx} : Web dashboard config editor should use category workspace navigation with metadata-driven config search and reusable `SettingsFeatureCard` component pattern
Applied to files:
web/src/components/dashboard/config-sections/GitHubSection.tsxTASK3.mdweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/tests/components/dashboard/**/*.test.{ts,tsx} : Web dashboard config editor tests should cover category switching, search functionality, dirty badges, and explicit manual-save workspace behavior instead of autosave assumptions
Applied to files:
TASK3.mdweb/src/components/dashboard/config-sections/TriageSection.tsxweb/src/components/dashboard/config-sections/ModerationSection.tsxweb/src/components/dashboard/config-sections/ChallengesSection.tsxweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/AiAutoModSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsx
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/routes/dashboard/**/*.{ts,tsx} : Server-rendered dashboard entry pages should include static metadata for SEO and consistency with client-side title updates
Applied to files:
TASK3.mdweb/src/components/dashboard/config-sections/StarboardSection.tsxweb/src/components/dashboard/config-sections/EngagementSection.tsx
📚 Learning: 2026-03-08T04:37:50.315Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:37:50.315Z
Learning: Applies to **/*.{js,ts,tsx,jsx} : Use single quotes — No double quotes except in JSON
Applied to files:
web/src/components/dashboard/config-sections/ChallengesSection.tsx
📚 Learning: 2026-03-08T04:37:50.316Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:37:50.316Z
Learning: All config sections must be added to `SAFE_CONFIG_KEYS` in `src/api/utils/configAllowlist.js` to enable API saves
Applied to files:
web/src/lib/config-updates.ts
📚 Learning: 2026-03-08T02:15:42.321Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-08T02:15:42.321Z
Learning: Applies to web/src/components/layout/dashboard-shell.tsx : Dashboard shell layout component should mount `DashboardTitleSync` to sync `document.title` on pathname changes for client-rendered pages
Applied to files:
web/src/components/dashboard/config-sections/StarboardSection.tsx
📚 Learning: 2026-03-08T04:37:50.316Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-08T04:37:50.316Z
Learning: Applies to web/src/app/dashboard/page.{ts,tsx} : Dashboard SSR entry points must export `metadata` using `createPageMetadata()` from `web/src/lib/page-titles.ts`
Applied to files:
web/src/components/dashboard/config-sections/StarboardSection.tsx
🪛 LanguageTool
TASK3.md
[uncategorized] ~42-~42: The official name of this software platform is spelled with a capital “H”.
Context: ...ver writes it to draftConfig - Fix: use value={draftConfig.github?.pollIntervalMinutes ?? 5} AND write b...
(GITHUB)
🪛 markdownlint-cli2 (0.21.0)
TASK3.md
[warning] 8-8: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 16-16: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
[warning] 17-17: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
[warning] 18-18: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
[warning] 20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 21-21: Ordered list item prefix
Expected: 1; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
[warning] 22-22: Ordered list item prefix
Expected: 2; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
[warning] 23-23: Ordered list item prefix
Expected: 3; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
[warning] 24-24: Ordered list item prefix
Expected: 4; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
[warning] 25-25: Ordered list item prefix
Expected: 5; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
[warning] 26-26: Ordered list item prefix
Expected: 6; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
[warning] 27-27: Ordered list item prefix
Expected: 7; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
[warning] 28-28: Ordered list item prefix
Expected: 8; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
[warning] 29-29: Ordered list item prefix
Expected: 9; Actual: 17; Style: 1/2/3
(MD029, ol-prefix)
[warning] 31-31: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 32-32: Ordered list item prefix
Expected: 1; Actual: 18; Style: 1/2/3
(MD029, ol-prefix)
[warning] 33-33: Ordered list item prefix
Expected: 2; Actual: 19; Style: 1/2/3
(MD029, ol-prefix)
[warning] 35-35: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 36-36: Ordered list item prefix
Expected: 1; Actual: 20; Style: 1/1/1
(MD029, ol-prefix)
[warning] 38-38: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🔇 Additional comments (5)
web/src/components/dashboard/config-sections/StarboardSection.tsx (1)
1-150: Well-structured component addressing all prior feedback.The implementation correctly:
- Imports
inputClassesfrom the shared module (addressing DRY concern)- Uses local state (
ignoredChannelsRaw) withuseEffectsync for external changes- Keeps
channelIdas an empty string rather thannullwhen cleared- Flushes
ignoredChannelsto draft on every keystroke while normalizing display on blurThe
parseNumberInputusage with the undefined guard (lines 72-73) follows the same pattern asTriageSection, maintaining consistency across config sections.web/src/components/dashboard/config-sections/GitHubSection.tsx (3)
1-12: LGTM!Imports are properly structured with ESM syntax, and the interface clearly defines the component's contract. The
'use client'directive is appropriate for a component with interactive form elements.
14-38: LGTM!Well-documented component with clear JSDoc. The toggle switch correctly uses the extracted
feedvariable and handles the disabled state appropriately.
39-51: LGTM!The grid is now mobile-first responsive, addressing the previous review concern. The channel ID input correctly trims whitespace and converts empty strings to
null. Accessibility is properly handled with matchinghtmlFor/idattributes.web/src/components/dashboard/config-sections/TriageSection.tsx (1)
223-236: Nice improvement on moderation log channel selection.Using
ChannelSelectorhere keeps the value guild-scoped and avoids saving arbitrary free-form channel IDs.
| # TASK: Fix remaining 21 review threads on PR #248 | ||
|
|
||
| Branch: `refactor/triage-prompt-rewrite` | ||
| Work in: `/home/bill/worktrees/volvox-bot-248` | ||
|
|
||
| ## Threads to fix | ||
|
|
||
| ### maintain-docs.md | ||
| 1. Add `# Maintain Docs` top-level heading (markdownlint) | ||
| 2. Replace hardcoded date `2026-03-04` with `YYYY-MM-DD` placeholder + comment | ||
| 3. Capitalize "Markdown" as proper noun | ||
| 4. Line 20 — fix whatever workflow issue CodeRabbit flagged (read the file) | ||
| 5. Line 61 — branch naming: CodeRabbit says `copilot/` prefix is required for GitHub Copilot coding agent branches — read the file and fix the branch naming if it uses a non-compliant format | ||
|
|
||
| ### Backend | ||
| 6. `tests/modules/triage-prompt.test.js` line 278 — add test that channel metadata with tag-like chars is escaped via `escapePromptDelimiters()` | ||
| 7. `src/prompts/community-rules.md` line 15 — change `mute` to `timeout` in the moderation ladder (classifier only supports `warn`, `timeout`, `kick`, `ban`, `delete`) | ||
| 8. `src/prompts/triage-classify.md` line 26 — update stale example `Rule 4: No spam/shilling` to match current rule `Rule 4: No spam or drive-by promotion` | ||
|
|
||
| ### Frontend — config-sections | ||
| 9. `web/src/components/dashboard/config-sections/AiAutoModSection.tsx` line 16 — import `inputClasses` from the shared module (`config-sections/shared.ts`) instead of defining it locally | ||
| 10. `web/src/components/dashboard/config-sections/ChallengesSection.tsx` line 73 — constrain `postTime` to a real clock value (use `type="time"` input or validate `HH:MM` format before saving) | ||
| 11. `web/src/components/dashboard/config-sections/ModerationSection.tsx` line 259 — `blockedDomains` currently only updates `draftConfig` on `onBlur`. Change to update on `onChange` (or both) so saves don't miss in-progress edits | ||
| 12. `web/src/components/dashboard/config-sections/TicketsSection.tsx` line 139 — read the file and fix whatever issue CodeRabbit found | ||
| 13. `web/src/components/dashboard/config-sections/TriageSection.tsx` line 225 — `moderationLogChannel` was regressed to a plain text input; restore it to use a `ChannelSelector` component | ||
| 14. `web/src/components/dashboard/config-sections/StarboardSection.tsx` — fix whatever major issue was flagged (read file) | ||
| 15. `web/src/components/dashboard/config-sections/GitHubSection.tsx` line 43 — read and fix | ||
| 16. `web/src/components/dashboard/config-sections/ChallengesSection.tsx` — read and fix the major issue | ||
| 17. `web/src/components/dashboard/config-sections/CommunityFeaturesSection.tsx` line 76 — use stricter type for feature config entries instead of `as { enabled?: boolean } | undefined` | ||
|
|
||
| ### Frontend — lib | ||
| 18. `web/src/lib/config-updates.ts` — restrict `section` type to object-valued config sections (not `keyof GuildConfig` which includes scalars) | ||
| 19. `web/src/lib/config-normalization.ts` line 80 — clamp `decimalToPercent` to [0, 100] for symmetry with `percentToDecimal` | ||
|
|
||
| ### Frontend — config-editor | ||
| 20. `web/src/components/dashboard/config-editor.tsx` line 451 — Ctrl+S silently fails and blocks browser save when there are validation errors. Fix: only call `e.preventDefault()` when we're actually handling the save (i.e., `hasChanges && !hasValidationErrors`), otherwise let the browser default fire | ||
|
|
||
| ## Rules | ||
| - Commit each logical group separately with conventional commits | ||
| - Run `pnpm format && pnpm lint` and `pnpm --prefix web lint && pnpm --prefix web typecheck` | ||
| - Do NOT push |
There was a problem hiding this comment.
Remove this PR-local checklist before merge.
This is transient scratch work with a machine-specific path and review-state notes, so it will stale immediately and leaks local environment details into the repo.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@TASK2.md` around lines 1 - 41, Delete the transient PR-local checklist block
(the “Remove this PR-local checklist before merge.” note and any
machine-specific paths or review-state lines) from TASK2.md so no local
environment or ephemeral review notes remain in the committed file; if the whole
file is only transient scratch work, remove TASK2.md entirely or replace it with
a short, permanent task list without local paths or private notes.
| # TASK: Fix 10 remaining PR #248 review threads | ||
|
|
||
| Branch: `refactor/triage-prompt-rewrite` | ||
| Work in: `/home/bill/worktrees/volvox-bot-248` | ||
|
|
||
| ## Fixes | ||
|
|
||
| ### 1. EngagementSection.tsx — stable badge keys | ||
| - File: `web/src/components/dashboard/config-sections/EngagementSection.tsx` line 53 | ||
| - Currently uses index-based key `badge-row-${i}`. When badges are reordered or deleted, React reuses wrong DOM nodes. | ||
| - Fix: give each badge a stable `id` (e.g. `badge.id ?? badge.name ?? index`) as the key | ||
|
|
||
| ### 2. AiAutoModSection.tsx — clamp threshold before converting | ||
| - File: `web/src/components/dashboard/config-sections/AiAutoModSection.tsx` line 99 | ||
| - `150` or `-5` gets saved as `1.5`/`-0.05` without clamping | ||
| - Fix: clamp parsed value to [0, 100] before `percentToDecimal()`: | ||
| ```tsx | ||
| const clamped = Math.min(100, Math.max(0, parsed)); | ||
| onThresholdChange(percentToDecimal(clamped)); | ||
| ``` | ||
|
|
||
| ### 3. ChallengesSection.tsx — single quotes in JSX strings | ||
| - File: `web/src/components/dashboard/config-sections/ChallengesSection.tsx` line 84 | ||
| - JSX string literals use double quotes; repo convention is single quotes | ||
| - Fix: change double-quoted JSX string attributes to single quotes where applicable (biome can auto-fix this) | ||
|
|
||
| ### 4. ChallengesSection.tsx — validate IANA timezone | ||
| - File: `web/src/components/dashboard/config-sections/ChallengesSection.tsx` line 84 | ||
| - Timezone is still free-text; typos silently break scheduling | ||
| - Fix: Use `Intl.supportedValuesOf('timeZone')` to validate, or add a `<datalist>` with common timezones, and show an error if the entered value isn't a valid IANA zone: | ||
| ```tsx | ||
| const isValidTimezone = (tz: string) => { | ||
| try { Intl.DateTimeFormat(undefined, { timeZone: tz }); return true; } | ||
| catch { return false; } | ||
| }; | ||
| ``` | ||
| Show a red error message below the input if invalid. | ||
|
|
||
| ### 5. GitHubSection.tsx — sync pollIntervalMinutes with draft state | ||
| - File: `web/src/components/dashboard/config-sections/GitHubSection.tsx` line 63 | ||
| - When `pollIntervalMinutes` is unset, renders `5` but never writes it to draftConfig | ||
| - Fix: use `value={draftConfig.github?.pollIntervalMinutes ?? 5}` AND write back on change (including the default 5): | ||
| ```tsx | ||
| onChange={(e) => { | ||
| const val = Math.max(1, parseInt(e.target.value, 10) || 5); | ||
| onFieldChange('pollIntervalMinutes', val); | ||
| }} | ||
| ``` | ||
|
|
||
| ### 6. ModerationSection.tsx — mobile-responsive rate-limit grids | ||
| - File: `web/src/components/dashboard/config-sections/ModerationSection.tsx` line 229 | ||
| - Fix `grid-cols-2` and `grid-cols-3` → `grid-cols-1 sm:grid-cols-2` and `grid-cols-1 sm:grid-cols-3` | ||
|
|
||
| ### 7. StarboardSection.tsx — use `''` not `null` for cleared channelId | ||
| - File: `web/src/components/dashboard/config-sections/StarboardSection.tsx` line 57 | ||
| - `StarboardConfig.channelId` is `string`, not `string | null` | ||
| - Fix: `onChange={(val) => onFieldChange('channelId', val ?? '')}` instead of `val ?? null` | ||
|
|
||
| ### 8. StarboardSection.tsx — ignoredChannels updates on change not just blur | ||
| - File: `web/src/components/dashboard/config-sections/StarboardSection.tsx` line 133 | ||
| - Save can fire while input has focus; latest value missed if user saves before blur | ||
| - Fix: update `draftConfig` on `onChange` too (keep raw state for display but also flush to draft): | ||
| ```tsx | ||
| onChange={(e) => { | ||
| setIgnoredChannelsRaw(e.target.value); | ||
| // also flush to draft so Ctrl+S captures current value | ||
| const parsed = e.target.value.split(',').map(s => s.trim()).filter(Boolean); | ||
| onIgnoredChannelsChange(parsed); | ||
| }} | ||
| ``` | ||
|
|
||
| ### 9. TriageSection.tsx — mobile-responsive numeric grids | ||
| - File: `web/src/components/dashboard/config-sections/TriageSection.tsx` line 182 | ||
| - Same as ModerationSection — `grid-cols-2` → `grid-cols-1 sm:grid-cols-2` | ||
|
|
||
| ### 10. config-updates.ts — fix updateArrayItem early return | ||
| - File: `web/src/lib/config-updates.ts` line 137 | ||
| - Early return on missing array breaks the empty-array initialization case | ||
| - Check existing tests in `web/tests/lib/config-updates.test.ts` to understand the expected behavior | ||
| - Fix: instead of returning `prev` when the array is missing, initialize it as `[]` and proceed with the update | ||
|
|
||
| ## Rules | ||
| - Commit each logical group (backend fixes together, frontend sections together, lib fixes together) | ||
| - Run `pnpm format && pnpm lint` and `pnpm --prefix web lint && pnpm --prefix web typecheck` | ||
| - Do NOT push |
There was a problem hiding this comment.
Keep ad-hoc review task docs out of version control.
Like TASK2.md, this is branch/worktree-specific planning state rather than durable docs, and it currently adds markdownlint noise on top of the stale local-path details.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~42-~42: The official name of this software platform is spelled with a capital “H”.
Context: ...ver writes it to draftConfig - Fix: use value={draftConfig.github?.pollIntervalMinutes ?? 5} AND write b...
(GITHUB)
🪛 markdownlint-cli2 (0.21.0)
[warning] 8-8: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 16-16: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
[warning] 17-17: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
[warning] 18-18: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
[warning] 20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 21-21: Ordered list item prefix
Expected: 1; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
[warning] 22-22: Ordered list item prefix
Expected: 2; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
[warning] 23-23: Ordered list item prefix
Expected: 3; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
[warning] 24-24: Ordered list item prefix
Expected: 4; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
[warning] 25-25: Ordered list item prefix
Expected: 5; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
[warning] 26-26: Ordered list item prefix
Expected: 6; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
[warning] 27-27: Ordered list item prefix
Expected: 7; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
[warning] 28-28: Ordered list item prefix
Expected: 8; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
[warning] 29-29: Ordered list item prefix
Expected: 9; Actual: 17; Style: 1/2/3
(MD029, ol-prefix)
[warning] 31-31: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 32-32: Ordered list item prefix
Expected: 1; Actual: 18; Style: 1/2/3
(MD029, ol-prefix)
[warning] 33-33: Ordered list item prefix
Expected: 2; Actual: 19; Style: 1/2/3
(MD029, ol-prefix)
[warning] 35-35: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 36-36: Ordered list item prefix
Expected: 1; Actual: 20; Style: 1/1/1
(MD029, ol-prefix)
[warning] 38-38: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@TASK3.md` around lines 1 - 85, Remove the ad-hoc TASK3.md from the PR: delete
the file from the repo (or run git rm --cached TASK3.md if you want to keep it
locally) and amend the commit so it is not part of the branch; to prevent
recurrence add TASK3.md to a local ignore (e.g., .git/info/exclude) or your
global gitignore rather than committing it to the project. Ensure no other
planning docs like TASK2.md are staged before finalizing the commit.
| {badges.map((badge, i) => ( | ||
| <div key={badge.label || `badge-row-${i}`} className="flex items-center gap-2"> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd web/src/components/dashboard/config-sections && wc -l EngagementSection.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 88
🏁 Script executed:
cd web/src/components/dashboard/config-sections && sed -n '35,100p' EngagementSection.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 2212
🏁 Script executed:
cd web/src/components/dashboard/config-sections && cat -n EngagementSection.tsx | head -100Repository: VolvoxLLC/volvox-bot
Length of output: 3853
Use a stable key for each badge row.
badge.label is editable, and new rows all start as '🌟 New Badge', so this list can produce duplicate keys immediately and remount a row while the user is typing. That usually shows up as lost focus/caret jumps and React key warnings. Use a stable per-row key instead. Until badges have persistent IDs, the index is safer here because rows are only appended/removed.
Suggested fix
- {badges.map((badge, i) => (
- <div key={badge.label || `badge-row-${i}`} className="flex items-center gap-2">
+ {badges.map((badge, i) => (
+ <div key={`badge-row-${i}`} className="flex items-center gap-2">Also applies to: 86-88
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/config-sections/EngagementSection.tsx` around
lines 41 - 42, The current badge list uses badge.label as the React key in the
badges.map rendering (in EngagementSection's badges.map and the similar map at
lines ~86-88), but badge.label is editable and can be duplicated which causes
remounts and lost focus; change the key to use the index from the map callback
(e.g., key={i} or key={`badge-row-${i}` where i is the map index) for both
places (the badges.map rendering and the other badge-row mapping at ~86-88) so
rows remain stable while editing until persistent IDs exist.
| <Input | ||
| className="w-20" | ||
| type="number" | ||
| min={0} | ||
| value={badge.days ?? 0} | ||
| onChange={(e) => { | ||
| const newBadges = [...badges]; | ||
| newBadges[i] = { | ||
| ...newBadges[i], | ||
| days: Math.max(0, parseInt(e.target.value, 10) || 0), | ||
| }; | ||
| onActivityBadgesChange(newBadges); | ||
| }} | ||
| disabled={saving} | ||
| /> | ||
| <span className="text-xs text-muted-foreground">days →</span> | ||
| <Input | ||
| className="flex-1" | ||
| value={badge.label ?? ''} | ||
| onChange={(e) => { | ||
| const newBadges = [...badges]; | ||
| newBadges[i] = { ...newBadges[i], label: e.target.value }; | ||
| onActivityBadgesChange(newBadges); | ||
| }} | ||
| disabled={saving} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd web/src/components/dashboard/config-sections && wc -l EngagementSection.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 88
🏁 Script executed:
cd web/src/components/dashboard/config-sections && sed -n '30,80p' EngagementSection.tsxRepository: VolvoxLLC/volvox-bot
Length of output: 1976
Add accessible names to the badge inputs.
These repeated inputs do not have labels, so assistive tech will expose them as generic edit fields with no clear purpose. Add a unique accessible name for the days input and the label input in each row.
Suggested fix
<Input
className="w-20"
type="number"
min={0}
+ aria-label={`Badge ${i + 1} minimum active days`}
value={badge.days ?? 0}
onChange={(e) => {
const newBadges = [...badges];
newBadges[i] = {
...newBadges[i],
@@
<Input
className="flex-1"
+ aria-label={`Badge ${i + 1} label`}
value={badge.label ?? ''}
onChange={(e) => {
const newBadges = [...badges];
newBadges[i] = { ...newBadges[i], label: e.target.value };
onActivityBadgesChange(newBadges);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Input | |
| className="w-20" | |
| type="number" | |
| min={0} | |
| value={badge.days ?? 0} | |
| onChange={(e) => { | |
| const newBadges = [...badges]; | |
| newBadges[i] = { | |
| ...newBadges[i], | |
| days: Math.max(0, parseInt(e.target.value, 10) || 0), | |
| }; | |
| onActivityBadgesChange(newBadges); | |
| }} | |
| disabled={saving} | |
| /> | |
| <span className="text-xs text-muted-foreground">days →</span> | |
| <Input | |
| className="flex-1" | |
| value={badge.label ?? ''} | |
| onChange={(e) => { | |
| const newBadges = [...badges]; | |
| newBadges[i] = { ...newBadges[i], label: e.target.value }; | |
| onActivityBadgesChange(newBadges); | |
| }} | |
| disabled={saving} | |
| /> | |
| <Input | |
| className="w-20" | |
| type="number" | |
| min={0} | |
| aria-label={`Badge ${i + 1} minimum active days`} | |
| value={badge.days ?? 0} | |
| onChange={(e) => { | |
| const newBadges = [...badges]; | |
| newBadges[i] = { | |
| ...newBadges[i], | |
| days: Math.max(0, parseInt(e.target.value, 10) || 0), | |
| }; | |
| onActivityBadgesChange(newBadges); | |
| }} | |
| disabled={saving} | |
| /> | |
| <span className="text-xs text-muted-foreground">days →</span> | |
| <Input | |
| className="flex-1" | |
| aria-label={`Badge ${i + 1} label`} | |
| value={badge.label ?? ''} | |
| onChange={(e) => { | |
| const newBadges = [...badges]; | |
| newBadges[i] = { ...newBadges[i], label: e.target.value }; | |
| onActivityBadgesChange(newBadges); | |
| }} | |
| disabled={saving} | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/config-sections/EngagementSection.tsx` around
lines 43 - 68, The badge inputs in EngagementSection lack accessible names;
update the two Input elements (the days numeric input and the label text input)
to provide unique accessible names per row—either add aria-label attributes that
include the row index (e.g., aria-label={`Days for activity badge ${i}`} and
aria-label={`Badge label ${i}`}) or generate unique ids and associate <label
htmlFor={id}> for each input; ensure the change is applied where badges are
updated (the onChange handlers/newBadges logic and disabled={saving} remain
unchanged) so each input is announced with a clear, unique name by screen
readers.
| /** Shared input styling for text inputs. */ | ||
| const inputClasses = | ||
| 'w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Reuse the shared inputClasses constant here.
This duplicates the styling token already centralized in web/src/components/dashboard/config-sections/shared.ts, so future input-style changes can drift between sections.
♻️ Proposed refactor
import { Input } from '@/components/ui/input';
import { parseNumberInput } from '@/lib/config-normalization';
import type { GuildConfig } from '@/lib/config-utils';
import { ToggleSwitch } from '../toggle-switch';
+import { inputClasses } from './shared';
-/** Shared input styling for text inputs. */
-const inputClasses =
- 'w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/config-sections/ModerationSection.tsx` around
lines 26 - 28, Replace the duplicated local inputClasses string in
ModerationSection.tsx with the centralized constant from the shared module:
remove the local const inputClasses and import the shared inputClasses (or the
exported name for shared input styling) from
web/src/components/dashboard/config-sections/shared.ts, then use that imported
symbol wherever the local variable was referenced so all sections share the same
styling token.
| <input | ||
| id="classify-budget" | ||
| type="number" | ||
| step="0.01" | ||
| min={0} | ||
| value={draftConfig.triage?.classifyBudget ?? 0} | ||
| onChange={(e) => { | ||
| const num = parseNumberInput(e.target.value, 0); | ||
| if (num !== undefined) onFieldChange('classifyBudget', num); | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| /> | ||
| </label> | ||
| <label htmlFor="respond-budget" className="space-y-2"> | ||
| <span className="text-sm font-medium">Respond Budget</span> | ||
| <input | ||
| id="respond-budget" | ||
| type="number" | ||
| step="0.01" | ||
| min={0} | ||
| value={draftConfig.triage?.respondBudget ?? 0} | ||
| onChange={(e) => { | ||
| const num = parseNumberInput(e.target.value, 0); | ||
| if (num !== undefined) onFieldChange('respondBudget', num); | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| /> | ||
| </label> | ||
| </div> | ||
| <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> | ||
| <NumberField | ||
| label="Classify Budget" | ||
| value={draftConfig.triage?.classifyBudget ?? 0} | ||
| onChange={(v) => onFieldChange('classifyBudget', v)} | ||
| disabled={saving} | ||
| step={0.01} | ||
| min={0} | ||
| /> | ||
| <NumberField | ||
| label="Respond Budget" | ||
| value={draftConfig.triage?.respondBudget ?? 0} | ||
| onChange={(v) => onFieldChange('respondBudget', v)} | ||
| disabled={saving} | ||
| step={0.01} | ||
| min={0} | ||
| /> | ||
| </div> | ||
| <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> | ||
| <NumberField | ||
| label="Default Interval (ms)" | ||
| value={draftConfig.triage?.defaultInterval ?? 3000} | ||
| onChange={(v) => onFieldChange('defaultInterval', v)} | ||
| disabled={saving} | ||
| min={1} | ||
| /> | ||
| <NumberField | ||
| label="Timeout (ms)" | ||
| value={draftConfig.triage?.timeout ?? 30000} | ||
| onChange={(v) => onFieldChange('timeout', v)} | ||
| disabled={saving} | ||
| min={1} | ||
| /> | ||
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | ||
| <label htmlFor="default-interval-ms" className="space-y-2"> | ||
| <span className="text-sm font-medium">Default Interval (ms)</span> | ||
| <input | ||
| id="default-interval-ms" | ||
| type="number" | ||
| min={1} | ||
| value={draftConfig.triage?.defaultInterval ?? 3000} | ||
| onChange={(e) => { | ||
| const num = parseNumberInput(e.target.value, 1); | ||
| if (num !== undefined) onFieldChange('defaultInterval', num); | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| /> | ||
| </label> | ||
| <label htmlFor="timeout-ms" className="space-y-2"> | ||
| <span className="text-sm font-medium">Timeout (ms)</span> | ||
| <input | ||
| id="timeout-ms" | ||
| type="number" | ||
| min={1} | ||
| value={draftConfig.triage?.timeout ?? 30000} | ||
| onChange={(e) => { | ||
| const num = parseNumberInput(e.target.value, 1); | ||
| if (num !== undefined) onFieldChange('timeout', num); | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| /> | ||
| </label> | ||
| </div> | ||
| <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> | ||
| <NumberField | ||
| label="Context Messages" | ||
| value={draftConfig.triage?.contextMessages ?? 10} | ||
| onChange={(v) => onFieldChange('contextMessages', v)} | ||
| disabled={saving} | ||
| min={1} | ||
| /> | ||
| <NumberField | ||
| label="Max Buffer Size" | ||
| value={draftConfig.triage?.maxBufferSize ?? 30} | ||
| onChange={(v) => onFieldChange('maxBufferSize', v)} | ||
| disabled={saving} | ||
| min={1} | ||
| /> | ||
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | ||
| <label htmlFor="context-messages" className="space-y-2"> | ||
| <span className="text-sm font-medium">Context Messages</span> | ||
| <input | ||
| id="context-messages" | ||
| type="number" | ||
| min={1} | ||
| value={draftConfig.triage?.contextMessages ?? 10} | ||
| onChange={(e) => { | ||
| const num = parseNumberInput(e.target.value, 1); | ||
| if (num !== undefined) onFieldChange('contextMessages', num); | ||
| }} | ||
| disabled={saving} | ||
| className={inputClasses} | ||
| /> | ||
| </label> | ||
| <label htmlFor="max-buffer-size" className="space-y-2"> | ||
| <span className="text-sm font-medium">Max Buffer Size</span> | ||
| <input | ||
| id="max-buffer-size" | ||
| type="number" | ||
| min={1} | ||
| value={draftConfig.triage?.maxBufferSize ?? 30} | ||
| onChange={(e) => { | ||
| const num = parseNumberInput(e.target.value, 1); | ||
| if (num !== undefined) onFieldChange('maxBufferSize', num); | ||
| }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
norm_file="$(fd 'config-normalization' web/src --type f -e ts -e tsx | head -n 1)"
echo "Inspecting: ${norm_file}"
sed -n '1,220p' "${norm_file}"
echo
echo "All current parseNumberInput call sites in dashboard config UI:"
rg -n -C2 'parseNumberInput\(' web/src/components/dashboard -g '!**/*.test.*'Repository: VolvoxLLC/volvox-bot
Length of output: 20379
Refactor numeric inputs to use local string state to preserve intermediate edits.
These fields are controlled by numeric state and immediately parse user input in onChange. This breaks two workflows:
- Clearing fields: typing
""returnsundefinedfromparseNumberInput, so the field never updates and appears stuck. - Entering decimals: typing
"0."parses to0viaNumber(), causing the field to re-render and lose the decimal point mid-entry.
Keep a local string buffer while editing, then commit the parsed number on blur or save. This affects all numeric inputs across dashboard config sections (Triage, Tickets, Reputation, Moderation, Memory, Community Settings, Starboard).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/config-sections/TriageSection.tsx` around lines
88 - 177, The numeric inputs are controlled as numbers so intermediate edits get
lost; change each input (e.g., ids "classify-budget", "respond-budget",
"default-interval-ms", "timeout-ms", "context-messages", "max-buffer-size") to
maintain a local string state (e.g., useState per field initialized from
draftConfig.triage.* converted to string), update that string in onChange (do
not call parseNumberInput there), and onBlur parse with parseNumberInput and
then call onFieldChange (or clear/commit as appropriate); ensure the input value
reads from the local string state and syncs back to draftConfig when the
component receives new props or on save.

Summary
Rewrites all triage prompts (classifier + responder) for clearer structure, better tone guidance, and adds channel context (name/topic) to conversation text so the AI can make context-aware decisions.
Changes
Feature: Channel context in triage prompts
triage-buffer.js— AddchannelNameandchannelTopicto buffer entry typedeftriage.js(accumulateMessage) — Capturechannel.nameandchannel.topicon every buffered messagetriage-respond.js(fetchChannelContext) — IncludechannelName/channelTopicin fetched context entriestriage-prompt.js(buildConversationText) — Render<channel-context>block at the top of conversation text when channel metadata is availableClassifier prompt rewrite
triage-classify-system.md— Rewritten: adds channel-context awareness, neutral restraint posture, evaluation order (moderate → respond → chime-in → ignore)triage-classify.md— Rewritten: reorders classifications (moderate first), adds "Volvox" name-mention nuance (company vs bot), adds grouping/restraint rules, documents<channel-context>sectionResponder prompt rewrite
triage-respond-system.md— Rewritten: adds<classification-context>(tone per classification type),<anti-abuse>baked in, actionable-advice and sentence-length guidancetriage-respond.md— Rewritten: removes{{antiAbuse}}, adds channel-context awareness note, structured<response-rules>with sub-sectionssearch-guardrails.md— Rewritten: cleaner format, adds explicit "go directly to JSON response" rulecommunity-rules.md— Simplified rule wordingAnti-abuse consolidation
anti-abuse.md— Deleted. Content folded intotriage-respond-system.md(system prompt is stronger position, single source of truth)triage-prompt.js(buildRespondPrompt) — RemovedloadPrompt('anti-abuse')andantiAbusetemplate variableTests
triage-prompt.test.js— Added 5 tests for<channel-context>rendering (present, missing topic, absent, ordering, fallback to context entries). Removed anti-abuse mock/assertions.triage.test.js— Added test forchannelName/channelTopicin buffer entries. UpdatedmakeMessagehelper with channel metadata.Test plan
npx vitest run tests/modules/triage-prompt.test.js— 30 passnpx vitest run tests/modules/triage-respond.test.js— 19 passnpx vitest run tests/modules/triage.test.js— 61 pass