Skip to content

Feature 198 custom overlay themes#201

Open
TomHacker69 wants to merge 2 commits into
Devnil434:mainfrom
TomHacker69:feature-198-custom-overlay-themes
Open

Feature 198 custom overlay themes#201
TomHacker69 wants to merge 2 commits into
Devnil434:mainfrom
TomHacker69:feature-198-custom-overlay-themes

Conversation

@TomHacker69

@TomHacker69 TomHacker69 commented Jul 20, 2026

Copy link
Copy Markdown

#198

Summary by CodeRabbit

  • New Features

    • Added overlay theme selection with built-in presets and custom theme creation.
    • Added customization controls for colors, opacity, borders, text sizing, trails, contrast, and motion.
    • Custom themes can be saved, selected, previewed, and deleted.
    • Added dashboard tabs for switching between Cameras and other available features.
    • Improved API organization and documentation for alerts, ingestion, snapshots, feedback, and themes.
  • Bug Fixes

    • Added validation and clear not-found handling for theme operations.

- Backend: auto-discover routers from apps/backend/routes/
- Frontend: Dashboard.jsx dynamically loads feature components from features/
- Existing routes updated with prefixes for auto-discovery
- Backend: theme CRUD API with preset and custom theme support
- Frontend: ThemeSelector, ThemeCustomizer, and theme utilities
- Feature tab registered in Dashboard via dynamic feature discovery
- Added integration tests for theme API
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The backend now autodiscovers prefixed feature routers and exposes a theme management API. The dashboard adds theme presets, custom-theme persistence and editing, plus dynamically loaded feature tabs with an Overlay Themes view.

Changes

Theme platform and dashboard integration

Layer / File(s) Summary
Aggregated route discovery
apps/backend/main.py, apps/backend/routes/__init__.py, apps/backend/routes/alerts.py, apps/backend/routes/feedback.py, apps/backend/routes/ingest.py, apps/backend/routes/snapshot.py
Feature routers are autodiscovered, assigned route prefixes and API tags, and mounted through one aggregated router.
Backend theme API
apps/backend/routes/themes.py, tests/integration/test_themes.py
Preset and custom themes are exposed through list, retrieve, save, and delete endpoints backed by JSON persistence and integration tests.
Client-side theme management
apps/dashboard/src/utils/themes.js, apps/dashboard/src/components/ThemeSelector.jsx, apps/dashboard/src/components/ThemeCustomizer.jsx, apps/dashboard/src/features/themes.jsx
Theme presets, localStorage persistence, theme selection, custom-theme creation, and saved-theme previews are added.
Dashboard feature-tab integration
apps/dashboard/src/pages/Dashboard.jsx
Feature modules are loaded dynamically, rendered as tabs, and separated from the conditional camera view and track sidebar.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Dashboard
  participant ViteModuleLoader
  participant themes.jsx
  participant ThemeSelector

  Dashboard->>ViteModuleLoader: import.meta.glob("./features/*.jsx")
  ViteModuleLoader->>themes.jsx: load feature module
  themes.jsx->>ThemeSelector: export ThemeSelector and label
  Dashboard->>ThemeSelector: render selected feature component
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding custom overlay themes for feature 198.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Warning

⚠️ This pull request shows signs of AI-generated slop (defensive_cruft, description_diff_mismatch). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/dashboard/src/components/ThemeCustomizer.jsx (1)

55-55: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Avoid reading from localStorage synchronously during every render.

Reading from localStorage directly in the component body means a blocking I/O call is executed on every re-render (e.g., every keystroke in the theme name or drag of a slider).

Consider reading it once into a state variable, or if you need it to stay synchronized across tabs, use React's useSyncExternalStore or an effect that syncs it to 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 `@apps/dashboard/src/components/ThemeCustomizer.jsx` at line 55, Update
ThemeCustomizer’s custom theme loading around getCustomThemes so localStorage is
not read synchronously during every render. Initialize and maintain the themes
through React state, using an appropriate effect or subscription only if
cross-tab synchronization is required, while preserving the existing custom
theme behavior.
apps/backend/routes/themes.py (1)

144-151: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer model_dump() over manual dictionary construction.

You can use Pydantic's built-in serialization to avoid manually mapping each field to a dictionary.

