Repository navigation
refactor(dashboard): v3 console dashboard, V7 schema and site redesign - #451
RedStar071 wants to merge 107 commits into
Conversation
|
1 finding · reviewed 🟠
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #451 +/- ##
===========================================
+ Coverage 48.13% 59.72% +11.59%
===========================================
Files 324 451 +127
Lines 7005 9811 +2806
Branches 1394 2049 +655
===========================================
+ Hits 3372 5860 +2488
+ Misses 2548 2454 -94
- Partials 1085 1497 +412
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
Reviewed the single commit on refactor/v3 against main.
startViewTransition()helper — centralizes the reduced-motion, no-support and already-running fallbacks, withwhenActive: "skip" | "bypass". Both modes match the hand-rolled behavior they replace inguild/cards.vueanddashboard.vue. When submit clears the staged changes, theisReadyToSubmitwatcher fires inside the submit transition and correctly takes thebypasspath.- Save bar — the Vue
<Transition>is replaced by ashowSaveChangesBarref committed inside a view transition. Thesave-changes-barrules already exist inview-transitions.css, and the bar gainsrole="region"with an existing i18n label. - Snowflake filters —
userId,moderatorIdandactorIdnow require digits only. - OAuth pages —
useRouteQueryis replaced withuseRoute().querycomputeds. Nothing else inapp/still uses it. - Tests — unit tests cover the helper and the schema.
claude-sonnet-5-5 | 𝕏
f86d8b6 to
8d38d76
Compare
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
…nputs Foundation layer for the v3 refactor stack. - `startViewTransition()` (`app/utils/view-transition.ts`) wraps `document.startViewTransition` with the reduced-motion, no-support and already-running fallbacks that the dashboard layout and the guild cards each hand-rolled. Both call sites now use it; the save bar is committed inside a view transition instead of a Vue `<Transition>`, so it rides along with a navigation transition instead of fighting it. - Snowflake filters on the three guild-log query schemas (`userId`, `moderatorId`, `actorId`) now require a numeric id, so a malformed filter answers 400 from validation instead of blowing up later. - The OAuth callback and guild pages read `next` / `guild_id` through `useRoute().query` rather than `useRouteQuery`, the only two uses of `@vueuse/router` outside the dashboard; the dashboard drops the remaining ones in a later layer. Co-authored-by: Claude <noreply@anthropic.com>
…h hints app/utils/constants.ts re-exported types that app/types already auto-imports, which made Nuxt warn about a duplicated import for each of them on every start. The dns-prefetch links repeated the preconnect links beside them, which unhead flags in the browser console.
…nowflake filter note
c6c694a to
1a8fcde
Compare
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
The invite links lost redirect_uri and response_type=code together with prompt=none when the invite became interactive. Put the first two back, pointing at /oauth/guild, which opens the server in the dashboard.
Add an Export/Import card to the General section. The file is the bot's wolfstar-settings.json (version 1); an import shows what it changes first, then writes the settings and replaces the automod rules in one transaction, with an audit event. Swap the hand-made activity bars for UChart (@nuxt/ui 4.12).
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
Reviewed the two commits pushed since the prior pullfrog review (60a5bedf): d479c8aa and a78f1414. I read the new server routes and utilities, the shared schema and types, Transfer.vue and the invite changes. I did not read the locale files, the lockfile or the tests line by line, and I did not run the test suite.
- Settings export and import —
GET /api/guilds/:guild/settings/exportandPOST .../settings/importusedefineWrappedResponseHandlerwithauth: true, the sharedauthorizeAutoModerationManagerhook and per-route rate limits. The file is size-checked byConfigurationImportSchema, parsed whole or refused with every issue, and unknown settings are reported instead of written. Apply works the plan out again under the settings lock and then the rules lock, writes both in one transaction and emitsguildSettingsUpdate. No-op imports write and audit nothing. - Rule writes in the settings transaction —
writeGuildDataandTransaction.submitaccept anextracallback so rule deletes, edits and creates share the settings transaction.writeAutoModerationRulesis now exported for it. - Transfer UI —
Transfer.vuepreviews before applying, blocks import while there are unsaved changes, and refreshes the cached rules key (it matchesuseAutomodRules). - General overview chart — the hand-rolled bars are replaced with
UChart(@tanstack/charts,@nuxt/ui4.12). - Bot invite redirect — the invite URLs now return the admin to
/oauth/guildafter the bot joins.
claude-sonnet-5-5 | 𝕏
| guild_id: guildID, | ||
| integration_type: "0", | ||
| permissions: "491121748", | ||
| redirect_uri: guildRedirectURI(), |
There was a problem hiding this comment.
The invite now depends on redirect_uri being registered in the Discord Developer Portal, and it is built from useSiteConfig().url rather than runtimeConfig.public.siteUrl (the source resolveDiscordRedirectURI() uses for sign-in). On any origin whose /oauth/guild is not registered (deploy previews, a mismatched site config), Discord rejects the invite with an invalid redirect error, including the main WolfStar invite in useInvites. Consider deriving it from the same siteUrl as the sign-in redirect and listing <NUXT_PUBLIC_SITE_URL>/oauth/guild next to the /oauth/callback note in CLAUDE.md troubleshooting and the PR's before-merging list.
| await transaction.submit( | ||
| rulesChanged | ||
| ? async (tx) => { | ||
| created = await writeAutoModerationRulePlan( |
There was a problem hiding this comment.
Imports block database requests
applyConfigurationImport() keeps the transaction opened by writeAutoModerationRules() active while transaction.submit() opens another transaction through the root db. The second transaction needs another pooled connection rather than reusing the first.
Ten overlapping imports for different servers can occupy all ten default connections while waiting for more. In the reproduced overlap, eight imports timed out and an unrelated database query waited almost 20 seconds. This also affects imports that change settings without changing rules. Pass a single transaction context through both writes instead of opening a second root transaction.
Artifacts
Executable PostgreSQL reproduction source
- Invokes the actual import and settings-write paths with scheduling barriers and real database operations, demonstrating the nested connection acquisition.
Runtime alias configuration for the reproduction
- Resolves the application's server and shared imports for direct execution of the unchanged TypeScript source.
Executable evidence capture runner
- Runs both comparison cases and source inspection while recording exact commands, working directory, exit codes, and observed output.
Database behavior without the import wrapper
- Runs ten concurrent settings writes without the import wrapper; all succeed and unrelated database work completes in 1 ms.
Database starvation during concurrent imports
- Runs ten concurrent settings-only imports through the actual implementation; eight time out and unrelated database work waits nearly 20 seconds.
Source, higher-stack, bot contract, and database inspection
- Captures the implicated source, exact higher-stack heads, both bot checkout identities, local database schema, and an empty tracked diff, confirming scope and unchanged product code.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: server/utils/settings-transfer/apply.ts
Line: 77-80
Comment:
**Imports block database requests**
`applyConfigurationImport()` keeps the transaction opened by `writeAutoModerationRules()` active while `transaction.submit()` opens another transaction through the root `db`. The second transaction needs another pooled connection rather than reusing the first.
Ten overlapping imports for different servers can occupy all ten default connections while waiting for more. In the reproduced overlap, eight imports timed out and an unrelated database query waited almost 20 seconds. This also affects imports that change settings without changing rules. Pass a single transaction context through both writes instead of opening a second root transaction.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if (!result.ok) { | ||
| review.value = { ...current, result }; | ||
| return; | ||
| } |
There was a problem hiding this comment.
Import errors trap the manager
confirmImport() leaves isImporting true when confirmation returns { ok: false }. This can happen after a successful preview: an imported list longer than 200 entries becomes invalid if another manager shortens or deletes the matching existing rule before confirmation.
The validation error appears, but Cancel stays disabled and Escape, the backdrop, and the header close button cannot dismiss the dialog. This is non-blocking, but the manager must reload to continue. Reset isImporting in a finally block so every completion path releases the dialog.
| if (!result.ok) { | |
| review.value = { ...current, result }; | |
| return; | |
| } | |
| if (!result.ok) { | |
| isImporting.value = false; | |
| review.value = { ...current, result }; | |
| return; | |
| } |
Artifacts
▶ Import preview before confirmation
- Uploaded the legacy-rule configuration and held the successful preview before dismissing it with Escape, showing that dismissal works before confirmation.
Successful import preview with Cancel available
- Captured the actual component after preview succeeded and before confirmation, showing the normal review state.
▶ Import error modal remains trapped after confirmation
- Confirmed the import, received the validation refusal, and attempted dismissal through Escape, header close, backdrop, and Cancel, showing that the error modal remains trapped.
Validation error with disabled Cancel
- Captured the settled refusal modal after dismissal attempts, showing the 200-entry validation error and disabled Cancel.
Successful Chromium validation output
- Recorded the executed browser command, working directory, exit code, modal text, and dismissal assertions, confirming the requested defect.
Executed Playwright reproduction script
- Contains the exact executed browser test that captured both states and asserted the failed dismissal behavior.
Component harness and real server-parser race fixture
- Loads the actual component and generates controlled responses with the real parser and planner; cleanup-only edits followed the successful run.
Controlled guild context and HTTP adapter
- Provides an active guild, clean settings draft, real English translations, and a fetch adapter without changing product code.
Actual component browser entry point
- Mounts the repository’s Transfer component inside the real Nuxt UI app provider for browser validation.
Browser harness presentation styles
- Loads Tailwind and Nuxt UI styles with a simple page container for the recorded component.
Convenience runner authored after validation
- Provides server startup, browser execution, and consolidated logging for a subsequent rerun; this runner itself was not executed.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/components/guild/settings/Transfer.vue
Line: 273-276
Comment:
**Import errors trap the manager**
`confirmImport()` leaves `isImporting` true when confirmation returns `{ ok: false }`. This can happen after a successful preview: an imported list longer than 200 entries becomes invalid if another manager shortens or deletes the matching existing rule before confirmation.
The validation error appears, but Cancel stays disabled and Escape, the backdrop, and the header close button cannot dismiss the dialog. This is non-blocking, but the manager must reload to continue. Reset `isImporting` in a `finally` block so every completion path releases the dialog.
```suggestion
if (!result.ok) {
isImporting.value = false;
review.value = { ...current, result };
return;
}
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
Agent (Codex): visual captures of the Overview and settings import preview in light and dark themes. These use the actual components and dashboard layout styles with sample server data and mocked log/import responses. The Overview is from |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the one commit pushed since the prior pullfrog review (ad973e70): d638169b (#477), which moves the dashboard to the bot's V7 contract at 788939a. I read all of the non-locale code in it. I skimmed the locale files for the empty-placeholder convention and did not run the test suite. The earlier threads on persisted-cache.client.ts and auth.ts are unchanged by this commit and stay open.
- Settings follow the new contract —
columns.ts,constants.tsandtypes.tsaddtimezone,updatesChannel,commandsRestrictions, thehoneypot*,appeals*andlogsMessageUpdateStyle/logsWebhookssettings, the new moderation tracking keys, and the new log channels (logsInvites,logsThreads,logsEvents,logsExpressions,logsIntegrations).modulesLogs,modulesCommands,logsCommandandlogsSettingsare gone fromGuildDataand kept only as legacy audit labels. - Validation —
validation.tsaddsvalidateTimeZone,oneOffor the honeypot action and message update style, a 0–60 range formoderationConfirmRecentMinutes, and a text list forappealsQuestions.BOT_ONLY_SETTINGS(commandsRestrictions,updatesChannel) are refused,nullreset included, which closes the earlier gap wherenullbypassed the validator. - Settings export and import —
TransferableSettingsgains the portable keys that now have columns. - Automod — adds the
Pollsrule type and theMessageSpam.perChanneloption.normalizeAutoModerationRuleOptionsis driven by the defaults, so no validator change is needed. - Dashboard — Logs is no longer gated by a module, the setup wizard always offers the events channel, and the
Channels.vuelog groups cover the new channels. Locale files get empty placeholders for untranslated keys.
claude-sonnet-5-5 | 𝕏
| ["honeypot.action", "honeypotAction"], | ||
| ["honeypot.direct-message", "honeypotDirectMessage"], | ||
| ["honeypot.warning", "honeypotWarning"], |
There was a problem hiding this comment.
Importing honeypot.action or honeypot.warning changes the saved settings but never updates the warning already posted in Discord. Members can still see “kicked” after a manager imports a Ban action, or see no warning after enabling it. This is non-blocking, but leaves members with the wrong warning.
The bot’s writeHoneypotSettings() refreshes that message when either setting changes. Add the same step here, or leave these settings bot-only. Normal upkeep does not repair the message when honeypotKeepActive is off.
Artifacts
- Executed repository TypeScript through an in-memory loader with mocked ORM and Discord boundaries, demonstrating the import mismatch and correct bot behavior.
- Ran the targeted harness in both modes and captured command, working directory, exit code, and observed output, making the comparison reproducible.
- Executed all three scenarios with the prior transferable-settings list, showing that honeypot imports were unsupported and did not create a saved-setting versus notice mismatch.
- Executed all three scenarios at the current head, showing persisted honeypot changes with unchanged notices while the bot comparison correctly synchronized each notice.
- Executed explicit two-commit diffs and source-tree checks without checking out another revision, preserving the current working tree.
- Captured the requested dependent diffs, absent honeypot integration in those snapshots, older related-repository schema, and unchanged current head, providing no evidence of a later full fix.
- The initial run stopped because the StickyRole mock lacked deleteAndCount, identifying a harness-only issue corrected before the successful comparison.
- The initial current-head run hit the same missing mock method before exercising imports, so it is retained as setup history rather than defect evidence.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: server/utils/settings-transfer/format.ts
Line: 25-27
Comment:
**Trap warning stays wrong**
Importing `honeypot.action` or `honeypot.warning` changes the saved settings but never updates the warning already posted in Discord. Members can still see “kicked” after a manager imports a Ban action, or see no warning after enabling it. This is non-blocking, but leaves members with the wrong warning.
The bot’s [`writeHoneypotSettings()`](https://github.com/wolfstar-project/wolfstar/blob/HEAD/projects/bot/src/lib/moderation/honeypot/settings.ts) refreshes that message when either setting changes. Add the same step here, or leave these settings bot-only. Normal upkeep does not repair the message when `honeypotKeepActive` is off.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Follow the bot's V7 contract to 3767791 (boost logs, dehoist, auto-messages) and bring the console design into the dashboard. - Honeypot and Appeals sections; the bot keeps the honeypot warning and the appeal decisions, so those controls are read-only here - Auto-Delete, Auto-Purge and Auto-Message as row tables with their own routes, bot limits, audit events and a channel check per guild - Confirm-recent window, kick and role tracking, webhook delivery, edited message style, dehoist and boost logs in Moderation and Events - Working time zone with the live clock, and the open reports count - Glossary, AGENTS.md and English locale copy
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
Reviewed the one commit pushed since the prior pullfrog review (d638169b): cd76b97b. I read the new server write layer and routes, the shared validators and settings choices, useAutoChannels, and the Honeypot, Appeals, Modules, Channels and General changes. I did not read AutoDelete.vue, AutoMessage.vue, AutoPurge.vue or the first half of auto/Editor.vue line by line, skimmed the locale files, and did not run the test suite.
- Automatic channel tables —
GET/PUT/DELETEroutes forautodelete,autopurgeandautomessageusedefineWrappedResponseHandlerwithauth: true, the sharedauthorizeGuildManagerhook (extracted from the automod route) and per-route rate limits. Writes run under a per-kindAsyncQueuepluspg_advisory_xact_lock, the channel must be a text channel of the guild, and each change is audited. - Validation —
parseAutoDeleteData,parseAutoPurgeDataandparseAutoMessageDatacheck types, ranges and the filter value before anything is written, and the editor mirrors the same limits. - New settings —
logsBoosts,moderationDehoist, amoderationConfirmRecentMinutesnumber field, atimezoneselect and the log delivery options.SETTING_CHOICESandSETTING_NUMBER_RANGESnow feed both the form schema and the server validators. - Honeypot, Appeals and Modules — new Honeypot and Appeals pages, and a Modules grid that mixes switches, bot-set-up modules and the three channel lists.
claude-sonnet-5-5 | 𝕏
| // The channel, the action and the warning change only through `/honeypot` in | ||
| // Discord, which posts or rewrites the warning in the channel as they change: | ||
| // they are read from the saved settings and never staged here. | ||
| const trapChannel = computed(() => guildSettings.value?.honeypotChannel ?? null); |
There was a problem hiding this comment.
This page treats the honeypot channel, action and warning as bot-only ("change only through /honeypot", because the bot posts or takes down the warning as they change), but the server does not enforce it: they are not in BOT_ONLY_SETTINGS, so PATCH /api/guilds/:guild/settings accepts them, and honeypot.action and honeypot.warning are in TransferableSettings. A crafted request can switch the action or warning without the bot reconciling the channel message. Consider adding the three keys to BOT_ONLY_SETTINGS (and dropping them from the import list), as was done for updatesChannel, or confirm the bot reconciles them from the database.
| if (result.ok) { | ||
| patchRows(requestGuildId, (current) => | ||
| current.some((row) => row.channelId === channelId) | ||
| ? current.map((row) => (row.channelId === channelId ? result.value : row)) | ||
| : [...current, result.value], | ||
| ); |
There was a problem hiding this comment.
A background read can replace a successful automatic-channel edit with older rows. usePersistedAsyncData() refreshes saved rows when the section opens, but setRow() and clearRow() neither cancel that read nor ignore its older answer. If the read finishes after the write, the table shows the old settings or brings back a removed row despite the success message.
This is non-blocking, but reopening the editor then shows older values. Cancel older reads when writing, or refresh and reconcile the list after the write.
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/composables/useAutoChannels.ts
Line: 88-93
Comment:
**Saved channel edits disappear**
A background read can replace a successful automatic-channel edit with older rows. `usePersistedAsyncData()` refreshes saved rows when the section opens, but `setRow()` and `clearRow()` neither cancel that read nor ignore its older answer. If the read finishes after the write, the table shows the old settings or brings back a removed row despite the success message.
This is non-blocking, but reopening the editor then shows older values. Cancel older reads when writing, or refresh and reconcile the list after the write.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
An unrecognized error occurred. The error is below.
|
…they read alone Settings activity labels a change with the setting's name as it is, and there "Action", "Bans" or "Tell Them Why" do not say which section they belong to. The honeypot and appeal settings now carry it: "Honeypot Action", "Appealable Bans", "Tell Caught Members Why", "Honeypot Catches Channel". - the Auto-Purge choice was "Delete" in the editor and "Filter" in the table; both read "Messages to purge", and the copy no longer calls it a filter, the word the glossary keeps for Auto Moderation rules - the glossary defines "caught" for the honeypot and notes the Auto-Purge collision
… loads The first entry to /app covers the console with WolfStar's mark turning above "Loading" until the server list and the open server are in, or the server failed to load. It does not come back on a server switch, where the section's skeleton stands in, and it fades in after a short beat, so a load that is done at once never flashes it. - COPY.md records it as the one exception to "Loading: Absent" - dashboard.loading is an empty placeholder in the other locales
The console design has the Events among the modules, with the switch the others have, and Commands and Logs as the only sections outside them. The switch is the logs column of Modules, which the dashboard reads and writes as modulesLogs. The Modules page counts it and turns it with the rest, the sidebar marks the Events while it is off, the setup offers it, and an export carries it as modules.logs. The bot dropped modules.logs from its V7 settings and logs every event whatever the column says. Until it reads it again, turning the Events off is stored and shown but stops no log. The column defaults to on, so no server changes behaviour. - the setup leaves out the event log channel when the Events are off - settings activity labels it "Events Module" - AGENTS.md says the bot does not read it yet
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
Reviewed the three commits pushed since the prior pullfrog review (cd76b97b): 97392d75, 03f8add4 and af3bb0b4. I read the non-locale code and the incremental diff; I skimmed the locale files, did not run the test suite, and could not inspect the vendor/wolfstar contract (the submodule is empty in this checkout).
- Dashboard loader —
DashboardLoadercovers the console on first entry with the bot's mark.bootingclears once the guild list is in and either no guild is open or its data loaded or errored, and it does not return when the admin switches servers. The anonymoususeUserpath resolves immediately, so the loader cannot stick for a signed-out visitor. - Events module switch —
modulesLogsis back as a module flag inguild-modules.ts, gates thechannelssidebar item, is recommended in the setup wizard, and only offers or writes the events channel when it is on.columns.ts,constants.tsand the settings transfer format already carry it. - Setting names — honeypot, appeal and auto-purge labels are renamed so they read alone (
Appealable Bans,Honeypot Action,Messages to purge), with matching GLOSSARY and COPY entries.
claude-sonnet-5-5 | 𝕏
… switch yet The Events switch looked live, but the bot logs every event whatever Modules.logs says, so an admin who turned it off would believe the logs stopped. The card now says WolfStar does not read the switch yet and that the log events still reach their logging channels. - the module registry carries the line as noteKey, to drop once the bot reads the switch
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the one commit pushed since the prior pullfrog review (af3bb0b4): e8d06ce0. I read all of it, including the spec; I did not run the test suite.
- Events card disclosure —
GUILD_MODULESentries take an optionalnoteKey, andmodulesLogssetsguild_settings.modules.notes.channels.Modules.vuerenders it as an info line under the description (icon isaria-hidden), so an admin who turns Events off is told the bot still delivers the log events. This closes the earlier thread onguild-modules.ts. - Copy and schema — adds the
notes.channelsstring inen, empty placeholders in the other locales (per the untranslated-keys rule) and the matchingguilds.schema.jsonentry. - Tests and docs — a new spec asserts the Events card carries the note and no other card does;
AGENTS.mdmentions the card line.
The two earlier threads on persisted-cache.client.ts and auth.ts are untouched by this commit and stay open.
claude-sonnet-5-5 | 𝕏
Editing an Auto-Purge or an Auto-Message without changing its interval wrote back the next run the dashboard had just read. The bot moves that time after each run, under no lock of the dashboard, so a run that happened in between was undone and the purge ran again the next minute, deleting the messages posted since. - the write leaves nextRunAt to the row while the interval stays, and a row that went away meanwhile starts an interval from now
The rule routes and the settings import took a Timeout with no duration: a new one, an existing rule changed into one, or a Timeout whose duration was cleared. The bot does nothing for such a rule, since Discord takes no timeout without an end: it drops the strike and returns, so with no soft action the rule never moderates. - the validator checks the action and the duration together whenever a request changes either, against the rule it edits or the defaults of a new one - a rule saved as one before can still be renamed or switched off - the bot's own validator, which this one is ported from, still takes them
The export read the settings this process had cached. The cache only learns of the dashboard's own writes, so after the bot or another instance saved a change, an export could still hand out the older values, and restoring that backup would undo the change. - readFreshSettings reads the database and refreshes the cache; the export uses it, the rules were read fresh already
…ule card Auto Moderation opened on a card with the module's name, what turning it off does and its switch. Moderation and the Auto-Delete, Auto-Purge and Auto-Message pages open on the same card now. - Moderation's card switches modulesModeration, staged like the rest of the page; turned off, WolfStar refuses the moderation commands - the Auto-* tables have no switch in the bot, they run while they have a channel, so their card says On or Off instead of offering one - the card is GuildModuleBanner, Auto Moderation's included - English and Italian copy; the other locales get empty placeholders
The bot reads modulesLogs again: with it off, it sends no log of the server's events, and the moderation commands answer that the module is off. The Events card no longer says the bot ignores the switch. - vendor/wolfstar from 3767791 to be7a3e5, contract unchanged - the note line of the module cards goes, no other module has one
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the five commits pushed since the prior pullfrog review (e8d06ce0). I read all of the non-locale code and the new specs; I skimmed the locale and schema files and did not run the test suite or inspect the vendor/wolfstar bump.
- Schedules keep the bot's next run —
upsertAutoPurgeandupsertAutoMessageacceptnextRunAt: number | null.nullleaves the column alone on update and uses an interval from now on create, so an edit that keeps the interval no longer writes back a stale time.nextRun()inauto-channels/write.tsreturnsnullwhile the interval stays. - Timeout rules need a duration —
parseAutoModerationRulePatchtakes the current rule (options,hardAction,hardActionDuration) and refuses a Timeout without a duration only when the request touches either field, so an old rule can still be renamed or switched off. The settings import passes{ options }only, which matches its replace-with-defaults semantics. - Fresh export —
settings/export.get.tsreads through the newreadFreshSettings, so a backup is not made from a stale cached copy. - Module banner — a shared
GuildModuleBannernow opens Auto Moderation, Moderation (itsmodulesModerationswitch joins the form'stoggles) and the Auto-Delete, Auto-Purge and Auto-Message pages, which show an on/off badge from whether a channel exists. - Events note dropped — the
noteKeyline on the Events card is removed along with its locale key, andAGENTS.mdnow says the bot reads the switch.
The two earlier threads on persisted-cache.client.ts and auth.ts are untouched by these commits and stay open.
claude-sonnet-5-5 | 𝕏
| export function readFreshSettings(guildId: string): Promise<ReadonlyGuildData> { | ||
| return processFetch(guildId, true); |
There was a problem hiding this comment.
readFreshSettings() can replace the cached settings while a PATCH is saving. The PATCH holds object A, but an overlapping export can read the old values into object B and put B in the cache. When the PATCH finishes, submit() updates only A. Later settings GETs keep returning B, so managers see old values even after reloading.
Read exports without replacing the shared cache, or coordinate fresh reads with writes.
Artifacts
- Authored and executed this harness against both revisions’ real settings functions with a blocked database commit, isolating the exact cache replacement race.
- Ran the regression on the selected base with an overlapping export read; all three post-commit reads returned de and the assertion passed.
- Ran the identical regression on PR head; export replaced A with old B, and three post-commit reads returned en-US instead of committed de, failing the assertion.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: server/database/settings/functions.ts
Line: 38-39
Comment:
**Saved settings look unchanged**
`readFreshSettings()` can replace the cached settings while a PATCH is saving. The PATCH holds object A, but an overlapping export can read the old values into object B and put B in the cache. When the PATCH finishes, `submit()` updates only A. Later settings GETs keep returning B, so managers see old values even after reloading.
Read exports without replacing the shared cache, or coordinate fresh reads with writes.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.





🔗 Linked issue
Supersedes #422.
🧭 Context
refactor/v3is now the integration branch for the v3 dashboard, the wayV7is in the bot's repository. The work was reviewed as a stack of eight PRs; seven of them are merged into this branch, so this PR carries all of it tomain. New work targetsrefactor/v3and shows up here.Merged into this branch: #452 header and footer, #453 marketing pages, #454
/appdashboard shell, #455 V7 schema and Prisma ORM 8, #457 auto-moderation rules, #461/profiledeprecation and user settings, #462 public commands page, #465 design-system selects, fields and dialogs, #466 steady header and the dashboard's Home and settings, #473 case filters and the case view, #476 import rule summaries, #477 V7 contract to 788939a.📚 Description
Foundation.
startViewTransition()owns the view-transition fallbacks the dashboard and the guild cards each hand-rolled. The OAuth pages readnextandguild_idfromuseRoute().query. In development,$apireaches the bot API through a same-origin proxy, and the auth devtools stylesheet no longer leaks into the app.Site. Redesigned header, footer, home and Staryl pages, and a rebuilt public
/commandspage. The whole site now has an Italian translation, and the color themes follow Discord's, custom theme included.Dashboard.
/appis one client-rendered page: server rail, sidebar with pinned and recent sections, bottom tab bar on phones, and a floating unsaved-changes bar. The guild lives in state, not in the URL, and the first entry shows WolfStar's mark turning until the server is in. The modules are Auto Moderation, Moderation (cases and reports), Roles, Events, Honeypot, Appeals, Auto-Delete, Auto-Purge and Auto-Message, each with its own section; the sidebar also has Overview, the Modules page, Commands and Logs. A server with no settings yet opens a five-step setup. Settings export to a file and import from one, and after an invite the admin lands on the server they invited the bot to. Features the bot does not expose yet are marked "Coming soon"./profileredirects to/app, and the preferences open as a user settings dialog.Logs. Moderation, warnings, commands and dashboard activity as tables from
mdup and cards below it, with per-tab counts and server-side paging. Moderation cases show the type the bot encodes inmetadata, not the closest enum action.Database. Prisma ORM 8 only, on the bot's V7 contract read from the
vendor/wolfstarsubmodule. Guild settings are read and written through the flatGuildDatamap; auto-moderation is one row per rule, written under a per-guild advisory lock.Validation. Snowflakes are checked against
BIGINT, log date filters are normalized to UTC, and settings changes pass throughparseSettingsChanges().Modules.logs, but the bot dropped that setting in V7 and still logs every event. Until the bot readsmodulesLogsagain and skips the event logs while it is off, the switch does nothing.Not ready to merge: successful saves can leave stale settings visible, earlier staged edits can disappear, configuration imports can exhaust database connections, and two repository requirements remain unsatisfied.
Summary
This PR brings the redesigned site,
/appdashboard, and shared V7 database contract tomain.Since the last review:
/apppage.The settings cache race in
server/database/settings/functions.ts, disappearing staged settings edits, and nested import transactions need to be fixed before merging. The outstanding theme-selector and homepage-layout requirements also need to be satisfied. The remaining display and recovery concerns are non-blocking.Acknowledged limitations: RedStar071 acknowledges in the PR description that the Events switch previously did nothing because the bot no longer read the setting; the pinned bot now contains the missing check. RedStar071 also acknowledges that the auto-moderation write lock has only mocked coverage and has not been exercised against a real database.
Reviews (16) · Last reviewed commit: "chore(deps): move to the bot's latest V7..." · Reviewed by Greptile