feat: add user guide documentation sidebar#3148
Conversation
- Add comprehensive user guide with 10 documentation collections - Include 49 articles covering all Checkmate features - Implement sidebar navigation with search functionality - Support light/dark theme with proper color tokens - Add article content renderer with multiple block types - Include image lightbox for documentation screenshots
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... 📒 Files selected for processing (20)
✏️ Tip: You can disable in-progress messages and the fortune message in your review settings. Tip CodeRabbit can enforce grammar and style rules using `languagetool`.Configure Note
|
| Cohort / File(s) | Summary |
|---|---|
App Integration client/src/App.jsx, client/src/Components/v1/Layouts/HomeLayout/index.jsx |
Wraps application with UserGuideSidebarProvider and renders UserGuideSidebar within HomeLayout; establishes provider pattern for sidebar state management. |
Sidebar Context & State client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx, client/src/Components/v1/UserGuide/index.jsx |
Introduces context provider managing sidebar open/close state, current path, content width; persists state to localStorage; exports context utilities and constants for theme dimensions. |
Sidebar Structure & Navigation client/src/Components/v1/UserGuide/SidebarWrapper..., client/src/Components/v1/UserGuide/TabBar..., client/src/Components/v1/UserGuide/SidebarHeader... |
Implements resizable sidebar container with draggable handle, tab switching (User Guide/Help), navigation controls, breadcrumbs, search toggle, and history management with back/forward buttons. |
Content Pages & Layouts client/src/Components/v1/UserGuide/UserGuideLanding..., client/src/Components/v1/UserGuide/CollectionPage..., client/src/Components/v1/UserGuide/ArticlePage... |
Three-level content hierarchy: landing page with collections grid, collection pages displaying article lists, and article pages with dynamic table of contents and content rendering; includes CSS styling with hover transitions. |
Content Rendering System client/src/Components/v1/UserGuide/ContentRenderer... |
Comprehensive content block renderer supporting headings, paragraphs, callouts, lists, icon cards, grid cards, tables, images with lightbox, code blocks, and article links; includes custom Markdown-like parser for inline formatting and internal article link syntax. |
Search & Discovery client/src/Components/v1/UserGuide/SearchResults... |
Search results page displaying prioritized matches across articles by title, description, and keywords; integrates with navigation callback. |
Supporting Components client/src/Components/v1/UserGuide/ImageLightbox.jsx, client/src/Components/v1/UserGuide/HelpSection... |
Image lightbox modal with keyboard accessibility and scroll lock; Help section with Discord and GitHub discussion links. |
Theme & Styling System client/src/Components/v1/UserGuide/styles/theme.js, client/src/Components/v1/UserGuide/styles/useUserGuideTheme.js |
Centralized design tokens (typography, spacing, brand colors) and theme-aware styling hook; supports light/dark mode switching via MUI theme integration; exports color palette and component styles. |
Configuration & Content Data client/src/Components/v1/UserGuide/content/userGuideConfig.js, client/src/Components/v1/UserGuide/content/index.js, client/src/Components/v1/UserGuide/content/contentTypes.js |
Defines collection metadata, article structure, and search configuration; exports retrieval helpers (getCollection, getArticle, searchArticles); provides JSDoc content block type definitions and article content registry. |
Documentation & Styling client/src/Components/v1/Sidebar/index.jsx, client/src/Components/v1/UserGuide/STYLE_GUIDE.md |
Adds z-index and background color to existing Sidebar; includes comprehensive style guide for documentation voice, structure, formatting, templates, and quality checklist. |
Sequence Diagram(s)
sequenceDiagram
actor User
participant App as App.jsx
participant Provider as UserGuideSidebarProvider
participant SidebarWrapper as SidebarWrapper
participant Context as UserGuideSidebarContext
participant ContentPages as ContentPages<br/>(Landing/Collection/Article)
User->>App: Load application
App->>Provider: Wrap with provider
Provider->>Context: Initialize (open/close state, path)
User->>SidebarWrapper: Toggle sidebar open
SidebarWrapper->>Context: Update isOpen = true
Context->>Context: Persist to localStorage
Context->>SidebarWrapper: Notify state change
SidebarWrapper->>ContentPages: Render UserGuideLanding
User->>ContentPages: Click collection
ContentPages->>SidebarWrapper: Call onNavigate(collectionId)
SidebarWrapper->>Context: Update currentPath
Context->>SidebarWrapper: Notify change
SidebarWrapper->>ContentPages: Render CollectionPage
User->>ContentPages: Click article
ContentPages->>SidebarWrapper: Call onNavigate(collectionId, articleId)
SidebarWrapper->>Context: Update currentPath
SidebarWrapper->>ContentPages: Render ArticlePage with ContentRenderer
ContentPages->>User: Display article with styled blocks
sequenceDiagram
actor User
participant SearchUI as Search Input
participant SearchResults as SearchResults
participant Config as userGuideConfig
participant Navigation as Article Navigation
User->>SearchUI: Type query (≥2 chars)
SearchUI->>SearchResults: Pass query
SearchResults->>Config: searchArticles(query)
Config->>Config: Filter & rank by title/description/keywords
Config->>SearchResults: Return results array
SearchResults->>SearchResults: Render result list
User->>SearchResults: Click result item
SearchResults->>Navigation: onNavigate(collectionId, articleId)
Navigation->>SearchResults: onClearSearch()
SearchResults->>SearchUI: Clear search, close results
sequenceDiagram
participant Article as ArticlePage
participant ContentRenderer as ContentRenderer
participant Theme as useUserGuideTheme
participant Parser as parseMarkdown
Article->>ContentRenderer: Pass content blocks
ContentRenderer->>Theme: Get theme colors/tokens
Theme->>ContentRenderer: Return isDark, colors, typography
loop For each content block
ContentRenderer->>Parser: parseMarkdown(text)
Parser->>Parser: Extract [[text]](collection/article) links
Parser->>ContentRenderer: Return mixed nodes/strings
ContentRenderer->>ContentRenderer: Render block with theme styles
end
ContentRenderer->>Article: Render styled content tree
Article->>Article: Track TOC scroll position
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~55 minutes
The PR introduces a substantial feature spanning 20+ interdependent files with varying complexity. Key components like ContentRenderer (902 lines with multiple rendering helpers and custom parsing logic), SidebarWrapper (475 lines with navigation history and resize management), and the content configuration system (2666 lines) require careful review. Multiple new patterns (context provider, theme hook, content block types) and integration points with existing code add to the cognitive load. Many files follow consistent patterns (CSS styling, simple wrapper components), but the overall coherence and state management flows require holistic understanding.
Poem
🐰 A sidebar springs to life with grace,
Collections dancing in their place,
With articles to guide the way,
And search to light a brighter day,
Dark themes gleam and tables shine,
Documentation—simply divine! ✨
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Description check | The description covers the summary, features, and changes but lacks completion of required checklist items from the template, which should all be marked before submission. | Complete all required checklist items by marking [ ] as [x], including local deployment testing, self-review, issue number inclusion, and code formatting verification. | |
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly and concisely summarizes the main feature being added: a user guide documentation sidebar for the application. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing touches
- 📝 Generate docstrings
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 help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In `@client/src/Components/v1/UserGuide/ArticlePage.jsx`:
- Around line 157-158: The JSX in ArticlePage.jsx contains hardcoded user-facing
strings that must be localized; update the ArticlePage component to replace the
literal texts ("In this article", "Article content coming soon.", "Previous",
"Next") with the i18n translation function t(...) (or useTranslation hook) and
ensure t is imported/available in the component, then wrap each literal in t
calls (e.g., the span rendering "In this article" and the elements rendering the
article placeholder and pagination labels) so the UI uses translated strings.
In `@client/src/Components/v1/UserGuide/CollectionPage.jsx`:
- Line 82: The hardcoded string "{collection.articleCount} articles in this
collection" in CollectionPage.jsx must be internationalized; replace it with a
call to the translation function (e.g., use t('collection.articleCount', {
count: collection.articleCount }) or an appropriate key) and ensure the
component imports/receives the t function (from react-i18next or project i18n)
so the number is passed as an interpolation/pluralization parameter; update the
translation key in your locale files accordingly.
In `@client/src/Components/v1/UserGuide/content/index.js`:
- Around line 1-117: The articleContents object currently hardcodes all
user-facing strings (headings, paragraphs, list items, etc.) which breaks
internationalization; update the content to use translation keys and the app's
translation function (or per-locale content files) so strings are resolved at
render time (e.g., replace literal texts inside articleContents with keys like
"getting-started.what-is-checkmate.overview" and call i18n/t or ContentRenderer
to resolve them), or split articleContents into locale-specific modules (e.g.,
articleContents.en.js/articleContents.de.js) and load by locale; locate the
articleContents constant in this file and adjust the consumers (ContentRenderer
or any component that renders these blocks) to call the translation function for
block.text, item.title, item.description, list items, and other user-facing
fields instead of embedding English literals.
In `@client/src/Components/v1/UserGuide/content/userGuideConfig.js`:
- Around line 31-64: Replace hardcoded user-facing strings in the exported
collections array (the collections constant and its nested article objects) with
translation keys: add fields like titleKey and descriptionKey on each collection
and article instead of using literal title/description values (and optionally
convert keywords to keywordKey if they need translation), and update callers to
resolve them with the i18n function (e.g., t(collection.titleKey) /
t(article.descriptionKey)); ensure the exported structure still provides IDs and
articleCount but uses keys for all UI text so the consuming component (which
currently reads collection.title and article.title) can switch to calling t(...)
on the new titleKey/descriptionKey fields.
In `@client/src/Components/v1/UserGuide/HelpSection.jsx`:
- Around line 17-119: Several user-facing strings in HelpSection.jsx (e.g., the
"Contact us" and "GitHub discussions" headings, their paragraph bodies like
"Can't find what you need? Our support team is here to help." and "Questions
about features, how-tos, or use cases? Join the community discussion.", and CTA
labels "Join our Discord" and "GitHub discussions") are hardcoded; wrap each
visible string in the i18n translation function t(...) and ensure the component
imports/uses the translation hook (e.g., useTranslation -> t) so headings,
paragraphs, and anchor text use t("...") instead of literal strings; update any
inline aria/alt/text variants the same way and keep the ExternalLink usage and
className="user-guide-help-button" intact.
In `@client/src/Components/v1/UserGuide/ImageLightbox.jsx`:
- Around line 33-78: The modal lacks dialog semantics and the close button is
icon-only/unlocalized; update the backdrop container (the element using
handleBackdropClick) to include role="dialog" and aria-modal="true" (and
optionally aria-labelledby/aria-describedby if headers/descriptions exist), and
change the close button (onClick={onClose}, X icon) to provide an accessible,
localized name by using the i18n t(...) function for the label (e.g. t("Close
(Esc)") or t("Close")) via aria-label (and keep or remove the title) so screen
readers announce the button; ensure you import/use the same t used across the
app.
In `@client/src/Components/v1/UserGuide/SearchResults.jsx`:
- Around line 100-166: Replace the clickable result row div with a semantic
interactive element (preferably a <button type="button"> or an <a> if
navigating) so keyboard focus and activation work; keep the existing className
"user-guide-search-result-item", inline styles, and the onClick handler
(handleResultClick) but move it to the button/a and ensure any child icons
(FileText, ArrowRight) and text (result.articleTitle, result.articleDescription,
result.collectionTitle) remain unchanged; also add an accessible name (either
the visible title or an aria-label) and ensure focus styles are preserved so
keyboard users can activate rows.
- Around line 15-158: The UI strings in SearchResults.jsx (e.g., the placeholder
"Type at least 2 characters to search...", the no-results message "No results
found for \"{query}\"", "Try different keywords or browse by topic", the "Clear
search" button label, the per-result "in {result.collectionTitle}", and the
results count span that currently renders {results.length} result(s) for
"{query}") must be wrapped with the i18n translator t(...) and use
pluralization/interpolation where appropriate; update the SearchResults
component to import t (or use useTranslation) and replace literal strings with t
keys, use t('search.resultsCount', { count: results.length, query }) or the i18n
pluralization API for the results count, and use t('search.noResults', { query
}) and t('search.typeAtLeast', { min: 2 }) etc., ensuring all visible text,
including the "Clear search" button and "in {result.collectionTitle}", use t
with interpolation rather than hard-coded strings.
In `@client/src/Components/v1/UserGuide/SidebarHeader.jsx`:
- Around line 106-107: Add i18n by importing and using the translation hook
(const { t } = useTranslation()) in the SidebarHeader component and replace all
hardcoded user-facing strings with t() calls: update prop values title="Back",
title="Forward", title="Home", title="Open in new tab", title="Search",
title="Close" to use t('userGuide.back') / t('userGuide.forward') /
t('userGuide.home') / t('userGuide.openInNewTab') / t('userGuide.search') /
t('userGuide.close'), replace placeholder="Search articles..." with
t('userGuide.searchPlaceholder'), and replace the fallback "User guide" with
t('userGuide.title'); ensure keys are consistent and add corresponding entries
to your locales if missing.
In `@client/src/Components/v1/UserGuide/SidebarWrapper.jsx`:
- Around line 198-208: Replace the hardcoded UI strings in the breadcrumbs
builder with translation keys via the project's translation function (e.g.,
useTranslation()/t) instead of literal "User guide" and "Help": update the items
array construction where items = [{ label: "User guide", onClick:
handleHomeClick }], the default return [{ label: "Help", onClick: () => {} }],
and any other literal labels in that switch block to call t('userGuide') and
t('help') (or appropriate i18n keys); import/use the translation hook at the top
of SidebarWrapper.jsx and keep existing handlers (handleHomeClick, setArticleId,
article, collection, items) unchanged.
In `@client/src/Components/v1/UserGuide/TabBar.jsx`:
- Around line 64-81: The TabItem labels "User guide" and "Help" are hardcoded in
TabBar.jsx; update them to use the translation function by replacing the label
props with t("user_guide") and t("help") (or the appropriate translation keys)
so TabItem calls use label={t("...")}; locate the TabItem components in the
TabBar component (props: activeTab, onTabChange, colors, typography) and ensure
the translation function t is imported/available in that module before using it.
- Around line 7-45: The tab item currently rendered as a non-focusable <div>
with onClick must be made keyboard-accessible: replace the outer div that uses
onClick/isActive/icon/label with a semantic <button type="button"> (or if you
must keep a div, add role="button" tabIndex={0} and a onKeyDown handler that
triggers onClick on Enter/Space) and ensure to preserve the existing className
and inline style logic; also add an appropriate ARIA state such as aria-pressed
or aria-selected when isActive to communicate selection to assistive tech.
In `@client/src/Components/v1/UserGuide/UserGuideLanding.jsx`:
- Around line 73-137: The collection card uses a clickable div which is not
keyboard-accessible; replace the outer <div key={collection.id}> with a semantic
interactive element (preferably a <button type="button> or an <a> if it
navigates) and wire the existing onClick={() => onNavigate(collection.id)} to
that element so keyboard activation works, preserve styling currently applied to
the div, and ensure the element has an accessible name (e.g., aria-label or use
the visible collection.title) and proper focus styles; if you must keep a
non-semantic element, add tabIndex={0} and handle onKeyDown to call
onNavigate(collection.id) for Enter/Space, but using a <button> is the preferred
fix.
- Around line 47-200: Replace hard-coded user-facing strings in the
UserGuideLanding component with i18n calls: import/use the translation hook
(e.g., t) and wrap headings ("Browse by topic", "About our docs", "There are a
few ways to explore our docs:", "In the product", helper text "Look for help
icons and tooltips throughout the interface.") and collection text
(collection.title, collection.description) with t(...), and change the article
count badge to use pluralization/interpolation (e.g., t('articles', { count:
collection.articleCount })) so the JSX inside the collections.map block and the
in-app info card all use t keys; add appropriate keys (e.g., collections.title,
collections.description, collections.articles) to your locale files and ensure
plural forms are defined.
In `@client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx`:
- Around line 14-18: The initializer for isOpen and the places that read/write
SIDEBAR_STATE_KEY should guard against localStorage throwing by wrapping all
localStorage accesses in try/catch and using safe fallbacks; specifically,
update the useState initializer that reads
localStorage.getItem(SIDEBAR_STATE_KEY) to catch errors and return false if
access fails, and update the code paths that call
localStorage.setItem/removeItem (the setter logic around setIsOpen and any
effect or handler at lines ~52-55) to perform writes inside try/catch and
silently ignore or fallback when storage is unavailable. Ensure you reference
SIDEBAR_STATE_KEY, isOpen and setIsOpen when making these changes so behavior
remains the same when localStorage is available.
🟡 Minor comments (8)
client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx-89-94 (1)
89-94: Localize the error string.Frontend user-facing strings should use
t("..."). This error can surface via error boundaries; please route it through i18n.client/src/Components/v1/UserGuide/content/contentTypes.js-168-175 (1)
168-175: GuardextractTocagainst non-array inputs.If
blocksis undefined/null, this will throw. A small guard keeps it safe.🛠️ Proposed fix
-export const extractToc = (blocks) => { - return blocks +export const extractToc = (blocks = []) => { + if (!Array.isArray(blocks)) return []; + return blocks .filter((block) => block.type === "heading") .map((block) => ({ label: block.text, href: `#${block.id}`, level: block.level, })); };client/src/Components/v1/UserGuide/SearchResults.css-1-10 (1)
1-10: Theme selector missing for dark-mode-specific styles.The comment indicates this is for "Dark theme," but unlike other CSS files in this PR (e.g.,
ContentRenderer.css), there's no[data-mui-color-scheme="dark"]selector. Thergba(255, 255, 255, 0.05)hover background is a white overlay that only works well on dark backgrounds—it will be nearly invisible on light theme.Consider adding theme-specific selectors for consistency:
Suggested fix
/* SearchResults styles (Dark theme) */ .user-guide-search-result-item { transition: background-color 150ms ease, border-color 150ms ease; } -.user-guide-search-result-item:hover { - background-color: rgba(255, 255, 255, 0.05) !important; - border-color: `#4a5568` !important; +.user-guide-search-result-item:hover { + background-color: rgba(0, 0, 0, 0.04) !important; + border-color: `#d0d5dd` !important; +} + +[data-mui-color-scheme="dark"] .user-guide-search-result-item:hover { + background-color: rgba(255, 255, 255, 0.05) !important; + border-color: `#4a5568` !important; }client/src/Components/v1/UserGuide/TabBar.css-7-13 (1)
7-13: Theme-specific selectors needed for light/dark mode compatibility.Similar to
SearchResults.css, these hover styles use dark-theme colors (rgba(255, 255, 255, 0.05)and#1a1a1a) without theme selectors. On light theme, the hover effect will be barely visible and the active state may look jarring.Suggested fix
.user-guide-tab-item:hover { - background-color: rgba(255, 255, 255, 0.05); + background-color: rgba(0, 0, 0, 0.04); } -.user-guide-tab-item.active:hover { - background-color: `#1a1a1a`; +[data-mui-color-scheme="dark"] .user-guide-tab-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.user-guide-tab-item.active:hover { + background-color: `#f5f5f5`; +} + +[data-mui-color-scheme="dark"] .user-guide-tab-item.active:hover { + background-color: `#1a1a1a`; }client/src/Components/v1/UserGuide/SearchResults.jsx-6-16 (1)
6-16: Guard against undefinedquerybefore callingtrim().If
queryis evernull/undefined, this throws.🔧 Suggested change
-const SearchResults = ({ query, onNavigate, onClearSearch }) => { +const SearchResults = ({ query = "", onNavigate, onClearSearch }) => {client/src/Components/v1/UserGuide/ArticlePage.jsx-29-41 (1)
29-41: Unused props:onBackandonBackToHome.These props are destructured but never used in the component. Remove them if they're not needed, or implement the intended functionality.
Suggested fix
const ArticlePage = ({ collection, article, - onBack, - onBackToHome, onNextArticle, onPrevArticle, nextArticle, prevArticle, tocItems, children, mode = "in-app", }) => {client/src/Components/v1/UserGuide/CollectionPage.jsx-27-27 (1)
27-27: UnusedonBackprop.The
onBackprop is destructured but never used in this component. Either remove it from the props or implement the intended back navigation functionality.Suggested fix
-const CollectionPage = ({ collection, onBack, onArticleClick }) => { +const CollectionPage = ({ collection, onArticleClick }) => {client/src/Components/v1/UserGuide/ArticlePage.jsx-43-43 (1)
43-43: UnusedIconComponentvariable.
IconComponentis computed fromcollection.iconbut is never used in the render output. Either remove it or add the intended icon rendering.Suggested fix if not needed
const { colors, typography, spacing, border, isDark } = useUserGuideTheme(); -const IconComponent = iconMap[collection.icon] || Rocket; const isInApp = mode === "in-app";
🧹 Nitpick comments (14)
client/src/Components/v1/UserGuide/content/userGuideConfig.js (1)
37-37:articleCountmay drift out of sync with actual article count.The
articleCountproperty is manually maintained and could become inconsistent with the actualarticles.length. Consider deriving it dynamically:♻️ Suggested approach
Remove the static
articleCountproperty and compute it dynamically ingetTotalArticleCount:export const getTotalArticleCount = () => { - return collections.reduce((sum, collection) => sum + collection.articleCount, 0); + return collections.reduce((sum, collection) => sum + collection.articles.length, 0); };Or add a getter function for individual collections:
export const getCollectionArticleCount = (collectionId) => { const collection = getCollection(collectionId); return collection?.articles.length ?? 0; };Also applies to: 70-70, 121-121, 160-160, 205-205, 238-238, 289-289, 328-328, 355-355, 382-382
client/src/Components/v1/UserGuide/styles/theme.js (2)
12-14: Duplicate font size values forxsandsm.Both
fontSize.xsandfontSize.smare set to12. If this is intentional for consistency, consider adding a comment. Otherwise,xsmight be intended to be smaller (e.g., 10 or 11).fontSize: { xs: 12, // Same as sm - intentional? sm: 12, ... }
203-209: Legacy exports default to dark mode - document expected migration path.The comment mentions these will be replaced by
useUserGuideThemehook usage. Consider adding a deprecation note or TODO to track this migration.// Legacy exports for backwards compatibility (dark mode defaults) -// These will be replaced by useUserGuideTheme hook usage +// `@deprecated` Use useUserGuideTheme hook instead for theme-aware styling +// TODO: Remove these exports once all components migrate to the hook export const colors = getColors("dark");client/src/Components/v1/UserGuide/SidebarHeader.css (2)
7-15: Consider using CSS custom properties instead of hardcoded colors.The hardcoded color values (
#1570EF,#0d0d0d) duplicate values fromtheme.js. Using CSS custom properties would ensure consistency and support theme switching.♻️ Suggested approach
Define CSS custom properties at the root (or component level) and reference them:
.user-guide-header-icon-button:hover { background-color: var(--user-guide-hover-bg, rgba(255, 255, 255, 0.1)) !important; color: var(--user-guide-brand-primary, `#1570EF`) !important; } .user-guide-header-icon-button.active { background-color: var(--user-guide-bg-alt, `#0d0d0d`); color: var(--user-guide-brand-primary, `#1570EF`); }Then set these variables from JavaScript based on the current theme mode.
7-10:!importantmay conflict with inline styles.The
!importantdeclarations are likely needed to override the inline styles set inSidebarHeader.jsx. Consider moving more styles to CSS to reduce the need for!important, which can make debugging harder.client/src/Components/v1/UserGuide/SidebarHeader.jsx (1)
86-161: Consider extracting repeated button styles into a shared style object.The navigation buttons (Back, Forward, Home) share identical styling. Extract to reduce duplication:
♻️ Suggested refactor
const iconButtonStyle = (enabled) => ({ display: "flex", alignItems: "center", justifyContent: "center", width: 28, height: 28, backgroundColor: "transparent", border: "none", borderRadius: border.radius, cursor: enabled ? "pointer" : "default", color: enabled ? colors.text.secondary : colors.border.default, flexShrink: 0, opacity: enabled ? 1 : 0.5, }); // Usage: <button onClick={onBack} disabled={!canGoBack} className="user-guide-header-icon-button" style={iconButtonStyle(canGoBack)} title={t("userGuide.navigation.back")} >client/src/Components/v1/UserGuide/ContentRenderer.css (1)
4-6: Remove empty CSS rule.This rule block is empty with only a comment. If inline styles handle these properties, the rule can be removed entirely.
Suggested fix
/* ContentRenderer styles - hover states and list styling */ -/* List styling - colors handled by inline styles for theme support */ -.user-guide-content-list li { - /* Font styles handled inline for theme-aware colors */ -} - /* Table row hover */client/src/Components/v1/UserGuide/CollectionPage.jsx (1)
16-25: DuplicateiconMapdefinition.This
iconMapis nearly identical to the one inArticlePage.jsx(lines 17-26). Consider extracting it to a shared module (e.g.,utils/iconMap.js) to avoid duplication and ensure consistency.client/src/Components/v1/UserGuide/ContentRenderer.jsx (4)
279-281: Redundant ternary expression.Both branches produce the same result. Simplify to just the expression.
Suggested fix
<span style={{ color: colors.text.secondary }}> - {item.bold ? parseMarkdown(item.text) : parseMarkdown(item.text)} + {parseMarkdown(item.text)} </span>
311-312: Same redundant ternary pattern.Apply the same simplification here.
Suggested fix
<span style={{ color: colors.text.secondary }}> - {item.bold ? parseMarkdown(item.text) : parseMarkdown(item.text)} + {parseMarkdown(item.text)} </span>
596-604: Hardcoded color bypasses theme.The code block text color
#E5E7EBis hardcoded, which won't adapt to theme changes. Consider using a theme token for consistency.Suggested fix
<pre style={{ fontSize: typography.fontSize.sm, fontFamily: typography.fontFamily.mono, - color: "#E5E7EB", + color: isDark ? "#E5E7EB" : colors.text.primary, margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-all", }} >
29-51: Third instance oficonMapduplication.This is the third file with a similar
iconMapdefinition (also inCollectionPage.jsxandArticlePage.jsx). Extract to a shared utility module to maintain a single source of truth.client/src/Components/v1/UserGuide/SidebarWrapper.jsx (2)
92-94: Silent error handling may hide issues.Consider logging the parse error in development to aid debugging.
Suggested fix
} catch (e) { - // Ignore parse errors + console.warn('Failed to parse saved sidebar state:', e); }
212-219: Hardcoded external URL.Consider moving this URL to a configuration file or environment variable for easier maintenance.
Suggested fix
+// At top of file or in a config module +const GITBOOK_URL = "https://bluewavelabs.gitbook.io/checkmate"; + const handleOpenInNewTab = () => { - let path = "https://bluewavelabs.gitbook.io/checkmate"; if (onOpenInNewTab) { onOpenInNewTab(); } else { - window.open(path, "_blank", "noopener,noreferrer"); + window.open(GITBOOK_URL, "_blank", "noopener,noreferrer"); } };
| In this article | ||
| </span> |
There was a problem hiding this comment.
Hardcoded user-facing strings require internationalization.
Per coding guidelines, the following strings should use the translation function t():
- Line 157: "In this article"
- Line 225: "Article content coming soon."
- Line 262: "Previous"
- Line 300: "Next"
Example fixes
- In this article
+ {t('userGuide.inThisArticle')}- Article content coming soon.
+ {t('userGuide.contentComingSoon')}- Previous
+ {t('common.previous')}- Next
+ {t('common.next')}🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/ArticlePage.jsx` around lines 157 - 158,
The JSX in ArticlePage.jsx contains hardcoded user-facing strings that must be
localized; update the ArticlePage component to replace the literal texts ("In
this article", "Article content coming soon.", "Previous", "Next") with the i18n
translation function t(...) (or useTranslation hook) and ensure t is
imported/available in the component, then wrap each literal in t calls (e.g.,
the span rendering "In this article" and the elements rendering the article
placeholder and pagination labels) so the UI uses translated strings.
| marginTop: spacing.sm, | ||
| }} | ||
| > | ||
| {collection.articleCount} articles in this collection |
There was a problem hiding this comment.
Hardcoded user-facing string.
Per coding guidelines, all user-facing strings must use the translation function t('your.key'). This string should be internationalized.
Suggested fix
- {collection.articleCount} articles in this collection
+ {t('userGuide.articlesInCollection', { count: collection.articleCount })}📝 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.
| {collection.articleCount} articles in this collection | |
| {t('userGuide.articlesInCollection', { count: collection.articleCount })} |
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/CollectionPage.jsx` at line 82, The
hardcoded string "{collection.articleCount} articles in this collection" in
CollectionPage.jsx must be internationalized; replace it with a call to the
translation function (e.g., use t('collection.articleCount', { count:
collection.articleCount }) or an appropriate key) and ensure the component
imports/receives the t function (from react-i18next or project i18n) so the
number is passed as an interpolation/pluralization parameter; update the
translation key in your locale files accordingly.
| // Article content - each article has its content defined here | ||
| // Import content as needed to keep bundle size manageable | ||
|
|
||
| const articleContents = { | ||
| // ============================================ | ||
| // GETTING STARTED COLLECTION | ||
| // ============================================ | ||
|
|
||
| "getting-started/what-is-checkmate": { | ||
| blocks: [ | ||
| { | ||
| type: "heading", | ||
| id: "overview", | ||
| level: 2, | ||
| text: "Overview", | ||
| }, | ||
| { | ||
| type: "paragraph", | ||
| text: "Checkmate is an open-source uptime and infrastructure monitoring platform. It helps you track website availability, server performance, and service health—all from a single dashboard.", | ||
| }, | ||
| { | ||
| type: "paragraph", | ||
| text: "Whether you're monitoring a personal project or managing enterprise infrastructure, Checkmate provides the tools you need to stay informed about your systems.", | ||
| }, | ||
| { | ||
| type: "heading", | ||
| id: "core-features", | ||
| level: 2, | ||
| text: "Core features", | ||
| }, | ||
| { | ||
| type: "icon-cards", | ||
| items: [ | ||
| { | ||
| icon: "Globe", | ||
| title: "Uptime monitoring", | ||
| description: | ||
| "Monitor websites and APIs with HTTP, ping, and TCP port checks.", | ||
| }, | ||
| { | ||
| icon: "Gauge", | ||
| title: "PageSpeed insights", | ||
| description: | ||
| "Track website performance with Google Lighthouse integration.", | ||
| }, | ||
| { | ||
| icon: "Server", | ||
| title: "Infrastructure monitoring", | ||
| description: "Monitor CPU, memory, disk, and network with the Capture agent.", | ||
| }, | ||
| { | ||
| icon: "Bell", | ||
| title: "Instant alerts", | ||
| description: "Get notified via email, Slack, Discord, PagerDuty, or webhooks.", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| type: "heading", | ||
| id: "monitoring-types", | ||
| level: 2, | ||
| text: "Monitoring types", | ||
| }, | ||
| { | ||
| type: "bullet-list", | ||
| items: [ | ||
| { | ||
| bold: "HTTP/HTTPS", | ||
| text: "Check website availability and response validation", | ||
| }, | ||
| { | ||
| bold: "Ping", | ||
| text: "Verify server reachability using ICMP", | ||
| }, | ||
| { | ||
| bold: "TCP port", | ||
| text: "Monitor specific services like databases and mail servers", | ||
| }, | ||
| { | ||
| bold: "Docker", | ||
| text: "Monitor Docker container status and health", | ||
| }, | ||
| { | ||
| bold: "PageSpeed", | ||
| text: "Track Core Web Vitals and performance scores", | ||
| }, | ||
| { | ||
| bold: "Infrastructure", | ||
| text: "Monitor hardware metrics with the Capture agent", | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| type: "heading", | ||
| id: "next-steps", | ||
| level: 2, | ||
| text: "Next steps", | ||
| }, | ||
| { | ||
| type: "article-links", | ||
| items: [ | ||
| { | ||
| collectionId: "getting-started", | ||
| articleId: "quick-start", | ||
| title: "Quick start guide", | ||
| description: "Create your first monitor in 5 minutes", | ||
| }, | ||
| { | ||
| collectionId: "getting-started", | ||
| articleId: "dashboard", | ||
| title: "Understanding the dashboard", | ||
| description: "Learn to navigate the interface", | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, |
There was a problem hiding this comment.
Documentation content lacks internationalization support.
Similar to userGuideConfig.js, all article content (headings, paragraphs, list items, callouts, etc.) is hardcoded in English. For a fully internationalized app, this content should be translatable.
Consider one of these approaches:
- Use translation keys and resolve them in ContentRenderer
- Organize content by locale (e.g.,
articleContents.en.js,articleContents.de.js) - Use a CMS or external content source that supports multiple languages
This is a significant undertaking given the volume of content (~49 articles), so it may be acceptable to defer to a future iteration.
As per coding guidelines, user-facing strings should use the translation function.
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/content/index.js` around lines 1 - 117,
The articleContents object currently hardcodes all user-facing strings
(headings, paragraphs, list items, etc.) which breaks internationalization;
update the content to use translation keys and the app's translation function
(or per-locale content files) so strings are resolved at render time (e.g.,
replace literal texts inside articleContents with keys like
"getting-started.what-is-checkmate.overview" and call i18n/t or ContentRenderer
to resolve them), or split articleContents into locale-specific modules (e.g.,
articleContents.en.js/articleContents.de.js) and load by locale; locate the
articleContents constant in this file and adjust the consumers (ContentRenderer
or any component that renders these blocks) to call the translation function for
block.text, item.title, item.description, list items, and other user-facing
fields instead of embedding English literals.
| export const collections = [ | ||
| { | ||
| id: "getting-started", | ||
| title: "Getting started", | ||
| description: "Essential first steps for new users.", | ||
| icon: "Rocket", | ||
| articleCount: 4, | ||
| articles: [ | ||
| { | ||
| id: "what-is-checkmate", | ||
| title: "What is Checkmate", | ||
| description: "Overview of monitoring capabilities and core features.", | ||
| keywords: ["introduction", "overview", "platform", "monitoring", "uptime"], | ||
| }, | ||
| { | ||
| id: "quick-start", | ||
| title: "Quick start guide", | ||
| description: "Create your first monitor in 5 minutes.", | ||
| keywords: ["quick", "start", "first", "monitor", "setup", "tutorial"], | ||
| }, | ||
| { | ||
| id: "dashboard", | ||
| title: "Understanding the dashboard", | ||
| description: "Navigation and key metrics explained.", | ||
| keywords: ["dashboard", "navigation", "interface", "home", "metrics"], | ||
| }, | ||
| { | ||
| id: "roles-permissions", | ||
| title: "User roles and permissions", | ||
| description: "User, Admin, and Superadmin roles explained.", | ||
| keywords: ["role", "permission", "admin", "superadmin", "user", "access"], | ||
| }, | ||
| ], | ||
| }, |
There was a problem hiding this comment.
Hardcoded UI strings should use translation keys.
All user-facing strings like title and description in the collections and articles are hardcoded in English. Per coding guidelines, these should use translation function t('your.key') for internationalization support.
Consider restructuring to use translation keys:
{
id: "getting-started",
- title: "Getting started",
- description: "Essential first steps for new users.",
+ titleKey: "userGuide.gettingStarted.title",
+ descriptionKey: "userGuide.gettingStarted.description",
icon: "Rocket",
...
}Then resolve the translations in the consuming component using t(collection.titleKey).
As per coding guidelines, all user-facing strings must use the translation function.
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/content/userGuideConfig.js` around lines
31 - 64, Replace hardcoded user-facing strings in the exported collections array
(the collections constant and its nested article objects) with translation keys:
add fields like titleKey and descriptionKey on each collection and article
instead of using literal title/description values (and optionally convert
keywords to keywordKey if they need translation), and update callers to resolve
them with the i18n function (e.g., t(collection.titleKey) /
t(article.descriptionKey)); ensure the exported structure still provides IDs and
articleCount but uses keys for all UI text so the consuming component (which
currently reads collection.title and article.title) can switch to calling t(...)
on the new titleKey/descriptionKey fields.
| <h2 | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.lg, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| marginBottom: spacing.sm, | ||
| marginTop: 0, | ||
| }} | ||
| > | ||
| Contact us | ||
| </h2> | ||
| <p | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| color: colors.text.secondary, | ||
| lineHeight: typography.lineHeight.normal, | ||
| margin: 0, | ||
| marginBottom: spacing.md, | ||
| }} | ||
| > | ||
| Can't find what you need? Our support team is here to help. | ||
| </p> | ||
| <a | ||
| href="https://discord.com/invite/NAb6H3UTjK" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="user-guide-help-button" | ||
| style={{ | ||
| display: "flex", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| gap: spacing.sm, | ||
| width: "100%", | ||
| padding: `${spacing.md} ${spacing.lg}`, | ||
| backgroundColor: colors.background.white, | ||
| border: border.default, | ||
| borderRadius: border.radius, | ||
| textDecoration: "none", | ||
| cursor: "pointer", | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| fontWeight: typography.fontWeight.medium, | ||
| color: colors.text.primary, | ||
| }} | ||
| > | ||
| Join our Discord | ||
| <ExternalLink size={14} strokeWidth={1.5} color={colors.text.muted} /> | ||
| </a> | ||
| </div> | ||
|
|
||
| {/* Ask the community */} | ||
| <div> | ||
| <h2 | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.lg, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| marginBottom: spacing.sm, | ||
| marginTop: 0, | ||
| }} | ||
| > | ||
| GitHub discussions | ||
| </h2> | ||
| <p | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| color: colors.text.secondary, | ||
| lineHeight: typography.lineHeight.normal, | ||
| margin: 0, | ||
| marginBottom: spacing.md, | ||
| }} | ||
| > | ||
| Questions about features, how-tos, or use cases? Join the community discussion. | ||
| </p> | ||
| <a | ||
| href="https://github.com/bluewave-labs/checkmate/discussions" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="user-guide-help-button" | ||
| style={{ | ||
| display: "flex", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| gap: spacing.sm, | ||
| width: "100%", | ||
| padding: `${spacing.md} ${spacing.lg}`, | ||
| backgroundColor: colors.background.white, | ||
| border: border.default, | ||
| borderRadius: border.radius, | ||
| textDecoration: "none", | ||
| cursor: "pointer", | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| fontWeight: typography.fontWeight.medium, | ||
| color: colors.text.primary, | ||
| }} | ||
| > | ||
| GitHub discussions | ||
| <ExternalLink size={14} strokeWidth={1.5} color={colors.text.muted} /> |
There was a problem hiding this comment.
Replace hardcoded copy with translations.
Headings, body text, and CTA labels should use t(...).
🔧 Suggested change
- Contact us
+ {t("userGuide.help.contactTitle")}
@@
- Can't find what you need? Our support team is here to help.
+ {t("userGuide.help.contactBody")}
@@
- Join our Discord
+ {t("userGuide.help.discordCta")}
@@
- GitHub discussions
+ {t("userGuide.help.githubTitle")}
@@
- Questions about features, how-tos, or use cases? Join the community discussion.
+ {t("userGuide.help.githubBody")}
@@
- GitHub discussions
+ {t("userGuide.help.githubCta")}As per coding guidelines, all user-facing strings must use t("...").
📝 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.
| <h2 | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.lg, | |
| fontWeight: typography.fontWeight.semibold, | |
| color: colors.text.primary, | |
| marginBottom: spacing.sm, | |
| marginTop: 0, | |
| }} | |
| > | |
| Contact us | |
| </h2> | |
| <p | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| color: colors.text.secondary, | |
| lineHeight: typography.lineHeight.normal, | |
| margin: 0, | |
| marginBottom: spacing.md, | |
| }} | |
| > | |
| Can't find what you need? Our support team is here to help. | |
| </p> | |
| <a | |
| href="https://discord.com/invite/NAb6H3UTjK" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="user-guide-help-button" | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| gap: spacing.sm, | |
| width: "100%", | |
| padding: `${spacing.md} ${spacing.lg}`, | |
| backgroundColor: colors.background.white, | |
| border: border.default, | |
| borderRadius: border.radius, | |
| textDecoration: "none", | |
| cursor: "pointer", | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| fontWeight: typography.fontWeight.medium, | |
| color: colors.text.primary, | |
| }} | |
| > | |
| Join our Discord | |
| <ExternalLink size={14} strokeWidth={1.5} color={colors.text.muted} /> | |
| </a> | |
| </div> | |
| {/* Ask the community */} | |
| <div> | |
| <h2 | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.lg, | |
| fontWeight: typography.fontWeight.semibold, | |
| color: colors.text.primary, | |
| marginBottom: spacing.sm, | |
| marginTop: 0, | |
| }} | |
| > | |
| GitHub discussions | |
| </h2> | |
| <p | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| color: colors.text.secondary, | |
| lineHeight: typography.lineHeight.normal, | |
| margin: 0, | |
| marginBottom: spacing.md, | |
| }} | |
| > | |
| Questions about features, how-tos, or use cases? Join the community discussion. | |
| </p> | |
| <a | |
| href="https://github.com/bluewave-labs/checkmate/discussions" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="user-guide-help-button" | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| gap: spacing.sm, | |
| width: "100%", | |
| padding: `${spacing.md} ${spacing.lg}`, | |
| backgroundColor: colors.background.white, | |
| border: border.default, | |
| borderRadius: border.radius, | |
| textDecoration: "none", | |
| cursor: "pointer", | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| fontWeight: typography.fontWeight.medium, | |
| color: colors.text.primary, | |
| }} | |
| > | |
| GitHub discussions | |
| <ExternalLink size={14} strokeWidth={1.5} color={colors.text.muted} /> | |
| <h2 | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.lg, | |
| fontWeight: typography.fontWeight.semibold, | |
| color: colors.text.primary, | |
| marginBottom: spacing.sm, | |
| marginTop: 0, | |
| }} | |
| > | |
| {t("userGuide.help.contactTitle")} | |
| </h2> | |
| <p | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| color: colors.text.secondary, | |
| lineHeight: typography.lineHeight.normal, | |
| margin: 0, | |
| marginBottom: spacing.md, | |
| }} | |
| > | |
| {t("userGuide.help.contactBody")} | |
| </p> | |
| <a | |
| href="https://discord.com/invite/NAb6H3UTjK" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="user-guide-help-button" | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| gap: spacing.sm, | |
| width: "100%", | |
| padding: `${spacing.md} ${spacing.lg}`, | |
| backgroundColor: colors.background.white, | |
| border: border.default, | |
| borderRadius: border.radius, | |
| textDecoration: "none", | |
| cursor: "pointer", | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| fontWeight: typography.fontWeight.medium, | |
| color: colors.text.primary, | |
| }} | |
| > | |
| {t("userGuide.help.discordCta")} | |
| <ExternalLink size={14} strokeWidth={1.5} color={colors.text.muted} /> | |
| </a> | |
| </div> | |
| {/* Ask the community */} | |
| <div> | |
| <h2 | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.lg, | |
| fontWeight: typography.fontWeight.semibold, | |
| color: colors.text.primary, | |
| marginBottom: spacing.sm, | |
| marginTop: 0, | |
| }} | |
| > | |
| {t("userGuide.help.githubTitle")} | |
| </h2> | |
| <p | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| color: colors.text.secondary, | |
| lineHeight: typography.lineHeight.normal, | |
| margin: 0, | |
| marginBottom: spacing.md, | |
| }} | |
| > | |
| {t("userGuide.help.githubBody")} | |
| </p> | |
| <a | |
| href="https://github.com/bluewave-labs/checkmate/discussions" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="user-guide-help-button" | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| gap: spacing.sm, | |
| width: "100%", | |
| padding: `${spacing.md} ${spacing.lg}`, | |
| backgroundColor: colors.background.white, | |
| border: border.default, | |
| borderRadius: border.radius, | |
| textDecoration: "none", | |
| cursor: "pointer", | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| fontWeight: typography.fontWeight.medium, | |
| color: colors.text.primary, | |
| }} | |
| > | |
| {t("userGuide.help.githubCta")} | |
| <ExternalLink size={14} strokeWidth={1.5} color={colors.text.muted} /> |
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/HelpSection.jsx` around lines 17 - 119,
Several user-facing strings in HelpSection.jsx (e.g., the "Contact us" and
"GitHub discussions" headings, their paragraph bodies like "Can't find what you
need? Our support team is here to help." and "Questions about features, how-tos,
or use cases? Join the community discussion.", and CTA labels "Join our Discord"
and "GitHub discussions") are hardcoded; wrap each visible string in the i18n
translation function t(...) and ensure the component imports/uses the
translation hook (e.g., useTranslation -> t) so headings, paragraphs, and anchor
text use t("...") instead of literal strings; update any inline aria/alt/text
variants the same way and keep the ExternalLink usage and
className="user-guide-help-button" intact.
| <div | ||
| onClick={onClick} | ||
| className={`user-guide-tab-item ${isActive ? "active" : ""}`} | ||
| style={{ | ||
| display: "flex", | ||
| flexDirection: "column", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| padding: "16px 8px", | ||
| cursor: "pointer", | ||
| backgroundColor: isActive ? colors.background.white : "transparent", | ||
| borderLeft: isActive | ||
| ? `2px solid ${colors.brand.primary}` | ||
| : "2px solid transparent", | ||
| borderBottom: `1px solid ${colors.border.default}`, | ||
| }} | ||
| > | ||
| <div | ||
| style={{ | ||
| color: isActive ? colors.brand.primary : colors.text.muted, | ||
| marginBottom: 8, | ||
| }} | ||
| > | ||
| {icon} | ||
| </div> | ||
| <span | ||
| style={{ | ||
| writingMode: "vertical-rl", | ||
| textOrientation: "mixed", | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: 12, | ||
| fontWeight: typography.fontWeight.normal, | ||
| color: isActive ? colors.brand.primary : colors.text.muted, | ||
| letterSpacing: "0.5px", | ||
| }} | ||
| > | ||
| {label} | ||
| </span> | ||
| </div> |
There was a problem hiding this comment.
Make tab items keyboard-accessible.
div with onClick is not focusable or operable via keyboard. Use a semantic button (or add role/tabIndex and key handlers) to avoid an accessibility blocker.
🔧 Suggested change
- <div
- onClick={onClick}
- className={`user-guide-tab-item ${isActive ? "active" : ""}`}
+ <button
+ type="button"
+ onClick={onClick}
+ aria-pressed={isActive}
+ className={`user-guide-tab-item ${isActive ? "active" : ""}`}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: "16px 8px",
cursor: "pointer",
+ border: "none",
backgroundColor: isActive ? colors.background.white : "transparent",
borderLeft: isActive
? `2px solid ${colors.brand.primary}`
: "2px solid transparent",
borderBottom: `1px solid ${colors.border.default}`,
}}
>
@@
- </div>
+ </button>📝 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.
| <div | |
| onClick={onClick} | |
| className={`user-guide-tab-item ${isActive ? "active" : ""}`} | |
| style={{ | |
| display: "flex", | |
| flexDirection: "column", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| padding: "16px 8px", | |
| cursor: "pointer", | |
| backgroundColor: isActive ? colors.background.white : "transparent", | |
| borderLeft: isActive | |
| ? `2px solid ${colors.brand.primary}` | |
| : "2px solid transparent", | |
| borderBottom: `1px solid ${colors.border.default}`, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| color: isActive ? colors.brand.primary : colors.text.muted, | |
| marginBottom: 8, | |
| }} | |
| > | |
| {icon} | |
| </div> | |
| <span | |
| style={{ | |
| writingMode: "vertical-rl", | |
| textOrientation: "mixed", | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: 12, | |
| fontWeight: typography.fontWeight.normal, | |
| color: isActive ? colors.brand.primary : colors.text.muted, | |
| letterSpacing: "0.5px", | |
| }} | |
| > | |
| {label} | |
| </span> | |
| </div> | |
| <button | |
| type="button" | |
| onClick={onClick} | |
| aria-pressed={isActive} | |
| className={`user-guide-tab-item ${isActive ? "active" : ""}`} | |
| style={{ | |
| display: "flex", | |
| flexDirection: "column", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| padding: "16px 8px", | |
| cursor: "pointer", | |
| border: "none", | |
| backgroundColor: isActive ? colors.background.white : "transparent", | |
| borderLeft: isActive | |
| ? `2px solid ${colors.brand.primary}` | |
| : "2px solid transparent", | |
| borderBottom: `1px solid ${colors.border.default}`, | |
| }} | |
| > | |
| <div | |
| style={{ | |
| color: isActive ? colors.brand.primary : colors.text.muted, | |
| marginBottom: 8, | |
| }} | |
| > | |
| {icon} | |
| </div> | |
| <span | |
| style={{ | |
| writingMode: "vertical-rl", | |
| textOrientation: "mixed", | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: 12, | |
| fontWeight: typography.fontWeight.normal, | |
| color: isActive ? colors.brand.primary : colors.text.muted, | |
| letterSpacing: "0.5px", | |
| }} | |
| > | |
| {label} | |
| </span> | |
| </button> |
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/TabBar.jsx` around lines 7 - 45, The tab
item currently rendered as a non-focusable <div> with onClick must be made
keyboard-accessible: replace the outer div that uses onClick/isActive/icon/label
with a semantic <button type="button"> (or if you must keep a div, add
role="button" tabIndex={0} and a onKeyDown handler that triggers onClick on
Enter/Space) and ensure to preserve the existing className and inline style
logic; also add an appropriate ARIA state such as aria-pressed or aria-selected
when isActive to communicate selection to assistive tech.
| <TabItem | ||
| id="user-guide" | ||
| label="User guide" | ||
| icon={<BookOpen size={18} strokeWidth={1.5} />} | ||
| isActive={activeTab === "user-guide"} | ||
| onClick={() => onTabChange("user-guide")} | ||
| colors={colors} | ||
| typography={typography} | ||
| /> | ||
| <TabItem | ||
| id="help" | ||
| label="Help" | ||
| icon={<HelpCircle size={18} strokeWidth={1.5} />} | ||
| isActive={activeTab === "help"} | ||
| onClick={() => onTabChange("help")} | ||
| colors={colors} | ||
| typography={typography} | ||
| /> |
There was a problem hiding this comment.
Localize tab labels with t(...).
These user-facing strings are hardcoded; please route them through the translation function.
🔧 Suggested change
- label="User guide"
+ label={t("userGuide.tabs.userGuide")}
@@
- label="Help"
+ label={t("userGuide.tabs.help")}As per coding guidelines, all user-facing strings must use t("...").
📝 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.
| <TabItem | |
| id="user-guide" | |
| label="User guide" | |
| icon={<BookOpen size={18} strokeWidth={1.5} />} | |
| isActive={activeTab === "user-guide"} | |
| onClick={() => onTabChange("user-guide")} | |
| colors={colors} | |
| typography={typography} | |
| /> | |
| <TabItem | |
| id="help" | |
| label="Help" | |
| icon={<HelpCircle size={18} strokeWidth={1.5} />} | |
| isActive={activeTab === "help"} | |
| onClick={() => onTabChange("help")} | |
| colors={colors} | |
| typography={typography} | |
| /> | |
| <TabItem | |
| id="user-guide" | |
| label={t("userGuide.tabs.userGuide")} | |
| icon={<BookOpen size={18} strokeWidth={1.5} />} | |
| isActive={activeTab === "user-guide"} | |
| onClick={() => onTabChange("user-guide")} | |
| colors={colors} | |
| typography={typography} | |
| /> | |
| <TabItem | |
| id="help" | |
| label={t("userGuide.tabs.help")} | |
| icon={<HelpCircle size={18} strokeWidth={1.5} />} | |
| isActive={activeTab === "help"} | |
| onClick={() => onTabChange("help")} | |
| colors={colors} | |
| typography={typography} | |
| /> |
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/TabBar.jsx` around lines 64 - 81, The
TabItem labels "User guide" and "Help" are hardcoded in TabBar.jsx; update them
to use the translation function by replacing the label props with
t("user_guide") and t("help") (or the appropriate translation keys) so TabItem
calls use label={t("...")}; locate the TabItem components in the TabBar
component (props: activeTab, onTabChange, colors, typography) and ensure the
translation function t is imported/available in that module before using it.
| <h2 | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: isInApp ? typography.fontSize.lg : typography.fontSize.xl, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| marginBottom: isInApp ? spacing.md : spacing.xl, | ||
| marginTop: 0, | ||
| }} | ||
| > | ||
| Browse by topic | ||
| </h2> | ||
|
|
||
| <div | ||
| style={{ | ||
| display: "grid", | ||
| gridTemplateColumns: isInApp | ||
| ? "1fr" | ||
| : "repeat(auto-fill, minmax(340px, 1fr))", | ||
| gap: isInApp ? spacing.md : spacing.lg, | ||
| marginBottom: isInApp ? spacing.xl : spacing["4xl"], | ||
| }} | ||
| > | ||
| {collections.map((collection) => { | ||
| const IconComponent = iconMap[collection.icon] || Rocket; | ||
| return ( | ||
| <div | ||
| key={collection.id} | ||
| onClick={() => onNavigate(collection.id)} | ||
| className="user-guide-collection-card" | ||
| style={{ | ||
| backgroundColor: colors.background.white, | ||
| border: border.default, | ||
| borderRadius: border.radius, | ||
| padding: spacing.lg, | ||
| cursor: "pointer", | ||
| }} | ||
| > | ||
| <div | ||
| style={{ display: "flex", alignItems: "flex-start", gap: spacing.md }} | ||
| > | ||
| <IconComponent | ||
| size={20} | ||
| strokeWidth={1.5} | ||
| color={colors.brand.primary} | ||
| /> | ||
| <div style={{ flex: 1 }}> | ||
| <div | ||
| style={{ | ||
| display: "flex", | ||
| alignItems: "center", | ||
| justifyContent: "space-between", | ||
| marginBottom: spacing.xs, | ||
| }} | ||
| > | ||
| <span | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| }} | ||
| > | ||
| {collection.title} | ||
| </span> | ||
| <span | ||
| style={{ | ||
| fontSize: typography.fontSize.xs, | ||
| color: colors.text.secondary, | ||
| backgroundColor: colors.background.alt, | ||
| padding: "2px 6px", | ||
| borderRadius: border.radius, | ||
| }} | ||
| > | ||
| {collection.articleCount} articles | ||
| </span> | ||
| </div> | ||
| <span | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| color: colors.text.secondary, | ||
| lineHeight: typography.lineHeight.normal, | ||
| display: "block", | ||
| }} | ||
| > | ||
| {collection.description} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
|
|
||
| {/* About our docs - Only shown in in-app mode */} | ||
| {isInApp && ( | ||
| <div | ||
| style={{ | ||
| backgroundColor: colors.background.white, | ||
| border: border.default, | ||
| borderRadius: border.radius, | ||
| padding: spacing.lg, | ||
| }} | ||
| > | ||
| <h3 | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| marginBottom: spacing.md, | ||
| marginTop: 0, | ||
| }} | ||
| > | ||
| About our docs | ||
| </h3> | ||
| <p | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.sm, | ||
| color: colors.text.secondary, | ||
| lineHeight: typography.lineHeight.normal, | ||
| margin: 0, | ||
| marginBottom: spacing.md, | ||
| }} | ||
| > | ||
| There are a few ways to explore our docs: | ||
| </p> | ||
|
|
||
| <div style={{ marginBottom: spacing.md }}> | ||
| <h4 | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.sm, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| marginBottom: spacing.xs, | ||
| marginTop: 0, | ||
| }} | ||
| > | ||
| In the product | ||
| </h4> | ||
| <p | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.sm, | ||
| color: colors.text.secondary, | ||
| lineHeight: typography.lineHeight.normal, | ||
| margin: 0, | ||
| }} | ||
| > | ||
| Look for help icons and tooltips throughout the interface. | ||
| </p> |
There was a problem hiding this comment.
Localize static copy and pluralization.
Strings like headings, helper text, and “{count} articles” should use t(...) with pluralization/interpolation.
🔧 Suggested change
- Browse by topic
+ {t("userGuide.landing.browseByTopic")}
@@
- {collection.articleCount} articles
+ {t("userGuide.landing.articlesCount", {
+ count: collection.articleCount,
+ })}
@@
- About our docs
+ {t("userGuide.landing.aboutTitle")}
@@
- There are a few ways to explore our docs:
+ {t("userGuide.landing.aboutIntro")}
@@
- In the product
+ {t("userGuide.landing.inProductTitle")}
@@
- Look for help icons and tooltips throughout the interface.
+ {t("userGuide.landing.inProductBody")}As per coding guidelines, all user-facing strings must use t("...").
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/UserGuideLanding.jsx` around lines 47 -
200, Replace hard-coded user-facing strings in the UserGuideLanding component
with i18n calls: import/use the translation hook (e.g., t) and wrap headings
("Browse by topic", "About our docs", "There are a few ways to explore our
docs:", "In the product", helper text "Look for help icons and tooltips
throughout the interface.") and collection text (collection.title,
collection.description) with t(...), and change the article count badge to use
pluralization/interpolation (e.g., t('articles', { count:
collection.articleCount })) so the JSX inside the collections.map block and the
in-app info card all use t keys; add appropriate keys (e.g., collections.title,
collections.description, collections.articles) to your locale files and ensure
plural forms are defined.
| <div | ||
| key={collection.id} | ||
| onClick={() => onNavigate(collection.id)} | ||
| className="user-guide-collection-card" | ||
| style={{ | ||
| backgroundColor: colors.background.white, | ||
| border: border.default, | ||
| borderRadius: border.radius, | ||
| padding: spacing.lg, | ||
| cursor: "pointer", | ||
| }} | ||
| > | ||
| <div | ||
| style={{ display: "flex", alignItems: "flex-start", gap: spacing.md }} | ||
| > | ||
| <IconComponent | ||
| size={20} | ||
| strokeWidth={1.5} | ||
| color={colors.brand.primary} | ||
| /> | ||
| <div style={{ flex: 1 }}> | ||
| <div | ||
| style={{ | ||
| display: "flex", | ||
| alignItems: "center", | ||
| justifyContent: "space-between", | ||
| marginBottom: spacing.xs, | ||
| }} | ||
| > | ||
| <span | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| fontWeight: typography.fontWeight.semibold, | ||
| color: colors.text.primary, | ||
| }} | ||
| > | ||
| {collection.title} | ||
| </span> | ||
| <span | ||
| style={{ | ||
| fontSize: typography.fontSize.xs, | ||
| color: colors.text.secondary, | ||
| backgroundColor: colors.background.alt, | ||
| padding: "2px 6px", | ||
| borderRadius: border.radius, | ||
| }} | ||
| > | ||
| {collection.articleCount} articles | ||
| </span> | ||
| </div> | ||
| <span | ||
| style={{ | ||
| fontFamily: typography.fontFamily.sans, | ||
| fontSize: typography.fontSize.base, | ||
| color: colors.text.secondary, | ||
| lineHeight: typography.lineHeight.normal, | ||
| display: "block", | ||
| }} | ||
| > | ||
| {collection.description} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Make collection cards keyboard-accessible.
Clickable divs aren’t focusable or operable via keyboard. Use a semantic button or link to avoid an accessibility blocker.
🔧 Suggested change
- <div
+ <button
+ type="button"
key={collection.id}
onClick={() => onNavigate(collection.id)}
className="user-guide-collection-card"
style={{
backgroundColor: colors.background.white,
border: border.default,
borderRadius: border.radius,
padding: spacing.lg,
cursor: "pointer",
+ width: "100%",
}}
>
@@
- </div>
+ </button>📝 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.
| <div | |
| key={collection.id} | |
| onClick={() => onNavigate(collection.id)} | |
| className="user-guide-collection-card" | |
| style={{ | |
| backgroundColor: colors.background.white, | |
| border: border.default, | |
| borderRadius: border.radius, | |
| padding: spacing.lg, | |
| cursor: "pointer", | |
| }} | |
| > | |
| <div | |
| style={{ display: "flex", alignItems: "flex-start", gap: spacing.md }} | |
| > | |
| <IconComponent | |
| size={20} | |
| strokeWidth={1.5} | |
| color={colors.brand.primary} | |
| /> | |
| <div style={{ flex: 1 }}> | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "space-between", | |
| marginBottom: spacing.xs, | |
| }} | |
| > | |
| <span | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| fontWeight: typography.fontWeight.semibold, | |
| color: colors.text.primary, | |
| }} | |
| > | |
| {collection.title} | |
| </span> | |
| <span | |
| style={{ | |
| fontSize: typography.fontSize.xs, | |
| color: colors.text.secondary, | |
| backgroundColor: colors.background.alt, | |
| padding: "2px 6px", | |
| borderRadius: border.radius, | |
| }} | |
| > | |
| {collection.articleCount} articles | |
| </span> | |
| </div> | |
| <span | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| color: colors.text.secondary, | |
| lineHeight: typography.lineHeight.normal, | |
| display: "block", | |
| }} | |
| > | |
| {collection.description} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <button | |
| type="button" | |
| key={collection.id} | |
| onClick={() => onNavigate(collection.id)} | |
| className="user-guide-collection-card" | |
| style={{ | |
| backgroundColor: colors.background.white, | |
| border: border.default, | |
| borderRadius: border.radius, | |
| padding: spacing.lg, | |
| cursor: "pointer", | |
| width: "100%", | |
| }} | |
| > | |
| <div | |
| style={{ display: "flex", alignItems: "flex-start", gap: spacing.md }} | |
| > | |
| <IconComponent | |
| size={20} | |
| strokeWidth={1.5} | |
| color={colors.brand.primary} | |
| /> | |
| <div style={{ flex: 1 }}> | |
| <div | |
| style={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "space-between", | |
| marginBottom: spacing.xs, | |
| }} | |
| > | |
| <span | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| fontWeight: typography.fontWeight.semibold, | |
| color: colors.text.primary, | |
| }} | |
| > | |
| {collection.title} | |
| </span> | |
| <span | |
| style={{ | |
| fontSize: typography.fontSize.xs, | |
| color: colors.text.secondary, | |
| backgroundColor: colors.background.alt, | |
| padding: "2px 6px", | |
| borderRadius: border.radius, | |
| }} | |
| > | |
| {collection.articleCount} articles | |
| </span> | |
| </div> | |
| <span | |
| style={{ | |
| fontFamily: typography.fontFamily.sans, | |
| fontSize: typography.fontSize.base, | |
| color: colors.text.secondary, | |
| lineHeight: typography.lineHeight.normal, | |
| display: "block", | |
| }} | |
| > | |
| {collection.description} | |
| </span> | |
| </div> | |
| </div> | |
| </button> |
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/UserGuideLanding.jsx` around lines 73 -
137, The collection card uses a clickable div which is not keyboard-accessible;
replace the outer <div key={collection.id}> with a semantic interactive element
(preferably a <button type="button> or an <a> if it navigates) and wire the
existing onClick={() => onNavigate(collection.id)} to that element so keyboard
activation works, preserve styling currently applied to the div, and ensure the
element has an accessible name (e.g., aria-label or use the visible
collection.title) and proper focus styles; if you must keep a non-semantic
element, add tabIndex={0} and handle onKeyDown to call onNavigate(collection.id)
for Enter/Space, but using a <button> is the preferred fix.
| const [isOpen, setIsOpen] = useState(() => { | ||
| if (typeof window === "undefined") return false; | ||
| const saved = localStorage.getItem(SIDEBAR_STATE_KEY); | ||
| return saved === "true"; | ||
| }); |
There was a problem hiding this comment.
Guard localStorage access to avoid runtime crashes.
localStorage can throw in private mode or restricted environments, and the initializer runs during render. Please wrap read/write in try/catch with safe fallbacks.
🛠️ Proposed fix
- const [isOpen, setIsOpen] = useState(() => {
- if (typeof window === "undefined") return false;
- const saved = localStorage.getItem(SIDEBAR_STATE_KEY);
- return saved === "true";
- });
+ const [isOpen, setIsOpen] = useState(() => {
+ if (typeof window === "undefined") return false;
+ try {
+ const saved = localStorage.getItem(SIDEBAR_STATE_KEY);
+ return saved === "true";
+ } catch {
+ return false;
+ }
+ });
@@
- useEffect(() => {
- if (typeof window === "undefined") return;
- localStorage.setItem(SIDEBAR_STATE_KEY, String(isOpen));
- }, [isOpen]);
+ useEffect(() => {
+ if (typeof window === "undefined") return;
+ try {
+ localStorage.setItem(SIDEBAR_STATE_KEY, String(isOpen));
+ } catch {
+ // noop: storage unavailable
+ }
+ }, [isOpen]);Also applies to: 52-55
🤖 Prompt for AI Agents
In `@client/src/Components/v1/UserGuide/UserGuideSidebarContext.jsx` around lines
14 - 18, The initializer for isOpen and the places that read/write
SIDEBAR_STATE_KEY should guard against localStorage throwing by wrapping all
localStorage accesses in try/catch and using safe fallbacks; specifically,
update the useState initializer that reads
localStorage.getItem(SIDEBAR_STATE_KEY) to catch errors and return false if
access fails, and update the code paths that call
localStorage.setItem/removeItem (the setter logic around setIsOpen and any
effect or handler at lines ~52-55) to perform writes inside try/catch and
silently ignore or fallback when storage is unavailable. Ensure you reference
SIDEBAR_STATE_KEY, isOpen and setIsOpen when making these changes so behavior
remains the same when localStorage is available.
There was a problem hiding this comment.
AI Code Review by LlamaPReview
🎯 TL;DR & Recommendation
Recommendation: Approve with suggestions.
This PR adds a comprehensive in-app user guide with minor performance and robustness suggestions.
📄 Documentation Diagram
This diagram illustrates the new user guide sidebar interaction flow from opening to content navigation.
sequenceDiagram
participant U as User
participant SW as SidebarWrapper
participant CTX as UserGuideContext
participant CR as ContentRenderer
U->>SW: Open sidebar
SW->>CTX: Update open state
CTX->>SW: State updated
SW->>U: Render sidebar UI
U->>SW: Navigate to article
SW->>CR: Load article content
CR->>U: Display content
note over SW,CR: PR #35;3148 added user guide documentation system
U->>SW: Close sidebar
SW->>CTX: Update closed state
🌟 Strengths
- Solid implementation of a feature-rich documentation sidebar with search, theming, and persistent state.
- Well-structured component separation and clean integration into the existing app layout.
| Priority | File | Category | Impact Summary (≤12 words) | Anchors |
|---|---|---|---|---|
| P2 | UserGuide/ArticlePage.jsx | Performance | Redundant scroll listeners may cause double execution and minor performance hit. | — |
| P2 | UserGuide/SidebarWrapper.jsx | Robustness | localStorage without error handling could break state persistence in edge cases. | — |
| P2 | UserGuide/ContentRenderer.jsx | Security | Custom markdown parser vulnerable to ReDoS if exposed to malicious input. | — |
| P2 | UserGuide/UserGuideSidebarContext.jsx | Architecture | Global sidebar state introduces architectural coupling with layout CSS. | path:App.jsx, path:HomeLayout/index.jsx |
| P2 | UserGuide/index.jsx | Maintainability | Large new module requires careful maintenance and clear architectural boundaries. | path:App.jsx, path:HomeLayout/index.jsx |
💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.
| window.addEventListener("scroll", handleScroll, { passive: true }); | ||
| document.addEventListener("scroll", handleScroll, { passive: true, capture: true }); |
There was a problem hiding this comment.
P2 | Confidence: High
Speculative: The scroll event listener is attached to both window and document with the same handler. This is redundant and may cause the handler to be called twice for a single scroll event, leading to unnecessary function executions. Additionally, using capture: true on the document listener is unnecessary for scroll events and adds complexity. This could cause minor performance degradation, especially on low-end devices or with many active articles.
| window.addEventListener("scroll", handleScroll, { passive: true }); | |
| document.addEventListener("scroll", handleScroll, { passive: true, capture: true }); | |
| window.addEventListener("scroll", handleScroll, { passive: true }); |
| const [isOpen, setIsOpen] = useState(() => { | ||
| if (typeof window === "undefined") return false; | ||
| const saved = localStorage.getItem(SIDEBAR_STATE_KEY); | ||
| return saved === "true"; | ||
| }); |
There was a problem hiding this comment.
P2 | Confidence: High
The sidebar state is persisted in localStorage and managed via React context provided at the app's root. The related_context shows integration in src/App.jsx (UserGuideSidebarProvider) and src/Components/v1/Layouts/HomeLayout/index.jsx (UserGuideSidebar). This creates a global, persistent UI state. The architectural impact is significant: it introduces a fixed-position, resizable sidebar that manipulates global CSS variables (--help-sidebar-padding) and body classes. This design could conflict with other global layout manipulations in the codebase. The ripple effect is that any component relying on the home-content-wrapper class (which gets a dynamic right margin) is now implicitly coupled to the sidebar's state.
| import SidebarWrapper from "./SidebarWrapper"; | ||
| import { | ||
| UserGuideSidebarProvider, | ||
| useUserGuideSidebarContext, | ||
| TAB_BAR_WIDTH, | ||
| DEFAULT_CONTENT_WIDTH, | ||
| } from "./UserGuideSidebarContext"; |
There was a problem hiding this comment.
P2 | Confidence: High
The new UserGuide component directory is a large, self-contained feature module (10+ files, ~5000 lines). This is generally good for separation of concerns. However, the index.jsx exports both a default component (UserGuideSidebar) and named exports (provider, context hook, constants). The related_context shows the provider is added to src/App.jsx and the component to HomeLayout/index.jsx. This creates a clear dependency chain. The maintainability concern is the sheer size and complexity of the module introduced in a single PR. Future changes to documentation structure, theming, or sidebar behavior will be concentrated here. Ensure there are clear boundaries between the presentation layer (components), content layer (content/), and state layer (context) as the feature evolves.
|
|
||
| // Restore state from localStorage on mount | ||
| useEffect(() => { | ||
| const savedState = localStorage.getItem(STORAGE_KEY); |
There was a problem hiding this comment.
P2 | Confidence: Medium
Speculative: The code uses localStorage without error handling. In environments where localStorage is unavailable (e.g., some privacy modes, strict CSP) or throws errors (e.g., quota exceeded), these uncaught exceptions could break the sidebar's state persistence and navigation history. This could lead to a degraded user experience where the sidebar fails to remember its open/closed state or navigation position.
| const savedState = localStorage.getItem(STORAGE_KEY); | |
| let savedState = null; | |
| try { | |
| savedState = localStorage.getItem(STORAGE_KEY); | |
| } catch (error) { | |
| console.warn('Failed to read from localStorage:', error); | |
| } | |
| // Proceed with savedState, which may be null |
| let key = 0; | ||
|
|
||
| // Supports: **bold**, `code`, [[link text]](collection/article) | ||
| const combinedRegex = /\*\*([^*]+)\*\*|`([^`]+)`|\[\[([^\]]+)\]\]\(([^)]+)\)/g; |
There was a problem hiding this comment.
P2 | Confidence: Medium
Speculative: The custom markdown parser uses a regular expression to identify and transform text patterns (bold, inline code, article links). This approach is vulnerable to ReDoS (Regular Expression Denial of Service) if a maliciously crafted article content contains nested or malformed patterns (e.g., many consecutive asterisks). While the risk is low as content is controlled internally, it's a potential attack vector if the content ingestion pipeline is ever exposed to user-generated input. A more robust parser or a sanitization library should be considered.
| const combinedRegex = /\*\*([^*]+)\*\*|`([^`]+)`|\[\[([^\]]+)\]\]\(([^)]+)\)/g; | |
| const MAX_INLINE_LENGTH = 1000; // Define a reasonable limit | |
| const parseMarkdown = (text) => { | |
| if (text.length > MAX_INLINE_LENGTH) { | |
| // Truncate or return plain text for safety | |
| return text.substring(0, MAX_INLINE_LENGTH); | |
| } | |
| // ... existing parsing logic | |
| }; |
Add three new documentation articles to the user guide: - Docker monitors: Setup and configuration for container monitoring - Bulk import monitors: CSV import guide for creating multiple monitors - Account settings: Profile, password, and account management Updates article counts in collections accordingly.
Summary
Features
Changes
src/Components/v1/UserGuide/- New user guide component directorysrc/Components/v1/Layouts/HomeLayout/index.jsx- Integrate user guide sidebarsrc/Components/v1/Sidebar/index.jsx- Add background color and z-index for proper layeringsrc/App.jsx- Add user guide context providerSummary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.