Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Dec 18, 2025

Improves the caption and mask editing experience in the desktop app, with a particular emphasis on simplifying font and style controls, enhancing color input usability, and adding new options for fade duration. Also improves the download progress reporting for Whisper models.

Summary by CodeRabbit

  • New Features

    • Font weight control for captions (400 / 500 / 700)
    • Fade duration controls for text and mask segments (0–1s)
  • Improvements

    • Time-aware multi-segment rendering and selection for text and masks
    • Real-time text annotation editing in screenshot editor
    • UI tweaks: segment truncation, handle visibility/layout, and caption outline default changed to off

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Warning

Rate limit exceeded

@richiemcilroy has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 27 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 906851e and 39fab9a.

📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/windows.rs (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/MicrophoneSelect.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/TargetDropdownButton.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/new-main/index.tsx (1 hunks)
  • apps/desktop/src/routes/(window-chrome)/settings/general.tsx (1 hunks)
  • apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx (3 hunks)
  • apps/desktop/src/utils/tauri.ts (3 hunks)

Walkthrough

Replaces boolean caption bold with numeric font_weight, adds fadeDuration to text/mask segments, refactors TextOverlay/MaskOverlay to render multiple time-visible segments, moves Whisper model download progress emission from Window to AppHandle, and updates rendering to honor per-segment fade and opacity.

Changes

Cohort / File(s) Summary
Whisper download & progress
apps/desktop/src-tauri/src/captions.rs
Removed window param from download_whisper_model; progress emitted via AppHandle (streaming bytes), instrumentation updated to skip app.
Caption settings & storage
apps/desktop/src/store/captions.ts, apps/desktop/src/routes/editor/CaptionsTab.tsx, apps/desktop/src/utils/tauri.ts
Replaced bold/wordTransitionDuration with numeric fontWeight (default 700); outline default changed to false; parsing/serialization updated; UI adds Font Weight selector.
Config UI & style controls
apps/desktop/src/routes/editor/ConfigSidebar.tsx
Replaced Font options with reduced Style/weight variants; added per-segment Fade Duration sliders; color input structure adjusted; size scaling logic tweaked.
Text & Mask segment models
apps/desktop/src/routes/editor/text.ts, apps/desktop/src/routes/editor/masks.ts, crates/project/src/configuration.rs
Added fadeDuration/fade_duration (defaults 0.15) to TextSegment and MaskSegment; added font_weight/fontWeight to CaptionSettings (default 700); removed bold.
Overlay multi-segment refactor
apps/desktop/src/routes/editor/TextOverlay.tsx, apps/desktop/src/routes/editor/MaskOverlay.tsx
Time-aware visibility, render multiple segment overlays, per-segment selection/drag/resize handlers, new measurement/sizing flow, background-click clearing.
Rendering: font weight, fade & opacity
crates/rendering/src/layers/captions.rs, crates/rendering/src/mask.rs, crates/rendering/src/text.rs, crates/rendering/src/layers/text.rs
Caption weight now numeric (NORMAL/MEDIUM/BOLD by thresholds); mask fade uses per-segment fade_duration; PreparedText gains opacity and font-size capped; color alpha multiplied by opacity.
Timeline / Track UI tweaks
apps/desktop/src/routes/editor/Timeline/TextTrack.tsx, apps/desktop/src/routes/editor/Timeline/Track.tsx
Truncation/overflow refinements; SegmentHandle visibility threshold and sizing/position adjustments.
Screenshot annotation edits
apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx
Added onInput live text updates; refined onBlur snapshot logic.
Minor / formatting
crates/video-decode/src/avassetreader.rs, other small imports
Minor error message formatting and import adjustments (StreamExt, Event); removed EVENT_NAME constant and Window imports where replaced by AppHandle.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas to focus:
    • TextOverlay.tsx — measurement, sizing, drag/resize correctness and lifecycle
    • MaskOverlay.tsx — visibility filtering and selection handlers
    • Rendering changes — font weight thresholds, opacity/fade computations, MAX_FONT_SIZE_PX cap
    • Tauri/native boundary — download_whisper_model emission via AppHandle

Possibly related PRs

Suggested labels

Desktop

Suggested reviewers

  • oscartbeaumont
  • Brendonovich

Poem

🐰 I hopped through weights and fades with glee,
Streams now whisper progress back to me.
Segments bloom and gently fade away,
Multi-overlays dance in bright array.
✨ A tiny rabbit cheers the new display!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main changes: text dragging improvements, caption model downloading enhancements, and other UI improvements across the editor.

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
Contributor

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src-tauri/src/captions.rs (1)

2086-2090: Log emit failures instead of silently discarding them with .ok()

The emit() method returns Result<>(), which must be handled per the coding guidelines. Currently at line 2157, the error is discarded via .ok(), preventing visibility into repeated emit failures.

Replace:

.emit(&app).ok();

With proper error logging:

if let Err(error) = DownloadProgress {
    progress,
    message: format!("Downloading model: {progress:.1}%"),
}
.emit(&app)
{
    tracing::warn!("Failed to emit DownloadProgress: {error}");
}

This surfaces emit issues without breaking the download.

🧹 Nitpick comments (2)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (2)

2635-2698: KSelect “Style” control should use an option from the options array as its value

The Style select declares options for {label, value} with weights 400/500/700, but passes a literal { label: "Custom", value: props.segment.fontWeight } as value. Depending on KSelect’s API, this can mean state.selectedOption() is undefined even when the font weight matches one of the predefined options, which may affect focus/selection semantics and accessibility.

Since you already compute the label fallback based on props.segment.fontWeight, a safer pattern is to derive value from the options list:

const FONT_WEIGHT_OPTIONS = [
  { label: "Normal", value: 400 },
  { label: "Medium", value: 500 },
  { label: "Bold", value: 700 },
] as const;

<KSelect
  options={FONT_WEIGHT_OPTIONS}
  optionValue="value"
  optionTextValue="label"
  value={
    FONT_WEIGHT_OPTIONS.find(o => o.value === props.segment.fontWeight)
      ?? FONT_WEIGHT_OPTIONS[0]
  }
  // ...
/>

This keeps KSelect’s notion of the selected option aligned with the actual weight while still letting you treat out‑of‑range weights as “Custom” in the label if desired.


2723-2736: Fade duration sliders for text and mask segments are wired consistently

Both the TextSegment and MaskSegment “Fade Duration” sliders bind to segment.fadeDuration in the [0,1] range with a default of 0.15, matching the new Rust defaults and caption settings. The additional clampNumber on the text slider helps sanitize any NaNs or out‑of‑range values; you might optionally apply the same clamp to the mask slider for symmetry, but it isn’t strictly necessary given the Slider’s own bounds.

Also applies to: 2895-2907

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb5c789 and f32226f.

📒 Files selected for processing (18)
  • apps/desktop/src-tauri/src/captions.rs (5 hunks)
  • apps/desktop/src/routes/editor/CaptionsTab.tsx (4 hunks)
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx (6 hunks)
  • apps/desktop/src/routes/editor/MaskOverlay.tsx (5 hunks)
  • apps/desktop/src/routes/editor/TextOverlay.tsx (3 hunks)
  • apps/desktop/src/routes/editor/Timeline/TextTrack.tsx (2 hunks)
  • apps/desktop/src/routes/editor/Timeline/Track.tsx (1 hunks)
  • apps/desktop/src/routes/editor/masks.ts (2 hunks)
  • apps/desktop/src/routes/editor/text.ts (2 hunks)
  • apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx (1 hunks)
  • apps/desktop/src/store/captions.ts (2 hunks)
  • apps/desktop/src/utils/tauri.ts (3 hunks)
  • crates/project/src/configuration.rs (7 hunks)
  • crates/rendering/src/layers/captions.rs (1 hunks)
  • crates/rendering/src/layers/text.rs (1 hunks)
  • crates/rendering/src/mask.rs (1 hunks)
  • crates/rendering/src/text.rs (3 hunks)
  • crates/video-decode/src/avassetreader.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use React Query hooks with Server Actions for mutations and perform precise cache updates using setQueryData/setQueriesData instead of broad invalidations

Files:

  • apps/desktop/src/routes/editor/Timeline/TextTrack.tsx
  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/Timeline/Track.tsx
  • apps/desktop/src/routes/editor/CaptionsTab.tsx
  • apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use strict TypeScript; avoid any type; leverage shared types from @cap/* packages
Follow camelCase naming for variables and functions; PascalCase for components; hooks must start with 'use' prefix
Use Biome for linting and formatting; match existing formatting conventions in the codebase
Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens
Use static skeletons for loading states that mirror content; avoid bouncing animations
Memoize expensive work, code-split naturally, and use Next/Image for remote assets

**/*.{ts,tsx,js,jsx}: Use 2-space indent for TypeScript files; format with Biome using pnpm format
Use Biome for code formatting and linting; run pnpm format regularly
Use kebab-case for file names (e.g., user-menu.tsx); use PascalCase for components

Files:

  • apps/desktop/src/routes/editor/Timeline/TextTrack.tsx
  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/text.ts
  • apps/desktop/src/store/captions.ts
  • apps/desktop/src/routes/editor/Timeline/Track.tsx
  • apps/desktop/src/routes/editor/masks.ts
  • apps/desktop/src/routes/editor/CaptionsTab.tsx
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never add any form of comments to code (single-line //, multi-line /* /, JSDoc /* */, or any other comment syntax); code must be self-explanatory through naming, types, and structure
Directory naming must use lowercase-dashed convention

Files:

  • apps/desktop/src/routes/editor/Timeline/TextTrack.tsx
  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/text.ts
  • apps/desktop/src/store/captions.ts
  • apps/desktop/src/routes/editor/Timeline/Track.tsx
  • apps/desktop/src/routes/editor/masks.ts
  • apps/desktop/src/routes/editor/CaptionsTab.tsx
  • apps/desktop/src/utils/tauri.ts
  • apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • apps/desktop/src/routes/editor/Timeline/TextTrack.tsx
  • crates/rendering/src/layers/text.rs
  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/text.ts
  • crates/rendering/src/mask.rs
  • apps/desktop/src/store/captions.ts
  • apps/desktop/src/routes/editor/Timeline/Track.tsx
  • crates/rendering/src/text.rs
  • apps/desktop/src/routes/editor/masks.ts
  • apps/desktop/src/routes/editor/CaptionsTab.tsx
  • crates/rendering/src/layers/captions.rs
  • apps/desktop/src/utils/tauri.ts
  • crates/project/src/configuration.rs
  • crates/video-decode/src/avassetreader.rs
  • apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
  • apps/desktop/src-tauri/src/captions.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • crates/rendering/src/layers/text.rs
  • crates/rendering/src/mask.rs
  • crates/rendering/src/text.rs
  • crates/rendering/src/layers/captions.rs
  • crates/project/src/configuration.rs
  • crates/video-decode/src/avassetreader.rs
  • apps/desktop/src-tauri/src/captions.rs
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TanStack Query v5 for all client-side server state and data fetching in TypeScript files

Files:

  • apps/desktop/src/routes/editor/text.ts
  • apps/desktop/src/store/captions.ts
  • apps/desktop/src/routes/editor/masks.ts
  • apps/desktop/src/utils/tauri.ts
apps/desktop/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/**/*.ts: Use @tanstack/solid-query for server state management in SolidJS components
Use generated commands and events from tauri_specta for IPC; never manually construct IPC calls
Listen directly to generated events from tauri_specta and use typed event interfaces

Files:

  • apps/desktop/src/routes/editor/text.ts
  • apps/desktop/src/store/captions.ts
  • apps/desktop/src/routes/editor/masks.ts
  • apps/desktop/src/utils/tauri.ts
🧠 Learnings (6)
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens

Applied to files:

  • apps/desktop/src/routes/editor/Timeline/TextTrack.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/web/**/*.tsx : Minimize useEffect usage in React components; compute during render, handle logic in event handlers, and ensure cleanups for subscriptions/timers

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Use `useEffectQuery`/`useEffectMutation` from `@/lib/EffectRuntime` in client components; do not call `EffectRuntime.run*` directly

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/desktop/**/*.ts : Listen directly to generated events from tauri_specta and use typed event interfaces

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/desktop/**/*.ts : Use tanstack/solid-query for server state management in SolidJS components

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-11-19T18:25:41.457Z
Learnt from: ItsEeleeya
Repo: CapSoftware/Cap PR: 1396
File: apps/desktop/src/routes/(window-chrome)/settings/general.tsx:183-192
Timestamp: 2025-11-19T18:25:41.457Z
Learning: In SolidJS, when using the Show component with the `keyed` attribute and a dynamic reactive value (like a memo result), the component will recreate its children when the condition value changes by reference. This pattern is useful for triggering CSS animations (like fade-in) when the rendered content needs to update. For example, in apps/desktop/src/routes/(window-chrome)/settings/general.tsx, `<Show when={previews()[theme.id]} keyed>` ensures the image re-animates when switching between different preview sources.

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
🧬 Code graph analysis (4)
apps/desktop/src/routes/editor/MaskOverlay.tsx (2)
apps/desktop/src/routes/editor/masks.ts (2)
  • MaskSegment (22-35)
  • evaluateMask (61-76)
apps/desktop/src/store/captions.ts (1)
  • updateSegment (66-75)
apps/desktop/src/utils/tauri.ts (2)
apps/desktop/src/routes/editor/masks.ts (3)
  • MaskSegment (22-35)
  • MaskKind (3-3)
  • MaskKeyframes (16-20)
apps/desktop/src/routes/editor/text.ts (1)
  • TextSegment (3-16)
apps/desktop/src/routes/editor/TextOverlay.tsx (2)
apps/desktop/src/routes/editor/text.ts (1)
  • TextSegment (3-16)
apps/desktop/src/utils/tauri.ts (1)
  • TextSegment (498-498)
apps/desktop/src-tauri/src/captions.rs (1)
apps/desktop/src-tauri/src/upload.rs (5)
  • app (712-713)
  • app (794-795)
  • stream (93-93)
  • progress (404-414)
  • progress (855-955)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Clippy (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (32)
crates/video-decode/src/avassetreader.rs (1)

174-174: LGTM - Good formatting improvement.

The change from positional to named parameter formatting ({other:?} instead of {:?}, other) improves readability and is consistent with modern Rust formatting practices. The output remains identical, and this makes the format string more self-documenting.

apps/desktop/src/routes/editor/text.ts (2)

15-15: LGTM: fadeDuration field addition is consistent with the broader PR changes.

The new fadeDuration property with a default of 0.15 aligns with the same pattern applied to MaskSegment and caption settings across the codebase.

Also applies to: 33-33


27-27: Verify the intent of the very small default size.

The default size changed to { x: 0.01, y: 0.01 } (1% of viewport), which is significantly smaller than typical defaults. This may be intentional for auto-sizing behavior where actual dimensions are computed dynamically, but could result in nearly invisible text if used directly without adjustment.

apps/desktop/src/utils/tauri.ts (1)

381-381: Generated file - types align with Rust backend changes.

This file is auto-generated by tauri-specta. The new optional fields (fontWeight in CaptionSettings, fadeDuration in MaskSegment and TextSegment) correctly reflect the corresponding Rust type definitions.

Also applies to: 439-439, 498-498

apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx (2)

570-573: LGTM: Real-time text updates improve UX.

The onInput handler correctly updates the annotation text in the store as the user types, using the proper SolidJS store update pattern.


582-589: LGTM: History snapshot logic is now more precise.

The updated logic correctly avoids pushing duplicate history entries by comparing against the original text from textSnapshot. This prevents unnecessary undo stack entries when the user opens and closes the text editor without making changes.

crates/rendering/src/layers/captions.rs (1)

545-551: LGTM: Font weight logic follows CSS conventions.

The numeric font weight thresholds (≥700 → BOLD, ≥500 → MEDIUM, else NORMAL) correctly align with standard CSS font-weight semantics and the UI options (400/500/700) provided in the editor.

apps/desktop/src/routes/editor/masks.ts (1)

33-33: LGTM: fadeDuration addition is consistent with TextSegment.

The new fadeDuration field with a default of 0.15 matches the pattern established in TextSegment and provides consistent fade behavior across mask and text overlays.

Also applies to: 57-57

crates/rendering/src/text.rs (4)

4-4: LGTM! Font size cap is reasonable.

The MAX_FONT_SIZE_PX constant provides a sensible upper bound to prevent excessively large font sizes that could cause rendering issues.


15-15: LGTM! Opacity field supports fade animation.

The new opacity field correctly extends PreparedText to support per-segment fade animations.


75-86: LGTM! Fade logic is correctly implemented.

The fade duration calculation properly handles both fade-in and fade-out transitions:

  • Clamps fade_duration to non-negative values
  • Computes time-based fade coefficients correctly
  • Multiplies fade-in and fade-out for smooth transitions
  • Defaults to full opacity when fade_duration is zero

93-94: LGTM! Font size calculation includes proper bounds.

The font size calculation correctly applies scaling factors and clamps the result to a safe range [1.0, MAX_FONT_SIZE_PX].

crates/rendering/src/layers/text.rs (1)

56-61: LGTM! Alpha composition correctly combines color and opacity.

The alpha calculation properly multiplies the text color's alpha channel with the fade opacity, ensuring both factors contribute to the final transparency. Both values are clamped to [0, 1] before multiplication, which is the correct approach.

crates/rendering/src/mask.rs (1)

86-95: LGTM! Mask fade logic is consistent with text rendering.

The fade duration handling for masks follows the same pattern as the text fade implementation, ensuring consistent behavior across mask and text overlays. The per-segment fade_duration replaces the previous hardcoded value, providing greater flexibility.

apps/desktop/src/routes/editor/Timeline/Track.tsx (3)

116-116: LGTM! Lower threshold improves usability for smaller segments.

Reducing the hidden threshold from 80 to 40 pixels allows resize handles to appear on smaller segments, improving the editing experience for shorter timeline items.


122-126: LGTM! Handle styling improvements enhance visual consistency.

The updated styling with fixed width (w-4 min-w-4), z-index positioning, and centered alignment with margin adjustments provides better visual consistency and layering for segment handles.


129-129: LGTM! Data attribute aids testing and debugging.

The data-hidden attribute provides a useful hook for automated testing and debugging without affecting the component's functionality.

apps/desktop/src/routes/editor/Timeline/TextTrack.tsx (1)

291-291: LGTM! Layout improvements ensure proper text truncation.

The additions of overflow-hidden, min-w-0, and w-full follow Tailwind best practices for text truncation within flex containers. The use of min-w-0 is particularly important as it allows flex children to shrink below their content size, enabling proper truncation. The change from max-w-[10rem] to max-w-full provides more flexible text display.

Also applies to: 328-331

apps/desktop/src/store/captions.ts (2)

25-26: LGTM! Default settings align with styling refactor.

The addition of fontWeight: 700 (bold) as a default and the change of outline to false are consistent with the broader UI styling improvements in this PR.


142-142: LGTM! Font weight properly serialized.

Including fontWeight in the saved caption settings ensures the new styling option is correctly persisted alongside other caption preferences.

apps/desktop/src/routes/editor/CaptionsTab.tsx (2)

278-297: LGTM! Deferred effects prevent unnecessary initial writes.

Using defer: true in the on() options correctly delays effect execution until dependencies actually change, avoiding unnecessary localStorage writes during initial component mount. This is a good practice for side-effect management.


792-857: Font Weight control implemented consistently.

The Font Weight control follows the established KSelect pattern used elsewhere in this file with proper option handling, disabled state, and dropdown animations.

One minor observation: Line 802 sets the value to { label: "Custom", value: getSetting("fontWeight") }, but the display logic in lines 829-838 correctly maps the weight value back to the appropriate label (Normal/Medium/Bold). The "Custom" label is effectively a placeholder that gets replaced during rendering, which works but could be slightly clearer.

apps/desktop/src/routes/editor/MaskOverlay.tsx (3)

17-26: LGTM! Multi-segment rendering enables better mask editing workflow.

The refactoring from single-mask to multi-segment rendering provides a more flexible editing experience:

  • Time-based filtering shows only currently visible masks
  • For loop correctly iterates visible segments
  • Show component appropriately renders selected vs unselected states
  • Fallback placeholder with hover styling improves discoverability

The implementation follows SolidJS best practices with proper reactivity.

Also applies to: 92-123


28-40: LGTM! Selection handlers properly manage mask interaction.

The selection logic correctly:

  • Derives selected mask from timeline selection state
  • Handles segment selection with proper event handling (preventDefault, stopPropagation)
  • Clears selection when clicking the background
  • Computes mask rectangles from evaluated state

The handlers follow established patterns and maintain proper event flow control.

Also applies to: 58-84


103-103: LGTM! Gray color scheme provides more neutral mask visualization.

The styling updates from blue to gray tones create a more subtle visual treatment for mask overlays and handles, aligning with the broader design system improvements in this PR.

Also applies to: 235-235, 281-281

crates/project/src/configuration.rs (2)

589-611: Mask/Text segment fade duration defaults look consistent and backward‑compatible

Adding fade_duration: f64 to MaskSegment and TextSegment with serde defaults of 0.15 matches the new UI sliders (0–1s, default 0.15) and preserves backwards compatibility for older projects that lack this field. No functional issues spotted here.

Also applies to: 627-690


778-866: CaptionSettings.font_weight and updated defaults are coherent with parsing/UI

The new font_weight: u32 field with default_font_weight() -> 700 plus the updated Default impl (setting font_weight and making outline false) lines up with parse_captions_json and the captions UI, which both assume a 700 default and outline disabled by default. The serde alias and rename_all keep JSON shape stable.

apps/desktop/src/routes/editor/TextOverlay.tsx (3)

21-63: Visible-segment selection, normalization, and update plumbing look solid

The refactor to derive visibleTextSegments from current time, track selectedTextIndex, and update segments via updateSegmentByIndex with produce is clean and defensive (null‑checking the segment). The SegmentWithDefaults/normalizeSegment layer provides consistent defaults from the Tauri shape without leaking optionality into the overlay, which should make the drag/resize logic more robust.

Also applies to: 145-173


175-233: TextSegmentOverlay measurement and drag/resize behavior is correct and well-bounded

The hidden measurement element plus measureAndUpdateSize (with guards for zero dimensions and container size) gives a good auto‑sizing path for segments without reliable size, and the effects watching mounted, container size, and text/font properties are careful to avoid measuring while resizing.

Drag (onMove) and resize (createResizeHandler) handlers correctly normalize deltas into [0,1] space, enforce minimum size and padding, clamp centers within bounds, and scale both fontSize and size on corner drags in a numerically stable way (using the prior font size as the baseline). I don’t see correctness issues here.

Also applies to: 285-430, 432-513


102-141: The pointer-events pattern is correctly implemented

The TextSegmentOverlay component applies pointer-events-auto (line 458), which explicitly overrides the parent's pointer-events-none. This is a valid CSS pattern: child elements with pointer-events explicitly set can be targets of pointer events even when the parent has pointer-events: none. The overlay will receive mouse events and fire onSelect correctly regardless of whether text is selected.

Likely an incorrect or invalid review comment.

apps/desktop/src/routes/editor/ConfigSidebar.tsx (2)

2517-2538: HexColorInput swatch + color input integration looks correct

The new swatch/button with an overlaid <input type="color"> keeps the internal text state and the visual color in sync, and calls onChange only with valid hex values coming from the color input or normalized text on blur. No issues from a behavior standpoint.


2613-2627: Font size slider scaling segment.size is numerically sound

Using newFontSize / (segment.fontSize || 48) as the scale factor and then multiplying segment.size by that factor (capped at 0.95) gives predictable proportional resizing as users drag the slider. Guarding on segment.size.x/y > 0.025 avoids blowing up default/placeholder sizes. This looks good.

Copy link
Contributor

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

🧹 Nitpick comments (2)
apps/desktop/src-tauri/src/captions.rs (1)

2092-2162: Streaming Whisper model download via AppHandle looks good; consider handling emit failures

The refactored download_whisper_model:

  • Uses AppHandle + http_client::HttpClient to stream the response body into a tokio::fs::File.
  • Tracks downloaded bytes to compute percentage when content_length is available.
  • Emits typed DownloadProgress events derived via tauri_specta::Event, which is a nice improvement over the old stringly-typed window event.

Two minor suggestions:

  • .emit(&app).ok(); silently discards any emission error. It might be worth at least logging failures (at debug/info) so they’re visible during debugging, instead of fully ignoring the Result.
  • Optionally, you could emit a final DownloadProgress with progress: 100.0 after a successful flush to guarantee the frontend always sees a completion state, even if content_length was missing or slightly off.
apps/desktop/src/routes/editor/ConfigSidebar.tsx (1)

2635-2683: KSelect “Style” control should use an option from the list for value to keep selection highlighting in sync

Right now KSelect is always given value={{ label: "Custom", value: props.segment.fontWeight }}, which isn’t one of the options. That means state.selectedOption() will never match an entry, so the menu won’t highlight/check the currently active style even when fontWeight is exactly 400/500/700.

You already compute the matching option in the Value render; you can reuse that for value so the control behaves like the other KSelects in this file:

-					<KSelect
-						options={[
-							{ label: "Normal", value: 400 },
-							{ label: "Medium", value: 500 },
-							{ label: "Bold", value: 700 },
-						]}
+					const styleOptions = [
+						{ label: "Normal", value: 400 },
+						{ label: "Medium", value: 500 },
+						{ label: "Bold", value: 700 },
+					] as const
+
+					<KSelect
+						options={styleOptions}
 						optionValue="value"
 						optionTextValue="label"
-						value={{
-							label: "Custom",
-							value: props.segment.fontWeight,
-						}}
+						value={
+							styleOptions.find(
+								(option) => option.value === props.segment.fontWeight,
+							) ?? null
+						}

and inside the Value render you can then rely on selectedOption() for the standard cases and only fall back to Custom (...) when no matching option is found.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f32226f and 906851e.

📒 Files selected for processing (8)
  • apps/desktop/src-tauri/src/captions.rs (6 hunks)
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx (6 hunks)
  • apps/desktop/src/routes/editor/MaskOverlay.tsx (6 hunks)
  • apps/desktop/src/routes/editor/Player.tsx (0 hunks)
  • apps/desktop/src/routes/editor/TextOverlay.tsx (3 hunks)
  • apps/desktop/src/routes/editor/text.ts (2 hunks)
  • apps/desktop/src/store/captions.ts (2 hunks)
  • crates/project/src/configuration.rs (7 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/routes/editor/Player.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/store/captions.ts
  • apps/desktop/src/routes/editor/text.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Never use dbg!() macro; use proper logging (tracing::debug!, etc.) instead
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them
Use duration.saturating_sub(other) instead of duration - other to avoid panics on underflow
Merge nested if statements: write 'if a && b { }' instead of 'if a { if b { } }'
Don't call .clone() on Copy types (integers, bools, etc.); copy them directly
Use function references directly: iter.map(foo) instead of iter.map(|x| foo(x))
Accept &[T] or &str instead of &Vec or &String in function parameters for flexibility
Use .is_empty() instead of .len() == 0 or .len() > 0 / .len() != 0
Don't assign () to a variable: write foo(); instead of let _ = foo(); or let x = foo(); when return is unit
Use .unwrap_or(val) instead of .unwrap_or_else(|| val) when the default is a simple/cheap value
Use 'for item in &collection' or 'for (i, item) in collection.iter().enumerate()' instead of 'for i in 0..collection.len()'
Use value.clamp(min, max) instead of manual if chains or .min(max).max(min) patterns
Always handle Result/Option or types marked #[must_use]; never ignore them

**/*.rs: Use rustfmt and workspace clippy lints for Rust code formatting and linting
Use snake_case for Rust module names and kebab-case for crate names
Never use dbg!() macro in Rust code; use proper logging instead (Clippy: dbg_macro = deny)
Always handle Result/Option or types marked #[must_use]; never ignore them (Rust compiler lint: unused_must_use = deny)
Never write let _ = async_fn() which silently drops futures; await or explicitly handle them (Clippy: let_underscore_future = deny)
Use saturating_sub instead of - for Duration to avoid panics (Clippy: unchecked_duration_subtraction = deny)
Merge nested if statements: use if a && b { } instead of if a { if b { } } (Clippy: collapsible_if = deny)
Don't call .clone() on Copy types; just copy them directly (Clippy: clone_on_copy = deny)
U...

Files:

  • crates/project/src/configuration.rs
  • apps/desktop/src-tauri/src/captions.rs
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Never add comments to code (//, /* */, ///, //!, #, etc.); code must be self-explanatory through naming, types, and structure

Files:

  • crates/project/src/configuration.rs
  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
  • apps/desktop/src-tauri/src/captions.rs
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use React Query hooks with Server Actions for mutations and perform precise cache updates using setQueryData/setQueriesData instead of broad invalidations

Files:

  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use strict TypeScript; avoid any type; leverage shared types from @cap/* packages
Follow camelCase naming for variables and functions; PascalCase for components; hooks must start with 'use' prefix
Use Biome for linting and formatting; match existing formatting conventions in the codebase
Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens
Use static skeletons for loading states that mirror content; avoid bouncing animations
Memoize expensive work, code-split naturally, and use Next/Image for remote assets

**/*.{ts,tsx,js,jsx}: Use 2-space indent for TypeScript files; format with Biome using pnpm format
Use Biome for code formatting and linting; run pnpm format regularly
Use kebab-case for file names (e.g., user-menu.tsx); use PascalCase for components

Files:

  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Never add any form of comments to code (single-line //, multi-line /* /, JSDoc /* */, or any other comment syntax); code must be self-explanatory through naming, types, and structure
Directory naming must use lowercase-dashed convention

Files:

  • apps/desktop/src/routes/editor/MaskOverlay.tsx
  • apps/desktop/src/routes/editor/TextOverlay.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
🧠 Learnings (6)
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/web/**/*.tsx : Minimize useEffect usage in React components; compute during render, handle logic in event handlers, and ensure cleanups for subscriptions/timers

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:40.743Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T14:29:40.743Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Use `useEffectQuery`/`useEffectMutation` from `@/lib/EffectRuntime` in client components; do not call `EffectRuntime.run*` directly

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/desktop/**/*.ts : Listen directly to generated events from tauri_specta and use typed event interfaces

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to apps/desktop/**/*.ts : Use tanstack/solid-query for server state management in SolidJS components

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-11-19T18:25:41.457Z
Learnt from: ItsEeleeya
Repo: CapSoftware/Cap PR: 1396
File: apps/desktop/src/routes/(window-chrome)/settings/general.tsx:183-192
Timestamp: 2025-11-19T18:25:41.457Z
Learning: In SolidJS, when using the Show component with the `keyed` attribute and a dynamic reactive value (like a memo result), the component will recreate its children when the condition value changes by reference. This pattern is useful for triggering CSS animations (like fade-in) when the rendered content needs to update. For example, in apps/desktop/src/routes/(window-chrome)/settings/general.tsx, `<Show when={previews()[theme.id]} keyed>` ensures the image re-animates when switching between different preview sources.

Applied to files:

  • apps/desktop/src/routes/editor/TextOverlay.tsx
📚 Learning: 2025-12-07T14:29:19.180Z
Learnt from: CR
Repo: CapSoftware/Cap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-07T14:29:19.180Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use Tailwind CSS for styling in web components; stay consistent with spacing and tokens

Applied to files:

  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
🧬 Code graph analysis (3)
apps/desktop/src/routes/editor/MaskOverlay.tsx (3)
apps/desktop/src/routes/editor/masks.ts (2)
  • MaskSegment (22-35)
  • evaluateMask (61-76)
apps/desktop/src/utils/tauri.ts (1)
  • MaskSegment (439-439)
apps/desktop/src/store/captions.ts (1)
  • updateSegment (65-74)
apps/desktop/src-tauri/src/captions.rs (2)
apps/desktop/src-tauri/src/upload.rs (4)
  • app (712-713)
  • app (794-795)
  • progress (404-414)
  • progress (855-955)
apps/desktop/src/utils/tauri.ts (1)
  • DownloadProgress (404-404)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (2)
apps/desktop/src/routes/editor/ui.tsx (3)
  • Field (25-47)
  • MenuItem (266-281)
  • Slider (65-149)
apps/desktop/src/store/captions.ts (1)
  • updateSegment (65-74)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Clippy (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
crates/project/src/configuration.rs (2)

591-610: Mask/Text segment fade_duration defaults look consistent and safe

Adding fade_duration: f64 with serde defaults of 0.15 for both MaskSegment and TextSegment matches the frontend sliders (0–1s) and preserves backward compatibility for existing projects via #[serde(default = "...::default_fade_duration")]. No issues from a type/serialization standpoint.

Also applies to: 613-625, 629-652, 654-690


780-809: CaptionSettings font_weight/fadeDuration wiring is coherent with the rest of the model

Introducing font_weight: u32 with alias "fontWeight" and default_font_weight() -> 700, plus the new default_fade_duration() -> 0.15 and Default impl updates, all align with the editor UI and TypeScript types. The aliasing/backwards-compatibility story looks correct, and changing outline to default false is a clear, explicit choice.

Also applies to: 821-833, 843-864

apps/desktop/src-tauri/src/captions.rs (2)

1784-1821: Caption settings now persist fontWeight and timing fields correctly

Including "fontWeight", "fadeDuration", "lingerDuration", and "wordTransitionDuration" in settings_obj keeps the saved JSON in sync with CaptionSettings and the editor UI. The numeric conversions are appropriate for the underlying u32/f32 fields.


1924-1990: parse_captions_json correctly hydrates new font_weight and duration fields

The loader now:

  • Reads fontWeight or font_weight with default 700 and passes it into CaptionSettings.font_weight.
  • Keeps outline defaulting to false, matching the Rust Default.
  • Hydrates fade/linger/word transition durations from either camelCase or snake_case keys with sensible defaults.

This keeps legacy caption files compatible while honoring the new styling options.

apps/desktop/src/routes/editor/ConfigSidebar.tsx (3)

2517-2538: HexColorInput swatch + native color picker integration looks solid

The added swatch button and overlaid <input type="color"> are wired cleanly to the existing text/prevColor state and onChange callback. The hex text and picker stay in sync without extra side effects.


2613-2627: Font size scaling logic for text segments is well‑guarded

Scaling segment.size proportionally when fontSize changes (with sensible min font size and a maxSize cap) should give intuitive resizing while avoiding zero/huge values. The checks on segment.size.x/y > 0.025 prevent exploding tiny segments.


2725-2737: Fade Duration sliders for text and mask segments align with the new data model

Both text and mask Fade Duration controls are constrained to [0, 1] seconds and default to 0.15, which matches the Rust defaults. Using clampNumber on the text side is a nice defensive guard; the mask slider relying on the slider’s range is acceptable here.

Also applies to: 2897-2910

apps/desktop/src/routes/editor/MaskOverlay.tsx (2)

14-40: Multi‑segment, time‑aware mask overlay and selection flow look consistent

The new currentAbsoluteTime + visibleMaskSegments memo, selectedMaskIndex derivation, and handleSelectSegment / handleBackgroundClick logic together give a clear model:

  • Only masks active at the current time are rendered.
  • Each visible mask gets a hitbox; clicking sets the mask selection in editor state.
  • When a mask is selected and visible, MaskOverlayContent takes over for drag/resize; otherwise a lightweight outline is shown.

This mirrors the TextOverlay behavior and keeps interactions scoped to the currently selected mask.

Also applies to: 58-85, 87-125


128-132: MaskOverlayContent refactor cleanly separates state and visuals

Passing a maskState accessor into MaskOverlayContent and deriving rect from it simplifies the component and keeps geometry in one place. The updated styling (gray border/handles) and reuse of the existing drag/resize math look correct, with no new correctness issues introduced.

Also applies to: 173-181, 225-270, 274-282

apps/desktop/src/routes/editor/TextOverlay.tsx (2)

23-41: Time‑aware multi‑segment text overlay and selection wiring are solid

The new visibleTextSegments memo, selectedTextIndex, shared createMouseDownDrag, and background click handler together give a clean interaction model: only active segments are rendered; you can select one from the timeline/overlay and then drag it around; pointer events are disabled when nothing is selected. This matches MaskOverlay and keeps the editor state the single source of truth.

Also applies to: 50-101, 103-141


146-174: TextSegmentOverlay normalization, auto‑sizing, and drag/resize behavior are implemented carefully

  • normalizeSegment provides robust defaults for partially‑populated segments coming from Tauri.
  • The hidden measurement node plus the two createEffect blocks give reliable auto‑sizing on mount and when content/typography change, while isResizing avoids fighting user‑driven corner resizes.
  • Drag and resize handlers clamp positions and sizes to sensible ranges and maintain padding from edges; corner resizing scales both fontSize and size coherently.

Overall this refactor significantly improves text overlay behavior without introducing new correctness issues.

Also applies to: 176-293, 295-445, 447-528

@richiemcilroy richiemcilroy merged commit 828b874 into main Dec 18, 2025
17 checks passed
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.

2 participants