Skip to content

feat: premium overlay styles, Companion, and experimental live typing - #975

Open
kxmXX wants to merge 2 commits into
altic-dev:mainfrom
kxmXX:feat/premium-overlays-companion-live-typing
Open

kxmXX wants to merge 2 commits into
altic-dev:mainfrom
kxmXX:feat/premium-overlays-companion-live-typing

Conversation

@kxmXX

@kxmXX kxmXX commented Sep 16, 2026

Copy link
Copy Markdown

Description

Adds a set of premium recording-overlay styles, a Companion overlay style, and an
experimental (default-off) live-typing path, and fixes the issues raised in the
first review round.

This is offered as a graft, not as a demand: take what is useful, drop or gate the
rest. It is a large visual PR, so it is reasonable to only take the visual half.

No ASR change. Parakeet/FluidAudio, the audio-capture engine and the mic tap are
untouched. Every new visual only consumes the audio level the app already publishes,
and live typing only consumes the partial transcript the app already computes.

Included:

  • Aurora rebuilt as a three-layer composition (atmosphere / mass / accent) with a
    shared AuroraComposition, so the Settings thumbnail and the live overlay draw the
    same geometry. Wave, Minimal and a circular Pulse whose ring is a
    travelling harmonics wave. OverlayColorTheme gains a highlight role and the
    gradients/spectrum ramp the styles share.
  • Companion as a new OverlayVisualStyle (not a second window, not an
    OverlaySize): a chromeless SVG-like character rendered in the overlay, with six
    elemental variants (fire / water / wind / earth / aurora / gothic), accessories,
    a face that follows the lifecycle, listening ripples and glowing eyes while the mic
    is live. It inherits the existing window, positioning and lifecycle code, so it
    disappears with the overlay like every other style.
  • Zoom band widened to 25 %–300 %. The Companion's base size was doubled, so the
    previous large look is now its 100 % reference, with its own 50 %–300 % band.
  • Notch: NotchPresentationPolicy now carries the visualizer canvas and icon
    policy; new Ambient Glow (Beta) presentation (no icon, no label, a wider 88×24
    canvas); bar count adapts to the canvas width instead of being fixed.
  • Live Typing (Experimental, default OFF) with three levels — full,
    committedChunks, finalOnly — an automatic downgrade ladder, an ownership model
    of prefix + owned range + trailing text, per-application capability probing, and a
    pasteboard fallback. Gated behind liveTypingExperimental; no application is
    certified.
  • Settings: Companion element/size/accessories inside the style picker, motion
    intensity in the Glow Advanced group, a live-typing toggle with a status row, and
    a search-index update.

Review round 1 — what was fixed

  • Live Typing is now bound to the recording target. The session uses the exact
    field captured when recording started (bindRecordingFocus) instead of resolving
    "whatever is focused now" on the first partial, and it re-checks that exact element
    before every write. A partial can no longer land in a field the user moved to.
  • A session that aborts after writing leaves a tombstone, so a later partial cannot
    restart a session over the same field and the final transcript is not inserted twice.
  • An absent or empty AX subrole is treated as secure, so an unreadable field is
    never streamed into. The rule is now a pure helper with direct test coverage.
  • Spoken Send is dispatched when Live Typing already owns the text: the key is
    posted through an empty plan on the normal safety path and the real outcome is
    reported, instead of returning .inserted and reporting a failed send.
  • The clipboard is no longer clobbered. The fallback writes the transcript as a
    transient/auto-generated item and restores the previous contents a moment later
    unless the user changed the clipboard. The transcript is in the dictation history
    either way.
  • enableStreamingPreview restored to its upstream default, so upgrading never
    silently disables the live preview for users who never touched the setting.
  • Overlay panel re-measures when the visual style or Companion size changes
    (previously it kept the old frame and clipped the new style).
  • Completion flash is now cleared after its dwell, not before it.
  • Scale slider labels now show the real 25 %–300 % band.

Type of Change

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Follows the accepted design conversations in
Discussion #949 — "UI needs a rework."
and
Discussion #963 — "Opt-in typed dictation-companion results and captured-target delivery".

#963 is the reason for the live-typing shape in this PR: it argues that a recording
client must validate the captured target rather than current focus, and that a changed
target must fail instead of redirecting output. Those two rules are what review round 1
pushed this PR into implementing. There is no dedicated tracking issue for the visual
work, so if the maintainers would rather fold it into the in-progress UI sweep, this PR
can be closed or reduced to the visual components without any hard feelings.

Related earlier feature requests: #103 (better notch animation), #743 (animated
voice-reactive notch), #865 (minimal overlay option).

Testing

  • Tested on Apple Silicon Mac (arm64)
  • Tested on Intel Mac (no Intel machine available)
  • Tested on macOS version: 27.0 (26A428); MACOSX_DEPLOYMENT_TARGET stays 15.0
  • Ran linter locally: swiftlint is not installed in this environment, so it was not run
  • Ran formatter locally: swiftformat is not installed in this environment, so it was not run
  • Ran tests locally: xcodebuild test -project Fluid.xcodeproj -scheme Fluid -destination 'platform=macOS'
    (with CODE_SIGNING_ALLOWED=NO) → 560 tests, 1 failure, and that failure is
    pre-existing on main: HotkeyShortcutTests.testKeyboardPayloadIgnoresStrayMouseButtonField
    ("Q" vs "A", an AZERTY-layout artefact present in the clean baseline).

New coverage added by this PR: OverlayPremiumSettingsTests.swift (~90 cases) and
LiveTypingSessionTests.swift (32 cases), both wired into the test target.

What was actually verified visually. The Debug bundle cannot be driven by
Accessibility automation here, so every visual claim comes from offscreen
ImageRenderer passes over the production components (same structs, same
AuroraComposition, same CompanionGeometry), plus a real Debug build that was
installed and launched. The screenshots below are those renders.

What was not verified: signed-release behaviour, real streaming
Parakeet-to-editor delivery, live typing against any third-party app, multi-display
placement, and the completion-flash timing on a real machine. Live Typing ships
default-off for exactly that reason and self-disables on any doubt.

Screenshots / Video

Offscreen renders of the production components. Top to bottom: styles × formats,
Aurora's three layers across levels and themes, the Companion's variants × lifecycle
states, Companion sizes and accessories, the Pulse ring, and the Ambient Glow notch row.

Overlay styles across formats

Multilayer Aurora across levels and themes

Companion variants and lifecycle states

Companion sizes and accessories

Pulse ring across formats

Ambient Glow notch canvas

(The image files live on a separate pr-975-assets branch in the fork so they are not
part of this PR's diff.)

Notes

  • Live Typing is default-off and experimental on purpose. It is the part of this PR
    most likely to need upstream redesign; the visual half is independent and can be taken
    on its own.
  • Ambient Glow is labelled Beta. A light spilling under the cutout is bounded by
    DynamicNotchKit's presentation clipping, so the glow lives inside the content rather
    than around the hardware notch.
  • BottomWaveformView is kept but is no longer referenced.
  • Reduce Motion is honoured by every new visual; no new dependency, no second
    AVAudioEngine, no new mic tap; installed preferences are only added to, never reset.
Commits
  • 4788c2e feat: premium overlays companion and experimental live typing
  • 1870780 fix: address review feedback on live typing, settings and overlay

@github-actions github-actions Bot added needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Sep 16, 2026
@github-actions

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Description
  • Type of Change
  • Related Issue or Discussion
  • Testing
  • Screenshots / Video

Visual files detected:

  • Sources/Fluid/ContentView.swift
  • Sources/Fluid/Persistence/SettingsStore.swift
  • Sources/Fluid/Services/MenuBarManager.swift
  • Sources/Fluid/Services/NotchOverlayManager.swift
  • Sources/Fluid/UI/Companion/CompanionGeometry.swift
  • Sources/Fluid/UI/Companion/CompanionModel.swift
  • Sources/Fluid/UI/Companion/CompanionVisualizer.swift
  • Sources/Fluid/UI/LiveTypingStatusRow.swift
  • Sources/Fluid/UI/Overlay/AudioEnvelopeFollower.swift
  • Sources/Fluid/UI/Overlay/AuroraVisualizer.swift
  • Sources/Fluid/UI/Overlay/MinimalVisualizer.swift
  • Sources/Fluid/UI/Overlay/NotchHaloRim.swift
  • Sources/Fluid/UI/Overlay/NotchStyleVisualizer.swift
  • Sources/Fluid/UI/Overlay/OrbitalGlow.swift
  • Sources/Fluid/UI/Overlay/OverlayAnchorGeometry.swift
  • Sources/Fluid/UI/Overlay/OverlayColorTheme.swift
  • Sources/Fluid/UI/Overlay/OverlayLifecycleState.swift
  • Sources/Fluid/UI/Overlay/OverlayProcessingRing.swift
  • Sources/Fluid/UI/Overlay/OverlaySettingsControls.swift
  • Sources/Fluid/UI/Overlay/OverlayStyleThumbnail.swift
  • Sources/Fluid/UI/Overlay/OverlaySurface.swift
  • Sources/Fluid/UI/Overlay/OverlayVisualStyle.swift
  • Sources/Fluid/UI/Overlay/OverlayVisualizerView.swift
  • Sources/Fluid/UI/Overlay/PillPreviewSizing.swift
  • Sources/Fluid/UI/Overlay/PulseVisualizer.swift
  • Sources/Fluid/UI/Overlay/WaveVisualizer.swift
  • Sources/Fluid/UI/SettingsSearch.swift
  • Sources/Fluid/UI/SettingsView.swift
  • Sources/Fluid/Views/BottomOverlayView.swift
  • Sources/Fluid/Views/NotchContentViews.swift
  • Tests/FluidDictationIntegrationTests/OverlayPremiumSettingsTests.swift

Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template.

If this remains incomplete for 48 hours after opening, the PR may be closed.

@greptile-apps

greptile-apps Bot commented Sep 16, 2026

Copy link
Copy Markdown

Greptile Summary

This revision addresses the previous review findings while retaining the PR’s premium overlay and experimental Live Typing features.

  • Binds Live Typing to the field focused when recording begins and revalidates focus before writes.
  • Preserves and restores clipboard contents after Live Typing fallback.
  • Dispatches Spoken Send actions after Live Typing has already inserted the transcript.
  • Restores the established streaming-preview default.
  • Keeps the completion visual active for its intended dwell and corrects scale endpoint labels.
  • Triggers live overlay resizing when visual style or Companion scale changes.
Confidence Score: 5/5

The PR appears safe to merge; the previous findings are resolved and no actionable regression was identified in the follow-up changes.

Live Typing now uses the recording-time focus target, restores clipboard state after fallback, and preserves Spoken Send behavior through the action-only delivery path. The preview default, completion dwell, and scale labels have also been corrected, and all previous threads were resolved.

Reviews (2): Last reviewed commit: "fix: address review feedback on live typ..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4788c2eb4d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +90 to +95
case .abort(let reason):
DebugLogger.shared.info(
"Live typing stopped: \(reason.rawValue)",
source: "LiveTyping"
)
self.reset()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve aborted live-typing ownership until final delivery

When Live Typing has already written a partial and the user or target app changes any surrounding text, session.partial returns .abort, but this reset discards hasWritten and all ownership information. A subsequent partial can then start a fresh session, and final delivery can fall through to the normal paste path, leaving the earlier partial in place and inserting the transcript again. Keep a terminal session/tombstone through final delivery so an aborted session that wrote text continues to suppress duplicate insertion.

Useful? React with 👍 / 👎.

Comment on lines +171 to +172
private func beginSession() {
guard let target = LiveTypingAXTarget.capture(preferredPID: nil) else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind live typing to the original recording target

If focus changes after recording starts but before the first streaming result arrives, this captures whichever element is focused at that later moment rather than the focus target saved for the recording. Live Typing can consequently insert a partial into an unrelated app or field; the final pipeline then restores the original target, detects a focus mismatch, and suppresses final insertion there, leaving stray text in the wrong field. Pass the captured recording PID/element into the live-typing session instead of resolving unrestricted focus on the first partial.

Useful? React with 👍 / 👎.

Comment thread Sources/Fluid/Services/ASRService.swift Outdated
Comment on lines +5588 to +5589
if LiveTypingController.shared.consumeFinalDelivery(plainText: plan.plainText) {
return .inserted

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Dispatch the spoken-send action after live insertion

When Live Typing owns the final text and Spoken Send is enabled, this early return bypasses TypingService.typeOutputPlanInstantly, which is the only path that validates focus and posts postInsertionKey. Returning .inserted also makes deliverSpokenSend report failure because didDispatchAction is false, so the text remains unsent every time both features are used together. The live-text handoff still needs to run the post-insertion action path and return its actual delivery outcome.

Useful? React with 👍 / 👎.

Comment on lines +214 to +216
let subrole = self.stringAttribute(kAXSubroleAttribute as CFString)
let isSecure = subrole == (kAXSecureTextFieldSubrole as String)
|| (subrole ?? "").localizedCaseInsensitiveContains("secure")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Treat an unreadable AX subrole as potentially secure

For an accessibility implementation that exposes readable/settable value and selection attributes but does not expose kAXSubroleAttribute, subrole is nil and this marks the field non-secure, allowing partial transcripts to be streamed into it. That contradicts the conservative isSecure() helper above, which treats an unknown subrole as unavailable specifically to avoid writing into an unrecognized secure field. Use that conservative result here so capability probing falls back to final-only delivery when the subrole cannot be verified.

Useful? React with 👍 / 👎.

Comment on lines +2309 to +2312
set {
objectWillChange.send()
self.defaults.set(newValue.rawValue, forKey: Keys.overlayVisualStyle)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resize the prepared panel when switching overlay styles

On the default floating-overlay configuration, the panel is prepared and sized before Settings is used. Switching to or from Companion changes the root view's intrinsic dimensions, but this setter sends no OverlaySizeChanged, show() only repositions the existing panel, and the emitted CompanionSettingsChanged notification has no observer in the repository. The existing NSPanel therefore retains its old frame and clips or mis-centers the new style until an unrelated size update occurs; trigger the controller's size-and-position refresh when the style changes.

Useful? React with 👍 / 👎.

Comment thread Sources/Fluid/Services/LiveTyping/LiveTypingController.swift
Comment thread Sources/Fluid/Services/ASRService.swift
Comment thread Sources/Fluid/Services/LiveTyping/LiveTypingController.swift Outdated
Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated
Comment thread Sources/Fluid/Views/BottomOverlayView.swift
Comment thread Sources/Fluid/UI/SettingsView.swift Outdated
Live Typing safety (Codex P1 / Greptile P1):
- Bind the session to the field that was focused when recording started
  (bindRecordingFocus) instead of resolving "whatever is focused now" when the
  first partial arrives, and re-check that exact element before every write.
  A partial can no longer land in a field the user moved to.
- Keep a tombstone when a session aborts after it already wrote text, so a later
  partial can not restart a session over it and the final transcript is not
  inserted a second time.
- Treat an absent or empty AX subrole as secure, matching isSecure(), so an
  unreadable field is never streamed into. Extracted as a pure, tested helper.
- Stop clobbering the clipboard on fallback: the transcript is written as a
  transient, auto-generated item and the previous contents are restored a moment
  later unless the user changed the clipboard.

Spoken Send (Codex P1 / Greptile P1):
- When Live Typing already owns the text, still dispatch the configured
  post-insertion key with an empty plan and report the real outcome, instead of
  returning .inserted and leaving the send undone.

Settings / overlay:
- Restore the upstream default for enableStreamingPreview so upgrading never
  silently disables the live preview.
- Post OverlaySizeChanged when the visual style or Companion size changes, so a
  visible panel re-measures instead of clipping the new style.
- Clear the completion flag after the flash dwell, not before it.
- Correct the scale slider endpoint labels to the real 25%-300% band.

Tests: 560 total, 1 pre-existing AZERTY failure on the baseline.
@github-actions github-actions Bot removed needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Sep 16, 2026
@kxmXX

kxmXX commented Sep 16, 2026

Copy link
Copy Markdown
Author

Thanks @greptile-apps and @chatgpt-codex-connector — all 11 findings were legitimate. They are fixed in 1870780 and the template/policy sections are now in the description.

Live Typing is now bound to the recording target

  • LiveTypingController no longer resolves "whatever is focused now" when the first partial arrives. ContentView.captureRecordingTargetContext() calls bindRecordingFocus(...) with the same CapturedFocusTarget the delivery path already stores, and the session only ever uses that exact element.
  • LiveTypingAXTarget.isStillFocused() re-checks that exact element before every write (reusing TypingService.isExactFocusTargetActive), so a partial can no longer land in a field the user moved to. A focus change now aborts the session and suppresses the final paste.

Aborted sessions keep their tombstone

  • LiveTypingSession.abortedAfterWrite is set by markAborted() whenever the session stops after it has written. The controller keeps that session until final delivery, so a later partial can not start a fresh session over the same field and the final transcript is not inserted a second time.

Spoken Send is dispatched

  • typeOutputPlanToActiveFieldAndWait now runs dispatchPostInsertionActionOnly(...) — the normal safety path with an empty plan — when Live Typing owns the text, and maps the result to .insertedAndActionDispatched / .insertedActionSuppressed / .inserted. The key is posted and deliverSpokenSend no longer reports a failed send. The fire-and-forget variant now calls its completion with .inserted instead of returning silently, so overlay dismissal and the pipeline summary are preserved.

The clipboard is no longer overwritten

  • preserveToPasteboard snapshots the pasteboard, writes the transcript as a org.nspasteboard.TransientType / AutoGeneratedType item, and restores the previous contents after a short grace period guarded by changeCount — so a copy the user made in the meantime is never clobbered. The transcript is in the dictation history regardless.

Unreadable subrole is treated as secure

  • The rule is now LiveTypingAXTarget.isSecureSubrole(_:), a pure helper that returns true for a nil, empty or unknown subrole; both isSecure() and capabilities() use it. Directly covered by tests, including nil → final-only.

Upgrade no longer disables live preview

  • enableStreamingPreview is back to the upstream ?? true default. That was my regression, not an intentional change.

Panel re-measures on style change

  • overlayVisualStyle and companionScale now post OverlaySizeChanged, which the overlay already observes for a full re-measure and re-anchor, instead of the unobserved CompanionSettingsChanged / no notification at all.

Completion flash keeps its dwell

  • clearDeliveryCompletion() moved to after the 300 ms sleep (and after the generation guard), so the completion visual is actually on screen for its dwell instead of for an incidental frame.

Scale labels

  • Endpoint labels corrected to 25 % and 300 %.

Tests: xcodebuild test → 560 tests, 1 failure, and that failure (HotkeyShortcutTests.testKeyboardPayloadIgnoresStrayMouseButtonField, "Q" vs "A") is pre-existing on main — it is an AZERTY-layout artefact in the clean baseline.

swiftlint and swiftformat are not installed in my environment, so those two checkboxes are deliberately left unchecked rather than claimed.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits. You can see your limits in the Codex usage dashboard.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1870780ef4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// still owe a post-insertion action (Spoken Send). The text is already
// in the field, so only the key is dispatched - and its real outcome is
// reported, so a spoken send is not reported as failed.
if LiveTypingController.shared.consumeFinalDelivery(plainText: plan.plainText) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Dispatch Spoken Send only after a successful live finalization

When Live Typing and Spoken Send are enabled together, this action-only branch treats every true result as a successful final insertion. However, consumeFinalDelivery also returns true when ownership was lost, the final replacement failed, or the value became unreadable—cases where it only copies the final transcript to the pasteboard. If focus remains on the original field, such as after an external edit, the key validation can still pass and send the stale partial or user-edited draft; distinguish successful finalization from suppressed delivery before dispatching the action.

Useful? React with 👍 / 👎.

Comment on lines +115 to +117
if !previous.isEmpty {
let rollback = NSRange(location: range.location, length: (newText as NSString).length)
_ = target.replace(range: rollback, with: previous)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Roll back an unverified first live write

If the first AX replacement mutates the field but immediate readback verification fails—for example because the target normalizes the value or exposes delayed AX updates—previous is empty, so this guard skips rollback even though newText may now be present. The session still has hasWritten == false, causing final delivery to use the normal paste path and duplicate the transcript; rollback must also replace the first write with an empty string, and an unverifiable rollback should leave a tombstone.

Useful? React with 👍 / 👎.

Comment on lines +250 to +254
self.session = LiveTypingSession(
targetPID: target.pid,
anchor: selection.location,
initialValue: value,
level: level

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Replace the initial selection instead of inserting before it

When dictation starts with a nonempty text selection, only selection.location is retained and the session begins with an owned range of length zero. The first partial is therefore inserted before the selected text, and because that original selection becomes part of trailingText, final reconciliation preserves it rather than replacing it as normal paste delivery would. Carry the initial selection length into the session's first owned range or fall back to final-only delivery for nonempty selections.

Useful? React with 👍 / 👎.

Comment on lines +95 to +101
guard let value = target.value() else {
self.downgrade(&session)
self.session = session
return
}

switch session.partial(text, currentValue: value) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop streaming when the caret moves within the target field

If the user moves the caret or changes the selection within the same focused field without first modifying its value, contextIsIntact still passes and the next partial rewrites the old owned range; replace then moves the caret back to the owned range's end. This can hijack the user's cursor and place their next keystroke in the wrong location. Read the current selection here and require session.caretIsAtOwnedEnd(...) before each live write.

Useful? React with 👍 / 👎.

Comment on lines +469 to +470
if NotchOverlayManager.shared.isBottomOverlayVisible {
NotchContentState.shared.markDeliveryCompleted()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark completion only for successful delivery paths

finishProcessingAndHideOverlay() is not success-only: ContentView also awaits it for an empty transcription at line 2642 and for a failed rewrite at line 3812. With the floating overlay visible, this unconditional mark displays the success check and adds the completion dwell for those failure/empty paths, giving users a false delivery confirmation; pass the actual outcome or mark completion only at confirmed-success call sites.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Sep 16, 2026

Copy link
Copy Markdown

No new blocking findings in 1870780. I re-checked the previously reported issues against the updated flow: Live Typing is bound to the captured recording target and revalidates exact focus before writes; post-write aborts retain a tombstone; unreadable subroles conservatively resolve to final-only; Spoken Send dispatches through the normal safety path; clipboard fallback preserves user contents with change-count guarding; preview defaults remain enabled; style/Companion-size changes trigger overlay remeasurement; completion flash clearing occurs after the dwell; and the scale labels match the 25%–300% range. The added pure-logic coverage is wired into the test target. The remaining test failure is documented as the pre-existing AZERTY baseline failure, so this follow-up is clear from my side.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

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