Skip to content

Conversation

@RohanExploit
Copy link
Owner

@RohanExploit RohanExploit commented Feb 11, 2026

Implemented new detection features inspired by western public grievance apps.

  • Added 'Public Facilities' (benches, parks) and 'Construction Safety' (helmets, hazards) detection using CLIP.
  • Integrated missing frontend components for 'Traffic Sign' and 'Abandoned Vehicle'.
  • Exposed previously implemented but hidden detectors: 'Accessibility', 'Water Leak', 'Crowd', 'Waste'.
  • Updated UI to display these new options in relevant categories.
  • Backend endpoints are optimized using existing CLIP infrastructure.
  • Deployment configuration remains compatible with Netlify and Render.

PR created automatically by Jules for task 13211897059197172537 started by @RohanExploit


Summary by cubic

Adds live CLIP detectors for Public Facilities, Construction Safety, Traffic Signs, and Abandoned Vehicles, with new routes and home entries. Simplifies Netlify config and relaxes security headers to fix deployment.

  • New Features

    • Backend: Added CLIP services and endpoints for /api/detect-public-facilities and /api/detect-construction-safety; added basic import test.
    • Frontend: Added detector components and routes for Traffic Sign, Abandoned Vehicle, Public Facilities, and Construction Safety; exposed Accessibility, Water Leak, Crowd, Waste, Public Facilities, and Construction in Home.
  • Bug Fixes

    • Removed frontend/netlify.toml to avoid config conflicts; updated root netlify.toml (X-Frame-Options=SAMEORIGIN).
    • Added frontend/public/_headers as a fallback to ensure consistent security headers during Netlify deploys.

Written for commit 65b517c. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Live camera-based detectors added: Public Facilities, Construction Safety, Traffic Sign, Abandoned Vehicle, Accessibility, Water Leak, Crowd, and Waste — with real-time overlays and Start/Stop detection.
    • New routes and home navigation entries to access each detector.
  • Tests

    • Basic import validation script added for the new detection endpoints and services.

…ed Vehicle detectors

- Added `detect_public_facilities_clip` and `detect_construction_safety_clip` to backend HF service.
- Added `/api/detect-public-facilities` and `/api/detect-construction-safety` endpoints.
- Created frontend components: `TrafficSignDetector`, `AbandonedVehicleDetector`, `PublicFacilitiesDetector`, `ConstructionSafetyDetector`.
- Updated `App.jsx` to route new detectors and expose existing ones (`Accessibility`, `WaterLeak`, `Crowd`, `Waste`).
- Updated `Home.jsx` to include buttons for new categories in "Road & Traffic", "Environment & Safety", and "Management".

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 11, 2026 10:11
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@netlify
Copy link

netlify bot commented Feb 11, 2026

Deploy Preview for fixmybharat failed. Why did it fail? →

Name Link
🔨 Latest commit 65b517c
🔍 Latest deploy log https://app.netlify.com/projects/fixmybharat/deploys/698c5b76d7387d000875ca6d

@github-actions
Copy link

🙏 Thank you for your contribution, @RohanExploit!

PR Details:

Quality Checklist:
Please ensure your PR meets the following criteria:

  • Code follows the project's style guidelines
  • Self-review of code completed
  • Code is commented where necessary
  • Documentation updated (if applicable)
  • No new warnings generated
  • Tests added/updated (if applicable)
  • All tests passing locally
  • No breaking changes to existing functionality

Review Process:

  1. Automated checks will run on your code
  2. A maintainer will review your changes
  3. Address any requested changes promptly
  4. Once approved, your PR will be merged! 🎉

Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken.

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds two backend CLIP detectors (public facilities, construction safety) with matching API endpoints, and four new frontend live-camera detector components (Traffic Sign, Abandoned Vehicle, Public Facilities, Construction Safety) plus routing and Home navigation entries; also adds a simple import test and removes a Netlify redirect line.

Changes

