Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions TASK2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# TASK2: Remaining Issue #144 Items

Already done (skip these):
- Loading skeletons ✅
- Error boundaries ✅
- Toast notifications ✅
- XP proxy validation ✅
- Rate limit stale cleanup ✅

## Items to implement now

### 1. Zustand store for member page
- File: `web/src/app/dashboard/[guildId]/members/page.tsx` (or similar)
- Find all useState/useEffect hooks managing member state
- Create `web/src/stores/members-store.ts` with Zustand
- Migrate: member list, loading state, search query, selected member, pagination
- Keep component using the store hooks

### 2. Zustand store for moderation page
- File: `web/src/app/dashboard/[guildId]/moderation/page.tsx` (or similar)
- Same pattern — create `web/src/stores/moderation-store.ts`
- Migrate: cases list, filters, loading, pagination

### 3. console.error cleanup in browser code
- `rg -rn "console\.error" web/src/` — find all occurrences in client components
- Replace with: toast.error() for user-facing errors, or keep console.error where it's truly logging-only
- Do NOT replace server-side console.error (only client components)

### 4. events.js function extraction
- File: `src/modules/events/` — check if there's a monolithic events file or inline handlers in interactionCreate.js
- Run: `wc -l src/modules/events/interactionCreate.js` to check size
- If handlers are inline (ticket_open, ticket_close, poll, etc.), extract each to separate files in `src/modules/handlers/`
- Update interactionCreate.js to import from handlers

### 5. Mobile responsiveness — critical fixes only
- `rg -rn "grid-cols-2\|grid-cols-3\|table\b" web/src/components/dashboard/ --include="*.tsx" | head -30`
- Add `sm:grid-cols-2` fallbacks where fixed grid-cols are used
- Add `overflow-x-auto` wrapper around data tables
- Focus on: member table, moderation cases table, config sections with grids

### 6. Migration gap — add placeholder comment
- Create `migrations/012_placeholder.cjs` with a comment explaining the gap
- Content: migration that logs a warning about the gap, does nothing (no-op)

### 7. Fix totalMessagesSent stat accuracy
- File: find where community page stats are calculated
- `rg -rn "totalMessagesSent\|messages_sent" web/src/ src/`
- Add a comment explaining the known limitation, or filter to last 30 days
- If it's a simple query change, fix it; if architectural, add a TODO comment

### 8. Review bot consolidation (GitHub config)
- Disable Copilot and Greptile PR reviewers — keep Claude (coderabbitai style) + CodeRabbit
- Check `.github/` for reviewer config files
- Check `CODEOWNERS` or `.github/pull_request_review_protection.yml`
- If via GitHub API: `gh api repos/VolvoxLLC/volvox-bot/automated-security-fixes`
- Note: Greptile may be configured in `.greptile.yaml` or via webhook — find and disable

## Architectural items (document only, don't implement)
These need separate planning — just add TODO comments:
- Server-side member search (needs Discord member cache or DB index)
- Conversation search pagination (needs full-text search index)
- Config patch deep validation (needs schema per config key)
- Logger browser shim (nice-to-have, low impact)

## Rules
- Commit each fix separately with conventional commits
- Run `pnpm format && pnpm lint && pnpm test`
- Run `pnpm --prefix web lint && pnpm --prefix web typecheck`
- Do NOT push
21 changes: 21 additions & 0 deletions migrations/012_placeholder.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Migration 012 — Placeholder (gap filler)
*
* There are two migrations numbered 004 in this project:
* - 004_performance_indexes.cjs
* - 004_voice_sessions.cjs
*
* This no-op migration occupies the 012 slot to make the numbering sequence
* explicit going forward. No schema changes are made here.
*/

/** @type {import('node-pg-migrate').MigrationBuilder} */
exports.up = async (pgm) => {
// No-op — this migration exists only to document the sequence gap
pgm.noTransaction();
};

/** @type {import('node-pg-migrate').MigrationBuilder} */
exports.down = async (_pgm) => {
// Nothing to undo
};
Comment on lines +1 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This placeholder does not fix the duplicate migration IDs.

The header documents that the repo already has two 004_* migrations. Filling the 012 slot leaves that conflict in place, so node-pg-migrate can still hit out-of-order or duplicate-ID problems. The fix needs to renumber the conflicting migration file(s), not add a no-op gap marker.

