feat(dashboard): config diff enhancements — unsaved indicator, Ctrl+S, undo, per-section revert#258
feat(dashboard): config diff enhancements — unsaved indicator, Ctrl+S, undo, per-section revert#258BillChirico merged 6 commits intomainfrom
Conversation
- Yellow dot appears on Save button when draft differs from saved config - Tooltip shows the count and names of changed sections for quick orientation - Dot is aria-hidden (purely decorative) to avoid noise for screen readers - Pairs with the existing unsaved-changes banner below the header Closes #133
- Pressing Ctrl+S (Windows/Linux) or Cmd+S (Mac) opens the diff preview modal - Shortcut is a no-op when the user is actively typing in an input, textarea, select, or contenteditable element — avoids surprising saves mid-edit - Shortcut is also a no-op when saving is in progress or there are validation errors - aria-keyshortcuts attribute on the Save button advertises the shortcut to AT Closes #133
- After a successful save, stores the previous config snapshot in state - 'Undo Last Save' button appears in the header while no new changes exist - Clicking it restores the pre-save draft so the user can review or re-save - Button auto-dismisses after 30 s — uses a useEffect/setTimeout that resets whenever prevSavedConfig changes, so the timer restarts on each new save - Snapshot is also discarded when the selected guild changes, preventing cross-guild undo confusion Closes #133
- ConfigDiff now accepts optional changedSections and onRevertSection props - When both are provided, a strip of section badges + Revert buttons appears in the card header, consistent with the modal's revert UX - Clicking a section badge's RotateCcw button calls onRevertSection(section), which resets that section in the draft back to the last saved value - config-editor passes changedSections and revertSection to the inline ConfigDiff so users can cherry-pick reverts without opening the full diff modal - Props are optional → ConfigDiff stays backward-compatible as a pure display Closes #133
📝 WalkthroughSummary by CodeRabbit
WalkthroughRemoved TASK.md documentation. Enhanced config-diff.tsx with per-section revert UI support via new changedSections and onRevertSection props. Modified config-editor.tsx to wire per-section revert functionality, add 30-second undo auto-dismiss timer, refine keyboard save handling, and improve undo button UX with dynamic section information. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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 |
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
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)
559-581: 🧹 Nitpick | 🔵 TrivialGuard should also check
isTypingbefore callinge.preventDefault().Currently,
e.preventDefault()is called after theisTypingcheck, which is correct. However, there's a subtle issue: if the user is typing and presses Ctrl+S, the function returns early without preventing the default browser behavior. This is actually the intended behavior per the PR objective ("no-op when focused in input/textarea/select"), so this is correct.One minor edge case:
isContentEditableis a property, not a method, and returns a string ('true','false', or'inherit') in some browsers, not a boolean. The truthiness check should still work, but for robustness you could explicitly compare.♻️ Optional: Explicit contentEditable check
const isTyping = target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA' || target?.tagName === 'SELECT' || - target?.isContentEditable; + target?.isContentEditable === true;🤖 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 559 - 581, The onKeyDown handler currently uses target?.isContentEditable in the isTyping check, but isContentEditable can be a string in some browsers; update the isTyping predicate in onKeyDown to explicitly normalize/check contentEditable (e.g., compare to true or the string 'true') so it reliably detects editable elements before allowing the function to return (and thus before any call to e.preventDefault()); locate the isTyping definition inside the onKeyDown function in config-editor.tsx and change only that boolean expression to a normalized check for isContentEditable.
🤖 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-diff.tsx`:
- Around line 16-24: The prop types should use the ConfigSection union for
stronger type-safety: change changedSections?: string[] to changedSections?:
ConfigSection[] and update onRevertSection?: (section: string) => void to
onRevertSection?: (section: ConfigSection) => void, and add the import for
ConfigSection from the types module; keep both props optional and ensure any
internal usages or callers are updated to pass ConfigSection values (or
cast/validate) so the compiler enforces only valid top-level config keys.
---
Outside diff comments:
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 559-581: The onKeyDown handler currently uses
target?.isContentEditable in the isTyping check, but isContentEditable can be a
string in some browsers; update the isTyping predicate in onKeyDown to
explicitly normalize/check contentEditable (e.g., compare to true or the string
'true') so it reliably detects editable elements before allowing the function to
return (and thus before any call to e.preventDefault()); locate the isTyping
definition inside the onKeyDown function in config-editor.tsx and change only
that boolean expression to a normalized check for isContentEditable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8a12c1eb-fce1-49d1-947d-215488f283d1
📒 Files selected for processing (3)
TASK.mdweb/src/components/dashboard/config-diff.tsxweb/src/components/dashboard/config-editor.tsx
💤 Files with no reviewable changes (1)
- TASK.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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-diff.tsxweb/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/src/components/dashboard/config-diff.tsxweb/src/components/dashboard/config-editor.tsx
🧠 Learnings (7)
📓 Common learnings
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
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
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
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
📚 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/src/components/dashboard/config-diff.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} : 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-diff.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-diff.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/**/*.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/src/components/dashboard/config-diff.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-diff.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/src/components/dashboard/config-editor.tsx
🔇 Additional comments (5)
web/src/components/dashboard/config-diff.tsx (2)
4-6: LGTM!Clean imports for the new per-section revert functionality. The
RotateCcwicon andButtoncomponent are appropriately imported for the revert controls.
112-140: LGTM!Well-structured per-section revert UI with good accessibility patterns:
fieldsetwitharia-labelfor grouping- Screen-reader-only
legend- Per-button
aria-labelfor revert actions- Proper conditional rendering when both props are provided
The visual styling with yellow badge indicators clearly communicates which sections have pending changes.
web/src/components/dashboard/config-editor.tsx (3)
548-555: LGTM!The 30-second auto-dismiss timer is correctly implemented:
- Properly guards with early return when no snapshot exists
- Uses
window.setTimeoutfor explicit global reference- Cleanup function prevents memory leaks on unmount or re-render
- Dependency array correctly triggers reset when a new save occurs
968-968: LGTM!The tooltip now provides useful context by showing the count and names of changed sections, improving discoverability of what's pending.
2104-2111: No changes needed; components are intentionally designed with different revert UX.The inline
ConfigDiffandConfigDiffModaldo not need to handle reverts identically. The modal renders per-section revert buttons in its own fieldset (prior to the diff view) and intentionally does not passchangedSectionsandonRevertSectiontoConfigDiffwhen rendering it at line 102 of config-diff-modal.tsx. The inlineConfigDiffin config-editor.tsx receives these props and renders revert buttons within its card header. This is a deliberate separation of concerns: the modal controls reverts at the top level, while the inline component integrates them into the diff view. Both implementations maintain consistent revert behavior through the sameonRevertSectioncallback.
🧹 Preview Environment Cleaned UpThe Railway preview environment for this PR has been removed. Environment: |
Summary
Improvements to the config diff/save flow.
Changes
Closes #133