- Replace Copper accent with Plum (light #6B4577 / dark #A67FB0) across the main
app and AutoFill extension.
- Replace the padlock app icon with a vault-door design (ring, hub, locking
handle) in Plum, plus all macOS sizes.
- Add an AppIconImage asset (separate from the AppIcon icon-set, which SwiftUI
can't reference directly at runtime) and show it on the unlock screen instead
of a generic SF Symbol.
- GroupFilterView: move "Clear Filter" out of the list and into a leading
toolbar button ("Clear", disabled when no filter is active), mirroring the
trailing "Done" button.
- Custom copper AccentColor and warm VaultBackground color assets (light/dark),
applied to both the main app and the AutoFill extension (which needed its own
copy of the asset catalog plus ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME
and an explicit .tint() modifier, since extensions don't pick up the app-wide
accent color the same way a full SwiftUI App/Scene does).
- GroupFilterView no longer uses DisclosureGroup for subgroups -- the whole tree
renders always-expanded (indented per depth) so active filters are visible at
a glance instead of requiring tap-to-expand.
- Added a placeholder app icon (padlock glyph, light/dark/tinted variants plus
macOS sizes) -- previously the icon slot existed but had no actual images,
which blocks App Store/TestFlight archive validation.
- Added ITSAppUsesNonExemptEncryption = NO (standard encryption only, via
KeePassKit) to skip the export-compliance prompt on each upload.
- Renamed the app from MyPass to KeeVault throughout: Xcode project/targets/
schemes, the MyPassCore package (now KeeVaultCore) and every import site,
folder and file names, bundle identifiers (org.antiloop222.keevault) and
their App Group/Keychain-group entitlements, and remaining UI/string
references -- MyPass was already taken as an App Store app name.
- FileBookmarkService: hold a security scope while creating the bookmark,
fixing "file doesn't exist" on iCloud Drive-backed vaults (NSCocoaErrorDomain
Code=4), which only surfaced on a real device since the Simulator strips
entitlements needed to reproduce this.
- Fix Keychain access group missing the Team ID prefix, which silently broke
saving the master password so Face ID was never offered after backgrounding.
- Add the autofill-credential-provider entitlement to the main app target
(previously only on the extension) and declare ProvidesPasswords in the
extension's Info.plist, so MyPass now registers as a selectable AutoFill
Passwords provider.
- CredentialMatcher: fall back to a scheme-prefixed re-parse when extracting a
host, since KDBX entries commonly store bare domains (e.g. "allocine.fr")
that URL(string:).host can't parse without an authority component. Fixes
AutoFill suggestions being unranked/wrong for such entries.
Root-caused the "MyPass doesn't appear in Settings > AutoFill Passwords"
issue: entitlements embed correctly on a real device build but are
silently stripped to empty on every Simulator build in this
environment (confirmed identically via CLI xcodebuild and Xcode's own
GUI build/run, before and after multiple rounds of removing/re-adding
capabilities in Signing & Capabilities). This is a Simulator/SDK-level
limitation in this environment, not a project misconfiguration --
verified by comparing against a real iphoneos device build, which
embeds the full App Group / Keychain Sharing / AutoFill Credential
Provider entitlements correctly.
While diagnosing, found and fixed a real, separate bug: AutoFill's
Keychain Sharing group had been set (via Xcode's capability re-add
flow, which defaults to the target's own bundle ID) to
org.antiloop222.mypass.autofill, while the main app uses
org.antiloop222.mypass -- a mismatch that would have prevented the
extension from ever reading the shared master password from Keychain,
even on a real device where entitlements embed correctly. Now aligned
to org.antiloop222.mypass on both targets, matching what
KeychainStore's hardcoded accessGroup expects in both AutoFill/
CredentialProviderViewController.swift and MyPass/ViewModels/
UnlockViewModel.swift.
Also includes the user's own Signing & Capabilities re-add (repopulated
App Group selections) and a bundle-identifier lowercase normalization
(org.antiloop222.MyPass -> org.antiloop222.mypass, matching the
already-lowercase App Group/Keychain group strings) made via Xcode's
GUI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Sets ENTITLEMENTS_REQUIRED = YES for both targets' build configurations.
Found while investigating why Settings > AutoFill & Passwords doesn't
list MyPass: Xcode's automatic-signing build step (ProcessProductPackaging)
silently produces an EMPTY entitlements blob for both the AutoFill
extension and the main app on this environment's Simulator builds,
despite AutoFill.entitlements/MyPass.entitlements being valid and
correctly referenced (verified with plutil, and confirmed manually
invoking codesign with the same file embeds the entitlements
correctly). This setting alone doesn't fix the underlying issue --
still investigating -- but is a correct, harmless hardening on its own
(surfaces an error if entitlements are ever genuinely missing, instead
of silently signing without them).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
- Remove dead provideCredentialWithoutUserInteraction override and
unlockSilently() helper (nothing registers credential identities,
so it could never run, and it was a footgun for future inline
QuickType work).
- Surface non-cancellation biometric unlock failures into
errorMessage instead of swallowing them, matching
UnlockViewModel.unlockWithBiometrics().
- Forward VaultSession.objectWillChange into ExtensionViewModel via
Combine so the lock/unlock UI transition no longer depends on an
accidental isUnlocking side effect, matching VaultViewModel's
pattern.
- Show the "Open MyPass" deep-link escape hatch whenever unlock
fails (errorMessage set), not only when there's no bookmark yet,
per the extension constraints spec.
- Add INFOPLIST_KEY_NSFaceIDUsageDescription to the MyPass and
AutoFill targets' Debug/Release build configs.
Also folds in pre-existing alphabetical reordering of two
PBXBuildFile/PBXFileReference entries in project.pbxproj from an
earlier task, since this same file is already being touched here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Replace the Xcode boilerplate in AutoFill/CredentialProviderViewController.swift
with real logic: prepareCredentialList(for:) builds ExtensionRootView (added in
Task 19) inside a UIHostingController, provideCredentialWithoutUserInteraction(for:)
silently unlocks via the shared Keychain token and completes/cancels the request.
Strip the storyboard's now-unused static nav-bar/button/actions, keeping the same
customClass so the system still instantiates this exact class.
Also add AutoFill target membership for MyPass/Services/FileBookmarkService.swift
and BiometricAuthService.swift (app-target files, referenced directly by the
extension per the plan) via explicit PBXFileReference/PBXBuildFile entries, since
Xcode's synchronized-group file system only auto-includes them in the MyPass
target by default.
Expected build state: exactly one error, "cannot find 'ExtensionRootView' in
scope" (reported once per simulator architecture), until Task 19 adds that type.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Removed the magnifying-glass toolbar button and the isSearching state
it drove -- .searchable() now shows the search field unconditionally,
which is simpler and was requested after hands-on use showed the extra
tap added no value. Updated the spec to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Supersedes Tasks 18-20 of the original plan with fixed bundle
identifiers (org.antiloop222, not com.christophevila), the correct
AutoFill/ folder name (renamed from autofill/ during Phase 2), and a
real bug fix found before implementation: the original Task 18 named
its view controller class differently from what the extension's
storyboard actually instantiates, which would have silently left the
default boilerplate UI running instead of any real code.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Confirmed by hands-on testing: NavigationSplitView's compact-width
collapse only pushes into the detail column when the sidebar drives
navigation via row selection. GroupFilterView is a multi-select filter,
not a picker, so on iPhone it showed only the sidebar with no way to
reach the entry list at all -- exactly the risk the final whole-branch
review flagged as unverified.
iOS now uses a plain NavigationStack with EntryListView as the root and
GroupFilterView presented as a sheet via the hamburger button. macOS
keeps the persistent NavigationSplitView sidebar, which does work
correctly at regular width.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
- GroupFilterViewModel now tracks lastToggledGroupId (the single group
most recently toggled to a non-empty selection), since toggling a
group with subgroups expands selectedGroupIds to include the whole
subtree, breaking the previous count==1 check used to infer the
active filter group.
- VaultViewModel exposes lastToggledGroupId, forwarded from
GroupFilterViewModel by VaultRootView (ContentView.swift), and
EntryListView.defaultGroupId now reads it instead of the broken
selectedGroupIds.count == 1 check, so the add-entry sheet defaults
new entries into the group actually being filtered by, not silently
back to root.
- EntryListView's search toggle button now clears vm.searchQuery when
hiding the search bar, so a hidden bar can no longer keep filtering
the list invisibly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Replaces group-drilling navigation with a flat, filterable/searchable
entry list backed by VaultViewModel.displayedEntries. SearchView's
role is fully absorbed by EntryListView's .searchable() modifier.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Replaces the group-drilling navigation model with a flat entry list,
a multi-select group-filter sidebar (unified NavigationSplitView on
both iOS and macOS instead of separate per-platform navigation code),
and a toggleable search bar. Covers the entry row layout (icon,
title, username, breadcrumb), group-filter subtree-selection
semantics, how group filter and search combine (AND), the new
explicit group-picker field EntryEditView needs now that there's no
implicit "current group", and confirms dark mode needs no new work
since the app already uses only semantic colors.
Brainstormed and approved with the user before writing this up.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Adds an eye-icon toggle to the master password field (UnlockView) so
users can verify what they typed before submitting.
Also maps KDBX4's ERROR_HEADER_HASH_VERIFICATION_FAILED -- surfaced
via the header HMAC check, which is password-derived -- to
KDBXError.invalidPassword alongside the existing
passwordAndOrKeyfileWrong check. KeePassKit uses this same error code
for both a wrong password/keyfile and genuine file corruption (it
doesn't distinguish the two), so the message hedges: "Incorrect
password, or this vault file is corrupted." Found while testing
against a real KDBX4 vault, which was surfacing this as a raw,
unreadable NSError string before the previous commit's LocalizedError
fix, and as *no* friendly message at all before this one (the error
code wasn't in the recognized set yet).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
UnlockViewModel.openFile(url:) called unlockWithPassword() immediately
after saving the bookmark, but the first-open UI (openFileSection) has
no password field yet -- password is still "" at that point, so every
first file pick failed before the user could type anything. Picking a
file now only saves the bookmark; the view naturally switches to the
password-entry state for the user to unlock explicitly.
Also makes KDBXError conform to LocalizedError with readable messages
per case, so any error path that reaches the generic catch clause
shows something useful instead of the default
"The operation couldn't be completed (MyPassCore.KDBXError error N)".
Found while testing the Phase 4 UI against a real KDBX file in the
simulator.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Completes Phase 4 (Task 17) -- ContentView now routes between UnlockView
and the vault UI (NavigationStack on iOS, 3-column NavigationSplitView on
macOS) instead of the leftover SwiftData template, finally resolving the
long-standing "cannot find type Item" build break. Vault locks on
scene-background (iOS) / resign-active (macOS).
Fixes two more real bugs surfaced now that everything actually links
together: EntryEditViewModel.save's dismiss closure needed @escaping
(used inside Task {}), and UnlockView's UTType(filenameExtension:)
needed `import UniformTypeIdentifiers`.
Also restores MyPass.entitlements, which had regressed to an empty
App Group array with Keychain Sharing missing entirely since the
"wire up AutoFill extension target" commit -- likely clobbered by
Xcode reconciling capabilities at some point without a rebuild in
between to catch it. Verified clean `xcodebuild` on both iOS Simulator
and macOS destinations, and `swift test` still passes all 18 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Also fixes real bugs found while making Phase 4 build clean on both
platforms:
- FileBookmarkService used .withSecurityScope unconditionally, but
that bookmark option is macOS-only -- iOS grants scoped access
automatically once the user picks a file. Now conditional per platform.
- EntryEditViewModel's groupId was non-optional with no default, but
EntryDetailView's edit-sheet call site (Task 14) has no group
reference to pass -- only the "add new entry" path actually needs
it, so groupId is now optional and validated at save time instead.
- Missing `import Combine` in EntryDetailView (Timer.publish/autoconnect)
and UnlockViewModel/VaultViewModel/EntryEditViewModel (@Published).
- GroupBrowserView's `group: Group` property collided with SwiftUI's
own Group view type -- qualified as MyPassCore.Group.
- EntryEditView's .keyboardType(.URL) and .navigationBarTitleDisplayMode
are iOS-only APIs, guarded with #if os(iOS) for the macOS build.
Verified clean (aside from the known, expected ContentView.swift/Item
breakage) on both iOS Simulator and macOS destinations.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Phase 3 (Tasks 10-11) app services: FileBookmarkService persists a
security-scoped bookmark for the KDBX file in the shared App Group;
BiometricAuthService wraps LAContext for Face ID/Touch ID; ClipboardService
copies text with an expiring clipboard entry on iOS/macOS.
Also fixes a real build issue found while verifying: the AutoFill
extension (iOS-only) was being embedded/signed unconditionally,
breaking macOS builds of the MyPass scheme with a bogus provisioning
error. Added platformFilters = (ios) to both the embed build file and
the target dependency so macOS builds skip it. Verified both iOS
Simulator and macOS builds now fail only on the known, expected
ContentView.swift/Item SwiftData breakage.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Adds App Groups + Keychain Sharing entitlements to the AutoFill
target (matching the main app's group.org.antiloop222.mypass /
org.antiloop222.mypass), sets REGISTER_APP_GROUPS so automatic
signing registers the capability, and links MyPassCore as a
framework dependency by reusing the existing local package product
reference. Verified with `xcodebuild -scheme AutoFill build` for the
iOS Simulator: the AutoFill.appex target builds, signs, and embeds
cleanly (the only build failure is the pre-existing, expected
ContentView.swift/Item SwiftData breakage in the MyPass app target,
unrelated to this change).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Research confirmed macOS AutoFill has no real scope reduction vs iOS
(same API, same app-or-website coverage) beyond requiring manual
per-platform enablement, and that Associated Domains is the
responsibility of the app being logged into, not the password
manager's extension -- MyPass's existing CredentialMatcher already
handles both domain- and bundle-ID-based serviceIdentifiers correctly.
Adds an onboarding note since enabling the extension can't be
automated on either platform.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Identifiers, the AutoFillExtension -> AutoFill rename, and the KDBX
library choice had all drifted from the approved spec since it was
written in May. Updates the spec to match what was actually built and
closes the now-resolved "which KDBX library" open question.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Also drops the stray empty AutoFillExtension/ directory left over from
an earlier abandoned attempt at creating this target, and cleans up
duplicate/stale scheme entries in xcschememanagement.plist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
The KeePassKit package never actually compiled: its Package.swift
didn't declare KissXML, the nested Argon2 submodule, or the in-tree
ChaCha20/TwoFish ciphers as dependencies, and its umbrella header's
framework-style <KeePassKit/...> imports don't resolve under SwiftPM's
non-framework header layout. Vendors KissXML locally (same issue as
KeePassKit) and fixes the resulting Swift-side API mismatches in
KDBXMapper/KDBXDocument (NSUUID bridges to UUID automatically,
KPKKey is abstract so KPKPasswordKey must be used directly, and a
few KeePassKit selectors were renamed by Swift's importer). Also
untracks MyPassCore/.build, which had been accidentally committed.
`swift test` now builds and passes all 18 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Adds the AutoFill Credential Provider Extension target (App Group +
Keychain Sharing capabilities, autofill-credential-provider
entitlement), removes the leftover SwiftData bootstrap from
MyPassApp.swift now that Item.swift is gone, and switches the bundle
ID / App Group / Keychain group prefix from com.christophevila to
org.antiloop222 to match the project's existing identifier.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
Implements KDBXError, KDBXMapper, and KDBXDocument for reading and writing KDBX files using KeePassKit.
Adds KDBXDocumentTests with fixture from KeePassKit's own test suite (Test_Password_1234.kdbx, password "1234").
Fixes KeePassKit Package.swift to include defaultLocalization for SPM compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add MyPassCore local Swift Package with directory structure for
Models, KDBX, TOTP, Keychain, AutoFill, and Session modules
- Add KeePassKit as git submodule (Vendor/KeePassKit) with SPM wrapper
Package.swift since upstream has no native SPM support
- Delete SwiftData boilerplate Item.swift from main app target
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>