uttrflow / uttrflow/uttrflow-swift
Design: whole-dictation policies are evaluated per piece, so a dictation long enough to be cut is formatted by questions asked of the wrong scope
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
**P1 — design. Pieces are a latency optimisation, and every whole-dictation decision is being made inside one.**
Working ahead cuts a recording into pieces so the model call for piece N overlaps recognition of
piece N+1 — `Docs/early-transcription.md`, and it is the reason the wait after key-up is short.
The cost was not paid for in the cleaning stage: policies whose question is about the whole
dictation are evaluated per piece, and there is no stage that sees the finished text before it
is inserted. #183, #177, #180, #210 and #216 are that one gap.
### The questions being asked of the wrong scope
| Issue | Policy | The question it actually asks |
|---|---|---|
| #183 | `TerminalStopPass` with `.offForShortMessages(sentences: 2)` | How many sentences are in **this message**? |
| #183 | `FirstWordPass` | Is the caret mid-sentence — for **this dictation**? |
| #177 | Language detection | What language is **this utterance**? |
| #180 | Conditioning vocabulary | What words might **this speaker** use? |
| #210 | `WarmedSession` | Which piece is the one the person is waiting on? |
Each is answered with the piece in hand, which is the one scope that cannot answer it. A chat
message dictated in two pieces loses the stop at the seam and never gets its final stop, because
no stage ever counts the sentences of the message. A paragraph continued from a mid-sentence
caret lower-cases the head of every later piece. Two halves of one sentence can be decoded as
two languages. The warm session is spent on an early piece nobody waits for, so the only piece
with a person waiting on it is the cold one.
### The target shape
**A join stage that owns whole-dictation decisions.** `PieceJoiner` exists and does layout
across pieces (`Docs/cleanup-design.md` §7). Give it the rest: it is the first and only place
where the whole text exists, so it is where whole-text policies belong. Per-piece cleaning keeps
everything whose question is local — fillers, stammers, spoken punctuation, spacing, numbers —
and the passes whose question is global move behind the join:
- terminal stop, once, against the sentence count of the finished text;
- first-word casing, once, against the caret state read at key-down;
- layout, which is already there.
**A dictation-scoped context, resolved once.** Language, conditioning vocabulary and the warm
session are properties of the dictation, not of a piece. Resolve them at key-down into one value
the pieces read, so the first piece's language decides the rest (#177), the dictionary and screen
are read once (#180), and the warm session is reserved for the final piece because that is the
only one with a person waiting (#210).
**Scope stated in the type, not in a comment.** A `CleaningPass` should not be able to see the
piece count, and a whole-text policy should not be constructible with a piece. Split the protocol
so the compiler enforces which scope a rule may ask about; that is what stops the next pass being
added at the wrong level.
### Why not simply pass the piece index around
It answers none of the five rows. A pass that knows it is piece 3 of 5 still cannot count the
sentences of a message whose later pieces do not exist yet, and threading an index through every
pass is the coupling that makes the ordering contract in `CleaningPipeline.standard` harder to
reason about rather than easier.
### Principles
Single responsibility: a piece is a unit of latency, and it has been made a unit of meaning as
well; separating the two gives each stage one reason to change. Interface segregation: a
local pass should not be handed a scope it must not use. KISS: two scopes named in the types,
rather than five policies each carrying its own workaround for not having the whole text.
YAGNI holds — nothing here is built for a policy that does not already exist and misbehave.
### Sequencing
The dictation-scoped context first, because #177, #180 and #210 close on it alone and it does not
move any pass. The scope split and the move of terminal-stop and first-word behind the join
second, with corpus cases for multi-piece dictations — `Docs/cleanup.md` records that paragraph
and list layout have no corpus case at all, and multi-piece punctuation has none either, so the
cases land first. Measured with `make bakeoff` before and after.
---
Design issue for the cluster #183, #177, #180, #210, #216. Related: #186 and #185, which are
about the same working-ahead loop but are latency rather than scope. Traced in the source, not
reproduced at runtime.
Contributor guide
Research direction
Start with Docs/early-transcription.md and Docs/cleanup-design.md §7, then trace PieceJoiner and CleaningPipeline.standard. Review the related issues and the sequencing described here, including the corpus gaps in Docs/cleanup.md. Done means the dictation-scoped context, scope split, post-join policies, corpus cases, and make bakeoff comparison are addressed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- desktop
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100