uttrflow / uttrflow/uttrflow-swift
Copy Diagnostics quotes dictated words in its "answer refused" lines, though the report promises "Counted, never quoted"
- 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
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