Redact PII — Privacy tab & the dialogs its flows imply.
Off by default. On the Mac the detection stack is delivered on demand
(Apple-Hosted Background Assets) the first time you enable it — so enabling shows a
brief one-time download, then it's a plain setting. Failures surface through the
normal project-sidebar status line, not a bespoke dialog. Full spec:
docs/design-redact-pii.md.
hand.raised.pii_llm_pass and pii_custom_names
exist in config.py but are inert (they emit runtime warnings and do nothing),
so they are deliberately not surfaced. pii_score_threshold stays a tuned
default — no threshold UI, per house rule.
.failed project-row subtitle — nothing bespoke.
This is the existing failure family from ProjectRow.swift — red
MessageKind.error glyph · one-line summary · the whole subtitle is a
click target opening the normal diagnostic popover for detail. Retry is
the standard right-click action. The title-bar pill carries the dominant category +
count exactly as for any other stage failure.
Summary text is chosen by the typed Cause: missing bundled model →
MISSING_DEP; CLI download failure → NETWORK. Same
precedence chain, same tooltip, same Retry. There is nothing here to design — only
wiring to add.
pipeline.py has no try/except and never calls
mark_stage_failed, unlike transcribe / topics / quotes / clusters. A
remove_pii() raise today propagates as an unclassified crash that can't
reach the .failed subtitle at all. The fix is to wrap the stage and route to
CauseCategoryEnum.MISSING_DEP / NETWORK like every other stage —
no new UI, just the missing wiring. See the apparatus review below.
--redact-pii run. Single inline status line (under the 50 MB banner cutoff), then cached.$ bristlenose run interviews/ --redact-pii ✓ Merged transcript [2.1s] Downloading language model… ✓ [6s] ✓ Redacted PII (47 entities) [3.4s] ✓ Topic segmentation [11s] # on failure — same fail-stop, classified: ✗ Redact PII — language model not installed Run python -m spacy download en_core_web_sm or reinstall.
mark_stage_running / _complete(STAGE_PII_REMOVAL) — resume & provenance know it ran.pii_enabled, so toggling correctly invalidates the downstream cache.check_pii() reports health; transcripts-cooked/ + pii_summary.txt (re-identification key, kept hidden) are written.mark_stage_failed. A raise becomes an unclassified crash that can't reach the .failed status line.en_core_web_sm; Presidio default + the bundle ship en_core_web_lg. In the frozen sidecar the sm probe → FrozenSidecarError. Works in dev only because both are installed. Reconcile first.RunProgressSubtitle.swift) and absent from timing.py stages — fine for the ring, but a stall/failure has no labelled surface._is_stage_verified — it re-redacts on every resume. Cheap, but inconsistent.Cause + mark_stage_failed (this is what makes the .failed status line reachable) →
native Privacy tab + piiEnabled UserDefaults key + one env line in
BristlenoseShared.swift → copy & i18n. The toggle is the small part; the
failure apparatus is the real work.