Skip to content

Conversation

@vcarl
Copy link
Member

@vcarl vcarl commented Oct 29, 2025

I've been moderately unhappy with the linting setup here for a while, mostly it complains at me for code formatting that then instantly gets cleaned up by Prettier, which is just a waste. This looks to catch a lot more useful code errors, like unnecessary nullchecks and sketchy assignment patterns.

This also fixes some quirks with the git hooks that should make them more consistent — they had previously been invoking line/etc commands manually; they should instead have referenced npm scripts. Now they'll hopefully be a little more consistent there.

This should also fix some irritations with local dev restarts, which turned out to be almost set up right. It wasn't correctly reloading the server code, relying on a full restart instead. Now it has HMR for both the React app as well as the Discord app

vcarl and others added 2 commits October 28, 2025 14:49
This commit completely solves the DX problem with a unified, single dev server
that intelligently handles both frontend and server-side changes.

## The Problem

Previously, `node --watch` restarted the entire Express server (including
Discord bot) on ANY file change in `./app`, causing:
- 6-8 second feedback loops for simple UI changes
- Discord bot reconnections on every file save
- Manual browser refreshes required
- Poor developer experience

## The Solution

Leverage Vite's existing dev server and module invalidation system:

1. **index.dev.js**: Listen to Vite's file watcher and reload only the server
   module (`app/server.ts`) when server-side files change, without restarting
   the entire Node process

2. **Frontend changes**: Vite HMR handles React components/routes instantly (<1s)

3. **Server changes**: Vite invalidates and reloads the server module, applying
   fresh Discord bot code without full process restart

4. **Discord gateway**: Added `globalThis` flag to prevent duplicate logins
   during module reloads

## Results

- Frontend changes: <1 second (Vite HMR)
- Server changes: ~2 seconds (module reload, no Discord reconnect)
- Single command: `npm run dev` works for everything
- No manual refresh needed for frontend changes
- Discord bot stays connected during UI development

## Files Changed

- `index.dev.js`: Added intelligent Vite watcher that reloads server module
- `package.json`: Removed `--watch` flag (Vite handles it)
- `app/discord/gateway.ts`: Added globalThis guard against duplicate init
- `app/server.ts`: Suppress Chrome DevTools 404s
- `app/helpers/sentry.server.ts`: Conditional Sentry init
- `README.md`: Updated dev documentation
- `notes/2025-10-27_1.md`: Comprehensive DX evaluation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
I've been moderately unhappy with the linting setup here for a while, mostly it complains at me for code formatting that then instantly gets cleaned up by Prettier, which is just a waste. This looks to catch a lot more useful code errors, like unnecessary nullchecks and sketchy assignment patterns.

This also fixes some quirks with the git hooks that should make them more consistent — they had previously been invoking line/etc commands manually; they should instead have referenced npm scripts. Now they'll hopefully be a little more consistent there.
@vcarl vcarl requested a review from Copilot October 29, 2025 02:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements comprehensive code quality improvements focused on TypeScript strict typing, import organization, and developer experience enhancements. The changes modernize the codebase with better type safety, consistent code style, and improved hot module reloading for development.

  • Enabled TypeScript strict type checking with recommendedTypeChecked and stylisticTypeChecked ESLint rules
  • Added Prettier import sorting plugin with configured import order (node → external → aliases → relative)
  • Implemented intelligent HMR in the dev server that hot-swaps server modules without full restarts

Reviewed Changes

Copilot reviewed 77 out of 79 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
package.json Removed dev-client script, updated dev:bot to remove --watch, added import sorting plugin and validation improvements
index.dev.js Completely refactored to implement smart HMR with server module reloading and file watching
eslint.config.js Enhanced with type-checked linting, added inline-type-imports rule, disabled unsafe type rules, added many TypeScript best practices
app/server.ts Added Chrome DevTools 404 suppression, changed || to ?? operators, reorganized imports
app/helpers/sentry.server.ts Added validation to only initialize Sentry with valid DSN
app/helpers/dateUtils.ts Removed unused dateToEntryMap logic in fillDateGaps (now always returns zero template)
app/helpers/modResponse.ts Changed to access potentially undefined array directly without null coalescing
app/discord/gateway.ts Added global flag to prevent duplicate Discord gateway initialization during HMR
app/models/session.server.ts Added explicit type assertions for session data, changed to use ??= operator
Multiple route files Reorganized imports following new import order pattern
Multiple helper/model files Replaced type + import with inline type imports, changed `
notes/ files Added detailed DX evaluation document and updated code examples with new import patterns

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const newVotes = {
...oldVotes,
[newVote]: uniq((oldVotes[newVote] || []).concat(userId)),
[newVote]: uniq(oldVotes[newVote].concat(userId)),
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Direct access to oldVotes[newVote] will throw if the key doesn't exist (accessing undefined.concat). This should use oldVotes[newVote] ?? [] or initialize the votes object with empty arrays for all resolutions.

Copilot uses AI. Check for mistakes.
}
const { channel, fields, user } = interaction;
const concern = fields.getField("concern").value;
const concern = fields.getTextInputValue("concern");
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Changed from fields.getField('concern').value to fields.getTextInputValue('concern'). Verify this API exists in the Discord.js version being used. The getTextInputValue method may not be available in older versions.

Copilot uses AI. Check for mistakes.
Comment on lines 184 to 188
const ticketsChannel = config.channel_id
? ((await interaction.guild.channels.fetch(
config.channel_id,
)) as TextChannel) || channel
)) as TextChannel)
: channel;
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Removed the || channel fallback after the type cast. If interaction.guild.channels.fetch() returns null/undefined (e.g., channel deleted), ticketsChannel will be undefined instead of falling back to channel, causing errors when used later.

Copilot uses AI. Check for mistakes.
export default async (bot: Client) => {
bot.on("messageCreate", async (msg) => {
if (msg.author?.id === bot.user?.id || !msg.guild) return;
if (msg.author.id === bot.user?.id || !msg.guild) return;
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Changed from optional chaining msg.author?.id to direct access msg.author.id. If msg.author is null/undefined (which can happen with partial messages), this will throw 'Cannot read property id of null/undefined'.

Suggested change
if (msg.author.id === bot.user?.id || !msg.guild) return;
if (!msg.author || msg.author.id === bot.user?.id || !msg.guild) return;

Copilot uses AI. Check for mistakes.
- Remove `format:check` in favor of just always writing
- Handle a type issue in ticket setup
- Don't use implied item type in guildsettingsform
- Fix broken dateUtil helper
- Disable `no-unnecessary-condition` because it appears to have excessive false positives
- Fix broken vote logic
- More correctly use `toString` instead of JSON.stringify
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants