<feed xmlns='http://www.w3.org/2005/Atom'>
<title>domain-dig.git, branch v5.0.0</title>
<subtitle>local-first ios domain inspection toolkit. dns, tls, rdap, audit.
</subtitle>
<id>http://git.krz.sh/krz/domain-dig.git/atom?h=v5.0.0</id>
<link rel='self' href='http://git.krz.sh/krz/domain-dig.git/atom?h=v5.0.0'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/'/>
<updated>2026-07-25T16:18:41+00:00</updated>
<entry>
<title>chore: cut v5.0.0 release (version bump)</title>
<updated>2026-07-25T16:18:41+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T05:31:51+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=cc1cb343b19b662ef59f9700702ed285e4591029'/>
<id>urn:sha1:cc1cb343b19b662ef59f9700702ed285e4591029</id>
<content type='text'>
Bumps the app to 5.0.0 now that all four v5.0.0 workstreams have landed:

- AppVersion.current 4.9.0 -&gt; 5.0.0
- MARKETING_VERSION 4.9.0 -&gt; 5.0.0 across all targets (app, widget, share
  extension, and both test bundles)
- CURRENT_PROJECT_VERSION 44 -&gt; 45 across all targets
- RELEASE_ROADMAP.md: v5.0.0 marked shipped, "Current version" -&gt; v5.0.0