♻️ Proposed fix
-    customs[body.name] = {
-        "name": body.name,
-        "boundingBoxColors": body.boundingBoxColors,
-        "label": body.label,
-        "confidence": body.confidence,
-        "trackingTrails": body.trackingTrails,
-        "accessibility": body.accessibility,
-    }
+    customs[body.name] = body.model_dump()
🤖 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 `@apps/backend/routes/themes.py` around lines 144 - 151, Replace the manual
dictionary construction assigned in the customs update with body.model_dump(),
preserving the existing key and serialized field values while relying on
Pydantic’s built-in serialization.
🤖 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 `@apps/backend/routes/themes.py`:
- Around line 104-118: Update _load_custom_themes and _save_custom_themes to
synchronize all custom-theme file access with a shared threading.Lock, including
the existence check, read, and write operations. Replace _load_custom_themes’
blanket exception handling with specific handling only for the intended
missing-file case, allowing JSON or I/O corruption errors to propagate instead
of returning {} and risking data loss.

In `@apps/dashboard/src/features/themes.jsx`:
- Around line 4-6: Update the default export in the themes feature module to be
a combined component that renders both ThemeSelector and ThemeCustomizer, while
preserving their existing named exports and the label value.

In `@apps/dashboard/src/pages/Dashboard.jsx`:
- Around line 31-34: Rename the activeComponent variable returned by the useMemo
in apps/dashboard/src/pages/Dashboard.jsx lines 31-34 to ActiveComponent, and
update its render usage at lines 92-92 to conditionally render <ActiveComponent
/>. No other changes are needed.

---

Nitpick comments:
In `@apps/backend/routes/themes.py`:
- Around line 144-151: Replace the manual dictionary construction assigned in
the customs update with body.model_dump(), preserving the existing key and
serialized field values while relying on Pydantic’s built-in serialization.

In `@apps/dashboard/src/components/ThemeCustomizer.jsx`:
- Line 55: Update ThemeCustomizer’s custom theme loading around getCustomThemes
so localStorage is not read synchronously during every render. Initialize and
maintain the themes through React state, using an appropriate effect or
subscription only if cross-tab synchronization is required, while preserving the
existing custom theme behavior.
🪄 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

Run ID: 070180b0-01aa-4e0f-8ae3-971e3d3562d0

📥 Commits

Reviewing files that changed from the base of the PR and between 2291373 and 57bd0eb.

📒 Files selected for processing (13)
  • apps/backend/main.py
  • apps/backend/routes/__init__.py
  • apps/backend/routes/alerts.py
  • apps/backend/routes/feedback.py
  • apps/backend/routes/ingest.py
  • apps/backend/routes/snapshot.py
  • apps/backend/routes/themes.py
  • apps/dashboard/src/components/ThemeCustomizer.jsx
  • apps/dashboard/src/components/ThemeSelector.jsx
  • apps/dashboard/src/features/themes.jsx
  • apps/dashboard/src/pages/Dashboard.jsx
  • apps/dashboard/src/utils/themes.js
  • tests/integration/test_themes.py

