MistDemo improvements: test split, CRUD, auth fix, native app (#271)#273
MistDemo improvements: test split, CRUD, auth fix, native app (#271)#273leogdion merged 19 commits intov1.0.0-beta.1from
Conversation
Documents the four-phase plan covering #260 (test target split into MistDemoKit library + MistDemo executable), #214 (Delete/Lookup/Modify commands plus UpdateCommand --force/conflict gaps), #255 (auth-token capture URL fix and reliability investigation), and #272 (native CloudKit SwiftUI demo app). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move all reusable code from Sources/MistDemo/ into a new MistDemoKit library target, leaving only the @main entry point in Sources/MistDemo/. This unblocks @testable import in MistDemoTests, which was previously failing because executable targets do not produce importable modules. Test files now import MistDemoKit; the mistdemo product, executable name, and external CLI behavior are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- UpdateConfig now carries a Bool force flag (parsed from --force) - UpdateCommand omits the recordChangeTag when force is set, letting the server overwrite without optimistic-lock checking - 409 CloudKitError responses are mapped to UpdateError.conflict so users get a clearer message and a recovery hint suggesting --force - New UpdateConfigTests cover force/no-force, output formats, fields, edge cases, and the new conflict error description/recovery Also adds shared ConfigKeys (force, recordNames, operationsFile, atomic) used by upcoming Delete/Lookup/Modify commands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeleteCommand wraps CloudKitService.deleteRecord with --force flag, record-change-tag support for optimistic locking, and 409-conflict mapping that mirrors UpdateCommand. The command emits a small DeleteResult struct (recordName, recordType, deleted) in any of the JSON/table/CSV/YAML output formats. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LookupCommand wraps CloudKitService.lookupRecords and accepts --record-names (comma-separated) or --record-name (single), with an optional --fields filter that maps to desiredKeys. Records that aren't found are silently dropped from stdout and reported to stderr so the JSON/CSV stdout stream stays parseable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ModifyCommand reads a JSON array of {op, recordType, recordName?,
fields?, recordChangeTag?} entries from --operations-file or stdin,
validates each (update/delete require recordName), and submits the
batch via CloudKitService.modifyRecords.
CloudKitService.modifyRecords now accepts an optional `atomic:` flag
(default false, preserving prior behavior) so the CLI can expose
--atomic for all-or-nothing batches. When non-atomic and the response
returns fewer records than requested, ModifyCommand prints a warning
to stderr so the JSON/CSV stdout stream stays parseable.
FieldsInput and FieldInputValue now conform to Sendable so they can
travel through the configuration layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL matching: replace url.includes('apple-cloudkit.com') with a
hostname-aware isCloudKitUrl() that parses the URL and checks for an
exact match or .apple-cloudkit.com suffix. Fixes the substring
vulnerability where attacker URLs like http://evil.com/?q=apple-cloudkit.com
would have matched. Applied to both the fetch and XHR overrides.
Capture reliability: the fetch/XHR override is best-effort because
CloudKit's auth iframe is sandboxed cross-origin. Add two reliable
fallbacks:
1. Poll container._auth._ckSession every 250ms while waiting for the
token. CloudKit JS itself populates this property after sign-in,
and it works even when the iframe blocks every other capture path.
2. On timeout, surface a visible manual-paste form (input + submit
button) instead of only logging instructions to the console. The
demo can recover gracefully when the automatic flow fails.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A SwiftUI multiplatform (macOS + iOS) demo app that targets the same iCloud.com.brightdigit.MistDemo container as the MistDemo CLI/web tool, but uses Apple's native CloudKit framework (CKContainer, CKDatabase, CKQuery) instead of MistKit. Designed to be shown side-by-side in presentations comparing the two stacks. Features (read-side parity with the MistDemo CLI): - AccountView — CKContainer.accountStatus() - ZoneListView — CKDatabase.allRecordZones() (parity with lookup-zones) - QueryView — CKDatabase.records(matching:) (parity with query) - RecordDetailView — renders each CKRecord field Builds and runs on macOS via `swift run MistDemoApp`. iOS / signed macOS app deployment requires wrapping the same source files in an Xcode app target with the iCloud + CloudKit capability — README documents the process. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- project.yml drives Xcode project generation via XcodeGen so iOS
and signed macOS builds are possible. The .xcodeproj itself stays
gitignored repo-wide; users run `xcodegen generate` once.
- MistDemoApp.entitlements declares the iCloud + CloudKit capability
against the iCloud.com.brightdigit.MistDemo container, fixing the
"process must have com.apple.developer.icloud-services entitlement"
CloudKit error from the SPM build.
- Replace the generic RecordRow with a typed Note model that mirrors
the Note record type in Examples/MistDemo/schema.ckdb (title,
index, image asset, createdAt, modified, plus CloudKit metadata).
QueryView sorts by index; RecordDetailView labels each field by
schema name and renders the image asset inline.
Both `swift build` and `xcodebuild -scheme MistDemoApp-{macOS,iOS}`
build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds create / update / delete operations alongside the existing read-side flow: - NativeCloudKitService now exposes createNote, updateNote, deleteNote using CKDatabase.save / record(for:) / deleteRecord(withID:). - NoteEditView is a sheet-presented form used for both create and edit modes. Title and index are required; image is optional and picked via fileImporter (security-scoped access maintained for the save). - QueryView gets a + toolbar item that presents the create sheet, and a swipe-to-delete action on each row. - RecordDetailView gets Edit and Delete toolbar buttons. Delete uses a confirmationDialog and pops back to the list on success. Save/delete callbacks bubble up so the parent re-runs the query and the UI stays in sync without explicit polling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NavigationLink(value: Note) inside QueryView was failing at runtime with "There is no next column after the detail column" because the detail column had no NavigationStack — pushes had nowhere to go. Switch the sidebar to selection-driven navigation (binding to a SidebarItem state, no NavigationLinks) and wrap the detail column's content in a NavigationStack. QueryView's NavigationLink(value: Note) + navigationDestination(for: Note.self) now resolve within the detail column's stack, so tapping a note pushes RecordDetailView correctly on both macOS and iOS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…272) Adds a new section in AccountView that issues the same 158__ web auth token MistKit / the MistDemo CLI consume, using CKFetchWebAuthTokenOperation against the private database. Useful for handing off auth from a native app to a server-side or CLI process, and reinforces the demo's MistKit-vs-CloudKit framework comparison. - NativeCloudKitService.fetchWebAuthToken(apiToken:) wraps the Operation in a checked continuation; returns the token string. - AccountView gets a TextField for the public CloudKit API token (persisted via @AppStorage so it survives launches), a Fetch button with progress state, the resulting token in selectable monospaced text, and a Copy button that uses NSPasteboard / UIPasteboard depending on platform. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avoids hardcoding the public CloudKit API token in source while making
fresh checkouts / new machines / fresh simulators auto-pick up the
value, instead of forcing the user to re-paste it in AccountView every
time.
Pipeline:
- .env (gitignored) holds CLOUDKIT_API_TOKEN locally
- `make generate` sources .env into the shell, then runs xcodegen
- project.yml adds environmentVariables: CLOUDKIT_API_TOKEN: ${CLOUDKIT_API_TOKEN}
to both the macOS and iOS scheme. xcodegen substitutes the value at
generate time. The whole *.xcodeproj is gitignored repo-wide, so the
substituted value never lands in git.
- AccountView.onAppear seeds @AppStorage from
ProcessInfo.processInfo.environment[CLOUDKIT_API_TOKEN] (only when
the field is empty, so manual paste still wins). A small caption
shows the source for presentation transparency. Literal "${...}"
placeholders (when .env wasn't set before generate) are rejected so
the TextField stays empty rather than showing the placeholder string.
Files added:
- .env.example template, committed, names the variable only
- .gitignore excludes .env
- Makefile `generate` (sources .env + runs xcodegen) and `clean`
- Doc updates in README
INFOPLIST_KEY_CloudKitApiToken was tried first but Xcode's INFOPLIST_KEY_*
synthesis only handles Apple-recognized keys, so we don't try to bake the
token into Info.plist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Load .env file via EnvironmentVariablesProvider so credentials don't require manual export
- Use Bundle.module to find index.html instead of hardcoded paths broken by MistDemoKit split
- Use request.head.authority instead of unavailable HTTPField.Name("Host") for loopback check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
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 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v1.0.0-beta.1 #273 +/- ##
=================================================
- Coverage 25.70% 25.66% -0.04%
=================================================
Files 95 95
Lines 8232 8232
=================================================
- Hits 2116 2113 -3
- Misses 6116 6119 +3
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:
|
Code Review: MistDemo Improvements (#271)This PR delivers four well-scoped phases. Here's my analysis grouped by area. Overview
Code QualityPositives:
Minor Issues:
SecurityHostname validation fix (index.html) — GOOD: The original substring check was vulnerable to subdomain spoofing and query-parameter injection: // Before (vulnerable)
url.includes('apple-cloudkit.com') // matches http://evil.com?q=apple-cloudkit.com
// After (correct)
const parsed = new URL(url, window.location.href);
return parsed.hostname === 'apple-cloudkit.com' ||
parsed.hostname.endsWith('.apple-cloudkit.com');The fix correctly parses the URL before checking the hostname. No new auth vulnerabilities introduced. Manual token paste form: Acceptable. The server-side loopback check ( PerformanceNo regressions. The one note:
Test CoverageGood coverage:
Gaps:
Miscellaneous
SummaryThis is solid work across all four phases. No critical bugs. The main asks before merge:
The auth-token security fix is well done and the library/executable restructure is clean. The 🤖 Reviewed with Claude Code |
There was a problem hiding this comment.
can we delete this file?
There was a problem hiding this comment.
merge MistDemo and MistDemoApp into one directory. Use standard layout for Xcode project with Swift package
|
|
||
| A SwiftUI demo app that talks to the same CloudKit container as the | ||
| [MistDemo CLI/web tool](../MistDemo/), but uses **Apple's native CloudKit | ||
| framework** (`CKContainer`, `CKDatabase`, `CKQuery`) instead of MistKit. |
There was a problem hiding this comment.
We are missing a crud interface in web
| | Surface | Stack | Use case | | ||
| |---|---|---| | ||
| | `MistDemo` CLI / web (`mistdemo`) | MistKit (CloudKit Web Services REST) | Server, Linux, command line, web | | ||
| | `MistDemoApp` (this directory) | Apple CloudKit framework | Native macOS / iOS apps | |
There was a problem hiding this comment.
we are missing a public database interface in both web and app
There was a problem hiding this comment.
We should use a environment value for the bundleIdPrefix and Development Team
Code Review — PR #273This is a solid PR covering four distinct features. The library split is the most architecturally significant change and is implemented cleanly. Feedback below is grouped by area. Overview
Bugs / CorrectnessSecurity-scope leak in
// In fileImporter result handler:
case .success(let urls):
if let url = urls.first {
guard url.startAccessingSecurityScopedResource() else {
saveError = "Couldn't access file"
return
}
imageURL = url
}Then the existing
The call returns Code Quality / Style
This is intentional but will cause surprising behavior in Plan files in diff
Test CoverageNew command configs have tests; command execution does not
Missing error case coverage The following error cases appear to lack tests:
MistDemoApp has zero tests Understandable for a native CloudKit app, but at minimum the model layer ( Minor Suggestions
SummaryThe library split and new CRUD commands are well-structured and follow project conventions. The main issues to address before merge are the security-scope leak in 🤖 Generated with Claude Code |
- Move MistDemoApp sources into the MistDemo Swift package as a new MistDemoApp library target; the Xcode app shell (App/MistDemoApp.swift) imports it and contains only @main - Extract MistDemo.swift's runner logic into MistDemoKit (MistDemoRunner, CloudKitCommand) so the executable target is reduced to a thin @main - Parameterize project.yml's bundle ID prefix and DEVELOPMENT_TEAM via .env so reviewers can build without the BrightDigit signing identity - Bump project.yml's macOS deployment target to 15.0 to match the package, and set PRODUCT_MODULE_NAME so the app target's swiftmodule doesn't collide with the SPM library named MistDemoApp - Delete the now-stale .claude/plans/mistdemo-improvements-271.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Package.swift: add MistDemoApp library target/product, add iOS(.v17) - App/MistDemoApp.swift: import the library; @main only - Sources/MistDemo/MistDemo.swift: collapse to @main calling MistDemoRunner.run() (logic now in MistDemoKit) - NativeCloudKitService / RootView: minimum-public ACL for cross-module access from App; public import Combine / SwiftUI to satisfy MemberImportVisibility - project.yml: parameterize BUNDLE_ID_PREFIX and DEVELOPMENT_TEAM, add local SPM packages reference, point sources at App/, depend on the MistDemoApp library product, bump macOS deployment target to 15.0, set PRODUCT_MODULE_NAME so the app's swiftmodule doesn't collide with the SPM library - .env.example: add BUNDLE_ID_PREFIX and DEVELOPMENT_TEAM - Native-README.md and source doc-comments: update paths and module name Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code ReviewOverviewThis PR delivers several improvements across MistDemo:
The structural split into 🔴 Blocking Issues1. Missing tests for all new commands The PR plan explicitly lists test files for
2.
3. Confirm
🟡 Medium Priority4. Both have an identical private 5. ModifyResultRow(op: "applied", recordName: ..., recordType: ...)When a delete succeeds, the output says 6. Redundant dependencies on In 🟢 Low Priority / Style7. Dead-code catch pattern Both catch let error as DeleteError { throw error } // dead — deleteRecord can't throw DeleteError
catch let error as CloudKitError { ... }The typed re-throw is never reached since the library functions don't throw command-layer error types. Remove or restructure the scope. 8. The check 9. Two plan files ( Positive Notes
Summary
🤖 Generated with Claude Code |
Code Review — PR #273: MistDemo improvementsOverviewThis PR delivers four significant improvements: library/executable split for testability, complete CRUD commands (Delete/Lookup/Modify), auth-token fixes, and a new native SwiftUI demo app. The scope is large (3803 additions) and mostly well-structured. Below are findings grouped by severity. 🔴 High PrioritySecurity: Hostname Validation is InsufficientFile: The
Recommended fix: guard let host = authority.split(separator: ":").first.map(String.init),
["localhost", "127.0.0.1", "::1"].contains(host) else {
return Response(status: .forbidden)
}Bug: Security-Scoped Resource Reference MismatchFile:
Missing Tests: New CRUD Commands
The PR test plan itself marks these as unchecked ( Bug: Errors Silently Dropped in Native QueryFile: return matchResults.compactMap { _, recordResult -> Note? in
switch recordResult {
case .success(let record): return Note(record)
case .failure: return nil // Silently dropped
}
}If records fail to parse, the view shows an empty list with no error indicator. Collect and surface failures. 🟡 Medium PriorityRedundant Default Values in Config StructsFile: configReader.string(..., default: ...) ?? MistDemoConstants.Defaults.zoneUsing both Non-Atomic Partial Failure Not Reflected in JSON OutputFile: Warning messages written to stderr for partial batch failures won't be detected by scripts consuming JSON stdout. Consider adding a structured Inconsistent Access Level in SwiftUI ViewsSeveral view files use Hardcoded Container Identifierpublic static let demoContainerIdentifier = "iCloud.com.brightdigit.MistDemo"This constant is duplicated between the CLI and native app. A shared constant in Unbounded Queries in Native AppThe 🟢 Low Priority / Positive NotesPositive: Bundle.module Resource LookupSwitching from fragile path-searching to Positive: Sendable ConformancesNew config structs ( Naming Inconsistency
Missing Doc CommentsNew commands and SwiftUI views lack any doc comments explaining their role. Even a one-line Environment Variable Security NoteThe Summary
The library/executable split and the auth-token fixes are solid changes. The main blockers before merge are the hostname validation fix and test coverage for the three new CRUD commands. The SwiftUI app ( 🤖 Reviewed with Claude Code |
…leak Splits files that bundled multiple top-level declarations so the new module conforms to the project's swiftlint rule: - Models/CloudKitModels.swift → Models/ZoneRow.swift + Models/Note.swift (Note now has a one-line comment documenting identity-based equality) - Services/NativeCloudKitError.swift extracted from NativeCloudKitService - Views/RootView.swift → keeps RootView; SidebarItem, SidebarView, and DetailColumnRoot move to their own files; unused import CloudKit dropped Also addresses two correctness issues from the PR review: - NoteEditView: track the security-scoped URL in @State so picking a new file, tapping Remove, or dismissing the sheet all balance the startAccessingSecurityScopedResource call. Check the Bool return value and surface a saveError when access is denied. Drop the now-redundant defer in save() — scope lifetime is bound to the view. - Delete the leftover .claude/plans/merge-mistdemo-directories.md so the planning artifact doesn't ship in the PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #273: MistDemo improvementsOverviewThis PR delivers four related improvements:
The scope is large but the changes are well-structured. Most decisions are sound. A few issues worth discussing before merge. Positives
Issues1.
|
Summary
swift testworks with@testable import--forceflag with conflict handlingTest plan
swift testpasses (859 tests) in Examples/MistDemoswift run mistdemo auth-tokenloads index.html and captures tokenswift run mistdemo delete/lookup/modifyagainst live container🤖 Generated with Claude Code
Perform an AI-assisted review on