krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v4.9.0: RELEASE_ROADMAP.md · raw
1# DomainDig Release Roadmap
2
3Priority lens: **new user-facing features.** The inspection engine is already
4deep (DNS, DNSSEC, CAA, TLS, TLSA/DANE, email security incl. BIMI/MTA-STS, RDAP,
5ports, geolocation, subdomains, availability). The next several releases invest
6in *reach and surfacing* — getting that data onto more iOS surfaces and into more
7workflows — rather than adding raw protocol checks.
8
9Current version: `v4.9.0`.
10
11## v4.4.1 Patch: Release Readiness — ✅ shipped
12
13- Consolidated Audit Mode onto the single `DomainDig/DomainDig/Audit*`
14 implementation and retired the prototype files.
15- Aligned `AppVersion.current`, Xcode marketing version, and build number.
16- Included audit sessions in backup/restore counts, summaries, and merge behavior.
17- Removed the retired `DomainDigCLI` target and refreshed README/architecture docs.
18
19## v4.5.0 Minor: Home Screen & Shortcuts Reach — ✅ shipped
20
21Goal: put DomainDig data and actions where the user already is.
22
23- **App Intents / Shortcuts** — `InspectDomainIntent`, `AddToWatchlistIntent`, and
24 `RunSweepIntent`, exposed via `DomainDigShortcuts` for Shortcuts, Spotlight, the
25 Action button, and Siri.
26- **`domaindig://` deep links** — `inspect`, `watch`, `domain` (detail), and
27 `sweep`, routed in `RootTabView`.
28- **WidgetKit portfolio widget** (Home Screen small/medium/large) — per-domain
29 health, certificate countdowns, and portfolio health counts, shared from the app
30 via an App Group; tapping a domain deep-links into its detail.
31
32Deferred to a later minor: **Lock Screen accessory widget families** and a richer
33per-widget "last change" indicator.
34
35## v4.6.0 Minor: Alerts, Glances & iPad — ✅ shipped
36
37Goal: make monitoring and results feel first-class across contexts.
38
39- **Sweep Live Activity** — a batch/watchlist sweep drives a Live Activity with a
40 progress bar, current domain, and change/warning counts on the Lock Screen and
41 in the Dynamic Island (`SweepActivityController` around the batch pipeline).
42- **Share extension** (`DomainDigShareExtension`) — "Dig Domain" accepts a web URL
43 from the system share sheet, extracts the host, and hands it to the app via the
44 App Group inbox; the app inspects it on next activation.
45- **iPad-optimized layout** — `RootTabView` renders a `NavigationSplitView`
46 (sidebar + detail) in the regular size class and the tab bar in compact.
47- **Actionable notifications** — per-domain `threadIdentifier` grouping, a
48 "Re-inspect" action, and taps that route into the domain's detail.
49
50Deferred: monitoring-alert Live Activities (only the sweep activity shipped) and
51Lock Screen accessory widget families (carried over from v4.5.0).
52
53## v4.7.0 Minor: Intelligence & Comparison — ✅ shipped
54
55Goal: help users interpret and organize, not just collect.
56
57- **Domain-vs-domain comparison** — `DiffService.compare(domainA:domainB:)`
58 reuses the existing section-diff builders; `DomainCompareView` (Watchlist
59 toolbar → "Compare Domains") picks two tracked domains and renders the result
60 with the existing diff section UI.
61- **Reputation / blocklist signals** — a new pluggable data source
62 (`ExternalDataService.reputation(domain:)`, Pro+) mirroring the existing
63 ownership/DNS-history/pricing enrichment pattern. Ships with no bundled
64 third-party endpoint; folds a listed status into risk score/factors and
65 insights, so it rides the existing report and monitoring change-severity
66 pipeline rather than needing bespoke monitoring wiring.
67- **Tags and saved views** for the watchlist — freeform tags per tracked
68 domain, tag filter chips, and named saved filter/sort/tag presets
69 (UserDefaults-backed; not yet part of backup/restore).
70
71## v4.8.0 Minor: Reporting & Sharing — ✅ shipped
72
73Goal: turn point-in-time snapshots into shareable, scheduled deliverables.
74
75- **Markdown and PDF export formats** — `DomainExportFormat` gains `.markdown`
76 and `.pdf` alongside text/csv/json. Markdown reuses the existing text-export
77 content via a line-based transform (never drifts from the text export); PDF
78 renders that Markdown via `UIGraphicsPDFRenderer`, mirroring the approach
79 `AuditExporter` already used for audit sessions.
80- **Scheduled report generation** — `ScheduledReportService` /
81 `ScheduledReportScheduler` (Settings → Scheduled Reports): a BGTaskScheduler-
82 driven daily/weekly job that builds a markdown/PDF/JSON report bundle for all
83 tracked domains, writes it locally, logs the run, and notifies when ready.
84 Mirrors `DomainMonitoringService`'s headless, storage-backed design; gated
85 behind the same Pro `.automatedMonitoring` capability.
86- **Stronger share affordances** — "Export Markdown"/"Export PDF" added to the
87 single-result, batch, watchlist, and workflow export menus; generated
88 scheduled reports are individually shareable from their log.
89- **Export consistency verified** — the local API already serves the canonical
90 `DomainReport` directly (no field allowlist), so `reputation`, `domainPricing`,
91 and every other field added since v4.7.0 already flow through automatically.
92 No code change was needed there.
93
94Deferred/scoped out: scheduled-report settings and logs are UserDefaults-only
95(not part of `DomainDataPortabilityService` backup/restore), same reasoning as
96v4.7.0's watchlist saved views — this is local automation config, not
97user-authored content.
98
99## v4.8.1 Patch: Reporting & Sharing Fixes — ✅ shipped
100
101Goal: fix what UAT of v4.8.0 turned up.
102
103- **Scheduled reports were unreachable manually** — the Overview section wrapped
104 every control in a single `VStack` inside one `List` row, so SwiftUI collapsed
105 them into one tap target and the Cadence `Picker` captured taps meant for
106 "Generate Now". Each control is now its own row.
107- **Pro gate completed on that screen** — `.automatedMonitoring` previously
108 disabled only the toggle, leaving both pickers and "Generate Now" interactive
109 on Free where they silently no-opped against the service-side guard.
110- **Markdown/PDF reports rendered `=` underlines as bullets** — the plain-text
111 transform only recognized `-`, so `batchText`'s title underline and its
112 48-character inter-report separators leaked through as literal list items.
113- **Duplicate DNS record values** — the report concatenated apex and wildcard
114 records without dedup, listing every value twice on domains with wildcard DNS.
115- **Inspect tab keyboard behavior** — removed the "Dismiss Keyboard" toolbar
116 button and the launch-time focus that raised the keyboard on app open.
117- **In-app purchases were unbuyable** — none of the four product ID constants in
118 `PurchaseService` matched the auto-renewable subscriptions configured in App
119 Store Connect, so `Product.products(for:)` returned nothing and `tier(for:)`
120 resolved every purchase to `.free`. Product IDs are permanent once created, so
121 the constants were corrected to match the store rather than the reverse.
122- **Local StoreKit testing** — added `DomainDig.storekit` mirroring the App Store
123 Connect group (Pro+ at level 1, Pro at level 2) and wired it into the Run
124 action, so the purchase and entitlement paths can be exercised without the
125 `DOMAIN_DIG_FORCE_PRO_PLUS` launch argument that bypasses StoreKit entirely.
126
127Follow-ups filed during UAT (#8, #9, #10) were all resolved in v4.8.2.
128
129## v4.8.2 Patch: Delivery Visibility & Build Health — ✅ shipped
130
131Goal: close the UAT follow-ups and make failures legible instead of silent.
132
133- **Disabled integrations no longer swallow events** (#8) — `enqueue(events:)`
134 filtered to enabled targets before writing any `DeliveryRecord`, so events
135 routed to a disabled integration vanished entirely. They now log a `.skipped`
136 entry with a reason. `sendTest` also respects `isEnabled`, which previously
137 delivered against targets that dropped every real event.
138- **"Process Queue Now" forces backed-off retries** (#9) — it only restarted the
139 processing task, never moving `nextAttemptAt`, so an item in backoff (up to an
140 hour) stayed undue and the button appeared inert. It now pulls queued items
141 forward, and reports an empty queue instead of doing nothing silently.
142- **Unreachable domains report as unreachable** (#10) — when the snapshot
143 fallback fired, the run compared old data against itself and claimed "No
144 meaningful changes" for a domain it never reached. `MonitoringDomainResult`
145 now carries `unreachableReason`, the summary says so, and a warning-severity
146 `monitoringFailure` reaches configured integrations.
147- **Swift 6 concurrency warnings cleared** — `SweepActivityAttributes` is
148 explicitly `nonisolated` (the app target sets
149 `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` while the widget target does not),
150 and `LocalAPIService`'s logger closures capture `self` coherently. Build is
151 warning-free.
152- **StoreKit configuration corrected and synced** — the scheme's path was wrong,
153 and the hand-authored file has been replaced by `SyncedProducts.storekit`,
154 synced against App Store Connect. Registered in the project without target
155 membership so it is not bundled into shipping builds.
156
157## v4.8.3 Patch: Static Analysis Cleanup — ✅ shipped
158
159Goal: clear the SonarCloud new-code backlog without changing behavior.
160
161- **Dead confidence conditionals fixed** (4 bugs) —
162 `DomainInspectionService`'s `confidenceFor*` helpers each returned
163 `error == nil ? .low : .low`. The conditional was inert, so the unused `error`
164 parameter was dropped alongside it.
165- **Identical switch branches merged** — 14 sites in `DomainViewModel` handled
166 `.empty(message)` and `.error(message)` with byte-identical bodies; they now
167 share one `case let .empty(message), let .error(message):`.
168- **Duplicate implementations consolidated** — `clearPresentedResults()` now
169 delegates to `reset()`, `String.nonEmpty` was folded into `nilIfEmpty`, and
170 `ExportFormat.id` derives from `fileExtension`.
171- **Nested ternaries extracted** — grade-to-tone and impact-to-color mappings
172 became `TLSGrade.tone`, `EmailSecurityGrade.tone`, and
173 `ChangeImpactClassification.color`, replacing `ContentView`'s private
174 `impactColor` and the duplicate mapping in `BatchResultsView`.
175- **Remaining smells** — empty closures and singleton inits documented, unused
176 protocol-conformance parameters marked `_`, `CloudSyncTrigger.import` renamed
177 to `imported` (raw value preserved), `_serverTrust`/`_tlsMetadata` renamed,
178 nested `if`s merged in the DER parser, and deep closure nesting flattened in
179 `PortScanService` and `IntegrationService`.
180
181Left open deliberately: `swift:S107` (initializer parameter counts on model
182memberwise inits), `swift:S115` (constants mirroring DoH/ipapi JSON keys),
183`swift:S1075` (false positives on `https://` literals), and two `swift:S117`
184hits on SwiftUI `$binding` shorthand in `AuditModeView`, which cannot be
185renamed. These want a *Won't Fix* / *Safe* resolution in SonarCloud rather than
186a code change.
187
188## v4.9.0 Minor: Accessibility, Appearance & Engineering Health — ✅ shipped
189
190Goal: make the app usable by every iOS user — full accessibility pass (#21),
191light mode, and the engineering scaffolding to keep both from regressing.
192
193- **Semantic colour system** — every hard-coded colour replaced with adaptive
194 colorsets in `Shared/Colors.xcassets` (Any/Dark + High Contrast variants),
195 shared by app, widget, and share extension via the synchronized `Shared`
196 group. Every status colour clears WCAG AA on its page, its card, and its
197 badge surface, in both schemes; measured, not asserted. The accent is now
198 blue (`#0000FF` light / `#4DA3FF` dark), split into foreground
199 (`StatusInfo`), fill (`AccentFill`), and on-fill (`AppOnAccent`) roles
200 because one value cannot serve as both text-on-dark and fill-behind-white.
201 `AppStatusTone` pairs each status foreground with an authored surface.
202- **Light mode unlocked** — the 16 scattered `.preferredColorScheme(.dark)`
203 calls removed; appearance (System/Light/Dark) is applied once at the
204 `WindowGroup` and exposed under Settings → Display. `.secondary` (3.29:1 on a
205 light card) replaced with `AppTextSecondary` across 191 sites.
206- **Dynamic Type & reflow** — `Label`-clipped empty-state titles fixed, the
207 44pt tap-target floor enforced (`AppCopyButton` was 30×30;
208 `controlMinHeight` was 42), `CardView`'s horizontal-scroll default flipped
209 to reflow, dense rows (`WatchlistRowView`, `BatchResultRowView`,
210 `PortfolioExpiryRow`) and the collapsible section headers rebuilt on
211 `ViewThatFits` so badges and buttons can never letter-wrap vertically, and
212 the widget clamped at `accessibility1` (fixed canvas, no scroll).
213- **VoiceOver** — labels on every icon-only control (label-in-name preserved
214 for Voice Control), selected-state on all toggles, badges read as one word,
215 heading-rotor navigation, dense rows collapsed to one element with the
216 detail on the More Content rotor (`accessibilityCustomContent`), technical
217 strings (DNS records, cipher suites) spoken with punctuation, and lookup/
218 sweep completion announcements. Widget rows read as a single phrase.
219- **Colour independence, motion, transparency** — widget status uses the badge
220 symbol vocabulary instead of colour-only dots; `differentiateWithoutColor`
221 adds symbols/borders on demand; all five animation sites honour
222 `reduceMotion`; the one material honours `reduceTransparency`.
223- **Accessibility audit harness** — `DomainDigUITests` runs
224 `performAccessibilityAudit()` over every primary screen at default and
225 AccessibilityXXXL, on CI (newest runtime, clean merge-result checkout) and
226 locally (`Scripts/audit-a11y.sh`, real floor runtime, wired to an opt-in
227 pre-push hook). `DOMAIN_DIG_SEED_FIXTURES` seeds deterministic in-memory
228 rows so the dense paths actually render under audit. The **enforcement
229 ratchet is engaged**: named findings in six categories fail CI, with
230 narrowly characterised, always-logged noise suppressions. Manual
231 verification checklist in `Docs/ACCESSIBILITY_VERIFICATION.md`; findings
232 burndown 20 → 11 with every remaining item characterised as system noise.
233- **Swift 6 language mode** (#27) — all three product targets build under
234 `SWIFT_VERSION = 6.0` with zero warnings. `SMTPChannel` became an actor
235 (fixing a real `CheckedContinuation` double-resume hazard),
236 `SweepActivityController` stores a Sendable activity id, and the remaining
237 isolation issues were resolved layer by layer. The UITests target stays on
238 Swift 5 (XCTest override isolation), recorded as a decision.
239- **Project hygiene** — the misleading project-level deployment target
240 (26.2 shadowing the real 17.6) reconciled; CI selects simulators
241 floor-aware instead of first-match.
242
243Deferred: the Phase 6 manual device passes (VoiceOver walkthrough, Voice
244Control, iPad Full Keyboard Access, Liquid Glass runtime check) are tracked in
245`Docs/ACCESSIBILITY_VERIFICATION.md` and #21 — they close on device, not in CI.
246
247## v5.0.0 Major: Contract Stabilization & Engineering Health
248
249Goal: earn long-term compatibility promises — and pay down the debt that the
250feature releases above will accumulate.
251
252- Define migration policy for persisted snapshots, backups, audits, workflows,
253 and settings.
254- Stabilize the public local API response contract; document compatibility
255 guarantees and planned deprecations.
256- **Extend the test net to the deterministic core.** v4.9.0 established
257 `DomainDigUITests` (accessibility audit + enforcement gate); unit coverage of
258 `DomainReportBuilder`, `DomainReportExporter`, `DomainDataPortabilityService`
259 (merge/replace dedup), and `DiffService` is still needed before locking down
260 external contracts.
261- **Decompose the god-files** behind that test net: `DomainViewModel.swift`
262 (~4.7k lines) and `ContentView.swift` (~3.8k lines) into focused units
263 (audit, monitoring, workflows, portability).
264
265## Cross-cutting note
266
267New feature surfaces (widgets, intents, extensions) each add a target and a
268persistence/entitlement seam. This project still has **no XCTest target** —
269v4.5.0 through v4.7.0 all shipped without the characterization-test safety net
270originally recommended before v4.7.0. That gap is now larger (comparison,
271reputation, and tags/saved-views all touch persisted models with hand-written
272backward-compatible decoders) and should be the very first thing v5.0.0 does,
273not a later item within it.