Comment on lines +104 to +118
def _load_custom_themes() -> dict:
if not os.path.exists(THEMES_FILE):
return {}
try:
with open(THEMES_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return {}


def _save_custom_themes(themes: dict):
os.makedirs(os.path.dirname(THEMES_FILE), exist_ok=True)
with open(THEMES_FILE, "w", encoding="utf-8") as f:
json.dump(themes, f, indent=2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Race condition and data loss hazard during concurrent file operations.

The endpoints for reading and writing custom themes are standard synchronous def functions, which FastAPI runs in a threadpool. Concurrent requests to POST /themes or DELETE /themes will cause race conditions because the file reads and writes are not synchronized.

Furthermore, if a read occurs while a write is truncating the file, json.load(f) will raise an exception. Because _load_custom_themes catches all exceptions and silently returns {}, the subsequent save operation will overwrite the file with just the single new or remaining theme, permanently wiping out all other custom themes.

Please use a threading.Lock to synchronize file access and remove the blanket except Exception clause so that file corruption fails loudly rather than causing silent data loss.

🔒️ Proposed fix to synchronize file operations
 import json
 import os
+import threading
 
 from fastapi import APIRouter, HTTPException
 from pydantic import BaseModel, Field
 
 logger = logging.getLogger(__name__)
 
 router = APIRouter(prefix="/themes", tags=["themes"])
 
 THEME_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "config", "themes")
 THEMES_FILE = os.path.join(THEME_DIR, "custom_themes.json")
+THEMES_LOCK = threading.Lock()
 
 os.makedirs(THEME_DIR, exist_ok=True)

# ... (skip to _load_custom_themes) ...

 def _load_custom_themes() -> dict:
-    if not os.path.exists(THEMES_FILE):
-        return {}
-    try:
+    with THEMES_LOCK:
+        if not os.path.exists(THEMES_FILE):
+            return {}
         with open(THEMES_FILE, "r", encoding="utf-8") as f:
             return json.load(f)
-    except Exception:
-        return {}
 
 
 def _save_custom_themes(themes: dict):
     os.makedirs(os.path.dirname(THEMES_FILE), exist_ok=True)
-    with open(THEMES_FILE, "w", encoding="utf-8") as f:
-        json.dump(themes, f, indent=2)
+    with THEMES_LOCK:
+        with open(THEMES_FILE, "w", encoding="utf-8") as f:
+            json.dump(themes, f, indent=2)
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 107-107: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(THEMES_FILE, "r", encoding="utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)


[warning] 115-115: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(THEMES_FILE, "w", encoding="utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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 `@apps/backend/routes/themes.py` around lines 104 - 118, Update
_load_custom_themes and _save_custom_themes to synchronize all custom-theme file
access with a shared threading.Lock, including the existence check, read, and
write operations. Replace _load_custom_themes’ blanket exception handling with
specific handling only for the intended missing-file case, allowing JSON or I/O
corruption errors to propagate instead of returning {} and risking data loss.

Comment on lines +4 to +6
export { ThemeSelector, ThemeCustomizer }
export default ThemeSelector
export const label = "Overlay Themes"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Render both ThemeSelector and ThemeCustomizer in the feature tab.

The Dashboard component renders the default export of this feature module. Currently, ThemeSelector is the default export, meaning ThemeCustomizer is orphaned and inaccessible in the UI.

To expose both the selector and the customizer, export a combined component as the default.

🐛 Proposed fix to render both components
-export { ThemeSelector, ThemeCustomizer }
-export default ThemeSelector
-export const label = "Overlay Themes"
+export { ThemeSelector, ThemeCustomizer };
+
+export default function ThemesFeature() {
+  return (
+    <div className="space-y-6">
+      <ThemeSelector />
+      <ThemeCustomizer />
+    </div>
+  );
+}
+
+export const label = "Overlay Themes";
📝 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.

Suggested change
export { ThemeSelector, ThemeCustomizer }
export default ThemeSelector
export const label = "Overlay Themes"
export { ThemeSelector, ThemeCustomizer };
export default function ThemesFeature() {
return (
<div className="space-y-6">
<ThemeSelector />
<ThemeCustomizer />
</div>
);
}
export const label = "Overlay Themes";
🤖 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 `@apps/dashboard/src/features/themes.jsx` around lines 4 - 6, Update the
default export in the themes feature module to be a combined component that
renders both ThemeSelector and ThemeCustomizer, while preserving their existing
named exports and the label value.

Comment on lines +31 to +34
const activeComponent = useMemo(() => {
const tab = featureTabs.find((t) => t.id === view);
return tab?.component || null;
}, [view, featureTabs]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Capitalize activeComponent to fix JSX component rendering.

In React, JSX requires user-defined component variables to begin with an uppercase letter. Using <activeComponent /> causes React to output an invalid literal HTML tag (<activecomponent>) instead of rendering the feature module.

  • apps/dashboard/src/pages/Dashboard.jsx#L31-L34: rename the state variable to ActiveComponent.
  • apps/dashboard/src/pages/Dashboard.jsx#L92-L92: update the rendering syntax to {ActiveComponent && <ActiveComponent />}.
📍 Affects 1 file
  • apps/dashboard/src/pages/Dashboard.jsx#L31-L34 (this comment)
  • apps/dashboard/src/pages/Dashboard.jsx#L92-L92
🤖 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 `@apps/dashboard/src/pages/Dashboard.jsx` around lines 31 - 34, Rename the
activeComponent variable returned by the useMemo in
apps/dashboard/src/pages/Dashboard.jsx lines 31-34 to ActiveComponent, and
update its render usage at lines 92-92 to conditionally render <ActiveComponent
/>. No other changes are needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant