fix(frontend): keep KnowledgeBase modal centered when side panel opens#12764
Conversation
Translate the DialogContent left by half the side panel width (150px) so the combined modal + side panel stays visually centered. Uses the CSS `translate` property to avoid conflicting with the existing zoom-in/out keyframe animation on `transform`. Unifies SidePanel animation timing to duration-300 ease-in-out for a coordinated feel.
…eBaseUploadModal - isPanelOpen replaces duplicated `sidePanel && sidePanelOpen` checks - modalHeight/modalBase/errorCount replace IIFE inside JSX height prop - showHelpButton replaces repeated `!hideAdvanced && currentStep === 1` condition
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
Codecov Report✅ All modified and coverable lines are covered by tests. ❌ Your project status has failed because the head coverage (49.82%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## release-1.10.0 #12764 +/- ##
=================================================
Coverage 52.79% 52.79%
=================================================
Files 2025 2025
Lines 184228 184241 +13
Branches 27403 28910 +1507
=================================================
+ Hits 97264 97273 +9
- Misses 85869 85875 +6
+ Partials 1095 1093 -2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Cristhianzl
left a comment
There was a problem hiding this comment.
1. Executive Summary
| Verdict | Comment |
|---|---|
| 🟠 Request changes | Non-blocking UX fix with a genuine DRY/magic-number issue and missing regression coverage. Security & structure checks pass. Minor polish needed before merge. |
The PR fixes a real UX bug (combined dialog visually off-center after the 300 px side panel slides in) via a CSS-only translate, plus three small local-scope refactors. Implementation is small and surgical. However:
- The
-150pxhardcoded shift is decoupled fromSIDE_PANEL_WIDTH(w-[300px]) — if the panel width ever changes, the modal will stop being centered. DRY / magic-number violation. - No test was added for the new
translate/isPanelOpenbehavior. The PR description claims "86 tests passing" but those are all pre-existing — none asserts the fix itself. - Tailwind animation syntax was normalized (
transition-opacity→transition-[opacity],transition-transform→transition-[transform]) — not wrong, but noise in the diff.
2. Security Review Mindset
| Question | Finding |
|---|---|
| What is this code trusting without verifying? | Nothing — purely presentational CSS translate + derived booleans. |
| What is the authoritative source? | Tailwind CSS translate utility + standard React props. No third-party integration. |
| What happens in the failure path? | If sidePanel prop is falsy, isPanelOpen is false, translate: "0 0" — modal renders centered as before. Safe default. |
| Who controls each value, could they lie? | sidePanelOpen / sidePanel are internal props set by the single consumer (KnowledgeBaseUploadModal). No user-controlled input. |
| Blast radius if wrong? | Cosmetic only — modal could be mis-centered. No data, authz, or secret exposure. |
🔴 CRITICAL security checklist: ✅ pass (no PII, no secrets, no LLM/AI, no third-party integration, no DB access, no logging).
3. File-by-File Review
3.1 src/frontend/src/modals/stepperModal/StepperModal.tsx
+ const isPanelOpen = Boolean(sidePanel) && sidePanelOpen;
...
- style={heightStyle}
+ style={{
+ ...heightStyle,
+ translate: isPanelOpen ? "-150px 0" : "0 0",
+ }}
className={cn(
- "... transition-[height,width,border-radius,opacity] duration-300 ease-in-out",
+ "... transition-[height,width,border-radius,opacity,translate] duration-300 ease-in-out",
...
- sidePanel && sidePanelOpen
+ isPanelOpenFindings:
| # | Severity | Finding |
|---|---|---|
| F1 | 🟠 IMPORTANT — Magic number / DRY | -150px is half of the side panel's w-[300px]. The constant exists (SIDE_PANEL_WIDTH in stepperModal/constants.ts:14) but is hardcoded as the Tailwind class "w-[300px]", so no numeric source of truth is reused. If the panel width ever changes, this translate will silently desync. Suggested fix: export a numeric SIDE_PANEL_WIDTH_PX = 300 alongside the Tailwind class and compute translate: \${-SIDE_PANEL_WIDTH_PX / 2}px 0``. |
| F2 | 🟡 RECOMMENDED — KISS / style merge | style={{ ...heightStyle, translate: ... }} — when heightStyle is undefined, spread still works but returns a translate-only object. Fine, just worth a comment that heightStyle may be undefined; current code handles it correctly via the spread. |
| F3 | 🟢 NICE TO HAVE — Naming | isPanelOpen is clear, but hasSidePanelOpen or isSidePanelVisible would be slightly less ambiguous (there's also a sidePanelOpen prop right above it, which reads as "is the prop truthy" vs "is there a panel AND is it open"). Minor. |
| F4 | 🟡 RECOMMENDED — Animation coupling | translate is listed in transition-[height,width,border-radius,opacity,translate] with duration-300. Good — this is the reason the panel and the shift are in sync. Verify the transition-[translate] utility is supported on the project's Tailwind version (Tailwind ≥ 3.3). |
| F5 | 🟢 OK | SRP preserved — file still has a single responsibility (render stepper dialog). |
| F6 | 🟢 OK | LOC: 128 lines — well under 500-line limit. |
3.2 src/frontend/src/modals/stepperModal/components/SidePanel.tsx
- "... transition-opacity duration-150",
+ "... transition-[opacity] duration-300 ease-in-out",
...
- "h-full transition-transform duration-300 ease-out",
+ "h-full transition-[transform] duration-300 ease-in-out",Findings:
| # | Severity | Finding |
|---|---|---|
| F7 | 🟡 RECOMMENDED — Behavior change beyond the stated fix | The opacity transition duration doubled (150 ms → 300 ms) and its easing was changed. This is justified by the PR description ("Unified SidePanel animation timing ... in sync"), but worth flagging: the side panel's fade-in is now perceptibly slower. Please verify visually and with accessibility (prefers-reduced-motion) that the new timing still feels snappy. |
| F8 | 🟢 OK — Tailwind syntax normalization | transition-opacity → transition-[opacity] and transition-transform → transition-[transform] are semantically equivalent for Tailwind. Switching to the arbitrary-value form is consistent with the expanded list in StepperModal.tsx, but it is a style change that increases diff churn. Either form is fine — recommend sticking to the shorthand utilities (transition-opacity) when listing a single property. |
3.3 src/frontend/src/modals/knowledgeBaseUploadModal/KnowledgeBaseUploadModal.tsx
+ const errorCount = Object.keys(form.validationErrors).length;
+ const modalBase = !hideAdvanced && form.showAdvanced ? 690 : 347;
+ const modalHeight = `${modalBase + errorCount * 16}`;
+
+ const showHelpButton = !hideAdvanced && form.currentStep === 1;
...
- height={(() => {
- const errorCount = ...
- return `${base + errorCount * 16}`;
- })()}
+ height={modalHeight}
...
- helpLabel={ !hideAdvanced && form.currentStep === 1 ? ... : undefined }
- onHelp={ !hideAdvanced && form.currentStep === 1 ? ... : undefined }
+ helpLabel={ showHelpButton ? ... : undefined }
+ onHelp={ showHelpButton ? ... : undefined }Findings:
| # | Severity | Finding |
|---|---|---|
| F9 | 🟢 OK — DRY | Good refactor. The duplicated !hideAdvanced && form.currentStep === 1 expression becomes showHelpButton at a single source. |
| F10 | 🟠 IMPORTANT — Magic numbers | 690, 347, 16 still appear in the extracted modalBase / modalHeight expressions. These existed before this PR (pre-existing), but since the author is already touching these lines to extract the variables, it's a cheap follow-up to extract: MODAL_HEIGHT_WITH_ADVANCED = 690, MODAL_HEIGHT_DEFAULT = 347, VALIDATION_ERROR_LINE_PX = 16. Per BUG_FIXES_RULE, "pre-existing issue in the same lines you're touching and trivial to fix → fix it in the same PR". |
| F11 | 🟢 OK | LOC: 151 lines — well under 500. |
| F12 | 🟢 OK | SRP preserved — single modal orchestration component. |
4. REVIEWER_RULE Checklist
🔴 CRITICAL (Blockers)
| Item | Status |
|---|---|
| No PII in any logs / prints / webhook messages | ✅ N/A — no logging |
| No secrets/credentials in code | ✅ |
| No duplicate types, classes, or logic (DRY) | 🟠 See F1 & F10 (magic numbers shared across files) |
| No file exceeds 500 lines | ✅ — all modified files ≤ 151 lines |
| No file has > 5 functions with DIFFERENT responsibilities | ✅ |
| No file has > 10 functions even with same responsibility | ✅ |
| No file has > 1 main class | ✅ |
| No mixed responsibility prefixes in same file | ✅ |
🔴 CRITICAL — AI/Chatbot Features
✅ N/A — no AI / LLM / agent code.
🔴 CRITICAL — Third-Party Integration Security
✅ N/A — no external integration.
🟠 IMPORTANT
| Item | Status |
|---|---|
| SRP respected | ✅ |
| No growing if/elif chains (OCP) | ✅ |
| LSP / ISP / DIP | ✅ N/A for this presentational layer |
| YAGNI | ✅ |
| Law of Demeter | ✅ (form.validationErrors, form.showAdvanced are 1-level accesses through a hook result — acceptable) |
Strong typing (no any / object) |
✅ |
| Inputs validated at boundaries | ✅ N/A — internal props |
| Error handling | ✅ N/A |
| Types/constants in dedicated files | SIDE_PANEL_WIDTH is in constants.ts but only as a Tailwind class, not as a reusable numeric constant (see F1). Modal height magic numbers (690, 347, 16) not extracted (see F10). |
🟡 RECOMMENDED
| Item | Status |
|---|---|
| Appropriate logging at key points | ✅ N/A |
| No unnecessary comments | ✅ |
| No over-engineering | ✅ |
🟢 TESTING
| Item | Status |
|---|---|
| Unit tests for core logic | |
| Tests cover success, error, and edge cases | |
| BOTH happy path AND adversarial tests | ❌ No test for: sidePanel truthy + sidePanelOpen=false (no translate), sidePanel=undefined + sidePanelOpen=true (no translate — Boolean(sidePanel) short-circuit), or the class-name transition rounded-l-xl rounded-r-none |
| Coverage ran and shown ≥ 75% | ❌ No coverage output posted on this PR |
| All created tests pass — zero failures | ✅ 86 pre-existing tests reported passing |
| Not prolonging legacy bad patterns | KnowledgeBaseUploadModal.test.tsx at 842 lines (pre-existing violation of 500-line limit — out of scope for this PR but worth a follow-up ticket) |
5. Required Changes (🟠 Before Merge)
- Extract the numeric side-panel width to eliminate the
-150pxmagic number (F1).- Add to
stepperModal/constants.ts:(or, pragmatically, export a separateexport const SIDE_PANEL_WIDTH_PX = 300; export const SIDE_PANEL_WIDTH = `w-[${SIDE_PANEL_WIDTH_PX}px]`;
SIDE_PANEL_HALF_OFFSET_PX = 150) - Replace in
StepperModal.tsx:translate: isPanelOpen ? `${-SIDE_PANEL_WIDTH_PX / 2}px 0` : "0 0",
- Add to
- Add a regression test in
stepperModal/__tests__/StepperModal.test.tsxasserting:translate: "-150px 0"(or the derived value) whensidePanelis provided andsidePanelOpenistrue.translate: "0 0"whensidePanelis omitted ORsidePanelOpenisfalse.- The
rounded-l-xl rounded-r-none border-r-0classes appear iffisPanelOpen.
6. Suggested Follow-ups (🟡 Optional, same PR or a separate ticket)
- Extract modal height magic numbers (
690,347,16) inKnowledgeBaseUploadModal.tsxto named constants inknowledgeBaseUploadModal/constants.ts(F10). - Revert Tailwind arbitrary-value transitions in
SidePanel.tsxback to shorthand (transition-opacity,transition-transform) to reduce diff churn — the behavior change that matters is theduration-300unification, not the syntax. - Verify visually the slower side-panel fade (150 ms → 300 ms) still feels responsive, and that the translate animation respects
prefers-reduced-motion(F7).
…nents Extract hardcoded numeric values to named constants for better maintainability: - Add SIDE_PANEL_WIDTH_PX (300) to StepperModal constants - Update translate calculation to use SIDE_PANEL_WIDTH_PX / 2 - Add modal height constants (MODAL_HEIGHT_WITH_ADVANCED, MODAL_HEIGHT_DEFAULT, VALIDATION_ERROR_LINE_HEIGHT) to KnowledgeBaseUploadModal This addresses DRY principle violations identified in code review where -150px was decoupled from the 300px side panel width.
…vior Add comprehensive test coverage for side panel translate and CSS class behavior: - Verify translate offset (-150px) when side panel is open - Verify no translate offset (0 0) when side panel is closed or omitted - Verify rounded corner classes (rounded-l-xl, rounded-r-none, border-r-0) when open - Verify default rounded class (rounded-xl) when closed These tests ensure the modal centering fix remains stable across future changes.
Dismissing this pending review since the issue has already been resolved. Thanks!
Summary
DialogContentleft by half the side panel width (-150px) using the CSStranslateproperty — separate fromtransformso it doesn't conflict with the existing zoom-in/out keyframe animationSidePanelanimation timing toduration-300 ease-in-outso both the dialog shift and panel slide-in are fully in syncRefactors included (no behavior change):
isPanelOpenreplaces the duplicatedsidePanel && sidePanelOpenchecks inStepperModalmodalHeight/modalBase/errorCountreplace the IIFE inside the JSXheightprop inKnowledgeBaseUploadModalshowHelpButtonreplaces the repeated!hideAdvanced && currentStep === 1conditionScope:
StepperModalhas a single consumer (KnowledgeBaseUploadModal), so no other flows are affected.Before
Modal.Errado.Cut.mp4
After
Modal.CorretoCut.mp4
Test plan
StepperModal.test.tsx,KnowledgeBaseUploadModal.test.tsx— 86 tests passing ✅