Skip to content

feat: refresh buttons, bubble controls, inner boundary + per-bubble studio actions - #7

Merged
deckyfx merged 4 commits into
masterfrom
feat/refresh-buttons
Jul 28, 2026
Merged

feat: refresh buttons, bubble controls, inner boundary + per-bubble studio actions#7
deckyfx merged 4 commits into
masterfrom
feat/refresh-buttons

Conversation

@deckyfx

@deckyfx deckyfx commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Portal refresh buttons — Re-detect, Re-translate, Re-render toolbar actions on Jobs and Library pages with loading spinners and error banners
  • Bubble inner-boundary refinement — server-side crop erosion in BubbleDetectionService; frontend Pad ± stepper persisted in localStorage
  • Extension push — completed job result image pushed to extension via OcrRoutes; background.ts / content.ts updated to receive and display it
  • Per-bubble studio controls — Re-OCR, Re-translate, Re-inpaint, Re-patch buttons in the Studio right panel with per-button loading states; font family + size selection per bubble; result image cache-busted via version signal after inpaint/patch operations

Changes

Server (C#)

  • TypesettingService: WhiteFillBubble, RenderOneBubble, optional fontFamily/fontSizeOverride on BubbleTranslation
  • PageTranslationService: ReocrBubbleAsync, RetranslateBubbleAsync, ReinpaintBubbleAsync, RepatchBubbleAsync; font settings threaded through RerenderAsync
  • PortalRoutes: 4 new POST endpoints per bubble (/reocr, /retranslate, /reinpaint, /repatch); UpdateBubbleRequest + PUT handler extended with font fields
  • OcrRoutes: push result image to extension on job completion
  • EF Core migration 20260727000001_AddBubbleFontSettingsFontFamily TEXT, FontSizeOverride INTEGER nullable columns on PageTranslationLogs

Frontend (SolidJS / Vite)

  • BubbleEditor: Render Style section (font family dropdown, font size 0 = auto-fit); Bubble Actions 2×2 grid with individual spinners and inline error banner
  • StudioPage: per-bubble handlers; resultUrl() cache-busts result image after reinpaint/repatch
  • JobsListPage / LibraryPage: Re-detect, Re-translate, Re-render toolbar buttons
  • api.ts + types.ts: fontFamily/fontSizeOverride on TranslationBubble; 4 new bubble action functions

Extension (TypeScript)

  • background.ts / content.ts: receive and display pushed result image from server

Test plan

  • Jobs page: Re-detect / Re-translate / Re-render buttons trigger correct server actions and show loading state
  • Studio pad stepper changes bubble crop padding and persists across reloads
  • Studio: selecting a bubble shows Render Style and Bubble Actions sections in editor
  • Re-OCR refreshes source text in editor
  • Re-translate refreshes translated text in editor
  • Re-inpaint white-fills bubble in result image; result reloads (cache-busted)
  • Re-patch re-renders text in bubble; result reloads
  • Font family and font size override apply on next re-patch / full re-render
  • EF migration applies cleanly on a fresh DB (dotnet ef database update)
  • Extension receives result image after job completes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added right-aligned Refresh actions to Jobs and Library pages with loading/disabling and spinning indicators.
    • Added per-bubble re-OCR, re-translation, re-inpainting, and re-patching in Studio, including adjustable bubble padding and immediate result refresh.
    • Added persisted per-bubble font family and font size overrides, and job-result delivery via background polling for translated images.
  • Bug Fixes
    • Improved bubble detection by refining boxes to the bubble inner boundary with cancellation support.
  • Chores
    • Bumped extension version to 1.3.4 and updated extension permissions to support background alarms.

…tudio actions

Features bundled in this PR:

1. Portal refresh buttons (Jobs + Library pages) — Re-detect, Re-translate,
   Re-render toolbar actions with loading spinners and error banners.

2. Bubble inner-boundary refinement — server-side crop erosion via
   BubbleDetectionService; frontend Pad ± stepper stored in localStorage.

3. Extension push — completed job result image pushed to extension via
   OcrRoutes; background.ts / content.ts updated to receive and display it.

4. Per-bubble studio controls (re-OCR, re-translate, re-inpaint, re-patch)
   — TypesettingService: WhiteFillBubble, RenderOneBubble, optional
     fontFamily/fontSizeOverride on BubbleTranslation
   — PageTranslationService: ReocrBubbleAsync, RetranslateBubbleAsync,
     ReinpaintBubbleAsync, RepatchBubbleAsync
   — PortalRoutes: 4 new POST endpoints per bubble; UpdateBubbleRequest
     extended with font fields
   — EF migration AddBubbleFontSettings (FontFamily TEXT, FontSizeOverride INTEGER)
   — BubbleEditor: Render Style section (font family + size), Bubble Actions
     2×2 grid with per-button loading states
   — StudioPage: per-bubble handlers wired up; result URL cache-busted via
     version signal after reinpaint/repatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e216b780-a9d7-4a88-88f8-a19346e67514

📥 Commits

Reviewing files that changed from the base of the PR and between c586e62 and f7daf50.

⛔ Files ignored due to path filters (1)
  • server/wwwroot/js/app.js is excluded by !server/wwwroot/js/app.js
📒 Files selected for processing (4)
  • extension/src/background.ts
  • extension/src/types.ts
  • server/src/Routes/OcrRoutes.cs
  • server/src/Services/BubbleDetectionService.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/src/Services/BubbleDetectionService.cs
  • server/src/Routes/OcrRoutes.cs
  • extension/src/background.ts

📝 Walkthrough

Walkthrough

Adds Jobs and Library refresh controls, refined bubble detection, persistent bubble rendering settings with per-bubble actions, database support for font overrides, and tracked OCR job result delivery from the server to the browser extension.

Changes

Feature updates

Layer / File(s) Summary
Page refresh controls
docs/feature-plan-next.md, server/ClientApp/src/pages/JobsListPage.tsx, server/ClientApp/src/pages/LibraryPage.tsx
Adds loading-aware Refresh controls for Jobs and Library data and documents related roadmap items.
Inner boundary detection
server/src/Services/BubbleDetectionService.cs
Refines RT-DETR and YOLOv8 bubble boxes using bright-region flood filling with cancellation support.
Bubble editing and rendering
server/src/Services/*, server/src/Routes/PortalRoutes.cs, server/ClientApp/src/components/*, server/ClientApp/src/pages/StudioPage.tsx, server/Migrations/*
Adds persisted font settings, padding-aware rendering, per-bubble OCR/translation/repaint/repatch actions, and Studio controls with cache-busted result images.
Tracked OCR job results
server/src/Routes/OcrRoutes.cs, server/src/Workers/*, server/Models/*, extension/src/*, extension/static/*, extension/*/package.json
Adds opt-in tracked OCR jobs, status/result-image endpoints, background processing, extension polling, image conversion, result-panel rendering, and version updates.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExtensionBackground
  participant OcrRoutes
  participant PageTranslationWorker
  participant ExtensionContent
  ExtensionBackground->>OcrRoutes: POST /ocr with track_job
  OcrRoutes->>PageTranslationWorker: enqueue translation job
  ExtensionBackground->>OcrRoutes: poll job status
  OcrRoutes-->>ExtensionBackground: return completed result image
  ExtensionBackground->>ExtensionContent: send job-result-ready data URL
Loading

Possibly related PRs

  • deckyfx/web-ocr#3: Touches the same bubble-detection and portal translation workflow.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.86% 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 clearly summarizes the main changes: refresh buttons, bubble controls, inner-boundary refinement, and Studio per-bubble actions.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/refresh-buttons

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.

@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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/src/Services/PageTranslationService.cs (1)

240-257: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Serialize per-job result-image mutations.

Concurrent rerender/reinpaint/repatch requests can read the same result.png, modify different copies, and let the last write silently discard the other update. Guard each job’s full read-modify-write sequence with a shared keyed async lock.

Also applies to: 347-356, 383-391

🤖 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 `@server/src/Services/PageTranslationService.cs` around lines 240 - 257, The
result-image read/render/write sequence in the surrounding
PageTranslationService method must be serialized per job to prevent concurrent
updates from overwriting one another. Add or reuse a shared keyed async lock
keyed by jobId, and acquire it before reading originalPath through writing
resultPath; apply the same guard to the related rerender/reinpaint/repatch
sequences, releasing it reliably after each operation.
server/ClientApp/src/pages/StudioPage.tsx (1)

222-234: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

handleRerender doesn't bust the result image cache.

Re-render regenerates the result file at the same URL, but resultImageVersion is only incremented by reinpaint/repatch, so the canvas keeps showing the stale cached image until another per-bubble action runs.

🐛 Proposed fix
       await rerenderJob(params.id, bubblePadding());
       await pollUntilDone();
       await refetchBubbles();
+      setResultImageVersion((v) => v + 1);
🤖 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 `@server/ClientApp/src/pages/StudioPage.tsx` around lines 222 - 234, Update
handleRerender to increment resultImageVersion after the rerender completes and
before refetching or displaying the updated bubbles, ensuring the regenerated
result image URL bypasses the browser cache. Leave the existing error and
loading-state handling unchanged.
🧹 Nitpick comments (3)
server/src/Routes/OcrRoutes.cs (1)

86-113: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Job status/result endpoints are unauthenticated and live in the OCR route group.

Two notes:

  1. GET /jobs/{id}/status and /jobs/{id}/result-image expose translated page images to any origin (default CORS, no auth) keyed only by an opaque id — confirm that matches the intended threat model for this server.
  2. These are a distinct endpoint group; consider a dedicated route extension class per the route-organization guideline.

As per coding guidelines, "Register services and API routes through ServiceExtensions.cs, using one route extension class per endpoint group under src/Routes/."

🤖 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 `@server/src/Routes/OcrRoutes.cs` around lines 86 - 113, Review the
unauthenticated access model for the /jobs/{id}/status and
/jobs/{id}/result-image endpoints and add the required authorization or access
restrictions if opaque job IDs are not sufficient for the intended threat model.
Move these endpoints out of the OCR route registration into a dedicated route
extension class under Routes, and register that group through
ServiceExtensions.cs while preserving their existing behavior.

Source: Coding guidelines

server/ClientApp/src/components/BubbleCanvas.tsx (1)

427-430: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Resize handles no longer sit on the drawn rect when bubblePadding > 0.

svgRect() is inset by pad, but the handle positions (Line 499) still derive from the un-padded rect(), so handles detach from the visible outline. Consider deriving handle positions from the same padded rect for visual consistency (drag math can keep using the true bounds).

🤖 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 `@server/ClientApp/src/components/BubbleCanvas.tsx` around lines 427 - 430,
Update the handle-position calculations near the handle rendering in
BubbleCanvas so they use the same padded SVG rectangle produced by svgRect(),
rather than the unpadded rect() bounds. Keep the true bounds used by drag
calculations unchanged, and ensure handles remain aligned with the visible
outline when bubblePadding is positive.
extension/src/content.ts (1)

57-57: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Drop the cast and use jobId to avoid attaching stale results.

The ToContentMsg union already narrows on msg.type, so msg as JobResultReadyMsg is unnecessary and hides future type drift. Also, appendJobImage appends unconditionally: if the user re-scanned since the job started, the image lands on a panel for a different selection, and a repeat message would append a second image. Passing msg.jobId and replacing any existing .socr-job-image (or ignoring results for a superseded scan) makes this deterministic.

♻️ Suggested change
-    else if (msg.type === "job-result-ready") appendJobImage((msg as JobResultReadyMsg).resultImageDataUrl);
+    else if (msg.type === "job-result-ready") appendJobImage(msg.resultImageDataUrl);
 function appendJobImage(resultImageDataUrl: string): void {
   if (!resultPanelEl) return;
   const inner = resultPanelEl.querySelector<HTMLElement>(".socr-panel-inner");
   if (!inner) return;
-  const section = document.createElement("div");
+  inner.querySelector(".socr-job-image")?.remove();
+  const section = document.createElement("div");

The JobResultReadyMsg import can then be dropped if unused elsewhere.

Also applies to: 311-322

🤖 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 `@extension/src/content.ts` at line 57, Update the “job-result-ready” handling
in the content message flow to use the narrowed message directly, remove the
unnecessary JobResultReadyMsg cast and import if unused, and pass msg.jobId into
appendJobImage. Make appendJobImage replace an existing .socr-job-image or
ignore results whose jobId no longer matches the active scan, preventing stale
or duplicate images.

Source: Linters/SAST tools

🤖 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 `@docs/feature-plan-next.md`:
- Around line 121-130: Update both fenced code blocks in the job events
documentation near the GET endpoint and emitted event examples to specify the
text language, changing each opening fence to ```text while preserving all
example content.

In `@extension/src/background.ts`:
- Around line 202-235: Replace the in-memory timer loop in pollJobResult with
persisted pending-job state in session storage, including jobId, serverUrl,
tabId, and the existing deadline/scope, and schedule recurring chrome.alarms
polling so MV3 worker termination can resume it. Add alarm startup handling that
reloads pending jobs and invokes pollJobResult, remove the recursive sleep-based
scheduling, clear persisted state and the alarm on completion or timeout, and
tolerate a small bounded number of non-OK responses or thrown network errors
before aborting.

In `@server/ClientApp/src/components/BubbleEditor.tsx`:
- Around line 267-284: Clamp the parsed value in the fontSizeOverride input
handlers to the declared 0–72 range before updating state or calling
props.onUpdate. Update the onInput and onChange logic in the visible input so
typed values such as negatives or values above 72 are normalized and only the
clamped value is persisted.

In `@server/ClientApp/src/pages/JobsListPage.tsx`:
- Around line 170-176: Add explicit accessible names to both icon-only refresh
buttons: in server/ClientApp/src/pages/JobsListPage.tsx lines 170-176, add
aria-label="Refresh jobs"; in server/ClientApp/src/pages/LibraryPage.tsx lines
183-190, add aria-label="Refresh library".
- Around line 170-176: Update the refresh button in JobsListPage so it is
disabled whenever jobs.loading is true, while preserving the existing refetch
handler and loading spinner behavior. Match the disabled-button handling used by
LibraryPage.

In `@server/ClientApp/src/pages/StudioPage.tsx`:
- Around line 66-68: Update the bubblePadding signal initialization to validate
the parsed localStorage value and fall back to a finite numeric default when
parsing yields NaN or another invalid value. Keep the existing
"studio-bubble-padding" storage key and ensure the resulting signal value
remains safe for BubbleCanvas attributes and rerender/reinpaint/repatch request
payloads.

In `@server/src/Routes/OcrRoutes.cs`:
- Around line 61-81: Replace the fire-and-forget Task.Run block in the tracked
OCR path with an InferenceQueue job handled by InferenceWorker, ensuring the
translation pipeline executes under the queue’s bounded concurrency. Have the
route enqueue the job and await its completion, while preserving captured job
data and MarkJobFailedAsync error handling; do not run TranslatePageAsync
directly from the route.

In `@server/src/Services/BubbleDetectionService.cs`:
- Line 172: Reorder the candidate-processing flow in BubbleDetectionService so
raw BubbleBox candidates undergo non-maximum suppression before
RefineToInnerBoundary allocates grids and performs flood-fill refinement. Apply
this to all noted candidate paths, then refine only surviving boxes and preserve
any existing post-refinement suppression if required.

---

Outside diff comments:
In `@server/ClientApp/src/pages/StudioPage.tsx`:
- Around line 222-234: Update handleRerender to increment resultImageVersion
after the rerender completes and before refetching or displaying the updated
bubbles, ensuring the regenerated result image URL bypasses the browser cache.
Leave the existing error and loading-state handling unchanged.

In `@server/src/Services/PageTranslationService.cs`:
- Around line 240-257: The result-image read/render/write sequence in the
surrounding PageTranslationService method must be serialized per job to prevent
concurrent updates from overwriting one another. Add or reuse a shared keyed
async lock keyed by jobId, and acquire it before reading originalPath through
writing resultPath; apply the same guard to the related
rerender/reinpaint/repatch sequences, releasing it reliably after each
operation.

---

Nitpick comments:
In `@extension/src/content.ts`:
- Line 57: Update the “job-result-ready” handling in the content message flow to
use the narrowed message directly, remove the unnecessary JobResultReadyMsg cast
and import if unused, and pass msg.jobId into appendJobImage. Make
appendJobImage replace an existing .socr-job-image or ignore results whose jobId
no longer matches the active scan, preventing stale or duplicate images.

In `@server/ClientApp/src/components/BubbleCanvas.tsx`:
- Around line 427-430: Update the handle-position calculations near the handle
rendering in BubbleCanvas so they use the same padded SVG rectangle produced by
svgRect(), rather than the unpadded rect() bounds. Keep the true bounds used by
drag calculations unchanged, and ensure handles remain aligned with the visible
outline when bubblePadding is positive.

In `@server/src/Routes/OcrRoutes.cs`:
- Around line 86-113: Review the unauthenticated access model for the
/jobs/{id}/status and /jobs/{id}/result-image endpoints and add the required
authorization or access restrictions if opaque job IDs are not sufficient for
the intended threat model. Move these endpoints out of the OCR route
registration into a dedicated route extension class under Routes, and register
that group through ServiceExtensions.cs while preserving their existing
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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bb76e9e6-3db9-4c7a-8c88-c140dafd145d

📥 Commits

Reviewing files that changed from the base of the PR and between a72fa61 and 8a6e379.

⛔ Files ignored due to path filters (2)
  • server/wwwroot/js/app.css is excluded by !server/wwwroot/js/app.css
  • server/wwwroot/js/app.js is excluded by !server/wwwroot/js/app.js
📒 Files selected for processing (25)
  • docs/feature-plan-next.md
  • extension/package.json
  • extension/src/background.ts
  • extension/src/content.ts
  • extension/src/types.ts
  • extension/static/content.css
  • extension/static/manifest.json
  • server/ClientApp/src/api.ts
  • server/ClientApp/src/components/BubbleCanvas.tsx
  • server/ClientApp/src/components/BubbleEditor.tsx
  • server/ClientApp/src/pages/JobsListPage.tsx
  • server/ClientApp/src/pages/LibraryPage.tsx
  • server/ClientApp/src/pages/StudioPage.tsx
  • server/ClientApp/src/types.ts
  • server/Migrations/20260727000001_AddBubbleFontSettings.Designer.cs
  • server/Migrations/20260727000001_AddBubbleFontSettings.cs
  • server/Migrations/AppDbContextModelSnapshot.cs
  • server/Models/RequestModels.cs
  • server/Models/ResponseModels.cs
  • server/src/Data/AppDbContext.cs
  • server/src/Routes/OcrRoutes.cs
  • server/src/Routes/PortalRoutes.cs
  • server/src/Services/BubbleDetectionService.cs
  • server/src/Services/PageTranslationService.cs
  • server/src/Services/TypesettingService.cs

Comment thread docs/feature-plan-next.md Outdated
Comment thread extension/src/background.ts Outdated
Comment thread server/ClientApp/src/components/BubbleEditor.tsx
Comment thread server/ClientApp/src/pages/JobsListPage.tsx
Comment thread server/ClientApp/src/pages/StudioPage.tsx
Comment thread server/src/Routes/OcrRoutes.cs
Comment thread server/src/Services/BubbleDetectionService.cs Outdated
- PageTranslationService: add per-job SemaphoreSlim keyed lock
  (GetImageLock) to serialize concurrent rerender/reinpaint/repatch
  read-modify-write operations on result.png
- StudioPage: increment resultImageVersion after handleRerender so
  full re-render also cache-busts the canvas result image URL
- BubbleCanvas: resize handles now derive positions from svgRect()
  (padded visual rect in SVG coords) instead of rect() + toSvg(),
  so handles stay aligned with the visible outline when bubblePadding > 0
- BubbleEditor: clamp fontSizeOverride to 0-72 in both onInput and
  onChange handlers to prevent out-of-range values being persisted
- content.ts: remove redundant JobResultReadyMsg cast (type is already
  narrowed by the if-check); appendJobImage now removes any existing
  .socr-job-image before inserting, preventing duplicate result panels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@deckyfx

deckyfx commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- StudioPage: NaN guard for bubblePadding localStorage init
- JobsListPage: aria-label + disabled state on refresh button
- LibraryPage: aria-label on refresh button
- docs: add language specifiers to bare fenced code blocks
- BubbleDetectionService: run NMS before RefineToInnerBoundary
- background.ts: replace in-memory poll loop with chrome.alarms
- OcrRoutes: replace Task.Run fire-and-forget with PageTranslationQueue
- Add PageTranslationQueue + PageTranslationWorker BackgroundService
- manifest.json: add alarms permission

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@deckyfx

deckyfx commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
server/src/Services/BubbleDetectionService.cs (2)

297-323: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Bound and cancel the flood fill.

RefineToInnerBoundary allocates bool[w,h] and visits every pixel inside each surviving box; large decoded pages can make bubble detection CPU- and memory-intensive. Add an image/box-area cap with a safe fallback, and pass the existing task cancellation token through Detect/RefineToInnerBoundary so cancellation can be respected during refinement.

🤖 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 `@server/src/Services/BubbleDetectionService.cs` around lines 297 - 323, Bound
the flood fill in RefineToInnerBoundary with an image/box-area threshold and
return the existing safe fallback when the cap is exceeded, avoiding unbounded
bool[w,h] allocation or traversal. Thread the existing cancellation token from
Detect into RefineToInnerBoundary and check it during initialization and the
queue-processing loop, propagating cancellation promptly without changing normal
refinement behavior.

325-331: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the inclusive flood-fill bounds.

maxX/maxY track inclusive pixel coordinates, while BubbleBox.Width is the number of pixels to crop. After the 2 px inset, maxX - minX/maxY - minY makes every refined box one pixel narrower in each dimension, so compensate the width/height for the inclusive end bounds.

Proposed fix
-        return new BubbleBox(minX, minY, maxX - minX, maxY - minY, box.Confidence);
+        return new BubbleBox(minX, minY, maxX - minX + 1, maxY - minY + 1, box.Confidence);
🤖 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 `@server/src/Services/BubbleDetectionService.cs` around lines 325 - 331, Update
the refined BubbleBox construction after the EdgeInset bounds validation to
account for inclusive maxX/maxY coordinates: calculate width and height with the
inclusive endpoint adjustment so the cropped dimensions retain both boundary
pixels, while leaving the fallback return box unchanged.
server/src/Routes/OcrRoutes.cs (2)

104-110: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

JpegToPng silently returns non-PNG bytes on decode failure.

When SKBitmap.Decode fails, the method returns the original JPEG bytes but the caller (Line 65) still labels them PngBytes and forwards them into PageTranslationItem. Downstream, PageTranslationService.TranslatePageAsync/TypesettingService.RenderTranslations name the parameter imagePng, implying a PNG is expected — passing mismatched-format bytes through the pipeline risks silent corruption or an obscure failure instead of a clear, attributable one.

🐛 Proposed fix
     private static byte[] JpegToPng(byte[] jpeg)
     {
         using var bmp = SKBitmap.Decode(jpeg);
-        if (bmp is null) return jpeg;
+        if (bmp is null) throw new InvalidOperationException("Failed to decode image for page-translation job");
         using var imgData = bmp.Encode(SKEncodedImageFormat.Png, 100);
         return imgData.ToArray();
     }
🤖 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 `@server/src/Routes/OcrRoutes.cs` around lines 104 - 110, Update JpegToPng so
SKBitmap.Decode failure does not return the original JPEG bytes; instead, fail
explicitly with an attributable exception or propagate a clear conversion
failure. Preserve the existing PNG encoding path for successfully decoded
bitmaps and ensure the caller does not forward non-PNG data as PngBytes.

60-67: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Isolate the job-tracking enqueue from the primary OCR response.

The translationQueue.Writer.WriteAsync(..., ct) call is inline in the request path, unguarded by a try/catch, and the channel is bounded (capacity 32, FullMode = Wait per PageTranslationQueue.cs). Two consequences:

  • If the write throws (client cancels ct, or the channel is somehow closed), the whole /ocr response fails with a 500 even though the OCR result was already successfully computed — a secondary, best-effort feature (extension result push) taking down the primary response.
  • If the channel is full (worker still draining a prior full OCR→translate→typeset pipeline), this await blocks the response until a slot frees, coupling ordinary OCR latency to an unrelated background feature's backpressure.

Wrap the enqueue in a try/catch (returning jobId = null on failure) and consider TryWrite with a fallback instead of blocking indefinitely on a full channel.

🛡️ Proposed fix
             string? jobId = null;
             if (req.TrackJob == true && boot.IsReady)
             {
                 jobId = Guid.NewGuid().ToString("N");
                 var pngBytes = JpegToPng(imageBytes);
-                await translationQueue.Writer.WriteAsync(new PageTranslationItem(jobId, pngBytes), ct);
+                try
+                {
+                    if (!translationQueue.Writer.TryWrite(new PageTranslationItem(jobId, pngBytes)))
+                        jobId = null; // queue full — skip tracking rather than blocking the response
+                }
+                catch (Exception ex)
+                {
+                    logger.LogWarning(ex, "Failed to enqueue page-translation job {JobId}", jobId);
+                    jobId = null;
+                }
             }
🤖 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 `@server/src/Routes/OcrRoutes.cs` around lines 60 - 67, Isolate the optional
enqueue in the OCR route so translation-queue failures cannot fail or delay the
primary response. Update the job-tracking block around translationQueue.Writer
and PageTranslationItem to use a non-blocking enqueue such as TryWrite, catch
enqueue exceptions including cancellation or a closed channel, and reset jobId
to null whenever enqueue does not succeed.
🤖 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 `@extension/src/background.ts`:
- Around line 231-283: Add a failure message type alongside JobResultReadyMsg,
then update handlePollAlarm to send a job-result-error notification via
sendToTab with the job identifier before clearing session state on deadline
expiry, server-reported status === "error", and reaching MAX_CONSECUTIVE_ERRORS.
Preserve the existing cleanup and return behavior for each terminal path.

---

Outside diff comments:
In `@server/src/Routes/OcrRoutes.cs`:
- Around line 104-110: Update JpegToPng so SKBitmap.Decode failure does not
return the original JPEG bytes; instead, fail explicitly with an attributable
exception or propagate a clear conversion failure. Preserve the existing PNG
encoding path for successfully decoded bitmaps and ensure the caller does not
forward non-PNG data as PngBytes.
- Around line 60-67: Isolate the optional enqueue in the OCR route so
translation-queue failures cannot fail or delay the primary response. Update the
job-tracking block around translationQueue.Writer and PageTranslationItem to use
a non-blocking enqueue such as TryWrite, catch enqueue exceptions including
cancellation or a closed channel, and reset jobId to null whenever enqueue does
not succeed.

In `@server/src/Services/BubbleDetectionService.cs`:
- Around line 297-323: Bound the flood fill in RefineToInnerBoundary with an
image/box-area threshold and return the existing safe fallback when the cap is
exceeded, avoiding unbounded bool[w,h] allocation or traversal. Thread the
existing cancellation token from Detect into RefineToInnerBoundary and check it
during initialization and the queue-processing loop, propagating cancellation
promptly without changing normal refinement behavior.
- Around line 325-331: Update the refined BubbleBox construction after the
EdgeInset bounds validation to account for inclusive maxX/maxY coordinates:
calculate width and height with the inclusive endpoint adjustment so the cropped
dimensions retain both boundary pixels, while leaving the fallback return box
unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7eb9c551-8c8e-4968-a4ee-35b7df9acf5c

📥 Commits

Reviewing files that changed from the base of the PR and between 0f819ac and c586e62.

⛔ Files ignored due to path filters (1)
  • server/wwwroot/js/app.js is excluded by !server/wwwroot/js/app.js
📒 Files selected for processing (12)
  • docs/feature-plan-next.md
  • extension/package.json
  • extension/src/background.ts
  • extension/static/manifest.json
  • server/ClientApp/src/pages/JobsListPage.tsx
  • server/ClientApp/src/pages/LibraryPage.tsx
  • server/ClientApp/src/pages/StudioPage.tsx
  • server/src/Routes/OcrRoutes.cs
  • server/src/ServiceExtensions.cs
  • server/src/Services/BubbleDetectionService.cs
  • server/src/Workers/PageTranslationQueue.cs
  • server/src/Workers/PageTranslationWorker.cs
🚧 Files skipped from review as they are similar to previous changes (6)
  • extension/package.json
  • server/ClientApp/src/pages/JobsListPage.tsx
  • server/ClientApp/src/pages/LibraryPage.tsx
  • extension/static/manifest.json
  • docs/feature-plan-next.md
  • server/ClientApp/src/pages/StudioPage.tsx

Comment thread extension/src/background.ts
- extension: add JobResultErrorMsg to ToContentMsg union; handlePollAlarm
  sends job-result-error on timeout, server-error, and network-error paths
- server/OcrRoutes: throw InvalidOperationException on JpegToPng decode
  failure instead of silently returning corrupt JPEG bytes
- server/OcrRoutes: replace blocking WriteAsync with non-blocking TryWrite
  for the page-translation job queue; catch enqueue failures so a full or
  closed queue never delays or fails the primary OCR response
- server/BubbleDetectionService: add CancellationToken to Detect /
  DetectRtDetr / DetectYolo / RefineToInnerBoundary; add MaxBfsArea cap
  (1 M pixels) to RefineToInnerBoundary to prevent unbounded allocation;
  fix inclusive pixel-count return (maxX - minX + 1, maxY - minY + 1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@deckyfx

deckyfx commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@deckyfx

deckyfx commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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