uttrflow / uttrflow/uttrflow-swift
A spoken mark after an abbreviation replaces its full stop: "5 p.m. question mark" becomes "5 p.m?"
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
## What happens
`WordShape.marked(_:with:)` (`Sources/UttrflowCore/Cleaning/WordShape.swift:73-79`) replaces a clause mark already on the end of a word with the new one:
```swift
if let last = text.last, ",.;:!?".contains(last), ",.;:!?".contains(mark) {
return String(text.dropLast()) + mark
}
```
For a word whose full stop is part of an abbreviation ("p.m.", "a.m.", "etc.", "e.g."), that stop is not a sentence mark, but it is dropped all the same. Both `SpokenPunctuationPass` (`Sources/UttrflowAI/Passes/SpokenPunctuationPass.swift:149`) and the mark-carrying on a removed word (`Draft.carryMarks`, `Sources/UttrflowCore/Cleaning/Draft.swift:215-227`) go through it. `FirstWordPass.endsSentence` already recognises this shape (a stop inside the word's core, `Sources/UttrflowAI/Passes/FirstWordPass.swift:70-74`), so the two disagree about what "p.m." is.
Measured through `CleaningPipeline.standard` in a throwaway test:
| Dictation | Expected | Actual |
|---|---|---|
| `Is it 5 p.m. question mark` | `Is it 5 p.m.?` | `Is it 5 p.m?` |
| `We left at 5 p.m. comma then ate.` | `We left at 5 p.m., then ate.` | `We left at 5 p.m, then ate.` |
| `Bring apples, pears, etc. exclamation mark` | `Bring apples, pears, etc.!` | `Bring apples, pears, etc!` |
| `Meet at 5 p.m. exclamation mark` | `Meet at 5 p.m.!` | `Meet at 5 p.m!` |
A full stop after an abbreviation is still merged correctly (`It ends at 5 p.m.` stays as it is).
## Why it matters
Times and "etc." are among the most common things dictated before a question mark or comma. The output is visibly misspelled ("p.m?") and has to be fixed by hand.
## How to reproduce
In `Tests/UttrflowCoreTests/WordShapeTests.swift`:
```swift
#expect(WordShape.marked("p.m.", with: "?") == "p.m.?")
#expect(WordShape.marked("p.m.", with: ",") == "p.m.,")
#expect(WordShape.marked("p.m.", with: ".") == "p.m.")
#expect(WordShape.marked("done.", with: "?") == "done?")
```
## Acceptance criteria
- A trailing full stop that belongs to a dotted abbreviation (a stop inside the word as well, as `FirstWordPass.endsSentence` reads it) is kept when a comma, question, exclamation, colon or semicolon mark is added; a second full stop is still not added.
- An ordinary word's clause mark is still replaced as today ("done." + "?" → "done?").
- The rows above hold through `CleaningPipeline.standard` in `Tests/UttrflowAITests/Passes/SpokenPunctuationPassTests.swift`.
## Where to start
- `Sources/UttrflowCore/Cleaning/WordShape.swift:72-84`.
- Tests: `Tests/UttrflowCoreTests/WordShapeTests.swift`, `Tests/UttrflowAITests/Passes/SpokenPunctuationPassTests.swift`.
- Run `make verify` (export `DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer` first) and read CONTRIBUTING.md.
**Size:** S, about an hour.
Contributor guide
Research direction
Start with Sources/UttrflowCore/Cleaning/WordShape.swift:72-84 and the reproductions in Tests/UttrflowCoreTests/WordShapeTests.swift. Then run the focused WordShape and SpokenPunctuationPass tests, followed by make verify with the specified DEVELOPER_DIR. Done means abbreviation stops are preserved for added marks, ordinary clause marks still replace normally, and the CleaningPipeline.standard rows pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- Under an hour
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 92/100