feat(studio-v2): Studio V2 — text style pipeline, layout redesign, canvas handles - #9
Conversation
…nvas handles
Phase 1 — server text-style pipeline:
- EF Core migration adds font_color, stroke_color, stroke_width, rotation,
text_align columns to PageTranslationLog
- TypesettingService applies all five style fields when burning text onto
the inpainted image (SkiaSharp: color, stroke, rotation, alignment)
- PageTranslationService maps new fields in MapBubble; PortalRoutes PATCH
accepts and persists them via UpdateBubbleBody
- POST /api/portal/jobs/{id}/inpaint runs white-fill inpaint asynchronously
through the InferenceQueue BackgroundService (status: processing→done/error)
Phase 2 — extension postMessage bridge:
- content.ts listens for window.postMessage web-ocr:image-updated from Studio,
relays to background as image-updated-relay
- background.ts handles relay → replacePageImages in the active tab
- types.ts: ImageUpdatedMsg + ImageUpdatedRelayMsg added
- Fix TS2339: src.dataset → img.dataset in replacePageImages
Phase 3 — Studio V2 frontend layout:
- StudioPage fully rewritten: STAGE_ORDER constant + sortedActiveStages memo
always places the earlier pipeline stage on the left regardless of toggle order
- Left and right panels independently collapsible via chevron buttons
- Split left panel: Stage 1 section (BubbleList) + Stage 3 section (text
overlay list) stacked vertically when both stages are active
- panelContext signal ("stage1" | "stage3" | null) drives context-sensitive
right panel: BubbleEditor for stage1 selections, TextStyleEditor for stage3
- Context-sensitive toolbar: Stage 1 → Detect + Inpaint; Stage 3 → Auto Texts
+ Burn Texts; both sets shown simultaneously when both stages are active
- handleBurnTexts posts window.postMessage web-ocr:image-updated after burn
- handleAutoTexts: redetect → poll → retranslate → poll → refetch
- New TextStyleEditor component: font family/size/color, stroke color/width,
text-align toggle, rotation slider + number input, optional delete button
Phase 4 — Stage 3 canvas rotation handle:
- BubbleCanvas: DragRotating state (centerSvgX/Y, startAngle, initialRotation,
currentRotation) tracks rotation during drag with live preview
- onRotate optional prop; committed on mouseup
- Rotation handle: dashed stem + white circle above top-centre of bounding box;
live angle badge (e.g. 45°) shown during drag
- Group SVG transform rotate(deg cx cy) applied only in showTextOverlay mode —
Stage 1 detection boxes are unaffected
- Stage 3 move and resize now wired to real handlers (previously no-ops)
- Text overlay respects actual bubble styles: fontColor, textAlign,
fontSizeOverride, -webkit-text-stroke scaled to canvas zoom
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughStudio V2 adds four-stage editing with read-only stages, text styling and rotation controls, backend persistence and rendering, inpainting workflows, and extension-based image refresh notifications. ChangesStudio V2
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant StudioPage
participant PortalRoutes
participant PageTranslationService
participant TypesettingService
participant ContentScript
participant BackgroundScript
StudioPage->>PortalRoutes: Update bubble styling or start inpainting
PortalRoutes->>PageTranslationService: Persist styling or run inpainting
PageTranslationService->>TypesettingService: Render styled translation
TypesettingService-->>StudioPage: Updated image
StudioPage->>ContentScript: Post image-updated event
ContentScript->>BackgroundScript: Relay job ID and result URL
BackgroundScript->>ContentScript: Broadcast image-updated notification
ContentScript->>ContentScript: Replace matching page images
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
server/ClientApp/src/components/TextStyleEditor.tsx (2)
296-335: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRotation number input loses intent while typing a negative value.
parseInt("-", 10) || 0collapses an in-progress-(and-1→ fine, but a cleared field) to0, snapping the slider and committing0on change. Guarding onNumber.isNaNkeeps the previous value instead.♻️ Proposed change
- onInput={(e) => { - const val = Math.min(180, Math.max(-180, parseInt(e.currentTarget.value, 10) || 0)); - setRotation(val); - }} + onInput={(e) => { + const n = parseInt(e.currentTarget.value, 10); + if (Number.isNaN(n)) return; + setRotation(Math.min(180, Math.max(-180, n))); + }}🤖 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/TextStyleEditor.tsx` around lines 296 - 335, Update the rotation number input handlers in TextStyleEditor’s Rotation control so an invalid or in-progress value such as "-" does not fall back to 0. Parse the input, use Number.isNaN to retain the current rotation when parsing fails, and apply the clamped value only when valid; preserve committing the resulting rotation in the onChange handler.
267-292: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winIcon-only alignment buttons need an explicit accessible name and
type="button".
titlealone is an unreliable accessible name for screen readers, and buttons without an explicit type default tosubmitif this editor is ever nested in a form.♻️ Proposed change
<button + type="button" onClick={() => { setTextAlign(align); commit("textAlign", align); }} title={align.charAt(0).toUpperCase() + align.slice(1)} + aria-label={`Align ${align}`} aria-pressed={isActive()} >🤖 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/TextStyleEditor.tsx` around lines 267 - 292, Update the alignment buttons rendered in the map within TextStyleEditor so each icon-only button has an explicit accessible name, such as an aria-label derived from align, and explicitly sets type="button" to prevent form submission. Preserve the existing title, click behavior, and pressed-state handling.server/src/Routes/PortalRoutes.cs (1)
393-433: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFourth copy of the same fire-and-forget job scaffolding.
redetect,retranslate,rerender, and nowinpaintall repeat identical scope/status/error-handling boilerplate. Consider extracting a local helper so status transitions and error reporting stay consistent as more job actions are added.♻️ Sketch
static IResult StartJobAction( string id, PageTranslationService pipeline, ILogger logger, Func<Task> work) { _ = Task.Run(async () => { using var scope = pipeline.CreateScope(); var db2 = scope.ServiceProvider.GetRequiredService<AppDbContext>(); try { await SetStatusAsync(db2, id, "processing"); await work(); await SetStatusAsync(db2, id, "done"); } catch (Exception ex) { logger.LogError(ex, "Job action failed for {Id}", id); try { await SetStatusAsync(db2, id, "error", ex.Message); } catch { } } }); return Results.Accepted(null, new { id }); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/Routes/PortalRoutes.cs` around lines 393 - 433, Extract the repeated fire-and-forget status and error-handling flow from the redetect, retranslate, rerender, and inpaint route handlers into a shared local helper such as StartJobAction, with a reusable status-update helper if needed. Update each handler, including the inpaint endpoint around InpaintOnlyAsync, to delegate processing, preserve processing/done/error transitions and error logging, and continue returning Accepted with the job id.
🤖 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/PLAN_studio_v2.md`:
- Line 179: Update docs/PLAN_studio_v2.md references to use PUT for the bubble
update endpoint, matching the implemented updateBubble client method and portal
route. Replace the outdated SSE mechanism descriptions in the affected sections
with the current non-SSE behavior, while preserving the existing endpoint paths
and workflow details.
In `@extension/src/content.ts`:
- Around line 339-352: Update replacePageImages so assigning resultUrl only
occurs for images belonging to the updated job, using the job-specific socrJobId
marker or an equivalently job-scoped URL match; remove the broad
src.pathname.includes("/result") condition that affects unrelated jobs.
In `@server/ClientApp/src/components/BubbleCanvas.tsx`:
- Around line 450-472: Update the resize-handle interaction in
handleHandleMouseDown/applyResize to account for the rotation applied by
groupTransform: inverse-rotate pointer drag deltas by -rotation before passing
them to applyResize, while preserving existing behavior for unrotated bubbles
and keeping the handles visually aligned with the rotated group.
In `@server/ClientApp/src/pages/StudioPage.tsx`:
- Around line 149-159: Update handleSelectStage1 and handleSelectStage3 so the
deselection comparison includes panelContext, not only selectedIndex(). Toggle
off only when the clicked index matches the current selection and the active
context is the same stage; otherwise retain the index and switch panelContext to
the clicked stage.
- Around line 576-632: Introduce a shared busy condition for the four stage
actions in StudioPage, combining isRedetecting(), isInpainting(), isAutoTexts(),
and isBurning(). Apply this guard to the disabled conditions of the Detect,
Inpaint, Auto Texts, and Burn Texts buttons so no action can start while any
other job-level action is running, while preserving each action’s existing
loading behavior.
In `@server/src/Services/PageTranslationService.cs`:
- Around line 497-500: Update TypesettingService.RenderOneBubble so its
RenderTextInBubble call forwards BubbleTranslation’s FontColor, StrokeColor,
StrokeWidth, Rotation, and TextAlign values in addition to the existing text,
font, and size fields, ensuring repatch rendering matches full rerender styling.
In `@server/src/Services/TypesettingService.cs`:
- Around line 243-247: Update the canvas setup in the typesetting flow around
ClipRect and RotateDegrees so rotated overlays are not clipped by the unrotated
bubble bounds. When rotation is present, skip the bubble ClipRect or use an
appropriately inflated clip region; preserve the current bubble-bound clipping
behavior when no rotation is applied.
---
Nitpick comments:
In `@server/ClientApp/src/components/TextStyleEditor.tsx`:
- Around line 296-335: Update the rotation number input handlers in
TextStyleEditor’s Rotation control so an invalid or in-progress value such as
"-" does not fall back to 0. Parse the input, use Number.isNaN to retain the
current rotation when parsing fails, and apply the clamped value only when
valid; preserve committing the resulting rotation in the onChange handler.
- Around line 267-292: Update the alignment buttons rendered in the map within
TextStyleEditor so each icon-only button has an explicit accessible name, such
as an aria-label derived from align, and explicitly sets type="button" to
prevent form submission. Preserve the existing title, click behavior, and
pressed-state handling.
In `@server/src/Routes/PortalRoutes.cs`:
- Around line 393-433: Extract the repeated fire-and-forget status and
error-handling flow from the redetect, retranslate, rerender, and inpaint route
handlers into a shared local helper such as StartJobAction, with a reusable
status-update helper if needed. Update each handler, including the inpaint
endpoint around InpaintOnlyAsync, to delegate processing, preserve
processing/done/error transitions and error logging, and continue returning
Accepted with the job id.
🪄 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: ae1c131a-057c-444e-a113-74f443dabe01
⛔ Files ignored due to path filters (2)
server/wwwroot/js/app.cssis excluded by!server/wwwroot/js/app.cssserver/wwwroot/js/app.jsis excluded by!server/wwwroot/js/app.js
📒 Files selected for processing (18)
docs/PLAN_studio_v2.mdextension/package.jsonextension/src/background.tsextension/src/content.tsextension/src/types.tsextension/static/manifest.jsonserver/ClientApp/src/api.tsserver/ClientApp/src/components/BubbleCanvas.tsxserver/ClientApp/src/components/TextStyleEditor.tsxserver/ClientApp/src/pages/StudioPage.tsxserver/ClientApp/src/types.tsserver/Migrations/20260730234211_AddTextStyleProperties.Designer.csserver/Migrations/20260730234211_AddTextStyleProperties.csserver/Migrations/AppDbContextModelSnapshot.csserver/src/Data/AppDbContext.csserver/src/Routes/PortalRoutes.csserver/src/Services/PageTranslationService.csserver/src/Services/TypesettingService.cs
| New component: `server/ClientApp/src/components/TextStyleEditor.tsx` | ||
|
|
||
| Fields: textarea for translated text, font family dropdown, font size number, color picker (FontColor), stroke color picker, stroke width, rotation number input, alignment toggle (L/C/R). | ||
| On change: calls `PATCH /api/portal/jobs/{id}/bubbles/{index}` with updated fields. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Doc references a PATCH bubble route that doesn't exist.
The implemented endpoint is PUT /api/portal/jobs/{jobId}/bubbles/{bubbleIndex} (server/src/Routes/PortalRoutes.cs Line 153, and updateBubble in server/ClientApp/src/api.ts uses PUT). Lines 179 and 260 say PATCH. Also Lines 194, 243, 246 and 285 still describe an "SSE" mechanism that Line 261 correctly says isn't needed.
Also applies to: 259-261
🤖 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 `@docs/PLAN_studio_v2.md` at line 179, Update docs/PLAN_studio_v2.md references
to use PUT for the bubble update endpoint, matching the implemented updateBubble
client method and portal route. Replace the outdated SSE mechanism descriptions
in the affected sections with the current non-SSE behavior, while preserving the
existing endpoint paths and workflow details.
- content.ts: scope replacePageImages to socrJobId only; drop broad `/result` path match that could overwrite images from other jobs - BubbleCanvas: store bubble rotation in DragResizing; inverse-rotate drag delta before applyResize so handles behave correctly on rotated Stage-3 overlays - StudioPage: handleSelectStage1/Stage3 deselect only when both index AND panelContext match, preventing spurious deselect when switching the same bubble between Stage 1 and Stage 3 - StudioPage: cross-stage busy guard — all four action buttons now disable while any one job-level action is running - TypesettingService.RenderOneBubble: forward FontColor, StrokeColor, StrokeWidth, Rotation, TextAlign to RenderTextInBubble so per-bubble repatch renders identically to full rerender - TypesettingService.RenderTextInBubble: skip axis-aligned ClipRect when rotation is applied; clipping in unrotated space was chopping off rotated glyphs at bubble-box edges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary
Phase 1 (server): EF Core migration adds 5 text-style columns (
font_color,stroke_color,stroke_width,rotation,text_align) toPageTranslationLog.TypesettingServiceburns all five styles onto the result image.PortalRoutesPATCH +PageTranslationServiceaccept and persist them. NewPOST /api/portal/jobs/{id}/inpaintendpoint queues async white-fill inpaint through the existingInferenceQueueBackgroundService.Phase 2 (extension bridge):
content.tslistens forwindow.postMessage({ type: "web-ocr:image-updated" })from Studio and relays it tobackground.ts→replacePageImagesin the active tab. NewImageUpdatedMsg/ImageUpdatedRelayMsgtypes. Fixed pre-existing TS2339 (src.dataset→img.dataset).Phase 3 (Studio V2 layout):
StudioPagefully rewritten —STAGE_ORDER+createMemosorts active stages so the earlier pipeline stage always renders on the left. Left/right panels are independently collapsible. Left panel splits into a Stage 1 section (BubbleList) and a Stage 3 section (text overlay list), stacked when both are active.panelContextsignal drives context-sensitive right panel (BubbleEditorvs newTextStyleEditor). Toolbar shows Stage 1 actions (Detect, Inpaint) and/or Stage 3 actions (Auto Texts, Burn Texts) based on active stages.handleBurnTextspostsweb-ocr:image-updatedafter burn. NewTextStyleEditorcomponent covers font, stroke, text-align, and rotation.Phase 4 (canvas rotation handle):
BubbleCanvasgains aDragRotatingstate and optionalonRotateprop. A dashed stem + circle handle appears above the selected overlay's top-centre in Stage 3; dragging it rotates the overlay with a live angle badge. The bubble<g>receives an SVGrotate(deg cx cy)transform only inshowTextOverlaymode so Stage 1 detection boxes are unaffected. Stage 3 move and resize now call real server handlers. Text overlay preview respects all bubble style fields.Test plan
dotnet build WebOcr.slnxdotnet ef database updatecd server && bun run typecheckcd server && bun run buildcd extension && bun run build🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes