uttrflow / uttrflow/uttrflow-swift

Copy Diagnostics quotes dictated words in its "answer refused" lines, though the report promises "Counted, never quoted"

Open
#645 0 comments 0 reactions 0 assignees View on GitHub
area:general bug good first issue P0 security
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

## What happens

The Diagnostics report meant to be pasted into bug reports builds its clean-up section from `countedCleanUp` (`Sources/UttrflowUX/DiagnosticsPresentation.swift:555-557`). The comment there reads: "Counted, never quoted: this string is pasted elsewhere, and dictated words are not a diagnostic."

The changes are counted, but the last line of `countedCleanUp` appends every refusal verbatim (`DiagnosticsPresentation.swift:469`):

```swift
+ record.refusals.map { " answer refused (\($0.engine)): \($0.reason)" }
```

A refusal's `reason` is the string from `TransformationError.outputRejected(reason)` (`Sources/UttrflowAI/TransformerRouter.swift:106-113`), and the meaning guard writes the user's words into those strings (`Sources/UttrflowAI/MeaningPreservationGuard.swift`):

- `:86`: "the … step took out '\(token.text)' …"
- `:127`: "the rewrite read '\(span.heard)' as a word it was not offered"
- `:248`: "the rewrite begins with '\(preamble)'"
- `:345` and `:481`: "the rewrite lost or replaced '\(token.text)'"
- `:370`: "the rewrite invented '\(token.text)'"
- `:485`: "the rewrite moved '\(token.text)'"

So after any dictation the model rewrite was refused for, Copy Diagnostics puts words from that dictation (names, numbers, whatever was said) on the clipboard for pasting into a public issue. The on-page row quotes them too (`:425-428`), which is fine because it stays on the Mac.

## Why it matters

People are asked to paste this report into GitHub issues. The report promises it carries no dictated words, so nobody checks it before posting.

## How to reproduce

Build a `CleaningRecord` with a `Refusal(engine: "model", reason: "the rewrite lost or replaced 'Zorvane'")` and call `DiagnosticsPresenter.countedCleanUp`. The output contains `Zorvane`.

## Acceptance criteria

- The copied report names the engine and the *kind* of refusal (lost word, invented word, moved word, preamble, number), never the words. For example, carry a closed enum or a word-free summary in `CleaningRecord.Refusal` next to the display reason.
- The on-page row may keep the quoted reason.
- A test in `Tests/UttrflowUXTests/DiagnosticsCleanUpTests.swift` feeds a refusal whose reason contains a unique word and asserts the word is absent from the copied report.

## Where to start

- `Sources/UttrflowUX/DiagnosticsPresentation.swift` (`countedCleanUp`), `Sources/UttrflowCore/Cleaning/CleaningRecord.swift` (`Refusal`), and `Sources/UttrflowAI/TransformerRouter.swift` (`refusals`).
- `Tests/UttrflowUXTests/DiagnosticsCleanUpTests.swift` already calls `countedCleanUp` (`:88`).
- Run `make verify`. See `CONTRIBUTING.md`.
- Size: small. The quickest correct fix is to report `answer refused (engine)` with no reason in the copied report.

Contributor guide

Open the contributing guide

Research direction

Start with countedCleanUp in Sources/UttrflowUX/DiagnosticsPresentation.swift, then inspect CleaningRecord.Refusal and refusal construction in TransformerRouter.swift. Extend the existing DiagnosticsCleanUpTests.swift coverage with a unique quoted word, run the focused test and make verify, and confirm copied diagnostics retain refusal context without the dictated word while the on-page row remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
desktop, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
86/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.