| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Replaces every hard-coded colour with semantic asset colours that adapt to
light, dark, and Increase Contrast. Dark mode stays locked, so this is a
pure refactor: the audit reports the same findings before and after.
The accent is now blue rather than cyan, per the tech/DNS theme.
Why custom values rather than the system palette: every system colour
fails WCAG AA in light mode. Measured on white — systemYellow 1.51:1,
systemOrange 2.20:1, systemGreen 2.22:1, systemCyan 2.54:1, systemRed
3.55:1. All of them pass in dark mode, which is why the dark-locked app
looked fine, and why unlocking light mode was never a matter of deleting
.preferredColorScheme(.dark). Every new value clears 4.5:1 as text on its
page, its card, and its own 16% badge tint — the way AppStatusBadgeView
actually draws it.
The accent needed splitting in two. As text on a dark background it must
be light; as a fill behind a white label it must be dark. #4DA3FF reads
8.00:1 as text on black but 2.63:1 behind white text, so StatusInfo /
AccentColor cover the foreground role and AccentFill covers
.borderedProminent. AppOnAccent is the label colour for a solid fill and
flips by scheme.
Colours live in Shared/Colors.xcassets rather than the app catalog: the
Shared folder is already a synchronized group in all three targets, so
the widget and share extension pick the palette up with no project-file
surgery. AccentColor stays in the app catalog as the global tint — and is
now actually defined, having been an empty colorset that silently left
system controls rendering in stock blue while custom chrome used cyan.
Two deliberate visual changes: orange folds into StatusWarning and pink
into StatusCritical. They encoded the same severity as the colours they
now share, and both sites also carry a text label.
Audit findings drop 15 to 14, and one of the originals turned out to be
a phantom: the Inspect contrast failure was the Run button in its
disabled state, which WCAG 1.4.3 exempts. testInspectScreen now types a
domain first so the audit measures an enabled control. Findings also
carry the offending element now, so the remaining clipped-text items name
themselves ("No Portfolio Yet", "Search domains") instead of being
anonymous.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The two-job CI matrix was buying two near-identical iOS 26.x runs at
double the macOS minutes. GitHub's macos-26 image ships only 26.x
simulator runtimes, so it cannot test the 17.6 floor at all, and floor
coverage was the entire justification for the second job.
Split the work by what each side can uniquely do instead:
CI keeps one job on the newest runtime. Its real value is not the
runtime — it is building a clean checkout of the merge result, which
catches a file that was never committed. A local run cannot, and that
failure mode is live here: DomainDig.xcodeproj is hand-edited and uses
file-system-synchronized groups, where an entire missing folder still
builds locally. sr.ht cannot run macOS, so this is the only place that
check exists. Collapsing the matrix also removed the deployment-target
math, since "newest" is always above the floor.
Scripts/audit-a11y.sh runs the audit against real runtimes, defaulting
to floor + current. It reads the deployment target from the project
rather than hard-coding it, selects the oldest runtime at or above it
(one below is useless — the app cannot install), and says so plainly
when the nearest installed runtime is a major version above the target
rather than implying floor coverage it does not have.
.githooks/pre-push runs the floor tier, and only when Swift, asset, or
project files changed. Pre-push rather than pre-commit because the suite
takes ~85s: at pre-commit that blocks every commit, and a hook routinely
bypassed with --no-verify is worse than none. Opt in per clone with
'git config core.hooksPath .githooks'.
Docs/ACCESSIBILITY.md records the split, the measured non-nested
coverage that motivates it, and the enforcement ratchet.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two problems the first CI run exposed.
Audit timeouts. Three tests failed with "Audit failed to complete in
time" (code -56) on the GitHub runner. That is the audit's own internal
deadline on a slower machine, not an app defect, and the harness had no
resilience to it. Audits now retry up to three times, and a screen that
still cannot be audited is reported via XCTSkip rather than passing.
Skips are distinct from passes in CI, so an unaudited screen stays
visible instead of being silently counted as clean. The Dynamic Type
sweep attempts every screen before skipping, so one slow screen cannot
drop the other four.
Overclaimed floor coverage. The two-simulator matrix was justified on
covering the oldest supported OS, but the macos-26 image ships only iOS
26.x runtimes, so "floor" resolved to 26.2 and "current" to 26.5 — the
run compared two 26.x images and never touched an 18.x one. The measured
non-nested coverage that motivated the matrix (18.6 vs 27.0) reproduces
locally but not on this runner. The workflow comment now states this
plainly, and the selection step emits a warning annotation when the
resolved floor sits a major version or more above the deployment target,
so the gap is visible in the CI UI rather than assumed away.
Installing an older runtime in CI is possible via xcodebuild
-downloadPlatform but costs several GB and minutes per job; left out
pending a call on whether that trade is worth it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Phase 0 of the accessibility pass: a regression guard that must exist
before any of the remedial phases, so their acceptance criteria are
enforced rather than asserted once by hand.
- Fix the project-level IPHONEOS_DEPLOYMENT_TARGET, which was 26.2 while
all three targets are 17.6. It was shadowed everywhere today, but any
target added later would silently inherit it and drop iOS 17.6 support
with no error.
- Add a DomainDigUITests target running performAccessibilityAudit on the
six primary screens, plus a sweep of every root screen at
AccessibilityXXXL. Uses the existing DOMAIN_DIG_FORCE_PRO_PLUS debug
argument so Pro-gated screens are reachable.
- Findings are reported, not failed. The audit surfaces violations that
exist today, so gating on them would block unrelated PRs until the
whole pass lands. Enforcement is a committed constant,
AccessibilityAuditHarness.enforcedAuditTypes, widened per audit type as
each phase clears a category.
- CI now runs xcodebuild test across two simulators. Audit coverage is
not nested between OS versions: on Tracked Domains, iOS 18.6 reported 2
findings and iOS 27.0 reported 6 (including contrast and
element-detection issues 18.6 never raised), while at accessibility
text sizes the Dashboard produced a hit-region finding on 18.6 that
27.0 did not.
- Simulator selection is now dynamic and floor-aware. The previous
selector took the first iPhone from any runtime, which can resolve to a
simulator below the deployment target where the app cannot install.
Baseline on iOS 18.6: 15 findings across 7 tests — text clipping on every
screen, contrast on Inspect and Settings, and a hit-region failure on the
Dashboard at accessibility text sizes.
|
| |
|
|
|
|
| |
Closes the last swift:S3358 in the new-code period. The hasChanges
branch nested a severity check inside the impactClassification ternary;
split into if/else. No behavior change.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fixes the 4 reported bugs and ~97 code smells flagged in the new-code
period. No behavior changes.
Bugs (swift:S3923) — DomainInspectionService's confidenceFor* helpers each
returned `error == nil ? .low : .low`, an inert conditional. Simplified to
`return .low` and dropped the now-unused `error` parameter.
Smells:
- Merged 14 identical `.empty`/`.error` switch branches in DomainViewModel
- Consolidated duplicate implementations (clearPresentedResults/reset,
String.nonEmpty/nilIfEmpty, ExportFormat.id/fileExtension)
- Extracted nested ternaries into TLSGrade.tone, EmailSecurityGrade.tone,
and ChangeImpactClassification.color; removed ContentView.impactColor
and the duplicate mapping in BatchResultsView
- Documented empty closures and singleton inits
- Marked unused protocol-conformance parameters `_`
- Renamed CloudSyncTrigger.`import` to `imported` (raw value preserved)
and SSLSessionDelegate's _serverTrust/_tlsMetadata
- Merged nested ifs in the DER parser; flattened closure nesting in
PortScanService and IntegrationService
- Replaced two-case switches with if/else
Left open: S107 (init parameter counts), S115 (constants mirroring DoH and
ipapi JSON keys), S1075 (false positives on https:// literals), and two
S117 hits on SwiftUI $binding shorthand. These want a Won't Fix resolution
in SonarCloud, not a code change.
|
| |
|
|
|
|
|
| |
The badge added alongside the SonarCloud badges claimed GPL v3, and the
README's License section claimed 'GPL 3.0 or later'. LICENSE has always
been MIT, so both statements misrepresented the project's terms on its
public front page. The section text predates this branch.
|
| | |
|
| |
|
|
|
|
|
|
| |
Bump AppVersion/marketing version to 4.8.2 and build number to 42 across the
app, widget, and share extension targets. Mark v4.8.2 (delivery visibility for
disabled integrations, forced queue retries, unreachable-domain reporting,
Swift 6 concurrency warnings, synced StoreKit configuration) shipped in
RELEASE_ROADMAP.md and record the UAT follow-ups as resolved.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Closes #10.
resolvedSnapshotAfterFallback replaces a failed lookup's snapshot with
the previous one, so alertDescriptor compared the old snapshot against
itself, found matching hashes, and the run reported 'No meaningful
changes' for a domain that was never actually reached. Nothing in the
UI or the monitoring log distinguished that from a genuine no-change.
MonitoringDomainResult now carries unreachableReason, set when the
fallback fires. It is Optional so already-persisted monitoring logs
still decode. The run summary reads 'Could not check — kept the previous
result' with the underlying error, and monitoringEvents emits a warning-
severity monitoringFailure so configured integrations hear about it
rather than seeing silence.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Closes #8, closes #9.
enqueue(events:) filtered to enabled targets before writing any
DeliveryRecord, so events routed to a disabled integration disappeared
with nothing in the Delivery Log. Disabled targets now record a .skipped
entry with a reason, matching how filter mismatches are already surfaced.
sendTest bypassed the isEnabled check entirely, so a test event
delivered against a target that silently dropped every real event —
exactly the wrong signal when someone is verifying their setup. It now
skips with the same reason.
processQueueNow only restarted the processing task; it never moved
nextAttemptAt, so an item in retry backoff stayed undue and the fresh
task went straight back to sleep. Backoff reaches an hour, so the button
appeared inert for the one case it exists to handle. It now pulls every
queued item forward, and reports when the queue is empty instead of
returning silently.
|
| |
|
|
|
|
|
|
|
|
|
| |
DomainDig.storekit sat beside the DomainDig/ source folder, and Xcode
treated the two as conflicting when creating the synced configuration —
the resulting confirmation dialog offered to replace the folder. Naming
the file SyncedProducts.storekit removes the collision entirely and
matches the convention Xcode uses for synced configurations elsewhere.
Updates the project file reference and the scheme's
StoreKitConfigurationFileReference to match.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Xcode synced DomainDig.storekit against App Store Connect, replacing the
hand-authored placeholders with real values: the app's internal ID
(6760368004), the real subscription group ID (22051301), a synchronized
timestamp, and group localizations.
Also registers the file in the project so Xcode can find and re-sync it,
but without target membership. Xcode's default added it to the Resources
build phase of all three targets, which would ship the test configuration
inside the app, widget, and share extension bundles. The scheme
references it by path for the Run action; it does not need bundling.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Compared against a known-good Xcode-generated configuration
(hutch/SyncedProducts.storekit) and corrected the hand-authored file:
- version bumped 4.0 -> 5.0, the format Xcode 26 writes
- added the appPolicies block Xcode always emits
- added the missing settings keys (_askToBuyEnabled,
_billingGracePeriodEnabled, _billingIssuesEnabled, _disableDialogs,
_renewalBillingIssuesEnabled, _timeRate)
- _developerTeamID set to ZCNAX3VL9D, matching DEVELOPMENT_TEAM in the
project rather than being left empty
- _applicationInternalID cleared. It previously held 6763610682, which
is the Apple ID of the Pro+ (Annually) subscription, not the app's
internal ID. That value is now on the subscription's internalID where
it belongs; the app ID is left empty rather than wrong.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The StoreKitConfigurationFileReference added in #11 used one '../' too
many, resolving outside the repository. Xcode resolves it relative to
the .xcodeproj's xcshareddata directory, so two levels reaches the repo
root.
SweepActivityAttributes is now explicitly nonisolated. The app target
sets SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor while the widget target
does not, so a type shared by both inferred a main-actor-isolated
ActivityAttributes conformance that ActivityKit cannot use from its
concurrent contexts.
LocalAPIService's logger closures captured self strongly while their
inner Tasks declared [weak self]. The weak capture is now on the outer
closure and bound before the Task, so the concurrently-executing closure
references an immutable strong local rather than the weak capture.
|
| |
|
|
|
|
|
| |
PR #11 merged after the v4.8.1 version bump, so the roadmap's v4.8.1
section omitted the product ID correction and the StoreKit configuration
file. Both are in the 4.8.1 tree; document them alongside the other
fixes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Adds DomainDig.storekit mirroring the App Store Connect setup: one
subscription group with Pro+ at level 1 and Pro at level 2, using the
corrected product IDs.
Wires it into the Run action so purchases resolve locally against
StoreKit instead of the App Store, and disables the
DOMAIN_DIG_FORCE_PRO_PLUS launch argument, which bypasses StoreKit
entirely and would mask whether the purchase path works.
Prices in the configuration are local-testing placeholders and do not
need to match App Store Connect.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
The four product ID constants did not match the auto-renewable
subscriptions configured in App Store Connect, so Product.products(for:)
returned an empty array and tier(for:) resolved every purchase to .free.
Product IDs are permanent in App Store Connect, so the code is corrected
to match the configured values rather than the reverse:
domaindig.pro.monthly -> domaindig.pro.month
domaindig.pro.yearly -> domaindig.pro.annually
domaindig.dataplus.monthly -> domaindig.proplus.monthly
domaindig.dataplus.yearly -> domaindig.proplus.annually
|
| |
|
|
|
|
|
|
| |
Bump AppVersion/marketing version to 4.8.1 and build number to 41 across the
app, widget, and share extension targets. Mark v4.8.1 (scheduled report tap
target, completed Pro gate, markdown underline rendering, DNS record dedup,
inspect tab keyboard behavior) shipped in RELEASE_ROADMAP.md, and note the
three follow-ups filed during UAT.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The plain-text-to-Markdown transform only recognized '-' underlines, but
batchText writes '=' — under the document title and as a 48-character
separator between reports. Both leaked through as literal bullets, so a
generated report opened with:
# Scheduled Watchlist Report
- ==========================
Title underlines are now consumed alongside the title, '=' underlines
promote to H2 the same as '-', and standalone divider runs render as a
Markdown horizontal rule.
Separately, the DNS section concatenated apex and wildcard records
without dedup. On a domain with wildcard DNS resolving to the same
addresses as the apex, every value was listed twice. Now deduped while
preserving order, so a wildcard value that genuinely differs is still
shown.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Scheduled Reports: the entire Overview section was wrapped in a single
VStack inside one List row, so SwiftUI collapsed every control into one
tap target and the menu-style Cadence Picker captured taps intended for
the Generate Now button. Unwraps the VStack so each control is its own
row, matching the pattern used in IntegrationsView and elsewhere.
Also extends the .automatedMonitoring gate to the two Pickers and the
Generate Now button. Previously only the Toggle was disabled, leaving a
button that appeared active on Free but silently no-opped against the
guard in ScheduledReportService.
Inspect tab: removes the keyboard toolbar's Dismiss Keyboard button and
the onAppear that focused the single-domain field at launch.
|
| | |
|
| |
|
|
|
|
|
|
|
| |
Validating only at send time meant an http:// URL saved fine and then
failed silently on delivery. Validate in upsert so the integration editor
surfaces it, and give the failure its own error case rather than reusing
the generic invalid-URL message.
The send-time guard stays as defense in depth for URLs saved before this.
|
| |
|
|
|
|
|
|
|
|
| |
Require HTTPS for outbound integration webhooks. Webhook URLs are
themselves secrets (Slack in particular), so an http:// endpoint leaked
both the URL and the alert payload in cleartext.
Disable DomainDebugLog in release builds. Every message used
privacy: .public, which opted out of OSLog redaction and wrote looked-up
domains to the unified log in shipped builds.
|
| |
|
|
|
|
|
| |
Bump AppVersion/marketing version to 4.8.0 and build number to 40 across the
app, widget, and share extension targets. Mark v4.8.0 (markdown/PDF export,
scheduled reports, stronger share affordances, verified local API consistency)
shipped in RELEASE_ROADMAP.md.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- ScheduledReportService (@MainActor, headless/storage-backed like
DomainMonitoringService): builds the latest report for every tracked domain
from persisted history, exports it via DomainReportExporter in the
configured format (markdown/PDF/JSON), writes it to a local Documents
subdirectory, logs the run, and fires a "Scheduled Report Ready" local
notification.
- ScheduledReportScheduler mirrors DomainMonitoringScheduler's BGTaskScheduler
approach with its own task identifier (net.cleberg.DomainDig.report.schedule,
added to Info.plist) and a daily/weekly cadence.
- ScheduledReportsView (Settings → Scheduled Reports): enable toggle, cadence
and format pickers, "Generate Now", and a log of past reports each shareable
via the existing ExportPresenter share sheet.
- Gated behind the existing .automatedMonitoring capability (Pro), consistent
with monitoring being the other background-automation feature.
- Settings/logs persist via UserDefaults (DomainExportFormat is now Codable),
not DomainDataPortabilityService backup/restore — this is local automation
configuration, not user-authored content, same reasoning as v4.7.0's
watchlist saved views.
|
| |
|
|
|
|
|
|
|
| |
Adds "Export Markdown"/"Export PDF" (and batch/workflow equivalents) to the
single-result, batch, watchlist, and workflow export menus, gated behind
.advancedExports like the existing CSV/JSON options. Matches the existing
menu structure and Pro-gate wording per file rather than introducing a new
shared component, consistent with how CSV/JSON were already duplicated across
these five menus before this change.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- DomainExportFormat gains .markdown and .pdf (CaseIterable, Identifiable,
titled), alongside the existing text/csv/json.
- Markdown reuses the existing text-export content verbatim via a line-based
transform (section "Title\n----" underlines become "## Title", the leading
title becomes an H1, other lines become bullets), so the two formats can
never drift apart.
- PDF renders that Markdown as a simple monospaced multi-page document via
UIGraphicsPDFRenderer (mirrors AuditExporter's existing PDF approach;
degrades to raw Markdown bytes on non-UIKit platforms).
- Replaced the single/batch/tracked-domains/workflow share call sites' hand-
written per-format switches with format-agnostic functions
(exportSingleReportData, exportBatchReportData, exportTrackedDomainsData,
exportWorkflowData) that delegate straight to DomainReportExporter, removing
the now-orphaned per-format helper functions those switches used to call.
|
| |
|
|
|
|
|
| |
Bump AppVersion/marketing version to 4.7.0 and build number to 39 across the
app, widget, and share extension targets. Mark v4.7.0 (comparison, reputation,
tags/saved views) shipped in RELEASE_ROADMAP.md and escalate the overdue
XCTest-target gap ahead of v5.0.0.
|
| |
|
|
|
|
|
|
|
|
|
| |
- TrackedDomain.tags: [String] (backward-compatible custom decode), with
updateTags(_:for:) and a normalized/deduplicated write path.
- Tag editing in TrackedDomainDetailView (comma-separated field, chip display).
- Tag filter chips in WatchlistView (TagFilterChipRowView), integrated into
filteredTrackedDomains alongside the existing filter/search.
- Saved views: name + snapshot the current tag/filter/sort as a
WatchlistSavedView preset (UserDefaults-backed, not part of backup/restore),
with a management sheet to apply or delete presets.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- New DomainReputationResult model (status: clean/listed/unknown, listed
sources, checked-at) and a `reputation(domain:)` method on ExternalDataService,
mirroring the existing pluggable-URL enrichment pattern (ownership history,
DNS history, extended subdomains, pricing). With no endpoint configured
(the default; DomainDig ships no bundled third-party reputation dependency)
it resolves to unavailable rather than "clean".
- New .reputation FeatureCapability/DataCapability, gated Pro+ like domainPricing.
- Threaded reputation/reputationError through LookupSnapshot and HistoryEntry
(backward-compatible decode) so results persist with history entries.
- Auto-fetched in performLookup alongside pricing; surfaced as a "Reputation"
info row, folded into DomainInsightEngine's risk score/factors and top-level
insights (a listed domain raises risk score and adds a factor/insight), and
exported in text, CSV, and JSON report output.
- Reputation-driven risk changes ride the existing change-severity pipeline, so
a listed status flip is visible to monitoring the same way any other risk
delta is, without bespoke monitoring wiring.
|
| |
|
|
|
|
|
|
|
|
| |
- DiffService.compare(domainA:domainB:) reuses the existing section-diff
builders to compare two distinct domains' latest reports, returning a new
DomainComparisonResult (parallel to the time-based DomainDiff).
- DomainCompareView: pick two tracked domains and render the comparison with
the existing DomainDiffView section renderer.
- Entry point: "Compare Domains" in the Watchlist toolbar menu, shown once at
least two domains are tracked.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
actionable notifications
- Sweep Live Activity: SweepActivityAttributes (Shared/), lock-screen + Dynamic
Island UI in the widget extension, driven by SweepActivityController wired into
the batch pipeline (begin/update/end); NSSupportsLiveActivities in Info.plist.
- Share extension (DomainDigShareExtension): accepts a web URL from the share
sheet, extracts the host, and hands it to the app via the App Group inbox
(DomainDigShareInbox); the app consumes it on activation and inspects it.
- iPad layout: RootTabView uses NavigationSplitView in the regular size class
and the tab bar in compact.
- Actionable notifications: per-domain threadIdentifier grouping, a Re-inspect
action, and tap routing into the domain detail via the intent router.
- Bump version to 4.6.0 (build 38) across app, widget, and share targets.
|
| |
|
|
|
|
| |
Update RELEASE_ROADMAP.md to show v4.4.1 and v4.5.0 as shipped (with the actual
delivered scope), set the current version to v4.5.0, and note the deferred Lock
Screen widget families. Refresh the README release target to v4.5.0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Finishes the v4.5.0 "Home Screen & Shortcuts reach" scope that the tag shipped
partially:
- Add RunSweepIntent (opens the app and runs refreshAllTrackedDomains via the
in-process router) and expose it in DomainDigShortcuts.
- Extend the domaindig:// scheme with `sweep` and `domain` (detail) actions;
route .detail to present TrackedDomainDetailView and .sweep to refresh the
watchlist. Move DomainDigDeepLink into Shared/ so the widget can build links.
- Add a WidgetKit extension (DomainDigWidgetExtension) with small/medium/large
Portfolio widgets showing health counts, per-domain status, and certificate
countdowns; tapping a domain deep-links into its detail.
- Share portfolio state via an App Group (group.net.cleberg.DomainDig): the app
writes a DomainDigWidgetData snapshot on launch/foreground and on watchlist
changes and reloads timelines; the widget reads the same store.
|
| |\
| |
| | |
Fix Swift 6 actor-isolation warning in InspectDomainIntent
|
| | |
| |
| |
| |
| |
| |
| |
| | |
The app target sets SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, so
DomainReportBuilder and DomainInspectionService are MainActor-isolated. The
unmarked perform() ran nonisolated, so calling build(from:) triggered an
"expression is 'async' but is not marked with 'await'" warning (an error in the
Swift 6 language mode). Mark perform() @MainActor to match AddToWatchlistIntent.
|
| |\ \
| |/
|/| |
Set package ecosystem to 'swift' in dependabot config
|
| |/ |
|
| |
|
|
|
|
|
| |
Compile gate on macos-26 for the GitHub mirror (builds.sr.ht has no macOS
images). Runs `xcodebuild build` on pull_request and manual dispatch, with
docs-only paths ignored. Mirrors the CI setup in the sibling hutch repo,
adapted to `build` because there is no test target yet (planned for v5.0.0).
|
| |
|
|
|
|
|
|
|
|
|
| |
- Add InspectDomainIntent that runs the headless inspection pipeline and
returns a summary for Shortcuts, Spotlight, the Action button, and Siri
- Add AddToWatchlistIntent that opens the app and tracks a domain through the
existing premium-checked path via an in-process router
- Register the domaindig:// URL scheme with inspect/watch deep links routed in
RootTabView (onOpenURL)
- Expose both intents through DomainDigShortcuts (AppShortcutsProvider)
- Bump AppVersion/marketing version to 4.5.0 and build number to 37
|
| |
|
|
|
|
|
|
|
|
|
| |
- Make DomainDig/DomainDig/Audit* the single active Audit Mode implementation
(models, views, exporter) with an Audit tab and session/export UI
- Include audit sessions in backup/restore lifecycle counts, summaries, and
merge behavior via DomainDataPortabilityService
- Remove the DomainDigCLI target, source file, scheme, and all project
references; keep the shared inspection/report pipeline for the app
- Align AppVersion.current to 4.4.1 and refresh README/architecture docs
- Add RELEASE_ROADMAP.md
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
historical context
and inferred enrichment.
- add derived intelligence fields for provider fingerprinting, classification,
ownership transitions, hosting transitions, subdomain history, risk signals,
and inferred timeline events
- expand DNS history beyond A/NS snapshots to retain A, AAAA, MX, NS, TXT, and
CNAME change state
- persist enriched intelligence in snapshots and history entries so analysis is
local-first and incremental
- add a dedicated Data+ Intelligence panel to current and historical domain
detail views
- surface intelligence events in timeline rows and include Data+ changes in diff
output
- preserve non-blocking inspection behavior by keeping enrichment additive to
the main lookup path
This makes Pro+ materially deeper for investigative workflows by improving
historical ownership visibility, infrastructure context, hosting change
detection, subdomain intelligence, and explainable risk signals.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Expose DomainDig as a programmable local domain intelligence engine over
localhost with explicit user opt-in and token-based authentication.
Highlights:
- add a localhost-only API server with safe start/stop lifecycle
- require a local token for every request and store it in Keychain
- add read endpoints for portfolio, domains, history, events, and monitoring
- add inspection endpoints that reuse the existing inspection engine
- add monitoring enable/disable control endpoints
- add structured JSON response envelopes with API versioning
- add lightweight capped request logging with clear/reset support
- add a Settings UI for Local API enablement, token management, status, and logs
- start the server on launch only when enabled
- include Local API secrets in local data reset cleanup
This is the first programmability release for DomainDig and establishes the
foundation for Shortcuts, scripts, and other local automation workflows.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- add a destructive Delete All Data action with confirmation, progress, success,
and failure handling
- centralize wipe behavior in DataResetService instead of scattering delete
logic in views
- clear local persistence, temp/export files, integration secrets,
notifications, caches, and in-memory app state
- reset sync, purchase, and integration services after wipe so the app returns
to a clean first-launch state
Polish batch and empty-state UX
- present the batch sweep summary after manual bulk searches complete, not only
from Watchlist
- align the Workflows empty state styling with other empty states by removing
the extra background card treatment
Bump the project version from 4.0.0 to 4.1.0
|
| |
|
|
|
|
|
|
|
|
|
|
| |
* Outbound webhook delivery
* Native Slack webhook integration
* SMTP-based email alerts
* Per-integration event filtering
* Reliable local delivery queue
* Delivery logs and retry visibility
* Integration management UI
* Normalized event severity model
* Canonical structured event payloads
|
| |
|
|
|
|
|
|
|
|
|
| |
* Portfolio summary cards
* Recent activity feed across tracked domains
* Attention-required queue prioritized by severity
* Certificate expiry visibility and expiring-soon section
* Quick portfolio filters for triage
* Deterministic per-domain health scoring
* Local portfolio search
* Apex-domain grouping for tracked assets
|