The screens that turn a capture into evidence about one app: an audit project (the app, and the
domains it is expected to contact), its sessions (named recordings, one per version and build,
plus a baseline) and the markers placed inside a session. The model underneath is
../spec/audit.md; the scope and the attribution method are
ADR 0008.
Audit (tab) ──▶ Project ──▶ Session ──▶ Evidence bundle (sheet, once the session has ended)
│ │ └─────▶ Comparison (with another ended audit session)
│ │ └─ Start session (sheet)
│ └─ Edit project (sheet)
└─ New project (sheet)
Dashboard ──(strip, only while a session records)──▶ Session
Control Center · Action Button · Shortcuts · Siri ──▶ a marker in the open session (no screen)
Audit is the fifth tab, between Captures and Settings. It is what the tool is used for once an assessment is under way — opened every session, with the audited app waiting in the background — so it does not belong two levels down in Settings beside preferences, nor inside Captures, whose screen is an inventory of files. Five is also the most a tab bar shows without a More item: there is no room for a sixth, and that is a constraint on what comes next, not an accident.
None of it is drawing. The decisions live in AuditPresentation and AuditForms
(TunnelVision/Models), pure and tested; one AuditViewModel feeds all three screens and the
Dashboard, because what they show is the same fact — which projects exist and which session is
recording — and a view model per screen would leave the list behind a session saying it still records
after it was ended.
A short grouped list, like Captures: name and how many sessions. No leading rail and no icon — the rule of the capture row: a mark on every row would say this is a project in a list of projects. The one exception gets its badge: the project whose session is recording. With no projects the screen teaches what the feature is for in one sentence and offers New project; it names no standard.
Version 2.4.0 (187) or
Baseline. Version and build identify a binary, so they are shown exactly as typed and never
grouped or reformatted. The recording one carries the badge.../spec/audit.md) — and the
footer says why, naming the other project when the open session is not this one’s: from here it
cannot be seen. With no sessions at all the footer says to begin with a baseline instead.literal role (it is read character by character) with its
note under it. The footer states the two accepted forms and the rule people get wrong — a wildcard
does not cover the name itself.Ordered by what the screen is opened for:
PinningEvidence
(pure, no default): Can be assessed only when inspection was on and the device trusted the
certificate; otherwise Not assessed, with a different sentence for each of the two reasons,
because each has its own way out. It is never passed or failed: nothing was observed. And the
readable case says how it is read — an app either refused the user-installed certificate or
accepted it — and that nothing was bypassed to find out
(ADR 0003).A session that has ended offers one row, Export evidence, under its status. A recording one
does not: a bundle is closed evidence (../spec/audit.md § Evidence bundle).
Tapping the row shares nothing. It writes the bundle — the row shows progress and every other row that writes is disabled meanwhile — and then opens a sheet that says what was written. Sharing is the button at the bottom of that sheet. The order is the one the connections export already has in Captures, and it matters more here: what leaves the device is a capture.
The sheet, top to bottom, and why each line is there:
ZIP, how many files — eight, the
report among them. The count is of the names the exporter returns, not a number of the screen’s.session.json’s contents):
connection metadata, what it shows against a requirement catalogue, a capture — and that
decrypted content is not part of it.findings.json says of its
verdicts, again in its own words: a verdict is not a test result, and none states that a
requirement is met.EvidenceCaptureStanding (pure, no default), which has three cases and not a tally with three
zeroes:
Then, if any, the packets that are in the capture but fall outside the session’s time window, with why they are there (their connection was also active during the session): without the sentence they read as traffic that should not be in the file. And always, last: anything that was sent unencrypted can be read in the capture. “No decrypted content” is easily read as “no content”, and from a medical app what travelled in the clear can be health data.
literal role, and Share.The bundle’s own sentences are quoted, not reworded. Items 2 and 3 show
EvidenceSessionDocument.contentsNote and EvidenceWording.verdictsNote as they are written into
the files. They are limits that were costly to settle, and a second wording on the screen would
sooner or later say something else than the file the user is about to send. The cost is visible: the
verdict note names toolCoverage, a key of findings.json. The sentences about the capture are the
screen’s own, from the string catalog, because they carry this export’s numbers; each reason is
worded as capture.json defines it.
An export that fails is a notice at the top of the session screen, never a sheet, and each of
the eight ways it can fail has a sentence of its own (AuditPresentation.evidenceExportFailed). A
catalogue this version does not include is named, and the sentence says no other was used
instead; a history that changed under the export says to export again; a failed write says nothing
was left behind and names the one cause a user can act on, free space. The section sits second on
the screen so that the notice appears where the row was tapped.
Closing the sheet does not delete the archive: the system share sheet may still be reading it. The next export does — there is at most one bundle on disk, in the app’s temporary directory.
An ended audit session lists, under its evidence bundle, the project’s other ended audit
sessions: Compare with. The section is absent when there is none — a heading over “nothing to
compare” would take room in the first session of every project, where it is least needed. A
baseline is not offered and offers nothing: it observes no version, and the model refuses it
(../spec/audit.md § Release diff). Neither is a session still recording.
The comparison is its own pushed screen, ordered by the question that opens it — what is new in this version?:
All of it is decided in ReleaseComparisonPresentation, pure and tested; the view draws. A
comparison that can no longer be made — the other session was deleted between being offered and
being tapped — is a sentence on the screen, not a spinner.
Measured on the Simulator with the seeded project (version 2.4.0 against 2.3.1): nine new domains,
forty in both; the longest host name wraps to a second line in the literal role instead of
truncating, and its verdict line stays under it.
AuditProjectForm.draft() and shown on the line that
has it — an allowlist of twenty entries with one loose warning on top would make the user hunt
for it — one issue at a time, top to bottom. Every rejection of a pattern has a sentence of its
own; a pasted URL is told which character cannot be there. Blank lines are ignored; a note without
a domain is not, because whoever wrote it believed they were allowing something.What is not asked. Device model, system version, tool version and the state of HTTPS inspection
are read from the device when the session starts (AuditRecordingConditions). The form does show,
before recording, whether pinning will be assessable — in the future tense, with its own copy —
because a session recorded without inspection cannot be fixed afterwards. The conditions are read
again at the instant recording starts: between opening the form and confirming it, the user may have
been to Settings.
A session left open keeps tagging every connection until someone ends it. So the Dashboard shows a strip directly under the monitoring control for as long as one is open — Audit session recording, whose project it is, and Open, which lands on that session with its project behind it in the stack. It has the shape of the monitoring strip on purpose: both are things happening right now. It is absent the rest of the time, so the Dashboard of someone who never audits is unchanged.
Mark now needs TunnelVision in front, and the moment that matters most — accepting the audited
app’s consent screen — happens with the audited app in front. So the three fixed markers can also be
placed from outside (../spec/audit.md § Marking from outside the app):
What they say afterwards is the whole design. Placed: “Consent given” marked at 21:58:34 in Example
Health. — the name, the time to the second, the project. Not placed: an error that begins No marker
was placed, and then why (no session is recording, or the history could not be written). There is no
third, quiet outcome.
Neither opens the app. Coming back later, the session screen already lists what was placed from outside: the tab re-reads when the app returns to the foreground.
The free-form marker (Other…) is not offered outside the app. It has to be typed, and by the time it is typed the instant is no longer the one being marked.
From idb ui describe-all on an iPhone 17, and what each measurement changed:
design-system.md).TouchTarget.minimum; adding
the minimum to the label stacked the two. The label keeps only the full width.Label inside a list row sends its icon to the row’s icon column, leaving Recording 28 pt
from its symbol and a form’s warning indented under a field that is not. The badge and the form
issue are an HStack.neutral while disabled.