Cohort / File(s) Summary
Backend CLIP Detectors
backend/hf_api_service.py, backend/routers/detection.py
Added detect_public_facilities_clip and detect_construction_safety_clip and corresponding POST endpoints /api/detect-public-facilities and /api/detect-construction-safety, following the existing CLIP detection pattern and error handling.
Frontend Detector Components
frontend/src/TrafficSignDetector.jsx, frontend/src/AbandonedVehicleDetector.jsx, frontend/src/PublicFacilitiesDetector.jsx, frontend/src/ConstructionSafetyDetector.jsx
Four new React components implementing live camera capture, periodic frame upload (every ~2s) to respective backend endpoints, and canvas overlay rendering of detection results and confidences.
Frontend Routing & Navigation
frontend/src/App.jsx, frontend/src/views/Home.jsx
Added lazy imports and routes for the new detectors; extended validViews and added Home navigation items for accessibility, construction, and public facilities.
Testing
test_detection_import.py
New import-validation test that attempts to import the two new endpoints and two new backend detector functions.
Static config
frontend/public/_redirects
Removed Netlify catch-all redirect line (/* /index.html 200).

Sequence Diagram

sequenceDiagram
    actor User
    participant Browser
    participant VideoCanvas as "Video + Canvas"
    participant API as "Backend API"
    participant CLIP as "HF CLIP Service"

    User->>Browser: Click Start Live Detection
    Browser->>VideoCanvas: request camera & start stream
    loop every ~2s (while live)
        Browser->>VideoCanvas: capture frame -> JPEG blob
        Browser->>API: POST /api/detect-[type] (form-data image)
        API->>CLIP: detect_[type]_clip(image)
        CLIP-->>API: detections JSON
        API-->>Browser: detections JSON
        Browser->>VideoCanvas: draw detection overlays
    end
    User->>Browser: Click Stop Detection
    Browser->>VideoCanvas: stop stream & cleanup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

ECWoC26

Poem

🐰 I hopped through code with nimble paws,

New eyes to spot what safety draws.
Cameras hum and CLIP explores,
Signs and sites and public doors.
A rabbit cheers—detection roars!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes: adding new detector features and updating the UI to support public grievance functionality, which aligns with the substantial backend (new endpoints/services) and frontend changes (multiple new components and UI updates).
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/new-detectors-ui-13211897059197172537

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@backend/routers/detection.py`:
- Around line 442-472: The new CLIP endpoints
(detect_public_facilities_endpoint, detect_construction_safety_endpoint,
detect_traffic_sign_endpoint, detect_abandoned_vehicle_endpoint) are reading raw
bytes via await image.read() and bypassing validation; update each to call
process_uploaded_image(image) (the same helper used by
detect_illegal_parking_endpoint and detect_street_light_endpoint) to perform
MIME/type, size and format checks and return validated/optimized bytes, then
pass those bytes into detect_public_facilities_clip /
detect_construction_safety_clip / detect_traffic_sign_clip /
detect_abandoned_vehicle_clip respectively; preserve the existing try/except
error handling pattern (catch invalid upload -> log and raise HTTPException 400,
other errors -> log and raise HTTPException 500).

In `@frontend/src/AbandonedVehicleDetector.jsx`:
- Around line 20-23: When assigning the camera stream returned from
navigator.mediaDevices.getUserMedia, handle the case where videoRef.current is
null to avoid leaking tracks: if videoRef.current is truthy set
videoRef.current.srcObject = stream, otherwise immediately stop all tracks on
the stream (stream.getTracks().forEach(t => t.stop())). Also ensure the
component's cleanup/unmount logic (the effect that calls getUserMedia) stops the
stream if it's still active so no tracks remain running; update the code around
videoRef and the getUserMedia promise to stop tracks in the null-branch and
during cleanup.
- Around line 46-60: The labels are all drawn at fixed coordinates inside the
detections.forEach loop, causing overlap; change the loop to use the item index
(e.g., detections.forEach((det, i) => { ... })) and compute a per-item y offset
like y = 30 + (i * 35) (mirror ConstructionSafetyDetector's approach) so each
label's y position is incremented; keep using label, textWidth, and the same
fillRect/fillText logic but replace the constant y with the indexed y to avoid
overlap.

In `@frontend/src/TrafficSignDetector.jsx`:
- Around line 1-189: The component files (TrafficSignDetector,
PublicFacilitiesDetector, ConstructionSafetyDetector, AbandonedVehicleDetector)
duplicate camera lifecycle, frame capture, canvas overlay and UI; extract a
reusable hook and/or wrapper component to remove duplication: create a hook
named useDetectorCamera that encapsulates startCamera, stopCamera, detectFrame,
canvas/video refs, interval management and error state, and a generic
LiveDetector component that accepts props (endpointUrl, overlayColor, title,
description, drawDetectionsOverride) to handle the UI and call drawDetections;
refactor TrafficSignDetector to use useDetectorCamera/LiveDetector and pass its
specific API route (`/api/detect-traffic-sign`), color and strings, and keep any
detector-specific drawing logic via a drawDetections or drawLabel prop so all
four detectors reuse the shared logic.

In `@frontend/src/views/Home.jsx`:
- Line 87: Replace the hardcoded English labels with i18n calls so translations
are used: update the items that currently use "Accessibility", "Construction",
and "Public Facilities" (the objects with id values 'accessibility',
'construction', 'public_facilities' in the array inside Home.jsx) to call the
translation function (t) with appropriate keys such as
t('home.issues.accessibility'), t('home.issues.construction'),
t('home.issues.public_facilities') respectively; also replace the other
hardcoded labels noted nearby (the items around those entries) to use the
existing t('home.issues.…') keys so all menu/issue labels are consistently
localized.
🧹 Nitpick comments (6)
frontend/src/TrafficSignDetector.jsx (2)

118-136: Stale closure: detectFrame captured once but invoked repeatedly by setInterval.

detectFrame is defined during render and closes over the current isDetecting value. Because it's passed to setInterval inside the effect, it never picks up subsequent state changes. It works here only because isDetecting is true when the interval starts and the guard at line 78 happens to pass. However, if additional state (e.g. a detection-result accumulator) is added later, this pattern will silently break.

Additionally, startCamera() is called without await, so the first few setInterval ticks fire before the camera stream is ready. The readyState guard (line 82) saves it, but a cleaner approach is to start the interval after the video loadeddata event.

Sketch: use refs for the detection loop and wait for camera readiness
+    const isDetectingRef = useRef(false);
+    const intervalRef = useRef(null);
+
     useEffect(() => {
-        let interval;
+        isDetectingRef.current = isDetecting;
         if (isDetecting) {
-            startCamera();
-            interval = setInterval(detectFrame, 2000);
+            startCamera().then(() => {
+                intervalRef.current = setInterval(() => {
+                    if (isDetectingRef.current) detectFrame();
+                }, 2000);
+            });
         } else {
             stopCamera();
-            if (interval) clearInterval(interval);
+            if (intervalRef.current) clearInterval(intervalRef.current);
             ...
         }
         return () => {
             stopCamera();
-            if (interval) clearInterval(interval);
+            if (intervalRef.current) clearInterval(intervalRef.current);
         };
     }, [isDetecting]);

77-116: Concurrent API calls: toBlob callback can overlap with the next interval tick.

detectFrame fires every 2 seconds, but the async fetch inside toBlob may take longer. Multiple in-flight requests will race to draw on the same canvas context, causing visual flickering. Consider guarding with a "request-in-flight" ref to skip a frame when the previous one hasn't completed.

backend/routers/detection.py (1)

448-448: Consider chaining exceptions with raise … from e.

Per Ruff B904, raise HTTPException(…) inside except blocks should use raise … from e (or from None) to preserve the exception chain for debugging. This matches best practice, though the existing endpoints in this file follow the same pattern.

Also applies to: 456-456, 464-464, 472-472

test_detection_import.py (1)

1-28: Consider converting to a proper pytest test.

This script uses print + sys.exit(1) instead of assertions, and manipulates sys.path manually. If the project uses pytest (which the existing backend/tests/test_new_detectors.py suggests), this would integrate better as a standard test:

Sketch: pytest-style import test
-import sys
-import os
-
-# Add the current directory to sys.path
-sys.path.append(os.getcwd())
-
-try:
-    from backend.routers.detection import (
-        detect_public_facilities_endpoint,
-        detect_construction_safety_endpoint
-    )
-    print("Successfully imported new endpoints from backend.routers.detection")
-except ImportError as e:
-    print(f"Failed to import new endpoints: {e}")
-    sys.exit(1)
-except Exception as e:
-    print(f"An error occurred during import: {e}")
-    sys.exit(1)
-
-try:
-    from backend.hf_api_service import (
-        detect_public_facilities_clip,
-        detect_construction_safety_clip
-    )
-    print("Successfully imported new service functions from backend.hf_api_service")
-except ImportError as e:
-    print(f"Failed to import new service functions: {e}")
-    sys.exit(1)
+from backend.routers.detection import (
+    detect_public_facilities_endpoint,
+    detect_construction_safety_endpoint,
+)
+from backend.hf_api_service import (
+    detect_public_facilities_clip,
+    detect_construction_safety_clip,
+)
+
+
+def test_new_endpoints_are_importable():
+    assert callable(detect_public_facilities_endpoint)
+    assert callable(detect_construction_safety_endpoint)
+
+
+def test_new_clip_functions_are_importable():
+    assert callable(detect_public_facilities_clip)
+    assert callable(detect_construction_safety_clip)

Also note: the second try block (line 20) lacks the broad except Exception handler that the first block has, making error handling asymmetric.

frontend/src/AbandonedVehicleDetector.jsx (2)

63-102: No guard against overlapping in-flight requests.

setInterval fires every 2 seconds regardless of whether the previous fetch has completed. Under slow network conditions, requests can pile up and race, potentially drawing stale detections over newer ones. Consider using a flag or replacing setInterval with a chained setTimeout that schedules the next call only after the previous one resolves.


104-122: Stale closure risk hidden by the eslint-disable.

setInterval(detectFrame, 2000) captures detectFrame from a single render. The current code works because the interval is torn down whenever isDetecting changes, but any future state/prop added to detectFrame would silently go stale. Wrapping detectFrame (and the other helpers) in useCallback, or switching to a setTimeout-chain pattern inside a ref, would make this more robust and allow removing the lint suppression.

Also, line 111 (if (interval) clearInterval(interval)) in the else branch is dead code — interval is only assigned in the if branch.

Comment on lines +442 to +472
@router.post("/api/detect-public-facilities")
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

try:
client = get_http_client(request)
detections = await detect_public_facilities_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Public facilities detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")

@router.post("/api/detect-construction-safety")
async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

try:
client = get_http_client(request)
detections = await detect_construction_safety_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Construction safety detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

New endpoints skip image validation that other CLIP endpoints perform.

The existing CLIP-based endpoints (e.g., detect_illegal_parking_endpoint at line 122, detect_street_light_endpoint at line 135) call process_uploaded_image(image) which validates and optimizes the upload. The new endpoints (and the traffic-sign / abandoned-vehicle ones added in this PR) read raw bytes with await image.read(), bypassing MIME-type checks, size limits, and format validation.

This creates an inconsistency and means these endpoints will accept arbitrarily large or malformed payloads that would be rejected elsewhere.

Proposed fix: use process_uploaded_image consistently
 `@router.post`("/api/detect-public-facilities")
 async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
-    try:
-        image_bytes = await image.read()
-    except Exception as e:
-        logger.error(f"Invalid image file: {e}", exc_info=True)
-        raise HTTPException(status_code=400, detail="Invalid image file")
+    _, image_bytes = await process_uploaded_image(image)
 
     try:
         client = get_http_client(request)
         detections = await detect_public_facilities_clip(image_bytes, client=client)
         return {"detections": detections}
     except Exception as e:
         logger.error(f"Public facilities detection error: {e}", exc_info=True)
         raise HTTPException(status_code=500, detail="Internal server error")
 
 `@router.post`("/api/detect-construction-safety")
 async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
-    try:
-        image_bytes = await image.read()
-    except Exception as e:
-        logger.error(f"Invalid image file: {e}", exc_info=True)
-        raise HTTPException(status_code=400, detail="Invalid image file")
+    _, image_bytes = await process_uploaded_image(image)
 
     try:
         client = get_http_client(request)
         detections = await detect_construction_safety_clip(image_bytes, client=client)
         return {"detections": detections}
     except Exception as e:
         logger.error(f"Construction safety detection error: {e}", exc_info=True)
         raise HTTPException(status_code=500, detail="Internal server error")

The same issue applies to detect_traffic_sign_endpoint (line 409) and detect_abandoned_vehicle_endpoint (line 426).

📝 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
@router.post("/api/detect-public-facilities")
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")
try:
client = get_http_client(request)
detections = await detect_public_facilities_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Public facilities detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/api/detect-construction-safety")
async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")
try:
client = get_http_client(request)
detections = await detect_construction_safety_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Construction safety detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
`@router.post`("/api/detect-public-facilities")
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
_, image_bytes = await process_uploaded_image(image)
try:
client = get_http_client(request)
detections = await detect_public_facilities_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Public facilities detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
`@router.post`("/api/detect-construction-safety")
async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
_, image_bytes = await process_uploaded_image(image)
try:
client = get_http_client(request)
detections = await detect_construction_safety_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Construction safety detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
🧰 Tools
🪛 Ruff (0.15.0)

[warning] 443-443: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


[warning] 448-448: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 453-453: Consider moving this statement to an else block

(TRY300)


[warning] 456-456: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 459-459: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


[warning] 464-464: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 469-469: Consider moving this statement to an else block

(TRY300)


[warning] 472-472: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In `@backend/routers/detection.py` around lines 442 - 472, The new CLIP endpoints
(detect_public_facilities_endpoint, detect_construction_safety_endpoint,
detect_traffic_sign_endpoint, detect_abandoned_vehicle_endpoint) are reading raw
bytes via await image.read() and bypassing validation; update each to call
process_uploaded_image(image) (the same helper used by
detect_illegal_parking_endpoint and detect_street_light_endpoint) to perform
MIME/type, size and format checks and return validated/optimized bytes, then
pass those bytes into detect_public_facilities_clip /
detect_construction_safety_clip / detect_traffic_sign_clip /
detect_abandoned_vehicle_clip respectively; preserve the existing try/except
error handling pattern (catch invalid upload -> log and raise HTTPException 400,
other errors -> log and raise HTTPException 500).

Comment on lines +20 to +23
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential camera stream leak if ref is null.

If the component unmounts between the getUserMedia resolution and line 22, videoRef.current will be null and the acquired stream's tracks will never be stopped. Consider stopping the stream in the else path.

Proposed fix
-            if (videoRef.current) {
-                videoRef.current.srcObject = stream;
-            }
+            if (videoRef.current) {
+                videoRef.current.srcObject = stream;
+            } else {
+                stream.getTracks().forEach(track => track.stop());
+            }
📝 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
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
} else {
stream.getTracks().forEach(track => track.stop());
}
🤖 Prompt for AI Agents
In `@frontend/src/AbandonedVehicleDetector.jsx` around lines 20 - 23, When
assigning the camera stream returned from navigator.mediaDevices.getUserMedia,
handle the case where videoRef.current is null to avoid leaking tracks: if
videoRef.current is truthy set videoRef.current.srcObject = stream, otherwise
immediately stop all tracks on the stream (stream.getTracks().forEach(t =>
t.stop())). Also ensure the component's cleanup/unmount logic (the effect that
calls getUserMedia) stops the stream if it's still active so no tracks remain
running; update the code around videoRef and the getUserMedia promise to stop
tracks in the null-branch and during cleanup.

Comment on lines +46 to +60
detections.forEach(det => {
// Basic drawing logic
const label = `${det.label} ${(det.confidence * 100).toFixed(0)}%`;
const textWidth = context.measureText(label).width;

// Top left corner for label
const x = 10;
const y = 30;

context.fillStyle = 'rgba(0,0,0,0.5)';
context.fillRect(x, y - 20, textWidth + 10, 30);

context.fillStyle = '#FF0000';
context.fillText(label, x + 5, y);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: All detection labels are drawn at the same position and overlap.

Unlike ConstructionSafetyDetector which offsets each label with y = 30 + (i * 35), this component renders every detection at a fixed (10, 30). When multiple detections are returned, each label overwrites the previous one — only the last is visible.

Proposed fix
-        detections.forEach(det => {
-             // Basic drawing logic
-             const label = `${det.label} ${(det.confidence * 100).toFixed(0)}%`;
-             const textWidth = context.measureText(label).width;
-
-             // Top left corner for label
-             const x = 10;
-             const y = 30;
+        detections.forEach((det, i) => {
+             const label = `${det.label} ${(det.confidence * 100).toFixed(0)}%`;
+             const textWidth = context.measureText(label).width;
+
+             // Stack detections vertically
+             const x = 10;
+             const y = 30 + (i * 35);
📝 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
detections.forEach(det => {
// Basic drawing logic
const label = `${det.label} ${(det.confidence * 100).toFixed(0)}%`;
const textWidth = context.measureText(label).width;
// Top left corner for label
const x = 10;
const y = 30;
context.fillStyle = 'rgba(0,0,0,0.5)';
context.fillRect(x, y - 20, textWidth + 10, 30);
context.fillStyle = '#FF0000';
context.fillText(label, x + 5, y);
});
detections.forEach((det, i) => {
const label = `${det.label} ${(det.confidence * 100).toFixed(0)}%`;
const textWidth = context.measureText(label).width;
// Stack detections vertically
const x = 10;
const y = 30 + (i * 35);
context.fillStyle = 'rgba(0,0,0,0.5)';
context.fillRect(x, y - 20, textWidth + 10, 30);
context.fillStyle = '#FF0000';
context.fillText(label, x + 5, y);
});
🤖 Prompt for AI Agents
In `@frontend/src/AbandonedVehicleDetector.jsx` around lines 46 - 60, The labels
are all drawn at fixed coordinates inside the detections.forEach loop, causing
overlap; change the loop to use the item index (e.g., detections.forEach((det,
i) => { ... })) and compute a per-item y offset like y = 30 + (i * 35) (mirror
ConstructionSafetyDetector's approach) so each label's y position is
incremented; keep using label, textWidth, and the same fillRect/fillText logic
but replace the constant y with the indexed y to avoid overlap.

Comment on lines +1 to +189
import React, { useRef, useState, useEffect } from 'react';

const API_URL = import.meta.env.VITE_API_URL || '';

const TrafficSignDetector = ({ onBack }) => {
const videoRef = useRef(null);
const canvasRef = useRef(null);
const [isDetecting, setIsDetecting] = useState(false);
const [error, setError] = useState(null);

const startCamera = async () => {
setError(null);
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 640 },
height: { ideal: 480 }
}
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
}
} catch (err) {
setError("Could not access camera: " + err.message);
setIsDetecting(false);
}
};

const stopCamera = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = videoRef.current.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoRef.current.srcObject = null;
}
};

const drawDetections = (detections, context) => {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);

context.strokeStyle = '#00FF00'; // Green
context.lineWidth = 4;
context.font = 'bold 18px Arial';
context.fillStyle = '#00FF00';

detections.forEach(det => {
// CLIP often returns empty box [] if it's purely classification,
// but the backend format might ensure a box or we skip drawing box if empty.
// If the box is empty, we just show text at top left or something.
// Assuming unified format returns box, or we handle empty box.

let x1 = 10, y1 = 30, x2 = 10, y2 = 30;
if (det.box && det.box.length === 4) {
[x1, y1, x2, y2] = det.box;
context.strokeRect(x1, y1, x2 - x1, y2 - y1);
}

// Draw label background
const label = `${det.label} ${(det.confidence * 100).toFixed(0)}%`;
const textWidth = context.measureText(label).width;

if (det.box && det.box.length === 4) {
context.fillStyle = 'rgba(0,0,0,0.5)';
context.fillRect(x1, y1 > 20 ? y1 - 25 : y1, textWidth + 10, 25);
context.fillStyle = '#00FF00';
context.fillText(label, x1 + 5, y1 > 20 ? y1 - 7 : y1 + 18);
} else {
// Fallback for classification without box
context.fillStyle = 'rgba(0,0,0,0.5)';
context.fillRect(10, 10, textWidth + 10, 25);
context.fillStyle = '#00FF00';
context.fillText(label, 15, 30);
}
});
};

const detectFrame = async () => {
if (!videoRef.current || !canvasRef.current || !isDetecting) return;

const video = videoRef.current;

if (video.readyState !== 4) return;

const canvas = canvasRef.current;
const context = canvas.getContext('2d');

if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
}

context.drawImage(video, 0, 0, canvas.width, canvas.height);

canvas.toBlob(async (blob) => {
if (!blob) return;

const formData = new FormData();
formData.append('image', blob, 'frame.jpg');

try {
const response = await fetch(`${API_URL}/api/detect-traffic-sign`, {
method: 'POST',
body: formData
});

if (response.ok) {
const data = await response.json();
if (data.detections) {
drawDetections(data.detections, context);
}
}
} catch (err) {
console.error("Detection error:", err);
}
}, 'image/jpeg', 0.8);
};

useEffect(() => {
let interval;
if (isDetecting) {
startCamera();
interval = setInterval(detectFrame, 2000);
} else {
stopCamera();
if (interval) clearInterval(interval);
if (canvasRef.current) {
const ctx = canvasRef.current.getContext('2d');
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
}
}
return () => {
stopCamera();
if (interval) clearInterval(interval);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDetecting]);

return (
<div className="mt-6 flex flex-col items-center w-full">
<h2 className="text-xl font-semibold mb-4 text-center">Traffic Sign Detector</h2>

{error && <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">{error}</div>}

<div className="relative w-full max-w-md bg-black rounded-lg overflow-hidden shadow-lg mb-6">
<div className="relative">
<video
ref={videoRef}
autoPlay
playsInline
muted
className="w-full h-auto block"
style={{ opacity: isDetecting ? 1 : 0.5 }}
/>
<canvas
ref={canvasRef}
className="absolute top-0 left-0 w-full h-full pointer-events-none"
/>
{!isDetecting && (
<div className="absolute inset-0 flex items-center justify-center">
<p className="text-white font-medium bg-black bg-opacity-50 px-4 py-2 rounded">
Camera Paused
</p>
</div>
)}
</div>
</div>

<button
onClick={() => setIsDetecting(!isDetecting)}
className={`w-full max-w-md py-3 px-4 rounded-lg text-white font-medium shadow-md transition transform active:scale-95 ${isDetecting ? 'bg-red-600 hover:bg-red-700' : 'bg-blue-600 hover:bg-blue-700'}`}
>
{isDetecting ? 'Stop Detection' : 'Start Live Detection'}
</button>

<p className="text-sm text-gray-500 mt-2 text-center max-w-md">
Point camera at traffic signs to detect issues.
</p>

<button
onClick={onBack}
className="mt-6 text-gray-600 hover:text-gray-900 underline"
>
Back to Home
</button>
</div>
);
};

export default TrafficSignDetector;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Heavy code duplication across all detector components.

TrafficSignDetector, PublicFacilitiesDetector, ConstructionSafetyDetector, and AbandonedVehicleDetector share ~95% identical code (camera lifecycle, frame capture, canvas overlay, UI shell). The only differences are the API endpoint URL, overlay color, title/description strings, and minor draw logic.

Extract a shared useDetectorCamera hook (or a generic <LiveDetector> component) parameterized by endpoint, color, and title. This eliminates the maintenance burden of keeping four near-identical files in sync.

🧰 Tools
🪛 Biome (2.3.14)

[error] 33-33: This callback passed to forEach() iterable method should not return a value.

Either remove this return or remove the returned value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In `@frontend/src/TrafficSignDetector.jsx` around lines 1 - 189, The component
files (TrafficSignDetector, PublicFacilitiesDetector,
ConstructionSafetyDetector, AbandonedVehicleDetector) duplicate camera
lifecycle, frame capture, canvas overlay and UI; extract a reusable hook and/or
wrapper component to remove duplication: create a hook named useDetectorCamera
that encapsulates startCamera, stopCamera, detectFrame, canvas/video refs,
interval management and error state, and a generic LiveDetector component that
accepts props (endpointUrl, overlayColor, title, description,
drawDetectionsOverride) to handle the UI and call drawDetections; refactor
TrafficSignDetector to use useDetectorCamera/LiveDetector and pass its specific
API route (`/api/detect-traffic-sign`), color and strings, and keep any
detector-specific drawing logic via a drawDetections or drawLabel prop so all
four detectors reuse the shared logic.

{ id: 'streetlight', label: t('home.issues.darkStreet'), icon: <Lightbulb size={24} />, color: 'text-slate-600', bg: 'bg-slate-50' },
{ id: 'traffic-sign', label: t('home.issues.trafficSign'), icon: <Signpost size={24} />, color: 'text-yellow-600', bg: 'bg-yellow-50' },
{ id: 'abandoned-vehicle', label: t('home.issues.abandonedVehicle'), icon: <Car size={24} />, color: 'text-gray-600', bg: 'bg-gray-50' },
{ id: 'accessibility', label: "Accessibility", icon: <Accessibility size={24} />, color: 'text-blue-600', bg: 'bg-blue-50' },
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded labels bypass i18n.

The new items use hardcoded English strings ("Accessibility", "Construction", "Public Facilities") while adjacent items use t('home.issues.…'). This breaks translations for non-English locales.

This pattern also applies to previously added items (lines 100–103), so it may be a broader cleanup, but ideally new code should not add more debt.

Also applies to: 104-104, 111-111

🤖 Prompt for AI Agents
In `@frontend/src/views/Home.jsx` at line 87, Replace the hardcoded English labels
with i18n calls so translations are used: update the items that currently use
"Accessibility", "Construction", and "Public Facilities" (the objects with id
values 'accessibility', 'construction', 'public_facilities' in the array inside
Home.jsx) to call the translation function (t) with appropriate keys such as
t('home.issues.accessibility'), t('home.issues.construction'),
t('home.issues.public_facilities') respectively; also replace the other
hardcoded labels noted nearby (the items around those entries) to use the
existing t('home.issues.…') keys so all menu/issue labels are consistently
localized.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

13 issues found across 9 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="test_detection_import.py">

<violation number="1" location="test_detection_import.py:15">
P2: The second `try/except` block is missing the general `Exception` handler that the first block has. If a non-`ImportError` exception occurs during the `hf_api_service` import (e.g., a `SyntaxError` or runtime error in a transitive dependency), it will produce an unhandled traceback instead of a clean error message. Add the missing handler for consistency and robustness.</violation>
</file>

<file name="frontend/src/PublicFacilitiesDetector.jsx">

<violation number="1" location="frontend/src/PublicFacilitiesDetector.jsx:77">
P2: The overlay canvas is reused for video frame capture (`drawImage` + `toBlob`), which paints a visible still frame over the live `<video>` element every detection cycle. This creates a perceptible flash/flicker until `drawDetections` clears the canvas asynchronously. Use a separate offscreen canvas for frame capture to keep the display overlay clean.</violation>

<violation number="2" location="frontend/src/PublicFacilitiesDetector.jsx:79">
P2: Race condition: overlapping detection requests can corrupt canvas state. `setInterval` fires every 2s, but `detectFrame` starts an async chain (`toBlob` → `fetch` → `drawDetections`) that may take longer than 2s. Without a guard, multiple requests can be in-flight simultaneously and their `drawDetections` callbacks race on the shared canvas context, causing flickering or stale results overwriting newer ones. Consider adding a ref-based cooldown or a busy flag (similar to `lastSentRef` in `SmartScanner.jsx`) to skip sending a new request while one is still pending.</violation>
</file>

<file name="frontend/src/views/Home.jsx">

<violation number="1" location="frontend/src/views/Home.jsx:87">
P2: Hardcoded label string bypasses i18n. All sibling items in this category use `t()` for their labels (e.g., `t('home.issues.trafficSign')`). This item (and the other two new items: `construction-safety`, `public-facilities`) will not be translatable. Use a translation key like `t('home.issues.accessibility')` instead.</violation>

<violation number="2" location="frontend/src/views/Home.jsx:104">
P2: This item uses the same icon (`AlertTriangle`) and color scheme (`text-orange-600`/`bg-orange-50`) as the existing `grievance` item, making them visually indistinguishable in the UI. Consider using a distinct icon (e.g., `HardHat` or `Construction` from lucide-react) or a different color to differentiate construction safety from grievance management.</violation>

<violation number="3" location="frontend/src/views/Home.jsx:104">
P2: Hardcoded label string bypasses i18n. Use a translation key like `t('home.issues.constructionSafety')` for multilingual support.</violation>

<violation number="4" location="frontend/src/views/Home.jsx:111">
P2: Hardcoded label string bypasses i18n. Use a translation key like `t('home.issues.publicFacilities')` for multilingual support.</violation>
</file>

<file name="frontend/src/ConstructionSafetyDetector.jsx">

<violation number="1" location="frontend/src/ConstructionSafetyDetector.jsx:77">
P2: Using the visible overlay canvas for both frame capture and detection display causes a visible flicker every 2 seconds. `drawImage` renders an opaque video frame on the overlay canvas, which remains visible until the async fetch completes and `drawDetections` clears it. Use a separate offscreen canvas for frame capture to avoid the flash.</violation>

<violation number="2" location="frontend/src/ConstructionSafetyDetector.jsx:107">
P2: `setInterval` fires every 2 seconds regardless of whether the previous detection request has completed. If the backend is slow, overlapping requests can cause out-of-order responses and stale detection labels. Consider using `setTimeout` chaining (schedule the next call only after the current one completes) to prevent overlapping requests.</violation>
</file>

<file name="frontend/src/AbandonedVehicleDetector.jsx">

<violation number="1" location="frontend/src/AbandonedVehicleDetector.jsx:53">
P1: Bug: All detection labels are drawn at the same fixed position (`x=10, y=30`). When the API returns multiple detections, they render on top of each other and only the last one is visible. The `y` coordinate should be offset for each detection (e.g., incremented by the label height per iteration).</violation>

<violation number="2" location="frontend/src/AbandonedVehicleDetector.jsx:108">
P2: Race condition: `setInterval` fires `detectFrame` every 2s regardless of whether the previous async detection (network request + canvas draw) has completed. If a request takes >2s, the next tick's `context.drawImage` will overwrite the canvas before the prior response's `drawDetections` runs, and out-of-order responses will show stale results. Consider using a flag or `setTimeout` chain to ensure the next frame is only requested after the current one completes.</violation>
</file>

<file name="backend/routers/detection.py">

<violation number="1" location="backend/routers/detection.py:445">
P1: Missing input validation and image optimization. This endpoint reads raw bytes via `image.read()` instead of using `process_uploaded_image(image)`, skipping file size limits, MIME type validation, image resizing, and EXIF stripping that all other CLIP detection endpoints use. This opens the endpoint to oversized uploads, non-image files, and unstripped EXIF metadata (which may contain GPS coordinates).</violation>
</file>

<file name="frontend/src/TrafficSignDetector.jsx">

<violation number="1" location="frontend/src/TrafficSignDetector.jsx:121">
P1: Camera stream leak: `startCamera` is async but not cancellable. If `isDetecting` is toggled off before `getUserMedia` resolves, the returned stream is assigned to the video element after cleanup has already run, leaving an orphaned media stream (camera stays on). Use an `AbortController` or a ref-based flag to discard the stream if the effect has been cleaned up.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


// Top left corner for label
const x = 10;
const y = 30;
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P1: Bug: All detection labels are drawn at the same fixed position (x=10, y=30). When the API returns multiple detections, they render on top of each other and only the last one is visible. The y coordinate should be offset for each detection (e.g., incremented by the label height per iteration).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/AbandonedVehicleDetector.jsx, line 53:

<comment>Bug: All detection labels are drawn at the same fixed position (`x=10, y=30`). When the API returns multiple detections, they render on top of each other and only the last one is visible. The `y` coordinate should be offset for each detection (e.g., incremented by the label height per iteration).</comment>

<file context>
@@ -0,0 +1,175 @@
+
+             // Top left corner for label
+             const x = 10;
+             const y = 30;
+
+             context.fillStyle = 'rgba(0,0,0,0.5)';
</file context>
Fix with Cubic

@router.post("/api/detect-public-facilities")
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P1: Missing input validation and image optimization. This endpoint reads raw bytes via image.read() instead of using process_uploaded_image(image), skipping file size limits, MIME type validation, image resizing, and EXIF stripping that all other CLIP detection endpoints use. This opens the endpoint to oversized uploads, non-image files, and unstripped EXIF metadata (which may contain GPS coordinates).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/routers/detection.py, line 445:

<comment>Missing input validation and image optimization. This endpoint reads raw bytes via `image.read()` instead of using `process_uploaded_image(image)`, skipping file size limits, MIME type validation, image resizing, and EXIF stripping that all other CLIP detection endpoints use. This opens the endpoint to oversized uploads, non-image files, and unstripped EXIF metadata (which may contain GPS coordinates).</comment>

<file context>
@@ -436,3 +438,35 @@ async def detect_abandoned_vehicle_endpoint(request: Request, image: UploadFile
+@router.post("/api/detect-public-facilities")
+async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
+    try:
+        image_bytes = await image.read()
+    except Exception as e:
+        logger.error(f"Invalid image file: {e}", exc_info=True)
</file context>
Fix with Cubic

useEffect(() => {
let interval;
if (isDetecting) {
startCamera();
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P1: Camera stream leak: startCamera is async but not cancellable. If isDetecting is toggled off before getUserMedia resolves, the returned stream is assigned to the video element after cleanup has already run, leaving an orphaned media stream (camera stays on). Use an AbortController or a ref-based flag to discard the stream if the effect has been cleaned up.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/TrafficSignDetector.jsx, line 121:

<comment>Camera stream leak: `startCamera` is async but not cancellable. If `isDetecting` is toggled off before `getUserMedia` resolves, the returned stream is assigned to the video element after cleanup has already run, leaving an orphaned media stream (camera stays on). Use an `AbortController` or a ref-based flag to discard the stream if the effect has been cleaned up.</comment>

<file context>
@@ -0,0 +1,189 @@
+    useEffect(() => {
+        let interval;
+        if (isDetecting) {
+            startCamera();
+            interval = setInterval(detectFrame, 2000);
+        } else {
</file context>
Fix with Cubic

print("Successfully imported new endpoints from backend.routers.detection")
except ImportError as e:
print(f"Failed to import new endpoints: {e}")
sys.exit(1)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: The second try/except block is missing the general Exception handler that the first block has. If a non-ImportError exception occurs during the hf_api_service import (e.g., a SyntaxError or runtime error in a transitive dependency), it will produce an unhandled traceback instead of a clean error message. Add the missing handler for consistency and robustness.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test_detection_import.py, line 15:

<comment>The second `try/except` block is missing the general `Exception` handler that the first block has. If a non-`ImportError` exception occurs during the `hf_api_service` import (e.g., a `SyntaxError` or runtime error in a transitive dependency), it will produce an unhandled traceback instead of a clean error message. Add the missing handler for consistency and robustness.</comment>

<file context>
@@ -0,0 +1,28 @@
+    print("Successfully imported new endpoints from backend.routers.detection")
+except ImportError as e:
+    print(f"Failed to import new endpoints: {e}")
+    sys.exit(1)
+except Exception as e:
+    print(f"An error occurred during import: {e}")
</file context>
Fix with Cubic

canvas.height = video.videoHeight;
}

context.drawImage(video, 0, 0, canvas.width, canvas.height);
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: The overlay canvas is reused for video frame capture (drawImage + toBlob), which paints a visible still frame over the live <video> element every detection cycle. This creates a perceptible flash/flicker until drawDetections clears the canvas asynchronously. Use a separate offscreen canvas for frame capture to keep the display overlay clean.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/PublicFacilitiesDetector.jsx, line 77:

<comment>The overlay canvas is reused for video frame capture (`drawImage` + `toBlob`), which paints a visible still frame over the live `<video>` element every detection cycle. This creates a perceptible flash/flicker until `drawDetections` clears the canvas asynchronously. Use a separate offscreen canvas for frame capture to keep the display overlay clean.</comment>

<file context>
@@ -0,0 +1,174 @@
+            canvas.height = video.videoHeight;
+        }
+
+        context.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+        canvas.toBlob(async (blob) => {
</file context>
Fix with Cubic

{ id: 'crowd', label: "Crowd", icon: <Users size={24} />, color: 'text-red-500', bg: 'bg-red-50' },
{ id: 'water-leak', label: "Water Leak", icon: <Waves size={24} />, color: 'text-blue-500', bg: 'bg-blue-50' },
{ id: 'waste', label: "Waste Sorter", icon: <Recycle size={24} />, color: 'text-emerald-600', bg: 'bg-emerald-50' },
{ id: 'construction-safety', label: "Construction", icon: <AlertTriangle size={24} />, color: 'text-orange-600', bg: 'bg-orange-50' },
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: Hardcoded label string bypasses i18n. Use a translation key like t('home.issues.constructionSafety') for multilingual support.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/views/Home.jsx, line 104:

<comment>Hardcoded label string bypasses i18n. Use a translation key like `t('home.issues.constructionSafety')` for multilingual support.</comment>

<file context>
@@ -100,12 +101,14 @@ const Home = ({ setView, fetchResponsibilityMap, recentIssues, handleUpvote, loa
         { id: 'crowd', label: "Crowd", icon: <Users size={24} />, color: 'text-red-500', bg: 'bg-red-50' },
         { id: 'water-leak', label: "Water Leak", icon: <Waves size={24} />, color: 'text-blue-500', bg: 'bg-blue-50' },
         { id: 'waste', label: "Waste Sorter", icon: <Recycle size={24} />, color: 'text-emerald-600', bg: 'bg-emerald-50' },
+        { id: 'construction-safety', label: "Construction", icon: <AlertTriangle size={24} />, color: 'text-orange-600', bg: 'bg-orange-50' },
       ]
     },
</file context>
Suggested change
{ id: 'construction-safety', label: "Construction", icon: <AlertTriangle size={24} />, color: 'text-orange-600', bg: 'bg-orange-50' },
{ id: 'construction-safety', label: t('home.issues.constructionSafety'), icon: <AlertTriangle size={24} />, color: 'text-orange-600', bg: 'bg-orange-50' },
Fix with Cubic

{ id: 'streetlight', label: t('home.issues.darkStreet'), icon: <Lightbulb size={24} />, color: 'text-slate-600', bg: 'bg-slate-50' },
{ id: 'traffic-sign', label: t('home.issues.trafficSign'), icon: <Signpost size={24} />, color: 'text-yellow-600', bg: 'bg-yellow-50' },
{ id: 'abandoned-vehicle', label: t('home.issues.abandonedVehicle'), icon: <Car size={24} />, color: 'text-gray-600', bg: 'bg-gray-50' },
{ id: 'accessibility', label: "Accessibility", icon: <Accessibility size={24} />, color: 'text-blue-600', bg: 'bg-blue-50' },
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: Hardcoded label string bypasses i18n. All sibling items in this category use t() for their labels (e.g., t('home.issues.trafficSign')). This item (and the other two new items: construction-safety, public-facilities) will not be translatable. Use a translation key like t('home.issues.accessibility') instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/views/Home.jsx, line 87:

<comment>Hardcoded label string bypasses i18n. All sibling items in this category use `t()` for their labels (e.g., `t('home.issues.trafficSign')`). This item (and the other two new items: `construction-safety`, `public-facilities`) will not be translatable. Use a translation key like `t('home.issues.accessibility')` instead.</comment>

<file context>
@@ -84,6 +84,7 @@ const Home = ({ setView, fetchResponsibilityMap, recentIssues, handleUpvote, loa
         { id: 'streetlight', label: t('home.issues.darkStreet'), icon: <Lightbulb size={24} />, color: 'text-slate-600', bg: 'bg-slate-50' },
         { id: 'traffic-sign', label: t('home.issues.trafficSign'), icon: <Signpost size={24} />, color: 'text-yellow-600', bg: 'bg-yellow-50' },
         { id: 'abandoned-vehicle', label: t('home.issues.abandonedVehicle'), icon: <Car size={24} />, color: 'text-gray-600', bg: 'bg-gray-50' },
+        { id: 'accessibility', label: "Accessibility", icon: <Accessibility size={24} />, color: 'text-blue-600', bg: 'bg-blue-50' },
       ]
     },
</file context>
Suggested change
{ id: 'accessibility', label: "Accessibility", icon: <Accessibility size={24} />, color: 'text-blue-600', bg: 'bg-blue-50' },
{ id: 'accessibility', label: t('home.issues.accessibility'), icon: <Accessibility size={24} />, color: 'text-blue-600', bg: 'bg-blue-50' },
Fix with Cubic

let interval;
if (isDetecting) {
startCamera();
interval = setInterval(detectFrame, 2000);
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: setInterval fires every 2 seconds regardless of whether the previous detection request has completed. If the backend is slow, overlapping requests can cause out-of-order responses and stale detection labels. Consider using setTimeout chaining (schedule the next call only after the current one completes) to prevent overlapping requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/ConstructionSafetyDetector.jsx, line 107:

<comment>`setInterval` fires every 2 seconds regardless of whether the previous detection request has completed. If the backend is slow, overlapping requests can cause out-of-order responses and stale detection labels. Consider using `setTimeout` chaining (schedule the next call only after the current one completes) to prevent overlapping requests.</comment>

<file context>
@@ -0,0 +1,174 @@
+        let interval;
+        if (isDetecting) {
+            startCamera();
+            interval = setInterval(detectFrame, 2000);
+        } else {
+            stopCamera();
</file context>
Fix with Cubic

canvas.height = video.videoHeight;
}

context.drawImage(video, 0, 0, canvas.width, canvas.height);
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: Using the visible overlay canvas for both frame capture and detection display causes a visible flicker every 2 seconds. drawImage renders an opaque video frame on the overlay canvas, which remains visible until the async fetch completes and drawDetections clears it. Use a separate offscreen canvas for frame capture to avoid the flash.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/ConstructionSafetyDetector.jsx, line 77:

<comment>Using the visible overlay canvas for both frame capture and detection display causes a visible flicker every 2 seconds. `drawImage` renders an opaque video frame on the overlay canvas, which remains visible until the async fetch completes and `drawDetections` clears it. Use a separate offscreen canvas for frame capture to avoid the flash.</comment>

<file context>
@@ -0,0 +1,174 @@
+            canvas.height = video.videoHeight;
+        }
+
+        context.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+        canvas.toBlob(async (blob) => {
</file context>
Fix with Cubic

let interval;
if (isDetecting) {
startCamera();
interval = setInterval(detectFrame, 2000);
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: Race condition: setInterval fires detectFrame every 2s regardless of whether the previous async detection (network request + canvas draw) has completed. If a request takes >2s, the next tick's context.drawImage will overwrite the canvas before the prior response's drawDetections runs, and out-of-order responses will show stale results. Consider using a flag or setTimeout chain to ensure the next frame is only requested after the current one completes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/AbandonedVehicleDetector.jsx, line 108:

<comment>Race condition: `setInterval` fires `detectFrame` every 2s regardless of whether the previous async detection (network request + canvas draw) has completed. If a request takes >2s, the next tick's `context.drawImage` will overwrite the canvas before the prior response's `drawDetections` runs, and out-of-order responses will show stale results. Consider using a flag or `setTimeout` chain to ensure the next frame is only requested after the current one completes.</comment>

<file context>
@@ -0,0 +1,175 @@
+        let interval;
+        if (isDetecting) {
+            startCamera();
+            interval = setInterval(detectFrame, 2000);
+        } else {
+            stopCamera();
</file context>
Fix with Cubic

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the civic “detectors” feature set by adding new CLIP-based backend detectors and exposing multiple (previously missing/hidden) detector flows in the frontend UI and routing.

Changes:

  • Added backend CLIP detectors + FastAPI endpoints for “Public Facilities” and “Construction Safety”.
  • Added/connected frontend detector screens and routes for Traffic Sign, Abandoned Vehicle, Public Facilities, Construction Safety, and exposed Accessibility/Water Leak/Crowd/Waste in routing.
  • Updated the Home view to show the new detector options in category grids.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
test_detection_import.py Adds a script intended to validate imports for new detector endpoints/services.
frontend/src/views/Home.jsx Adds new detector entries to the Home category grid.
frontend/src/App.jsx Registers new detector routes and expands the navigation allow-list.
frontend/src/TrafficSignDetector.jsx New live camera detector screen calling /api/detect-traffic-sign.
frontend/src/AbandonedVehicleDetector.jsx New live camera detector screen calling /api/detect-abandoned-vehicle.
frontend/src/PublicFacilitiesDetector.jsx New live camera detector screen calling /api/detect-public-facilities.
frontend/src/ConstructionSafetyDetector.jsx New live camera detector screen calling /api/detect-construction-safety.
backend/routers/detection.py Adds imports and new FastAPI endpoints for the new detectors.
backend/hf_api_service.py Adds CLIP label/target definitions for public facilities and construction safety.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +118 to +123
useEffect(() => {
let interval;
if (isDetecting) {
startCamera();
interval = setInterval(detectFrame, 2000);
} else {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Live detection is triggered on a fixed setInterval without guarding against an in-flight request. If a request takes >2s, multiple overlapping uploads can pile up, increasing backend load and causing out-of-order rendering. Add an inFlight flag / abort controller to skip starting a new detection while the previous one is still running (or schedule the next tick after completion).

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +110
useEffect(() => {
let interval;
if (isDetecting) {
startCamera();
interval = setInterval(detectFrame, 2000);
} else {
stopCamera();
if (interval) clearInterval(interval);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Same interval-driven live detection issue: without an in-flight guard, slow network/backend responses can cause overlapping requests and unnecessary load. Add an inFlight/abort mechanism so only one detection runs at a time.

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +110
useEffect(() => {
let interval;
if (isDetecting) {
startCamera();
interval = setInterval(detectFrame, 2000);
} else {
stopCamera();
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Same interval-driven live detection issue: without an in-flight guard, slow responses can cause overlapping requests and unnecessary load. Add an inFlight/abort mechanism so only one detection runs at a time.

Copilot uses AI. Check for mistakes.
Comment on lines +443 to +448
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

These new endpoints read raw bytes via await image.read() and skip the repo’s existing process_uploaded_image(...) validation/optimization used by most detection endpoints. This can allow very large uploads, increases memory pressure, and bypasses consistent image verification. Use process_uploaded_image(image) here (and return the optimized image_bytes) to align with the rest of the router.

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +465
async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Same as the public-facilities endpoint: reading raw bytes and skipping process_uploaded_image(...) bypasses centralized validation/optimization and size controls. Prefer process_uploaded_image(image) for consistent handling of uploads.

Copilot uses AI. Check for mistakes.
interval = setInterval(detectFrame, 2000);
} else {
stopCamera();
if (interval) clearInterval(interval);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This use of variable 'interval' always evaluates to false.

Suggested change
if (interval) clearInterval(interval);

Copilot uses AI. Check for mistakes.
interval = setInterval(detectFrame, 2000);
} else {
stopCamera();
if (interval) clearInterval(interval);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This use of variable 'interval' always evaluates to false.

Suggested change
if (interval) clearInterval(interval);

Copilot uses AI. Check for mistakes.
interval = setInterval(detectFrame, 2000);
} else {
stopCamera();
if (interval) clearInterval(interval);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This use of variable 'interval' always evaluates to false.

Suggested change
if (interval) clearInterval(interval);

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +11
from backend.routers.detection import (
detect_public_facilities_endpoint,
detect_construction_safety_endpoint
)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Import of 'detect_public_facilities_endpoint' is not used.
Import of 'detect_construction_safety_endpoint' is not used.

Copilot uses AI. Check for mistakes.
from backend.hf_api_service import (
detect_public_facilities_clip,
detect_construction_safety_clip
)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Import of 'detect_public_facilities_clip' is not used.
Import of 'detect_construction_safety_clip' is not used.

Suggested change
)
)
_imported_clip_functions = [
detect_public_facilities_clip,
detect_construction_safety_clip,
]

Copilot uses AI. Check for mistakes.
google-labs-jules bot and others added 2 commits February 11, 2026 10:20
- Removed `frontend/public/_redirects` which was conflicting with `netlify.toml` and causing deployment verification failures.
- Verified that required components (`TrafficSignDetector`, `AbandonedVehicleDetector`, etc.) exist and are correctly imported in `App.jsx`.
- Verified that `frontend/src/views/Home.jsx` has all necessary icon imports.
- Confirmed local build passes successfully with `npm run build`.

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
- Created `frontend/netlify.toml` to support deployments where the Base Directory is set to `frontend`.
- This ensures that if the root `netlify.toml` is ignored, the frontend build command and publish directory are still correctly defined.
- Verified that all required detector components (`TrafficSign`, `AbandonedVehicle`, `PublicFacilities`, `ConstructionSafety`, etc.) exist and are imported correctly.
- Confirmed that `frontend/public/_redirects` is removed to avoid conflicts.
- Verified local build with `npm run build` in `frontend/`.

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
@github-actions
Copy link

🔍 Quality Reminder

Thanks for the updates! Please ensure:
- Your changes don't break existing functionality
- All tests still pass
- Code quality standards are maintained

*The maintainers will verify that the overall project flow remains intact.*

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="frontend/netlify.toml">

<violation number="1" location="frontend/netlify.toml:24">
P2: `X-XSS-Protection: 1; mode=block` is deprecated and can actually *introduce* XSS vulnerabilities in otherwise safe websites (per OWASP and MDN). The XSS Auditor has been removed from all major browsers. Either remove this header entirely or explicitly disable it with `"0"`, and consider adding a `Content-Security-Policy` header instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- Removed `frontend/netlify.toml` to avoid configuration ambiguity.
- Updated root `netlify.toml` to change `X-Frame-Options` to `SAMEORIGIN` to fix "Header rules" CI failure.
- Restored `frontend/public/_redirects` and `frontend/public/_headers` as file-based fallbacks, ensuring robust deployment even if toml config is partial.
- Confirmed all referenced frontend components exist and backend endpoints are verified.

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="netlify.toml">

<violation number="1" location="netlify.toml:22">
P2: Security header `X-Frame-Options` relaxed from `DENY` to `SAMEORIGIN` without apparent justification. This PR adds detection features and UI routes — none of which typically require same-origin framing. If the app doesn't embed itself in iframes, keep `DENY` for stronger clickjacking protection. If framing is needed (e.g., for an internal iframe-based component), please document the reason.</violation>
</file>

<file name="frontend/public/_headers">

<violation number="1" location="frontend/public/_headers:4">
P2: `X-XSS-Protection: 1; mode=block` is deprecated and can paradoxically introduce XSS vulnerabilities in otherwise safe websites (per MDN and OWASP). Modern browsers have removed their XSS Auditor. Set this to `0` and rely on a `Content-Security-Policy` header instead for XSS protection.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Frame-Options = "SAMEORIGIN"
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: Security header X-Frame-Options relaxed from DENY to SAMEORIGIN without apparent justification. This PR adds detection features and UI routes — none of which typically require same-origin framing. If the app doesn't embed itself in iframes, keep DENY for stronger clickjacking protection. If framing is needed (e.g., for an internal iframe-based component), please document the reason.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At netlify.toml, line 22:

<comment>Security header `X-Frame-Options` relaxed from `DENY` to `SAMEORIGIN` without apparent justification. This PR adds detection features and UI routes — none of which typically require same-origin framing. If the app doesn't embed itself in iframes, keep `DENY` for stronger clickjacking protection. If framing is needed (e.g., for an internal iframe-based component), please document the reason.</comment>

<file context>
@@ -22,7 +19,7 @@
   for = "/*"
   [headers.values]
-    X-Frame-Options = "DENY"
+    X-Frame-Options = "SAMEORIGIN"
     X-Content-Type-Options = "nosniff"
     X-XSS-Protection = "1; mode=block"
</file context>
Suggested change
X-Frame-Options = "SAMEORIGIN"
X-Frame-Options = "DENY"
Fix with Cubic

/*
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 11, 2026

Choose a reason for hiding this comment

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

P2: X-XSS-Protection: 1; mode=block is deprecated and can paradoxically introduce XSS vulnerabilities in otherwise safe websites (per MDN and OWASP). Modern browsers have removed their XSS Auditor. Set this to 0 and rely on a Content-Security-Policy header instead for XSS protection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/public/_headers, line 4:

<comment>`X-XSS-Protection: 1; mode=block` is deprecated and can paradoxically introduce XSS vulnerabilities in otherwise safe websites (per MDN and OWASP). Modern browsers have removed their XSS Auditor. Set this to `0` and rely on a `Content-Security-Policy` header instead for XSS protection.</comment>

<file context>
@@ -0,0 +1,5 @@
+/*
+  X-Frame-Options: SAMEORIGIN
+  X-Content-Type-Options: nosniff
+  X-XSS-Protection: 1; mode=block
+  Referrer-Policy: strict-origin-when-cross-origin
</file context>
Fix with Cubic

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant