Scaffold MistDemo (CLI + App + Web) for v1.0.0-beta.2 endpoints#371
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review
OverviewGood structural work. The three layers (CLI stubs, SwiftUI app extensions, web refactor) are well-separated. The only "real" production code landed here is Real code:
|
| Area | Status |
|---|---|
zones/modify endpoint + tests |
✅ Ready |
| Static asset serving | ✅ Ready |
| APNs delegate wiring | ✅ Acceptable for demo |
| CLI stub commands | ✅ Correct scaffolding |
| Integration phases | ✅ Correct scaffolding |
| Duplicate APNs entitlement key | Minor — clean up before merge |
| Deprecated API warning | Minor — add suppression comment |
public import in app-only files |
Minor — flip to internal import |
| Missing 501 tests | Worth adding before merging |
| zones modify: no body assertion in delete test | Worth a one-liner |
Overall the scaffold is clean and well-organized. The conventions from the main library (extension-per-concern, internal ACL, async/await throughout) are applied consistently throughout. The main items to address before merging are the import modifiers, the deprecated-API warning, and adding the 501 smoke tests.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v1.0.0-beta.2 #371 +/- ##
================================================
Coverage ? 66.94%
================================================
Files ? 132
Lines ? 2971
Branches ? 0
================================================
Hits ? 1989
Misses ? 982
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| #if canImport(AppKit) && !targetEnvironment(macCatalyst) | ||
| @NSApplicationDelegateAdaptor(PushNotificationDelegate.self) | ||
| private var pushDelegate | ||
| #elseif canImport(UIKit) | ||
| @UIApplicationDelegateAdaptor(PushNotificationDelegate.self) | ||
| private var pushDelegate | ||
| #endif |
There was a problem hiding this comment.
can we use type aliases to reduce this?
| #if canImport(AppKit) && !targetEnvironment(macCatalyst) | ||
| NSApplication.shared.registerForRemoteNotifications() | ||
| pushTokenStatus = .requesting | ||
| #elseif canImport(UIKit) | ||
| UIApplication.shared.registerForRemoteNotifications() | ||
| pushTokenStatus = .requesting |
There was a problem hiding this comment.
use a shared type alias or protocol
| // branches, which the rule mis-flags. | ||
| // swiftlint:disable file_types_order | ||
|
|
||
| #if canImport(AppKit) && !targetEnvironment(macCatalyst) |
There was a problem hiding this comment.
use protocols and extensions to unify this
| // OTHER DEALINGS IN THE SOFTWARE. | ||
| // | ||
|
|
||
| #if canImport(AppKit) || canImport(UIKit) |
There was a problem hiding this comment.
let's unify this with protocols and extensions
bc0f719 to
5d43cb3
Compare
| #if canImport(AppKit) && !targetEnvironment(macCatalyst) | ||
| extension NSApplication: RemoteNotificationRegistering { | ||
| internal static var sharedApplication: NSApplication { shared } | ||
| } | ||
| #elseif canImport(WatchKit) | ||
| extension WKApplication: RemoteNotificationRegistering { | ||
| internal static var sharedApplication: WKApplication { shared() } | ||
| } | ||
| #elseif canImport(UIKit) | ||
| extension UIApplication: RemoteNotificationRegistering { | ||
| internal static var sharedApplication: UIApplication { shared } | ||
| } | ||
| #endif |
There was a problem hiding this comment.
move this to a separate file
| #endif | ||
| #endif | ||
|
|
||
| // swiftlint:enable file_types_order |
There was a problem hiding this comment.
fix this issue instead of ignoring it
| // share one extension here. watchOS's `WKApplicationDelegate` uses | ||
| // parameter-less selectors, so its variants live in | ||
| // `PlatformApplicationDelegate+WKApplicationDelegate.swift` instead. | ||
| #if (canImport(AppKit) && !targetEnvironment(macCatalyst)) || (canImport(UIKit) && !os(watchOS)) |
There was a problem hiding this comment.
put this in a separate file
| ) | ||
| } | ||
|
|
||
| // swiftlint:disable cyclomatic_complexity |
There was a problem hiding this comment.
let's replace this with a dictionary instead
- Move RemoteNotificationRegistering protocol + per-platform conformances out of PlatformApplication.swift into RemoteNotificationRegistering.swift. - Split the shared AppKit/UIKit APNs callback extension out of PlatformApplicationDelegate.swift into PlatformApplicationDelegate+RemoteNotifications.swift; the file now holds only the @objc protocol. - Replace QueryCommand.buildComparisonFilter's switch with a comparisonFilterBuilders lookup table, removing the cyclomatic_complexity disable. - Remove the inline file_types_order disable: the platform typealiases now live in a neutrally-named PlatformAliases.swift (no alias matches the filename, so none is misread as a main_type in project-mode lint). The resulting file_name mismatch is waived via a documented .swiftlint.yml entry instead of an inline suppression. Whole-tree swiftlint clean (0 violations); filter-parsing tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — PR #371: Scaffold MistDemo for v1.0.0-beta.2 endpointsOverall this is a well-structured scaffolding PR. The A few issues worth addressing before this leaves draft: Bug / Correctness
public import FoundationThe CLAUDE.md convention is explicit: use
?? UUID().uuidStringUsing a randomly-generated UUID as a stable Leftover Commented-Out Code
// #else
// pushTokenStatus = .failed(
// message: "APNs registration is unavailable on this platform."
// )
// #endifThis block looks like a draft of platform-gated fallback that was left in. If it's deferred intentionally, a Performance / API usage
The method loops sequentially over record names, issuing one
Same pattern — each email lookup is awaited before the next starts. Silent error suppression
operation.perSubscriptionResultBlock = { _, result in
if case .success(let subscription) = result {
rows.append(SubscriptionRow(subscription))
}
}Individual MinorDuplicate APNs entitlement keys ( Both Remaining acceptance criteria (per PR description)The three draft checklist items are clearly called out — wiring the 9 landed-but-501 server routes, confirming the new app views drive real native CloudKit calls, and a clean 🤖 Generated with Claude Code |
- Native push-notification delegate (AppKit/UIKit-universal) wired via PushTokenReceiver protocol + static-weak bridge; aps-environment entitlements added with both iOS and macOS key forms. - New CloudKitStore extensions (Subscriptions, Assets, Records, Users, Zones, PushTokens) and their corresponding SwiftUI views. - CLI command + integration-phase stubs for create-token, register-token, list-subscriptions, lookup-subscription, rereference-asset, resolve (REST surface tracked in #52 / #53 et al.). - PendingStub utility for endpoints not yet wrapped by MistKit. - Web demo: index/styles/JS reshuffle and WebServer adjustments. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion CRUD
Correct CloudKit JS calls against the bundled reference (methods that
didn't exist on the SDK):
- records/changes: fetchRecordChanges -> fetchRecordZoneChanges({ zoneID, syncToken })
- users/caller: fetchUserIdentityMe -> fetchCurrentUserIdentity
- users lookup: discoverUserIdentity({...}) -> discoverUserIdentityWith{EmailAddress,UserRecordName}
- zones create: saveRecordZones wraps zoneName under zoneID
- zones/changes: use fetchDatabaseChanges
- cycle-safe JSON serializer (registerForNotifications result is cyclic)
MistKit side:
- wire POST /api/zones/modify (create/delete) via CloudKitService.modifyZones
- register subscriptions/modify pending stub (#51)
UI:
- render Zones and Subscriptions lists as tables (raw payload in <details>)
- Subscriptions Create (query/zone; record-type select + firesOn checkboxes) and Delete
- constrain <pre> width so long payloads never overflow their card
Tests: add WebServerTests+Zones; index tests fetch extracted /styles.css and /js/*.js
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
records/lookup, records/changes, zones/{list,lookup,changes} and
users/{caller,discover,lookup/email,lookup/id} have shipped MistKit
wrappers but no demo-server route, so MistKit mode 404'd. Register them
as 501 pending stubs (tracked by #370 — the route wiring, not the
wrappers) so the panels return the structured tracking JSON instead.
Kept separate from addPendingEndpoints (whose doc says "wrapper hasn't
landed") via addUnwiredLandedEndpoints, since here only the route is
pending. Replacing a stub with a real handler (per WebServer+Zones)
remains for #370's both-mode-coverage criterion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses review #4329644631: centralize the AppKit/UIKit divergence behind named abstractions instead of repeating `#if canImport(AppKit) … #elseif canImport(UIKit)` at call sites, using the mechanism that fits each site. - PlatformApplication: add PlatformApplicationDelegateAdaptor typealias and a @mainactor RemoteNotificationRegistering protocol (with a static entry point) that NSApplication/UIApplication conform to. - @main: single @PlatformApplicationDelegateAdaptor, no #if branches. - CloudKitStore+PushTokens: collapse the two identical registration arms to one PlatformApplication.registerSharedForRemoteNotifications() call. - PushNotificationDelegate: move the genuinely-divergent didReceiveRemoteNotification selectors into platform extensions (PushNotificationDelegate+ReceiveNotification.swift); class body is #if-free. Fix the red tvOS/watchOS "Build on macOS (Platforms)" jobs, all in this same code: - Canonical "push available" condition ((AppKit && !macCatalyst) || (UIKit && !watchOS)) applied consistently; the old UIKit branch matched watchOS where UIApplication is unavailable. - swipeActions (unavailable on tvOS): shared deleteSwipeAction helper used by SubscriptionsView and ZoneListView. - DisclosureGroup (unavailable on tvOS/watchOS): inline non-collapsible fallback in CompositionDisclosure. Verified: swift build (macOS) + xcodebuild iOS/tvOS/watchOS all succeed, 932 MistDemo tests pass, swift-format/swiftlint/periphery clean on touched code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Split WebRequests.ModifyZones and MockBackend call structs into their own files (file_length), refactor addUnwiredLandedEndpoints to a data-driven loop (function_body_length), and pair the file_types_order disable/enable in PlatformApplication. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The push-notification delegate bridge had top-level declarations that depend on Apple-only features but weren't conditionally compiled, breaking the MistDemo workflow's Linux/Windows builds: - PushTokenReceiver / PlatformApplicationDelegate: @objc protocols require the Objective-C runtime → gate on canImport(ObjectiveC) - PushNotificationDelegate: subclasses NSObject and conforms to the SwiftUI-defined ApplicationDelegate typealias → gate on canImport(SwiftUI) On Linux/Windows MistDemoApp now compiles to just the pure-Swift SidebarItem enum. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Move RemoteNotificationRegistering protocol + per-platform conformances out of PlatformApplication.swift into RemoteNotificationRegistering.swift. - Split the shared AppKit/UIKit APNs callback extension out of PlatformApplicationDelegate.swift into PlatformApplicationDelegate+RemoteNotifications.swift; the file now holds only the @objc protocol. - Replace QueryCommand.buildComparisonFilter's switch with a comparisonFilterBuilders lookup table, removing the cyclomatic_complexity disable. - Remove the inline file_types_order disable: the platform typealiases now live in a neutrally-named PlatformAliases.swift (no alias matches the filename, so none is misread as a main_type in project-mode lint). The resulting file_name mismatch is waived via a documented .swiftlint.yml entry instead of an inline suppression. Whole-tree swiftlint clean (0 violations); filter-parsing tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
eb946ca to
38a4ac7
Compare
Code Review - PR 371Draft scaffold for v1.0.0-beta.2 endpoints (CLI stubs, SwiftUI app views, Hummingbird web app + real zones/modify route). Review focuses on what has landed here, not the open checklist items in the PR description. Positive Highlights
Issues1. Commented-out code in
2. Indentation mismatch in The 3. Non-obvious comment removed from The diff removes the explanation of how 4. Missing test for concurrent create+delete in Tests cover create-only and delete-only, but not a request that simultaneously creates and deletes zones in one batch. CloudKit allows this in a single Questions
VerdictThe scaffold is well-structured and the |
|
Code Review - PR 371: Scaffold MistDemo for v1.0.0-beta.2 endpoints Good scaffold PR overall. The pending-stub pattern is clean and grep-able, the real zones/modify route has solid test coverage, and the HTML/CSS/JS split is well-motivated. A few issues worth addressing before this merges. BUGS / CORRECTNESS
Inside requestPushNotificationRegistration() there are three stray comment lines (the else/endif from a half-removed conditional). Delete them. The intended fallback is unreachable and misleads readers into thinking there is a conditional that got cut.
addUnwiredLandedEndpoints only registers POST for users/discover. Per CLAUDE.md, discoverAllUserIdentities() maps to GET /users/discover while discoverUserIdentities(lookupInfos:) maps to POST /users/discover. Registering only POST leaves the GET variant returning 404. Both need a stub entry:
lookupRecords(names:) issues one database.record(for:) call per name. CloudKit supports batch fetch via CKFetchRecordsOperation. Worth leaving a TODO at minimum - the asymmetry vs the REST records/lookup endpoint (which batches in one call) is exactly the kind of divergence this demo is meant to surface. CODE CONVENTIONS (CLAUDE.md)
Every new command file has comments that restate what the names already say: The configuration type, Creates a new instance, Executes the command. CLAUDE.md: Do not explain WHAT the code does, since well-named identifiers already do that. The type-level doc on the struct (endpoint name and tracking issue) is the right level; the per-member docs add noise without value.
Minor: PendingStub.Body is a private struct captured only inside JSONEncoder().encode(...) so there is no actual race. But since PendingStub is public and the project enforces Sendable compliance, adding Sendable to the conformance list keeps the pattern consistent. DESIGN / ARCHITECTURE
Tests like indexExposesCloudKitJsHandlers call body(at:) twice, spinning up two full application instances per test. This will slow the suite as more tests accumulate. Consider making app a single test-scoped fixture shared across calls within one test function.
The preconditionFailure only fires when a listed name is missing from the bundle. Adding a new js module without updating this list silently results in a 404. A comment noting this list must stay in sync with the script tags in index.html would prevent the footgun.
PushNotificationDelegate.receiver = self in CloudKitStore.init means any CloudKitStore created during testing stomps the receiver on the shared delegate. No tests exercise this path yet so it is fine for now, but noting this singleton side-effect will help whoever writes those tests. WHAT IS GOOD
BEFORE MARKING READY Issue 2 (missing GET route for users/discover) is a correctness gap in the stub wiring; fix that alongside the commented-out code (issue 1) before promoting from draft. Generated with Claude Code |
Part of #370 — scaffolds the MistDemo CLI, MistDemoApp (SwiftUI/native CloudKit), and Web app (Hummingbird) so every CloudKit Web Services endpoint in the v1.0.0-beta.2 milestone has a visible surface, with the MistKit-vs-CloudKit-JS asymmetry made explicit in the web app.
What's included
CLI — 6 pending stub commands registered in
MistDemoRunner(resolve,rereference-asset,list-subscriptions,lookup-subscription,create-token,register-token); each prints "pending #N" and exits 0. SharedPendingStubhelper.Integration phases — 6 stub
IntegrationPhasefiles (not wired intotest-public/test-private, kept green).MistDemoApp (native CloudKit) — sidebar extended from 3 → 8 items; new
Records,Subscriptions,PushTokens,Assets,Usersviews +CompositionDisclosure.Web app —
index.htmlrefactored intostyles.css+ per-areajs/*.jsmodules. Both-mode panels (MistKit server vs CloudKit JS browser) across the milestone surface.POST /api/zones/modifyroute (create/delete) viaCloudKitService.modifyZones+ tests.fetchRecordZoneChanges,fetchCurrentUserIdentity,discoverUserIdentityWith{EmailAddress,UserRecordName},saveRecordZoneszone shape,fetchDatabaseChanges, cycle-safe JSON serializer).records/lookup,records/changes,zones/{list,lookup,changes},users/*).Status vs acceptance criteria
Done: CLI commands + registration, integration phases, app sidebar/views, HTML/JS/CSS refactor, web pending stubs, real
zones/modify, CloudKit JS coverage.Remaining (why this is still a draft):
records/lookup,records/changes,zones/{list,lookup,changes}(mirrorWebServer+Zones), andusers/{caller,discover,lookup/email,lookup/id}(needs the public+web-authuserContextServiceinWebBackendFactory)../Scripts/lint.sh(build + swiftlint + header.sh + periphery) and fullswift testclean.Implementing the underlying MistKit wrappers for #31/#41/#49/#50/#52/#53 stays out of scope (each lands in its own PR).
🤖 Generated with Claude Code