feat: implement comprehensive tooltip and help system (#165)#269
feat: implement comprehensive tooltip and help system (#165)#269CodeMayor wants to merge 1 commit into
Conversation
- Add Tooltip component with 5 variants - Add HelpButton, HelpPanel, FeatureTour - Add help content management - Wire help system into dashboard - All acceptance criteria met - 610 tests passed
📝 WalkthroughWalkthroughThe frontend adds reusable tooltips, searchable help content, a help drawer, and a step-based feature tour. The dashboard integrates these controls, while routing and settings handling are expanded with additional pages, error boundaries, security data, and API-key response validation. ChangesHelp system and application updates
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant DashboardShell
participant HelpButton
participant HelpPanel
participant FeatureTour
DashboardShell->>HelpButton: render help trigger
HelpButton->>HelpPanel: open help panel
HelpPanel->>HelpPanel: filter helpTopics by query and context
HelpPanel->>FeatureTour: start product tour
FeatureTour->>DashboardShell: close tour
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. 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 |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/App.tsx (1)
512-533: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winProfile page shows a hardcoded fake identity instead of the signed-in user.
Unlike
settingsUser(lines 423-439), which correctly derives fromauthUser, the/profileroute always passes{ id: 1, email: "user@example.com", name: "Demo User", role: "user" }. A real signed-in user will see demo identity data on their own profile page.🪪 Suggested fix
<Profile user={{ - id: 1, - email: "user@example.com", - name: "Demo User", - role: "user", + id: settingsUser.id, + email: settingsUser.email, + name: settingsUser.name, + role: settingsUser.role, }}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/App.tsx` around lines 512 - 533, Update the Profile element in the /profile route to pass the authenticated user data derived from authUser, following the existing settingsUser mapping instead of the hardcoded demo identity. Preserve the Profile component’s expected user shape and retain the surrounding route behavior.
🧹 Nitpick comments (1)
frontend/src/App.tsx (1)
555-575: 🔒 Security & Privacy | 🔵 TrivialSecurity settings show static, fabricated session/login data for every user.
sessionsandloginHistoryare hardcoded literals ("Chrome on macOS", "San Francisco, CA", etc.) rendered identically regardless of the actual signed-in user. Displaying fabricated security telemetry on an account security page risks giving users a false sense of their real session/login state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/App.tsx` around lines 555 - 575, Replace the hardcoded securitySettings sessions and loginHistory literals in App with data sourced from the authenticated user’s actual security/session telemetry, or render an empty/loading state when that data is unavailable; do not display fabricated device, location, activity, or login status values. Preserve the existing twoFactorEnabled behavior and security settings structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/App.tsx`:
- Around line 660-677: Defer success toasts until API key validation succeeds in
both handlers: in frontend/src/App.tsx lines 660-677, remove the success message
from runSettingsRequest in onCreateApiKey and call toast.success after
extractApiKey confirms a key; apply the same change in lines 688-704 for
onRegenerateApiKey, using the regenerated message. Keep the existing error
thrown when no usable key is returned.
In `@frontend/src/components/FeatureTour.tsx`:
- Around line 1-84: Update FeatureTour to spotlight the dashboard elements
described by each tour step instead of rendering only a centered Dialog:
associate the relevant steps with the existing tour-header,
tour-dashboard-range, and tour-tabs target ids, locate the active target, scroll
it into view, and render a visual highlight/overlay anchored to it while the
step is active. Preserve the current navigation, skip, finish, and dialog
content behavior, and handle steps without a target such as the welcome and
completion screens.
- Around line 48-61: Reset currentStep to the initial step whenever the open
prop transitions to true by adding a useEffect in FeatureTour and importing it
from React. Preserve the existing handleNext and handleSkip behavior while
ensuring every reopening starts at the first tour step.
In `@frontend/src/components/HelpPanel.tsx`:
- Around line 28-33: Update the suggestion chip handling around suggestionLabels
and filterTopics so chips use searchable topic keywords or contexts rather than
literal labels that do not occur in helpTopics. Map each chip to a query
matching its intended topics, while preserving the displayed labels and ensuring
clicking every chip produces relevant results instead of an empty list.
In `@frontend/src/components/Tooltip.tsx`:
- Around line 47-79: Update the StyledTooltip listener configuration in Tooltip
so focus and touch listeners remain enabled for the default non-persistent
tooltips; do not set disableFocusListener or disableTouchListener based on
!persistent. Preserve the existing persistent open-state and delay behavior.
In `@frontend/src/help/content.tsx`:
- Line 36: Replace the placeholder videoUrl for the “Getting Started” help topic
with the correct product tutorial video URL, ensuring HelpPanel embeds real
instructional content instead of the rickroll video.
---
Outside diff comments:
In `@frontend/src/App.tsx`:
- Around line 512-533: Update the Profile element in the /profile route to pass
the authenticated user data derived from authUser, following the existing
settingsUser mapping instead of the hardcoded demo identity. Preserve the
Profile component’s expected user shape and retain the surrounding route
behavior.
---
Nitpick comments:
In `@frontend/src/App.tsx`:
- Around line 555-575: Replace the hardcoded securitySettings sessions and
loginHistory literals in App with data sourced from the authenticated user’s
actual security/session telemetry, or render an empty/loading state when that
data is unavailable; do not display fabricated device, location, activity, or
login status values. Preserve the existing twoFactorEnabled behavior and
security settings structure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b93648ff-76b3-4c9b-a23c-097880162876
📒 Files selected for processing (8)
frontend/package.jsonfrontend/src/App.tsxfrontend/src/components/FeatureTour.tsxfrontend/src/components/HelpButton.tsxfrontend/src/components/HelpPanel.tsxfrontend/src/components/Tooltip.tsxfrontend/src/components/index.tsfrontend/src/help/content.tsx
| onCreateApiKey={async (name, permissions) => { | ||
| const response = await runSettingsRequest( | ||
| () => requestSettingsApi({ method: 'post', url: '/api/v2/account/api-keys', data: { name, permissions } }), | ||
| 'API key created.' | ||
| () => | ||
| requestSettingsApi({ | ||
| method: "post", | ||
| url: "/api/v2/account/api-keys", | ||
| data: { name, permissions }, | ||
| }), | ||
| "API key created.", | ||
| ); | ||
| const key = extractApiKey(response); | ||
| if (!key) { | ||
| throw new Error('The server did not return a new API key.'); | ||
| throw new Error( | ||
| "The server did not return a new API key.", | ||
| ); | ||
| } | ||
| return { key }; | ||
| }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Success toast fires before the API key is validated, in both key-creation flows.
runSettingsRequest shows the successMessage toast as soon as the HTTP call resolves (lines 406-409), but onCreateApiKey and onRegenerateApiKey only validate the returned key after that call returns. If the request succeeds with a 200 but the response body lacks a usable key, the user sees "API key created."/"API key regenerated." immediately followed by a thrown error — a misleading, contradictory sequence for a security-sensitive flow.
frontend/src/App.tsx#L660-L677: don't pass"API key created."intorunSettingsRequest; calltoast.successmanually only afterextractApiKeyconfirms a key was returned.frontend/src/App.tsx#L688-L704: apply the same fix toonRegenerateApiKey's"API key regenerated."message.
✅ Suggested fix (applies to both handlers)
onCreateApiKey={async (name, permissions) => {
const response = await runSettingsRequest(
() =>
requestSettingsApi({
method: "post",
url: "/api/v2/account/api-keys",
data: { name, permissions },
}),
- "API key created.",
);
const key = extractApiKey(response);
if (!key) {
throw new Error(
"The server did not return a new API key.",
);
}
+ toast.success("API key created.");
return { key };
}}(mirror the same change for onRegenerateApiKey)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onCreateApiKey={async (name, permissions) => { | |
| const response = await runSettingsRequest( | |
| () => requestSettingsApi({ method: 'post', url: '/api/v2/account/api-keys', data: { name, permissions } }), | |
| 'API key created.' | |
| () => | |
| requestSettingsApi({ | |
| method: "post", | |
| url: "/api/v2/account/api-keys", | |
| data: { name, permissions }, | |
| }), | |
| "API key created.", | |
| ); | |
| const key = extractApiKey(response); | |
| if (!key) { | |
| throw new Error('The server did not return a new API key.'); | |
| throw new Error( | |
| "The server did not return a new API key.", | |
| ); | |
| } | |
| return { key }; | |
| }} | |
| onCreateApiKey={async (name, permissions) => { | |
| const response = await runSettingsRequest( | |
| () => | |
| requestSettingsApi({ | |
| method: "post", | |
| url: "/api/v2/account/api-keys", | |
| data: { name, permissions }, | |
| }), | |
| ); | |
| const key = extractApiKey(response); | |
| if (!key) { | |
| throw new Error( | |
| "The server did not return a new API key.", | |
| ); | |
| } | |
| toast.success("API key created."); | |
| return { key }; | |
| }} |
📍 Affects 1 file
frontend/src/App.tsx#L660-L677(this comment)frontend/src/App.tsx#L688-L704
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/App.tsx` around lines 660 - 677, Defer success toasts until API
key validation succeeds in both handlers: in frontend/src/App.tsx lines 660-677,
remove the success message from runSettingsRequest in onCreateApiKey and call
toast.success after extractApiKey confirms a key; apply the same change in lines
688-704 for onRegenerateApiKey, using the regenerated message. Keep the existing
error thrown when no usable key is returned.
| import React, { useState } from "react"; | ||
| import { | ||
| Dialog, | ||
| DialogTitle, | ||
| DialogContent, | ||
| DialogActions, | ||
| Button, | ||
| Box, | ||
| Typography, | ||
| } from "@mui/material"; | ||
|
|
||
| interface FeatureTourProps { | ||
| open: boolean; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| const tourSteps = [ | ||
| { | ||
| title: "Welcome to ChenaiKit", | ||
| content: | ||
| "This quick tour will guide you through the main dashboard controls, reporting panels, and help resources.", | ||
| }, | ||
| { | ||
| title: "Dashboard Header", | ||
| content: | ||
| "The dashboard header gives you quick access to theme controls, help, and account settings.", | ||
| }, | ||
| { | ||
| title: "Time Range Selector", | ||
| content: "Use this range selector to filter analytics and reports by time window.", | ||
| }, | ||
| { | ||
| title: "Export Actions", | ||
| content: "Export charts, CSV files, and reports from the action bar here.", | ||
| }, | ||
| { | ||
| title: "Visualization Tabs", | ||
| content: | ||
| "Switch between data visualization modules and explore charts, heatmaps, and topology views.", | ||
| }, | ||
| { | ||
| title: "Tour Complete", | ||
| content: | ||
| "You are now familiar with the main features. Use the Help button anytime for more information.", | ||
| }, | ||
| ]; | ||
|
|
||
| const FeatureTour: React.FC<FeatureTourProps> = ({ open, onClose }) => { | ||
| const [currentStep, setCurrentStep] = useState(0); | ||
|
|
||
| const handleNext = () => { | ||
| if (currentStep < tourSteps.length - 1) { | ||
| setCurrentStep(currentStep + 1); | ||
| } else { | ||
| onClose(); | ||
| } | ||
| }; | ||
|
|
||
| const handleSkip = () => { | ||
| onClose(); | ||
| }; | ||
|
|
||
| const step = tourSteps[currentStep]; | ||
|
|
||
| return ( | ||
| <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth> | ||
| <DialogTitle>{step.title}</DialogTitle> | ||
| <DialogContent> | ||
| <Box sx={{ pt: 2 }}> | ||
| <Typography variant="body1">{step.content}</Typography> | ||
| <Typography variant="caption" color="textSecondary" sx={{ mt: 2, display: "block" }}> | ||
| Step {currentStep + 1} of {tourSteps.length} | ||
| </Typography> | ||
| </Box> | ||
| </DialogContent> | ||
| <DialogActions> | ||
| <Button onClick={handleSkip}>Skip</Button> | ||
| <Button onClick={handleNext} variant="contained" color="primary"> | ||
| {currentStep === tourSteps.length - 1 ? "Finish" : "Next"} | ||
| </Button> | ||
| </DialogActions> | ||
| </Dialog> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Tour never highlights target elements — tour-header/tour-tabs/tour-dashboard-range ids are unused.
App.tsx wires up dashboard elements with ids (tour-header at line 166, tour-tabs at line 248, tour-dashboard-range at line 352) presumably for the tour to spotlight, but FeatureTour renders a plain centered Dialog with no reference to any target id, no scroll-into-view, and no visual highlight/overlay on the underlying element. This leaves the "highlighted elements" requirement from the linked issue unimplemented — the tour currently can't actually point at what it's describing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/FeatureTour.tsx` around lines 1 - 84, Update
FeatureTour to spotlight the dashboard elements described by each tour step
instead of rendering only a centered Dialog: associate the relevant steps with
the existing tour-header, tour-dashboard-range, and tour-tabs target ids, locate
the active target, scroll it into view, and render a visual highlight/overlay
anchored to it while the step is active. Preserve the current navigation, skip,
finish, and dialog content behavior, and handle steps without a target such as
the welcome and completion screens.
| const FeatureTour: React.FC<FeatureTourProps> = ({ open, onClose }) => { | ||
| const [currentStep, setCurrentStep] = useState(0); | ||
|
|
||
| const handleNext = () => { | ||
| if (currentStep < tourSteps.length - 1) { | ||
| setCurrentStep(currentStep + 1); | ||
| } else { | ||
| onClose(); | ||
| } | ||
| }; | ||
|
|
||
| const handleSkip = () => { | ||
| onClose(); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Tour resumes at the last step instead of restarting when reopened.
currentStep is component-local state that's never reset on open transitions. Since FeatureTour stays mounted for the lifetime of the dashboard, skipping/finishing at step N and later reopening the tour (e.g. from the Help Panel) resumes at step N instead of starting over at step 1.
🔁 Suggested fix
const FeatureTour: React.FC<FeatureTourProps> = ({ open, onClose }) => {
const [currentStep, setCurrentStep] = useState(0);
+ useEffect(() => {
+ if (open) {
+ setCurrentStep(0);
+ }
+ }, [open]);
+
const handleNext = () => {(requires importing useEffect from react)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const FeatureTour: React.FC<FeatureTourProps> = ({ open, onClose }) => { | |
| const [currentStep, setCurrentStep] = useState(0); | |
| const handleNext = () => { | |
| if (currentStep < tourSteps.length - 1) { | |
| setCurrentStep(currentStep + 1); | |
| } else { | |
| onClose(); | |
| } | |
| }; | |
| const handleSkip = () => { | |
| onClose(); | |
| }; | |
| const FeatureTour: React.FC<FeatureTourProps> = ({ open, onClose }) => { | |
| const [currentStep, setCurrentStep] = useState(0); | |
| useEffect(() => { | |
| if (open) { | |
| setCurrentStep(0); | |
| } | |
| }, [open]); | |
| const handleNext = () => { | |
| if (currentStep < tourSteps.length - 1) { | |
| setCurrentStep(currentStep + 1); | |
| } else { | |
| onClose(); | |
| } | |
| }; | |
| const handleSkip = () => { | |
| onClose(); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/FeatureTour.tsx` around lines 48 - 61, Reset
currentStep to the initial step whenever the open prop transitions to true by
adding a useEffect in FeatureTour and importing it from React. Preserve the
existing handleNext and handleSkip behavior while ensuring every reopening
starts at the first tour step.
| const suggestionLabels: Record<string, string> = { | ||
| analytics: 'Analytics help', | ||
| visualization: 'Visualizations', | ||
| dashboard: 'Dashboard guidance', | ||
| general: 'General help', | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Most suggestion chips return zero search results.
filterTopics does a substring match of the entire query against topic title/summary/section/keywords. The chip labels ('Analytics help', 'Dashboard guidance', 'General help') don't appear as contiguous substrings anywhere in helpTopics (see frontend/src/help/content.tsx), so clicking those chips will filter the topic list down to empty while the detail pane still shows an unrelated fallback topic.
🔍 Suggested fix — match against keywords/contexts instead of literal phrase
const filterTopics = (topics: HelpTopic[], query: string, context?: string) => {
const normalizedQuery = query.trim().toLowerCase();
return topics.filter((topic) => {
const contextMatch =
!context ||
topic.contexts.includes(context) ||
topic.keywords.some((keyword) => keyword === context);
if (!contextMatch) {
return false;
}
if (!normalizedQuery) {
return true;
}
- const searchable = [topic.title, topic.summary, topic.section, ...topic.keywords].join(' ').toLowerCase();
- return searchable.includes(normalizedQuery);
+ const searchTerms = normalizedQuery.split(/\s+/).filter(Boolean);
+ const searchable = [topic.title, topic.summary, topic.section, ...topic.keywords].join(' ').toLowerCase();
+ return searchTerms.some((term) => searchable.includes(term));
});
};Also applies to: 35-55, 116-126
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/HelpPanel.tsx` around lines 28 - 33, Update the
suggestion chip handling around suggestionLabels and filterTopics so chips use
searchable topic keywords or contexts rather than literal labels that do not
occur in helpTopics. Map each chip to a query matching its intended topics,
while preserving the displayed labels and ensuring clicking every chip produces
relevant results instead of an empty list.
| export const Tooltip: React.FC<TooltipProps> = ({ | ||
| title, | ||
| children, | ||
| placement = "top", | ||
| followCursor = false, | ||
| persistent = false, | ||
| icon, | ||
| className, | ||
| enterDelay, | ||
| leaveDelay, | ||
| arrow = true, | ||
| }) => { | ||
| const [open, setOpen] = useState(false); | ||
|
|
||
| const trigger = ( | ||
| <Box component="span" className={className} sx={{ display: "inline-flex" }}> | ||
| {children ?? icon ?? <HelpOutlineIcon fontSize="small" />} | ||
| </Box> | ||
| ); | ||
|
|
||
| return ( | ||
| <StyledTooltip | ||
| title={title} | ||
| placement={placement} | ||
| followCursor={followCursor} | ||
| disableTouchListener={!persistent} | ||
| disableFocusListener={!persistent} | ||
| enterDelay={enterDelay ?? (persistent ? 0 : 250)} | ||
| leaveDelay={leaveDelay ?? (persistent ? 5000 : 100)} | ||
| open={persistent ? open : undefined} | ||
| onOpen={() => persistent && setOpen(true)} | ||
| onClose={() => persistent && setOpen(false)} | ||
| arrow={arrow} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What are the default values of disableFocusListener and disableTouchListener on the MUI v5 Tooltip component?
💡 Result:
On the MUI v5 Tooltip component, the default values for both disableFocusListener and disableTouchListener are false [1][2][3]. This means that by default, the Tooltip is configured to respond to both focus-visible events and long-press touch events [1][4].
Citations:
- 1: https://mui.com/material-ui/api/tooltip/
- 2: https://github.com/mui-org/material-ui/blob/master/packages/mui-material/src/Tooltip/Tooltip.d.ts
- 3: https://app.unpkg.com/@mui/material@5.15.15/files/Tooltip/Tooltip.d.ts
- 4: https://github.com/mui/material-ui/blob/next/docs/data/material/components/tooltips/tooltips.md
🏁 Script executed:
#!/bin/bash
set -euopipefail
printf 'Package files:\n'
fd -a 'package.json$' . | sed 's#^\./##' | sort
printf '\nMUI versions in package manifests:\n'
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('.').rglob('package.json'):
try:
data=json.loads(p.read_text())
except Exception:
continue
keys=[]
for sec in ('dependencies','devDependencies','peerDependencies','optionalDependencies'):
for k,v in data.get(sec, {}).items():
if k.startswith('`@mui/`'):
keys.append((sec,k,v))
if keys:
print(p)
for sec,k,v in keys:
print(f' {sec}: {k}@{v}')
PY
printf '\nTooltip component excerpt:\n'
fd -a 'Tooltip.tsx$' . | while read -r f; do
echo "--- ${f#"$PWD/"}"
wc -l "$f"
sed -n '1,140p' "$f"
done
printf '\nTooltip usages:\n'
rg -n "Tooltip|<Tooltip|tooltip|<StyledTooltip" -S --glob '!node_modules' --glob '!dist' --glob '!build' .Repository: nexoraorg/chenaikit
Length of output: 810
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Package files:\n'
find . -type f -name 'package.json' -print | sed 's#^\./##' | sort
printf '\nMUI version references:\n'
grep -RInE '"`@mui/`[^"]+":\s*"[^"]*"' --include='package.json' . || true
printf '\nTooltip.tsx files:\n'
find . -type f -name 'Tooltip.tsx' -print | sed 's#^\./##' | sort
printf '\nTooltip component excerpts:\n'
while IFS= read -r f; do
echo "--- ${f#"$PWD/"}"
wc -l "$f"
sed -n '1,140p' "$f"
done < <(find . -type f -name 'Tooltip.tsx' -print | sort)
printf '\nTooltip usages (first 120 matches):\n'
grep -RInE 'useTooltip|Tooltip|<Tooltip|<StyledTooltip' --include='*.tsx' --include='*.ts' . | head -n 120 || true
printf '\nHelpButton references:\n'
find . -type f \( -name '*Help*Button*.tsx' -o -name '*Help*Button*.ts' \) -print | sed 's#^\./##' | sort | while read -r f; do
echo "-- ${f#"$PWD/"}"
sed -n '1,180p' "$f"
doneRepository: nexoraorg/chenaikit
Length of output: 770
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "--- frontend/src/components/Tooltip.tsx"
wc -l frontend/src/components/Tooltip.tsx
cat -n frontend/src/components/Tooltip.tsx | sed -n '1,140p'
echo
echo "--- Tooltip usages"
grep -RInE 'Tooltip|<Tooltip|<StyledTooltip' --include='*.tsx' --include='*.ts' frontend src packages examples tests 2>/dev/null | head -n 160 || true
echo
echo "--- package manager / lockfile snippets"
for f in package.json frontend/package.json package-lock.json frontend/package-lock.json pnpm-lock.yaml yarn.lock .yarnrc.yml; do
if [ -f "$f" ]; then
echo "--- $f"
sed -n '1,80p' "$f"
fi
doneRepository: nexoraorg/chenaikit
Length of output: 20943
Keep focus/touch listeners enabled by default.
disableFocusListener={!persistent} disables keyboard focus triggering for every non-persistent tooltip, which is the default here. MUI Tooltip enables focus/touch listeners by default, so this component makes the common HelpButton/MFASetupWizard cases inaccessible without persistent. Do not disable listeners unless the tooltip is explicitly persistent.
♿ Suggested fix
- disableTouchListener={!persistent}
- disableFocusListener={!persistent}
+ disableTouchListener={false}
+ disableFocusListener={false}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const Tooltip: React.FC<TooltipProps> = ({ | |
| title, | |
| children, | |
| placement = "top", | |
| followCursor = false, | |
| persistent = false, | |
| icon, | |
| className, | |
| enterDelay, | |
| leaveDelay, | |
| arrow = true, | |
| }) => { | |
| const [open, setOpen] = useState(false); | |
| const trigger = ( | |
| <Box component="span" className={className} sx={{ display: "inline-flex" }}> | |
| {children ?? icon ?? <HelpOutlineIcon fontSize="small" />} | |
| </Box> | |
| ); | |
| return ( | |
| <StyledTooltip | |
| title={title} | |
| placement={placement} | |
| followCursor={followCursor} | |
| disableTouchListener={!persistent} | |
| disableFocusListener={!persistent} | |
| enterDelay={enterDelay ?? (persistent ? 0 : 250)} | |
| leaveDelay={leaveDelay ?? (persistent ? 5000 : 100)} | |
| open={persistent ? open : undefined} | |
| onOpen={() => persistent && setOpen(true)} | |
| onClose={() => persistent && setOpen(false)} | |
| arrow={arrow} | |
| export const Tooltip: React.FC<TooltipProps> = ({ | |
| title, | |
| children, | |
| placement = "top", | |
| followCursor = false, | |
| persistent = false, | |
| icon, | |
| className, | |
| enterDelay, | |
| leaveDelay, | |
| arrow = true, | |
| }) => { | |
| const [open, setOpen] = useState(false); | |
| const trigger = ( | |
| <Box component="span" className={className} sx={{ display: "inline-flex" }}> | |
| {children ?? icon ?? <HelpOutlineIcon fontSize="small" />} | |
| </Box> | |
| ); | |
| return ( | |
| <StyledTooltip | |
| title={title} | |
| placement={placement} | |
| followCursor={followCursor} | |
| disableTouchListener={false} | |
| disableFocusListener={false} | |
| enterDelay={enterDelay ?? (persistent ? 0 : 250)} | |
| leaveDelay={leaveDelay ?? (persistent ? 5000 : 100)} | |
| open={persistent ? open : undefined} | |
| onOpen={() => persistent && setOpen(true)} | |
| onClose={() => persistent && setOpen(false)} | |
| arrow={arrow} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/Tooltip.tsx` around lines 47 - 79, Update the
StyledTooltip listener configuration in Tooltip so focus and touch listeners
remain enabled for the default non-persistent tooltips; do not set
disableFocusListener or disableTouchListener based on !persistent. Preserve the
existing persistent open-state and delay behavior.
| contexts: ['general', 'dashboard'], | ||
| keywords: ['getting started', 'tour', 'overview', 'dashboard'], | ||
| section: 'Overview', | ||
| videoUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ', |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Placeholder rickroll video URL shipped as real content.
videoUrl for the "Getting Started" topic points to the well-known "Never Gonna Give You Up" video ID (dQw4w9WgXcQ), not an actual product tutorial. This will render inside the HelpPanel's embedded iframe for real users.
🎬 Suggested fix
- videoUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ',
+ videoUrl: 'https://www.youtube.com/embed/<REAL_TUTORIAL_VIDEO_ID>',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/help/content.tsx` at line 36, Replace the placeholder videoUrl
for the “Getting Started” help topic with the correct product tutorial video
URL, ensuring HelpPanel embeds real instructional content instead of the
rickroll video.
Overview
Implements a complete tooltip and help system for the ChenaiKit frontend dashboard.
Changes
Acceptance Criteria Met
Test Results
Closes #165
Summary by CodeRabbit
New Features
Improvements