<feed xmlns='http://www.w3.org/2005/Atom'>
<title>domain-dig.git/DomainDig, branch v5.0.1</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.1</id>
<link rel='self' href='http://git.krz.sh/krz/domain-dig.git/atom?h=v5.0.1'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/'/>
<updated>2026-07-25T17:19:37+00:00</updated>
<entry>
<title>feat: owner Pro+ allowlist via CloudKit, cut v5.0.1</title>
<updated>2026-07-25T17:19:37+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T17:14:58+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=963ce3fb34bd6c6bd72f1aa956ce602267b90e9c'/>
<id>urn:sha1:963ce3fb34bd6c6bd72f1aa956ce602267b90e9c</id>
<content type='text'>
Grants the app owner Pro+ without a purchase, keyed to their CloudKit
user-record ID so it works on the release App Store build.

- OwnerAccess holds the owner's CloudKit user-record ID (opaque, per-Apple-ID,
  scoped to the app's container; safe to publish — CloudKit verifies identity
  server-side, so it can't be presented by anyone else).
- PurchaseService resolves the allowlist against CloudKit once per launch and,
  on a match, records a persisted owner grant so it applies instantly and
  offline thereafter. The grant only ever elevates the tier to .proPlus and
  defers to the existing #if DEBUG overrides, so real purchases and free/pro
  testing are unaffected. cachedTier / cachedEntitlement were refactored to
  fall back to the owner grant only when no debug override or stored purchase
  applies.
- Supersedes the DEBUG record-ID reveal (PR #60): its only purpose was to read
  the owner's ID, which is now hardcoded, so the reveal is not shipped.

Release cut: MARKETING_VERSION 5.0.0 -&gt; 5.0.1, CURRENT_PROJECT_VERSION 45 -&gt; 46,
AppVersion.current -&gt; 5.0.1, roadmap updated. App builds clean; unit suite 63/63.
</content>
</entry>
<entry>
<title>fix: align the redirect-row copy button to the trailing edge</title>
<updated>2026-07-25T16:52:10+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T16:49:54+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=7156c088a438526182c517fafa35d31990d44f18'/>
<id>urn:sha1:7156c088a438526182c517fafa35d31990d44f18</id>
<content type='text'>
In the Redirects section, each hop rendered the 44pt AppCopyButton inline
between the URL and the "(final)" label with no spacer, so the tall tap target
broke the row baseline and pushed "(final)" to the right.

Move the copy button to the trailing edge behind a Spacer and keep "(final)"
next to the URL — matching the certificate-SAN rows and the "Final URL" row in
the same card, which already lay their copy buttons out this way.
</content>
</entry>
<entry>
<title>fix: point Rate at the App Store and drop the empty Legal section (#56)</title>
<updated>2026-07-25T16:49:23+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T16:45:32+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=45741a51601a3075ad825795be2dc36a637ac742'/>
<id>urn:sha1:45741a51601a3075ad825795be2dc36a637ac742</id>
<content type='text'>
- Rate DomainDig now opens the App Store write-review URL
  (AppLinks.writeReview, derived from the real app id) instead of the in-app
  StoreKit prompt, so an explicit tap always reaches the review composer. Drops
  the now-unused StoreKit import and requestReview environment.
- The copyright line moves into the "Support the App" section footer, removing
  the empty Section content closure SonarCloud flagged (swift:S1186).
</content>
</entry>
<entry>
<title>feat: use the real App Store ID in AppLinks (#56)</title>
<updated>2026-07-25T16:49:23+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T16:41:21+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=269d9590711144057a9f579761c30ff903a75eec'/>
<id>urn:sha1:269d9590711144057a9f579761c30ff903a75eec</id>
<content type='text'>
The listing is already live (per the README badge), so replace the placeholder
appStoreID with the real 6760368004. The App Store listing, write-review, and
Share URLs now resolve to the actual app. Removes the release TODO.
</content>
</entry>
<entry>
<title>feat: enrich Settings → App Info with metadata &amp; resource links (#56)</title>
<updated>2026-07-25T16:49:23+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-25T16:35:46+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/domain-dig.git/commit/?id=074052763638a36e274e18d00f5f5ab21846be28'/>
<id>urn:sha1:074052763638a36e274e18d00f5f5ab21846be28</id>
<content type='text'>
Replaces the three-row App Info screen with a full About/Resources/Support/
Legal layout, driven by a declarative AppInfoRow model with all URLs centralized
in one AppLinks namespace.

- About: Version now shows "5.0.0 (build N)" (CFBundleShortVersionString +
  CFBundleVersion), plus Storage, Backup Schema, and Minimum iOS (17.6).
- Resources: What's New (bundled ReleaseNotes.json sheet, no network),
  Documentation &amp; FAQ, Source Code, Privacy Policy, and Acknowledgements
  ("no third-party dependencies" + MIT license).
- Support: Report an Issue (a sheet that shows the locally-assembled
  version/OS/device diagnostics before offering Email or GitHub — nothing is
  collected silently) and Contact (mailto).
- Support the App: Rate (SwiftUI's @Environment(\.requestReview), which handles
  the scene internally and respects Apple's throttling — the modern, safer
  equivalent of the issue's SKStoreReviewController + connectedScenes path) and
  Share (ShareLink to the App Store listing).
- Legal: copyright footer with the current year.

External rows use Link/openURL with an "opens outside the app" accessibility
hint; the screen is standard adaptive Form controls with semantic colors, so it
tracks Dynamic Type and light/dark automatically.

New files (AppLinks, AppInfo, ReleaseNotes[.swift/.json], AppInfoView) auto-
compile/bundle via the synchronized DomainDig/ group. AppInfoTests covers the
mailto builder, version format, diagnostics contents, and that the bundled notes
ship and parse. Unit suite green.

Placeholder pending the App Store listing: AppLinks.appStoreID (the review/share
URLs derive from it), flagged with a TODO.
</content>
</entry>
<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>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>
</feed>