As per coding guidelines, "Database migrations must be sequentially numbered with non-conflicting IDs; rename conflicting migration files to resolve out-of-order execution errors."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/012_placeholder.cjs` around lines 1 - 21, The placeholder
migration doesn't resolve duplicate migration IDs — instead rename the
conflicting files (004_performance_indexes.cjs and 004_voice_sessions.cjs) to
unique, sequential IDs so there are no duplicate "004_" prefixes: choose the
next available numbers in sequence, update the filenames accordingly (preserving
the rest of each filename and their exports.up/exports.down), remove this no-op
placeholder if you used 012 for one of them, and run your migration tooling to
verify no out-of-order/duplicate-ID errors remain.

38 changes: 33 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/api/routes/conversations.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,10 @@ router.get('/', conversationsRateLimit, requireGuildAdmin, validateGuild, async

if (req.query.search && typeof req.query.search === 'string') {
paramIndex++;
// Uses idx_conversations_content_trgm (GIN/trgm) added in migration 004
// Uses idx_conversations_content_trgm (GIN/trgm) added in migration 004.
// TODO: ILIKE + OFFSET pagination is O(n) on large datasets. For better
// performance at scale, switch to a full-text search index (e.g. tsvector
// with GIN) and use keyset/cursor pagination instead of OFFSET.
whereParts.push(`content ILIKE $${paramIndex}`);
values.push(`%${escapeIlike(req.query.search)}%`);
}
Expand Down
5 changes: 5 additions & 0 deletions src/api/routes/guilds.js
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,11 @@ router.get('/:id/analytics', requireGuildAdmin, validateGuild, async (req, res)
}),
dbPool
.query(
// NOTE: totalMessagesSent (and related stats) reflect cumulative all-time counts
// from user_stats, which has no time-series granularity. The user_stats table
// stores running totals per user with no timestamp column for filtering.
// TODO: For time-bounded accuracy (e.g. "last 30 days"), add a
// message_events log table and aggregate from that instead.
`SELECT
COUNT(DISTINCT user_id)::int AS tracked_users,
COALESCE(SUM(messages_sent), 0)::bigint AS total_messages_sent,
Expand Down
7 changes: 7 additions & 0 deletions src/api/routes/members.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,13 @@ router.get('/:id/members', membersRateLimit, requireGuildAdmin, validateGuild, a
// (searches all guild members by username/nickname prefix), otherwise use
// cursor-based listing. Sort is applied after enrichment and is scoped to
// the returned page; it does NOT globally sort all guild members.
//
// TODO: Server-side member search accuracy — Discord's guild.members.search()
// only searches the bot's in-memory member cache (populated by the GUILD_MEMBERS
// privileged intent). For large guilds (100k+ members) the cache is incomplete.
// For full coverage, consider: (a) adding a members DB table populated from
// guildMemberAdd events + bulk sync on startup, or (b) using the Discord HTTP
// API directly with the Search Guild Members endpoint which searches all members.
let memberList;
let paginationCursor = null;
if (search) {
Expand Down
5 changes: 5 additions & 0 deletions src/api/utils/validateConfigPatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,10 @@ export function validateConfigPatchBody(body, SAFE_CONFIG_KEYS) {
return { error: 'Value validation failed', status: 400, details: valErrors };
}

// TODO: Deep per-key schema validation — currently validateSingleValue only checks
// type/range for known paths. Unknown paths pass through without structural validation.
// For full coverage, add a per-key JSON schema registry (one schema per top-level config
// section) and run deep validation against it here before accepting the patch.

Comment on lines +83 to +87
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

TODO acknowledged — schema validation gap documented.

The comment clearly describes the current limitation (unknown paths bypass structural validation) and proposes a reasonable solution (per-key JSON schema registry). This is good documentation of technical debt.

Would you like me to help by:

  1. Opening an issue to track this schema validation enhancement, or
  2. Drafting an implementation approach using a library like ajv for JSON schema validation?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/utils/validateConfigPatch.js` around lines 83 - 87, Add deep per-key
JSON Schema validation before accepting config patches: create a JSON Schema
registry keyed by top-level config sections and integrate an AJV validator (or
equivalent) into the patch acceptance flow in validateConfigPatch.js so that,
for each incoming patch path you first select the matching top-level schema and
run full schema.validate against the patch object; keep validateSingleValue for
existing type/range checks but reject patches that fail the AJV schema
validation and return clear validation errors.

return { path, value, topLevelKey };
}
6 changes: 6 additions & 0 deletions src/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
* - Timestamp formatting
* - Structured output
* - Console transport (file transport added in phase 3)
*
* TODO: Logger browser shim — this module uses Winston + Node.js APIs (fs, path) and cannot
* be imported in browser/Next.js client components. If client-side structured logging is
* needed (e.g. for error tracking or debug mode), create a thin `web/src/lib/logger.ts`
* shim that wraps the browser console with the same interface (info/warn/error/debug)
* and optionally forwards to a remote logging endpoint.
*/

import { existsSync, mkdirSync, readFileSync } from 'node:fs';
Expand Down
Loading
Loading