Fix RTL text rendering for Arabic, Hebrew, and other right-to-left scripts#63
Open
MHegazy89 wants to merge 27 commits into
Open
Fix RTL text rendering for Arabic, Hebrew, and other right-to-left scripts#63MHegazy89 wants to merge 27 commits into
MHegazy89 wants to merge 27 commits into
Conversation
… add state-change debounce to prevent DoS (CWE-400)
fix: add session token auth and connection limit to DirectorServer
fix: enforce connection limit, offload broadcast to background queue,…
The page sidebar was only visible when multiple pages existed, but the "Add Page" button lives inside the sidebar — making it impossible to add pages from a single-page state. Also, pressing play always reset to page 0 (the welcome text) instead of reading whichever page the user was currently editing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds customizable color and brightness controls for teleprompter annotation cues (e.g. [pause], [smile], [breath]). Previously these were hardcoded to white at fixed opacities. - Cue Color: 6 color presets (matches highlight color options) - Cue Brightness: 4 levels (Dim, Low, Medium, Bright) - Settings preview includes [pause] sample annotation - Remote viewer (BrowserServer) updated to use cue color - DirectorServer state includes cue color for consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: always show page sidebar and read current page on play
feat: add cue color and brightness settings for annotation text
In Classic and Voice-Activated modes, the overlay would immediately switch to a "Done" screen and auto-dismiss 1 second after the auto-scroll reached the last word. This made timed mode unusable because speakers typically finish talking 10-20 seconds after the scroll ends. Now in timer-based modes (Classic/Voice-Activated), when the scroll reaches the end on the last page: - The prompter text stays visible instead of switching to "Done" - The overlay does not auto-dismiss - The speaker can close manually via the X button or Esc key Word Tracking mode behavior is unchanged (auto-dismiss is appropriate there since it knows when the speaker actually finishes). Fixes f#29
Address code review findings: - ExternalDisplayView: gate doneView and speechRecognizer.stop() on wordTracking mode, matching NotchOverlayView/FloatingOverlayView - BrowserServer: suppress isDone in classic/silencePaused modes on last page so browser clients keep showing prompter text - Revert accidental DEVELOPMENT_TEAM change in project.pbxproj
The timerWordProgress was incrementing unboundedly after the scroll reached the end, unlike the SwiftUI views which guard with !isDone. Add a scrollDone check before incrementing to stop wasting CPU on the 100ms broadcast timer.
SFSpeechRecognizer silently returns no results when receiving multi-channel audio buffers. USB audio interfaces like the RODECaster Pro II send 2-channel 48kHz audio, and the previous `format: nil` tap delivered these buffers unchanged to the recognition request. Create a mono AVAudioFormat at the hardware sample rate when the device has more than one channel and pass it to installTap, letting AVAudioEngine handle the downmix automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce an overlay transparency mode that uses a blurred background and adjustable tint opacity. - Add NotchBlurView (NSVisualEffectView wrapper) to provide behind-window blur. - Update NotchOverlayView and preview to render either a solid black island or a blurred, clipped island with a dark tint driven by opacity. - Add NotchSettings properties overlayTransparency and overlayTransparencyOpacity, persisted via UserDefaults (keys: "overlayTransparency", "overlayTransparencyOpacity") and initialized with sensible defaults. - Expose a Toggle and an opacity Slider in SettingsView to enable transparency and control amount; update reset defaults to include the new settings. This enables a see-through notch overlay option where desktop content shows through while keeping text readable via a configurable dark tint.
Add overlay transparency with blur and slider
…cognition fix: downmix multi-channel audio to mono for SFSpeechRecognizer
…pm-scroll Fix: Keep text visible after WPM auto-scroll reaches the end
Addresses two user-reported bugs: (1) highlight not tracking at the right speed, jumping erratically or lagging behind speech, and (2) mic appearing to stall out and stop picking up audio after ~60 seconds. Root causes identified and fixed: **Seamless recognition restart (P0)** - Split cleanupRecognition() so AVAudioEngine stays alive across SFSpeechRecognitionTask restarts, eliminating audio gaps - Add pre-emptive 55-second restart timer to beat Apple's ~60s timeout - Update matchStartOffset to recognizedCharCount before each restart so new sessions match from the correct position - Thread-safe request swapping via NSLock for audio I/O thread safety - Add contextualStrings from remaining source text for better STT accuracy **Fix fuzzy matching false positives (P1)** - Remove overly permissive `contains` check from isFuzzyMatch that caused "and" to match "demand", "the" to match "other", etc. - Tighten prefix matching to require minimum 3-char words - Require exact match for 2-char words (no edit distance tolerance) - Fix charLevelMatch skip-both fallback: no longer advances lastGoodOrigIndex on genuine mismatches (gibberish no longer matches) - Fix wordLevelMatch +1 space overcount on last matched word - Fix unicode scalar vs Character count mismatch in charLevelMatch **Confidence gating (P2)** - Replace blind max(charResult, wordResult) with agreement-based selection - Add sliding window requiring 2-of-3 recent results to agree before committing large forward jumps (small steps always pass through) **Retry resilience (P3)** - Distinguish timeout errors (code 1110/216) from real errors - No retry limit for expected timeouts; immediate soft restart - Backoff with retry limit only for genuine errors **Architecture cleanup (P4)** - Merge two polling timers in observeDismiss() into one - Fix retain cycle in dismiss() asyncAfter closure - Add isDismissing guard to prevent double-dismiss - Fix cancelled-task error callback race in restartTask() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ned mode Two bugs fixed: 1. Clicking a page in the sidebar then pressing Play always started from page 1 instead of the selected page. Root cause: the `sidebarSelection` setter wrapped the `currentPageIndex` update in `DispatchQueue.main.async`, deferring it asynchronously. Since SwiftUI binding setters are already called on the main thread, this wrapper was unnecessary and caused `run()` to read the stale value (0) before the update applied. Fix: remove the async dispatch so `currentPageIndex` is updated synchronously on selection. 2. `showPinned()` was the only display mode not calling `installKeyMonitor()`, so the ESC key did not work to dismiss the overlay in pinned mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: start playback from selected page and install key monitor in pin…
fix: voice tracking highlight and mic stall bugs
…cripts - MarqueeTextView: Detect RTL content via isRTLUnicodeScalar(), flip VStack alignment to .trailing for RTL, and set .environment(\.layoutDirection) so word flow lines render right-to-left. - HighlightingTextEditor: Add isRTLUnicodeScalar() and containsRTLText() helpers, plus updateWritingDirection() that sets textView.baseWritingDirection to .rightToLeft when RTL content is detected, ensuring proper caret and text alignment in the editor. Fixes garbled/truncated display when dictating or editing Arabic, Hebrew, Persian, Urdu, and other RTL text.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes garbled and incorrect text rendering when using Arabic, Hebrew, Persian, Urdu, and other right-to-left (RTL) scripts in Textream.
Changes
MarqueeTextView.swift:
isRTLUnicodeScalar()helper to detect RTL script Unicode scalars (Hebrew, Arabic, Syriac, Thaana, NKo, etc.)isRTLproperty that checks if any word contains RTL charactersVStackalignment to.trailingfor RTL content.environment(\.layoutDirection, .rightToLeft)on each line'sHStackso word flow renders correctly right-to-leftHighlightingTextEditor.swift:
isRTLUnicodeScalar()function covering Hebrew, Arabic, Syriac, Thaana, NKo, Samaritan, Mandaic, Arabic Extended forms, and presentation formscontainsRTLText()convenience helperupdateWritingDirection()that setstextView.baseWritingDirection = .rightToLeftwhen RTL content is detectedupdateWritingDirection()on initial setup and on text changes (e.g., paste)Test Plan