Extract RegionKit from WhereCore#66
Merged
Merged
Conversation
Groundwork only: adds the RegionKit SPM library (LogKit dep) with its RegionLog logging facade and module README/AGENTS. No behavior change; nothing depends on it yet.
Relocate Coordinate, GeoPolygon/BoundingBox, LongitudeSpan, GeoJSON, Region, Region+Ordering, RegionAttributor, and RegionGeometryCatalog (plus the bundled *.geojson and the region.* string catalog entries) from WhereCore into the new lower-layer RegionKit module. WhereCore now depends on RegionKit and calls into it for region lookup; every consumer (WhereCore, WhereUI, WhereWidgets, the app, RegionViewer, and their tests) imports RegionKit explicitly. RegionAttributor now logs via RegionLog (subsystem com.stuff.regionkit) instead of WhereLog; the now-unused WhereLog.regionAttributor category is removed and the region-map viewer's failure log moves to RegionLog too. The seven region test files move to RegionKit/Tests; a RegionKitTests bundle is wired in Project.swift (registered in the Stuff-iOS-Tests scheme) and StuffTestHost plus WhereCoreTests/WhereUITests depend on RegionKit so Bundle.module resolves the GeoJSON at runtime. Closes plan to-dos: move-sources and move-tests.
Update root AGENTS (libraries, package products, hosted test bundles), Where/AGENTS (module list, RegionKit/WhereCore layering, localization split, new-region steps now pointing at RegionKit), and the RegionViewer + WhereWidgets README/AGENTS to reflect the new RegionKit dependency. RegionKit's own README/AGENTS shipped with the scaffold commit. Closes plan to-do: docs-verify.
Generalize LogViewerUI to observe multiple LogStores and merge them chronologically (LogViewerConfiguration/LogViewerModel gain a stores: array; the single-store store: init stays as a convenience). The Where Settings log viewer now passes [WhereLog.store, RegionLog.store] so RegionKit's RegionLog entries show alongside the app's in one merged, filterable stream. Adds LogViewerModel merge/clear/live-observe tests across two stores and a multi-store hosting test. Docs updated (LogViewerUI README/AGENTS, Where/AGENTS).
LogViewerModel observed its stores by calling an instance method
(await self?.observe()), which keeps self alive for the streams' whole
lifetime — so the model (and its observation task) never deinit while the
viewer's stores stay quiet. Mirror YearReportModel instead: spawn one task
per store that iterates the stream with a per-iteration 'guard let self else
{ break }' and holds only a weak reference between log lines, so deinit runs
and cancels every task. ObservationHandle now tracks/cancels multiple tasks.
Adds a deinitsWhileObservingStores test, and fixes doc nits (LogViewerModel
type comment now reflects multiple stores; RegionKit's Region+Ordering doc no
longer names higher-layer types).
kyleve
commented
Jul 7, 2026
| import Testing | ||
| import WhereCore | ||
|
|
||
| struct RegionOrderingTests { |
Owner
Author
There was a problem hiding this comment.
Actually, I think we may want to move the region ordering stuff up into WhereCore and keep RegionKit only about regions and geofencing.
Owner
Author
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Done — moved Region+Ordering (declarationOrder / rankedByDayCount) and its test up into WhereCore, so RegionKit stays about regions and geofencing. Nothing in RegionKit's sources referenced the ordering helpers, so it was a clean lift; WhereCore/WhereUI consumers still get them via import WhereCore, and the test uses the real RegionDayTally row again. Docs updated to point ordering at WhereCore. (commit b83f68b)
Per PR review: RegionKit should stay about regions and geofencing, so the day-count ranking (Region.declarationOrder / rankedByDayCount) belongs in WhereCore alongside the presence/day-count domain. Move Region+Ordering.swift and its test out of RegionKit into WhereCore (the extension imports RegionKit for the Region type); nothing in RegionKit referenced them. The test uses the real WhereCore RegionDayTally row again. Docs updated to point ordering at WhereCore.
kyleve
added a commit
that referenced
this pull request
Jul 8, 2026
…ere) (#65) ## Summary Makes the tab bar and app-icon badge reflect "things that need you," and adds a notification for unresolved data issues — plus a setting to keep the conditional tabs always visible. - **Conditional tabs** (`MainTabs.swift`): the `TabView` uses a `selection` binding + `TabID` enum. Elsewhere and Resolve appear when they have content **or** are the currently selected tab, so resolving the last issue (or emptying Elsewhere) never bounces you off the tab you're on — the "All clear" state stays visible and the tab drops out the next time you switch tabs. - **"Hide empty tabs" setting** (default on): a new `hideEmptyTabs` preference (mirrored observably on `YearReportModel`, like `driftThreshold`) with a Settings → Tabs toggle. Turning it off keeps the Elsewhere and Resolve tabs permanently in the tab bar instead of hiding them when empty. - **App-icon badge** now = missing-day backlog (when reminders are enabled) + current-year data-issue count (when issue alerts are enabled). Issues badge the icon even when logging reminders are off (`ReminderReconciler` + `LoggingReminderScheduler` disabled-branch now applies the passed badge instead of clearing to 0). Per design this may double-count missing days, which appear in both. - **New local notification** for unresolved data issues, fired at the evening reminder time and cleared when the count hits 0 — a mirror of the daily-summary trio: `DataIssueAlertScheduling` + `UserNotificationDataIssueAlertScheduler` + `NoopDataIssueAlertScheduler` (SPI-gated, test-only), driven by `DataIssueAlertReconciler`. - **New `issueAlertsEnabled` preference** (default on) gating both the notification and the data-issue portion of the badge, independent of `remindersEnabled`, with a Settings toggle (`RemindersSettingsModel`, `SettingsView`, strings + xcstrings) and a launch/foreground config step (`WhereSession`, `WhereLaunch`). - **Headless issue count**: `DataIssueScanner.currentIssueCount(...)` derives primary regions internally via the shared `Region.primaryRegions(in:count:)` helper that `RegionRanking` also reuses, so the headless count matches the Resolve tab's ranking (no ranking logic is duplicated; only the cold notification path re-reads the report — the hot badge path in `ReminderReconciler` reuses its in-hand report). - **Freshness wiring**: `DayJournal` now invalidates the scanner and re-reconciles both the badge and the alert after every write, including `dismissIssue`/`restoreIssue` (which previously committed without re-badging). ## Test plan - [x] `WhereCoreTests` — new `DataIssueAlertReconcilerTests` and `UserNotificationDataIssueAlertSchedulerTests`; updated `ReminderReconcilerTests` (combined badge + reminders-off badge), `DayJournalTests`, `WhereServicesTests`, `WhereLogTests`. - [x] `WhereUITests` — new `hideEmptyTabsDefaultsOnAndPersistsAcrossModels`; updated `RemindersSettingsModelTests` (issue-alert toggle), `WhereLaunchTests` (new launch step), plus `NoopDataIssueAlertScheduler` injected across session/reset/tracking tests. - [x] Full `WhereCoreTests` + `WhereUITests` bundles pass on the iOS 17 simulator via `tuist test Stuff-iOS-Tests`. - [ ] Manual on device/simulator: enable issue alerts, seed an issue → verify the icon badges and the notification is delivered; resolve/dismiss it → verify both clear. Toggle "Hide empty tabs" off → verify Elsewhere/Resolve stay visible when empty. ### Notes for reviewers - Two "no issues" reconciler tests were rewritten after they exposed that `MissingDaysDetector` flags the whole *elapsed* current year regardless of primary regions — so an empty/cleared store mid-year still has a missing-days issue. They now use genuinely-zero-issue scenarios (an empty store at the very start of the year, and a resolve-by-dismissing-every-issue flow). - Review feedback addressed: `NoopDataIssueAlertScheduler` is `@_spi(Testing)` behind `#if DEBUG` (per the testing-hook convention), the concrete `reconcile(...)` is documented, and the `currentIssueCount` doc clarifies the shared-helper (non-duplication) design. - Merged latest `main` twice along the way (`#64` manual-entry audit trail, `#66` RegionKit extraction); the `WhereLog.Category` count now lands at 19, and `ReminderReconciler`'s `Region.primaryRegions` use picked up `import RegionKit`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits GeoJSON, geometry, and region lookup out of
WhereCoreinto a new lower-layer RegionKit SPM library. The dependency flow is nowWhereUI → WhereCore → RegionKit → LogKit, and WhereCore calls into RegionKit for region lookup.What moved into
Where/RegionKit/Coordinate,GeoPolygon/BoundingBox,LongitudeSpan,GeoJSON,Region,RegionAttributor(+RegionPolygons),RegionGeometryCatalog(RegionOutline/RegionGeometryKind/RegionGeometryError).us-states.geojson,canada.geojson,europeanUnion.geojson, and the sixregion.*entries in a new RegionKitLocalizable.xcstrings(Region.localizedNamestill usesbundle: .module).Where/RegionKit/Tests/.RegionKit stays focused on regions and geofencing. Deliberately kept in the upper layers:
RegionDayLocations/RegionDayPointand the day-count ranking (Region+Ordering—declarationOrder/rankedByDayCount) live inWhereCore(presence/day-count domain), andRegionStylestays in WhereUI (presentation).Logging
RegionAttributornow logs via a newRegionLogfacade (subsystemcom.stuff.regionkit, its own store) instead ofWhereLog; the now-unusedWhereLog.regionAttributorcategory is removed and the region-map viewer's failure log moves toRegionLogtoo.To keep those logs visible in-app,
LogViewerUInow accepts multipleLogStores and merges them chronologically. The Settings log viewer is configured with[WhereLog.store, RegionLog.store], so RegionKit's entries show alongside the app's in one filterable stream (each still tagged by subsystem/category).Wiring
Package.swift: new RegionKit product/target; WhereCore and WhereUI depend on it. Consumers use explicitimport RegionKit(no@_exported).Project.swift: newRegionKitTestsbundle (in theStuff-iOS-Testsscheme + atestScheme); RegionKit product added to the Where app, WhereWidgets, RegionViewer, StuffTestHost, WhereCoreTests, and WhereUITests soBundle.moduleresolves the GeoJSON at runtime.Also in this PR (from review follow-up)
LogViewerModellifecycle fix: it observed via an instance-method call (await self?.observe()) that keptselfalive for the streams' lifetime, so the model never deinit'd while stores were quiet. Reworked to oneTaskper store with a per-iterationguard let self else { break }(mirroringYearReportModel), with adeinitsWhileObservingStorestest.Region+Ordering(+ test) up intoWhereCoreso RegionKit stays about regions and geofencing.LogViewerModel/RegionKitdoc comments updated.Docs
New
RegionKitREADME/AGENTS; updated rootAGENTS.md,Where/AGENTS.md, and the RegionViewer / WhereWidgets / LogViewerUI docs.Testing
swiftformat --lintclean.tuist test Stuff-iOS-Tests(iPhone 17, iOS 26.2) green, including the newRegionKitTestsand the multi-store / deinitLogViewerUITests.Commits