The three version sources are kept in lockstep, matching the v4.4.1 alignment
policy. App Store archive/submit remains a manual step outside the repo.
</content>
</entry>
<entry>
<title>docs: mark v5.0.0 engineering complete in the roadmap</title>
<updated>2026-07-25T05:29:18+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T05:28:29+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=e818eefdeeee202784f14a01f62ef6fc7ac40935'/>
<id>urn:sha1:e818eefdeeee202784f14a01f62ef6fc7ac40935</id>
<content type='text'>
All four v5.0.0 workstreams have landed on main: the deterministic-core test net
(#43), the Local API v1 contract (#45), the versioned store-migration policy
(#46), and the god-file decomposition (#47-#53, DomainViewModel 4864-&gt;4170 and
ContentView 3881-&gt;1625). Rewrites the v5.0.0 section to record what shipped per
item with PR references, notes the inspection-core split is deferred as a design
change rather than a mechanical move, and lists cutting the release (version
bump + submission) as the only remaining step. Refreshes the now-stale
cross-cutting note (the "no XCTest target" gap was closed first).
</content>
</entry>
<entry>
<title>refactor: extract result section views into ResultSectionViews (v5 step 4, 7/n)</title>
<updated>2026-07-25T05:23:45+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T05:22:34+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=0571e751f8d6f2975b7ea9fdbd30f5b77c05e836'/>
<id>urn:sha1:0571e751f8d6f2975b7ea9fdbd30f5b77c05e836</id>
<content type='text'>
Seventh slice of the god-file decomposition; second ContentView.swift slice.

- Moves the nine result detail section views into ResultSectionViews.swift:
  DomainSectionView, OwnershipSectionView, IntelligenceSectionView,
  SubdomainsSectionView, DNSSectionView, WebSectionView, EmailSectionView,
  NetworkSectionView, and PortRowsView. Pure move.
- Promotes the one-line appLoadingStyle() View helper private -&gt; internal: the
  moved section views and the staying LoadingCardView both use it. That is the
  only non-deletion edit to ContentView.swift.

The shared primitives (CardView, SectionTitleView, LabeledValueRow,
ResultColors) stay in ContentView.swift and are reached cross-file.

ContentView.swift: 2738 -&gt; 1626 lines (3881 at the start of the ContentView
pivot; -2255 total). App builds clean; unit suite 58/58. No project.pbxproj
change.
</content>
</entry>
<entry>
<title>refactor: extract Settings screens into SettingsViews.swift (v5 step 4, 6/n)</title>
<updated>2026-07-25T05:16:00+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T05:15:15+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=48999737a37ef16cad0d84e71fa1b229bb04903f'/>
<id>urn:sha1:48999737a37ef16cad0d84e71fa1b229bb04903f</id>
<content type='text'>
Pivots the god-file decomposition to ContentView.swift (the other ~3.9k-line
file). Unlike DomainViewModel, this is a set of independent SwiftUI View structs,
so it splits with no shared-state entanglement.

- Moves the Settings surface into SettingsViews.swift: SettingsView (the tab
  root, presented from RootTabView) plus its nine file-private section screens
  (Display, History &amp; Network, iCloud Sync, Local API, Monitoring, Import &amp;
  Export, Data Management, About) and the DataImportPreviewSheet. Pure move.
- The private section screens stay file-private together in the new file, reached
  only through SettingsView's navigation links. Zero visibility changes were
  needed: the block references nothing file-private to ContentView.swift, so the
  ContentView diff is pure deletion.

ContentView.swift: 3881 -&gt; 2739 lines. App builds clean; unit suite 58/58.
No project.pbxproj change (DomainDig/ is a synchronized group).
</content>
</entry>
<entry>
<title>refactor: extract history query surface into DomainViewModel+History (v5 step 4, 5/n)</title>
<updated>2026-07-25T05:11:47+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T05:10:46+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=321a10f85435fdcbfb54f109ee53df895a550a42'/>
<id>urn:sha1:321a10f85435fdcbfb54f109ee53df895a550a42</id>
<content type='text'>
Fifth slice of the DomainViewModel decomposition.

- Moves the history query/compare surface into DomainViewModel+History.swift:
  per-domain snapshot reads (historyEntries/historyEntry/previousHistoryEntry),
  timeline grouping (timelineEntries/timelineSections), the two-snapshot
  selection model (toggle/clear/selectedSnapshots), diff generation and
  navigation (generateDiff*/moveTo*DiffChange/currentDiffTargetSectionID), and
  the resolver-mismatch notes. Pure move.
- The inspection *pipeline* that produces history — performLookup,
  applySnapshot, saveHistoryEntry, persistHistory, snapshot-metadata bookkeeping
  — stays on the main type with the inspection code.

This slice needs zero visibility changes: every dependency (the history array,
diff state, latestSnapshot) was already internal. The main-file diff is pure
deletion.

DomainViewModel.swift: 4306 -&gt; 4171 lines (4864 at the start of step 4; -693
total). App builds clean; unit suite 58/58. No project.pbxproj change.
</content>
</entry>
<entry>
<title>refactor: extract workflow surface into DomainViewModel+Workflows (v5 step 4, 4/n)</title>
<updated>2026-07-25T05:07:52+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T05:05:53+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=9092ab60f82152cc4ae38677c3af4a76013f4e54'/>
<id>urn:sha1:9092ab60f82152cc4ae38677c3af4a76013f4e54</id>
<content type='text'>
Fourth slice of the DomainViewModel decomposition (off main; the audit/
monitoring/export stack has merged).

- Moves the workflow surface into DomainViewModel+Workflows.swift: workflow
  lookup (workflow(withID:)/workflowsContaining), the DomainWorkflow
  collaboration checks, the CRUD mutators (create/update/delete/add/remove/move),
  runWorkflow/rerunCurrentDomain, and refreshWorkflowList. Pure move. The
  TrackedDomain overloads of canEdit/canDelete/collaborationLabel stay on the
  main type (they're watchlist collaboration, not workflow).
- Promotes the shared helpers the moved methods reach to internal, all staying
  on the main type: persistWorkflows (also called by clearWorkflows),
  startBatchLookup (the batch primitive shared with manual/watchlist runs),
  normalizedDomain/normalizedDomains, loadWorkflows, and the activeWorkflowRunID/
  Name state. The extension carries its own fileprivate String.nilIfEmpty,
  matching the per-file pattern already used across the codebase.

DomainViewModel.swift: 4433 -&gt; 4306 lines (4864 at the start of step 4).
App builds clean; unit suite 58/58. No project.pbxproj change.
</content>
</entry>
<entry>
<title>refactor: extract export/portability into DomainViewModel+Export (v5 step 4, 3/n)</title>
<updated>2026-07-25T05:00:29+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T04:59:07+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=e5d6c6c30df78e1d9b07088c256421aa663cb173'/>
<id>urn:sha1:e5d6c6c30df78e1d9b07088c256421aa663cb173</id>
<content type='text'>
Third slice of the DomainViewModel decomposition, stacked on the monitoring
split.

- Moves the export/data-portability surface into DomainViewModel+Export.swift:
  the single-report/batch/tracked-domain/timeline/workflow exporters, the
  full-backup and portable-slice exporters, prepareDataImport/applyDataImport,
  and persistCurrentAppSettings. The export-only WorkflowExportPayload struct
  moves with them. Pure move, no logic changes.
- The four report-projection helpers the exporters call (currentBatchReports,
  reports(for:), timelineReports, workflowReports) are promoted private -&gt;
  internal and stay on the main type: they build DomainReports through the
  shared report layer (report(for:)/reportBuilder), so they belong with
  inspection, not export. Those four visibility drops are the only non-deletion
  edits to DomainViewModel.

DomainViewModel.swift: 4601 -&gt; 4434 lines. App builds clean; unit suite 58/58.
No project.pbxproj change (DomainDig/ is a synchronized group).
</content>
</entry>
<entry>
<title>refactor: extract monitoring surface into DomainViewModel+Monitoring (v5 step 4, 2/n)</title>
<updated>2026-07-25T05:00:29+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T04:52:39+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=7805888980f6d41bcc881c5d2e6001b2d8e69351'/>
<id>urn:sha1:7805888980f6d41bcc881c5d2e6001b2d8e69351</id>
<content type='text'>
Second slice of the DomainViewModel decomposition, stacked on the audit split.

- Moves the monitoring configuration surface — notification authorization, the
  settings mutators (enabled/scope/interval/adaptive/sensitivity/quiet-hours/
  alert filter/alerts/selection), toggleMonitoring, runMonitoringNow, the manual
  run, and the per-domain interval/status labels — plus the private
  intervalLabel helper into DomainViewModel+Monitoring.swift. Pure move.
- persistMonitoringSettings(...) and sanitizeMonitoringSelection() are promoted
  private -&gt; internal: the tracked-domain lifecycle (add/delete/clear) on the
  main type calls them too, so they stay put and are now reachable cross-file.
  The two visibility drops are the only non-deletion edits to DomainViewModel.

DomainViewModel.swift: 4761 -&gt; 4601 lines. App builds clean; unit suite 58/58.
No project.pbxproj change (DomainDig/ is a synchronized group).
</content>
</entry>
<entry>
<title>refactor: extract audit surface into DomainViewModel+Audit (v5 step 4, 1/n)</title>
<updated>2026-07-25T05:00:29+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T04:45:56+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=e597cdf5b7084634e300af39b622c8552c9cc5e8'/>
<id>urn:sha1:e597cdf5b7084634e300af39b622c8552c9cc5e8</id>
<content type='text'>
First slice of the v5.0.0 god-file decomposition. DomainViewModel.swift is a
single ~4.9k-line class body; this begins splitting it into focused
`DomainViewModel+&lt;Concern&gt;.swift` extensions, one cohesive concern at a time,
with each move behavior-preserving and verified by build + the test net.

- Moves the audit read/CRUD/export surface (audits/auditSession/auditTimeline,
  updateAuditStatus/Notes, toggleAuditChecklistItem, add/update/remove
  AuditFinding, exportAuditData) into DomainViewModel+Audit.swift as an
  extension. Pure move — no logic changes.
- `startAudit(for:)` intentionally stays on the main type: it drives a live
  inspection to seed the session, so it belongs with the inspection pipeline
  until that is extracted. `persistAuditSessions()` is promoted from private to
  internal so both files can call it (its only cross-file dependency).

No project.pbxproj change is needed — DomainDig/ is a file-system-synchronized
group, so the new file is picked up automatically.

DomainViewModel.swift: 4864 -&gt; 4761 lines. App builds clean; unit suite 58/58.
</content>
</entry>
<entry>
<title>feat: versioned store-migration policy for persisted data (v5 step 2)</title>
<updated>2026-07-25T04:37:00+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T04:35:18+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=99e4623af1b08f36120a01b67cbe60df99668651'/>
<id>urn:sha1:99e4623af1b08f36120a01b67cbe60df99668651</id>
<content type='text'>
Third v5.0.0 roadmap item: define and implement a migration policy for the
on-device persisted store (tracked domains, history/snapshots, audits,
workflows, monitoring, settings), so data upgrades cleanly across app versions
instead of relying on a one-shot marker.

- DataMigrationService is reworked from a single boolean marker
  (`data.migrations.v3_4_0`) into a versioned runner keyed by an integer store
  schema version (`data.storeSchemaVersion`). It runs each step once in
  ascending order up to `currentStoreSchemaVersion`, stamping the version as it
  goes. Adding a future migration is now a `case N:` plus a version bump.

  Policy guarantees, all covered by tests:
  - Forward-only and idempotent; every step must be safe on an empty/older store.
  - Never downgrades: a store written by a newer build (higher version) is left
    byte-for-byte untouched.
  - Pre-versioning installs are handled: a set legacy boolean marker reads as
    "already at v1", so the v1 normalization never re-runs for them.

  v1 is the existing normalization pass (dedup + drop the legacy `watchedDomains`
  key + sanitize monitoring settings), now expressed as migration step 1.

- Docs/data-migration.md documents the persisted surface, the two independent
  version lines (store vs. backup export), when to use lenient decoding vs. a
  migration step, the runner contract, an "adding a migration" checklist, and
  backup-import compatibility. Linked from the README.

- DataMigrationServiceTests: 6 tests over legacy fixtures — fresh-store stamping,
  legacy `watchedDomains` migration + key drop, in-place dedup of the stored
  blob, idempotence, legacy-marker-as-v1, and the no-downgrade guard. Full unit
  suite: 58 passing.
</content>
</entry>
</feed>
