Response style: drop articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments ok. Shorter synonyms preferred. Technical terms stay precise. Structure: [thing] [action] [reason]. [next step]. Suspend for security warnings, irreversible-action confirmations, multi-step sequences where compression risks misunderstanding. Code, commits, PRs always standard writing — caveman never applies there.
Standing directive, peer to Caveman, modeled on the Headroom context-compression plugin. Goal: spend the fewest tokens that still solve the task at full fidelity. Applies to this agent and every subagent/tool — carry a one-line Headroom reminder into subagent prompts.
- Read narrowly. Prefer
Grep/Globand targeted line-rangeReads over whole-file reads. Never re-read a file already in context, and never re-read a file you just edited to "verify" —Edit/Writefail loudly if they don't apply. - Slice, don't dump. For large files or tool output, use
head/tail/jq/byte-ranges to reach the relevant span; quote only the lines that carry the answer. - Batch. Issue independent tool calls together in one turn rather than serially.
- Delegate heavy reading. Fan out broad searches and multi-file audits to subagents (e.g.
Explore); keep their conclusions in the main thread, not the raw file contents they waded through. - Output economy. Relay the result and the few lines that prove it. Don't echo large command output, restate established context, or recap prior turns.
Suspend only where compression would drop load-bearing detail: full error text needed to diagnose, security warnings, irreversible-action confirmations, multi-step sequences. (Caveman governs prose terseness; Headroom governs how much you read and emit.)
MiniStore is a fork of SideStore/AltStore: a self-hosted iOS app store that lets users
sideload apps using only their Apple ID. No VPN integration is present. Device
communication over Wi-Fi is handled by minimuxer (a Rust library) bridged through the
SideStore/ Swift layer (MinimuxerWrapper.swift, IfManager.swift).
Claude may edit CLAUDE.md and any file under .claude/ without asking for
confirmation. These are documentation and intelligence files — not app code — and
keeping them current is a standing instruction. All other permission rules (no
force-push, no destructive ops without confirmation, etc.) remain in effect.
| File | What it covers |
|---|---|
.claude/map.md |
Every source file in every target — read before touching an unfamiliar file |
.claude/patterns.md |
Recurring implementation patterns (operations, CoreData, notifications, UI) |
.claude/decisions.md |
Architecture Decision Records — the why behind non-obvious choices |
.claude/gotchas.md |
Known landmines, silent failures, and non-obvious behaviours |
.claude/regression-watch.md |
Load-bearing invariants — change one and you reintroduce a fixed bug. Read before touching operations/refresh/nav-bar/backup code |
.claude/github-info.md |
gh CLI syntax, flags, release management, Actions patterns |
.claude/swift-isp.md |
Swift ISP, protocol-oriented design, performance, reliability, Swift 6.2 language features |
.claude/swift6-compat.md |
Swift 6/6.2 strict-concurrency errors, warnings, and new language features |
.claude/xcode26.md |
Xcode 26 build system, iOS 26 UIKit/Liquid Glass, Swift 6.2, new APIs, deprecations |
.claude/sidestore-delta.md |
What MiniStore changed vs upstream SideStore — read before merging upstream |
.claude/plugin_index.md |
Registry of every active Claude Code plugin/skill/agent |
.claude/plugins/*.md |
Per-plugin reference docs (caveman, headroom, taste, impeccable) |
.claude/update_log.md |
Change log for intelligence file updates |
AltStore/ Main app target (Swift + ObjC)
Browse/ Source browser tab
My Apps/ Installed apps tab + expiry countdown
Managing Apps/ AppManager.swift — install / refresh / revoke operations
Operations/ NSOperation subclasses for each async task
Components/ Shared UI cells, cards, reusable views
Settings/ App settings UI
AltStoreCore/ Shared framework (models, extensions, CoreData stack)
AltWidget/ WidgetKit extension
AltBackup/ Companion backup app (bundled as IPA inside main app)
SideStore/ minimuxer + IfManager bridging layer
MinimuxerWrapper.swift Swift bridge to minimuxer Rust library
IfManager.swift Network interface introspection
Shared/ ObjC/C headers shared between targets
xcconfigs/ Per-target xcconfig files (all inherit Build.xcconfig)
Build.xcconfig Single source of truth for version, bundle IDs, team ID
side.json MiniStore self-update source (stable) — hosts MiniStore itself for
in-app updates ONLY. See "side.json disambiguation" below.
sidenightly.json MiniStore self-update source (nightly) — activated by "Enable Beta
Updates" toggle. Same structure as side.json, nightly track only.
scripts/ci/ Python CI helpers (workflow.py, release notes, metadata)
.github/workflows/ pr.yml / nightly.yml / stable.yml / attach_build_products.yml
Note: There is NO
TunnelProv/directory in this repo. MiniStore has no NetworkExtension target. Do not create one.
Full file-by-file reference:
.claude/map.md— covers every Swift, ObjC, and resource file in all targets.
| Target | Bundle ID (Release) | Entitlements |
|---|---|---|
| SideStore (main app) | com.SideStore.SideStore |
AltStoreFree.entitlements (CI) / AltStore.entitlements (paid) |
| AltWidgetExtension | com.SideStore.SideStore.AltWidgetExtension |
AltWidgetExtension.entitlements |
| AltBackup | separate IPA embedded in main app bundle | AltBackup.entitlements |
Debug builds append .$(DEVELOPMENT_TEAM) to every bundle ID (e.g.
com.SideStore.SideStore.S32Z3HMYVQ). Never hard-code bundle IDs.
Local builds:
make build # xcodebuild archive → SideStore.xcarchive
make fakesign # ldid fake-sign all extensions using Release entitlements
make ipa # package SideStore.xcarchive → SideStore.ipa
make build-and-test # build + run unit tests on iPhone 17 Pro / iOS 26 simulator
make clean # remove SideStore.ipa and build/CodeSigning.xcconfig (gitignored, see CodeSigning.xcconfig.sample) overrides
DEVELOPMENT_TEAM, CODE_SIGN_ENTITLEMENTS, and signing identity for local paid
builds. CI always uses AltStoreFree.entitlements with CODE_SIGNING_ALLOWED=NO.
Key xcconfig variables:
MARKETING_VERSION— user-visible version (e.g.0.6.3), set inBuild.xcconfigDEVELOPMENT_TEAM— Apple team ID, defaultS32Z3HMYVQfor CIORG_IDENTIFIER— reverse-DNS prefix, defaultcom.SideStoreAPP_GROUP_IDENTIFIER— shared app group (com.SideStore.SideStorein release)
| Workflow | Trigger | Output |
|---|---|---|
pr.yml |
PR open / push to PR branch (Swift/config files only) | MiniStore-pr.N+sha.ipa attached to PR |
nightly.yml |
Cron 00:00 UTC + manual dispatch | Nightly release if commits since last success |
stable.yml |
Tag push or manual dispatch (version_override input) | Stable release |
attach_build_products.yml |
On completion of "Pull Request MiniStore Build" | Artifact links posted to PR |
All pipelines run on macos-26 / Xcode 26.2. Version strings are stamped by
scripts/ci/workflow.py set-marketing-version before building.
PR builds trigger on changes to:
**/*.swift, **/*.m, **/*.h, Makefile, **/*.xcconfig,
AltStore.xcodeproj/**, Dependencies/**, scripts/**, .github/workflows/pr.yml
One branch per session, persistent across resumes.
- Resumed session:
.claude/.current-branchexists → hook auto-checkouts it → continue on that branch. Do not create a new branch. - New session: no
.current-branchfile → create one branch usingclaude/<short-description>(no session ID), then immediately run:All work for that session stays on that one branch.echo "claude/<branch-name>" > .claude/.current-branch
- Starting genuinely new work in a new session: create a new branch, overwrite
.current-branch. .claude/.current-branchis gitignored — it is local state only, never committed.- Never push directly to
main.
git push -u origin claude/<branch>The default branch is develop. Never target main — it does not exist in this repo.
Self-update raw URLs (updated 2026-06-20): the self-update URLs hardcoded in
Source.swiftnow point to this repo —The-Big-Mini/MiniStore/develop/side.json(andsidenightly.json). The separateMiniStore-Publicmirror was retired; this repo is public and serves the JSON directly fromdevelop. Existing installs migrate from the old MiniStore-Public URLs automatically (seeresolveOrCreateMiniStoreSourcelegacy/known-bad lists).
Exponential backoff on network errors only: 2 s → 4 s → 8 s → 16 s. Never retry on 403 or 422.
type(scope): short imperative description
Why this change is needed.
https://claude.ai/code/session_<id>
Types: fix feat refactor docs chore test
MiniStore seeds two built-in sources at launch (in DatabaseManager.prepareDatabase()):
| Source | URL | Purpose |
|---|---|---|
| Mini's Repo | https://OofMini.github.io/Minis-Repo/mini.json |
Personal curated app source |
| MiniStore Updates (stable) | https://raw.githubusercontent.com/The-Big-Mini/MiniStore/develop/side.json |
Self-update source — stable channel |
| MiniStore Updates (nightly) | https://raw.githubusercontent.com/The-Big-Mini/MiniStore/develop/sidenightly.json |
Self-update source — nightly channel |
The self-update source uses two separate JSON files, each containing a single releaseChannels track:
| File | Track | Who sees it |
|---|---|---|
side.json |
stable |
Everyone (default) |
sidenightly.json |
nightly |
Users who enable "Beta Updates" in Settings |
When the user toggles "Enable Beta Updates" in Settings, SettingsViewController.switchMiniStoreUpdateSource() calls Source.setSourceURL() on the CoreData record to switch it between the two URLs. Source.fetchMiniStoreUpdateSource() tries both identifiers (stable and nightly) so the lookup always succeeds regardless of which was last active.
Both self-update sources are excluded from the Sources tab via a fetch predicate that filters out both Source.stableUpdateIdentifier and Source.nightlyUpdateIdentifier.
On first launch, DatabaseManager.prepareDatabase() deletes any source with identifier
sidestore.io/apps-v2.json. This cleans up the old SideStore source for users migrating
from SideStore. The deletion is intentional and permanent.
Two CoreData migration policies redirect old SideStore source URLs:
Source11To17MigrationPolicy— handles oldapps.sidestore.ioURL → altStoreSourceURLSource17To17_1MigrationPolicy— handles oldapps.sidestore.ioURL → altStoreSourceURL
These must be kept — users upgrading from old SideStore versions need them.
There are two completely separate JSON files that could be confused.
Self-update source (side.json) |
Personal app source (mini.json) |
|
|---|---|---|
| Purpose | Self-update only — lists MiniStore (the app) so it can update itself | Personal curated app source — lists EeveeSpotify, X, YTLite, etc. |
| Authored in | The-Big-Mini/MiniStore (this repo) |
OofMini/Minis-Repo (separate repo) |
| Served from | The-Big-Mini/MiniStore (this repo, develop branch — hardcoded in Source.swift) |
OofMini.github.io (GitHub Pages) |
| URL | https://raw.githubusercontent.com/The-Big-Mini/MiniStore/develop/side.json |
https://OofMini.github.io/Minis-Repo/mini.json |
| Who consumes it | MiniStore reads it to detect new versions of itself | Any AltStore-compatible client the user adds it to |
| Contents | Single release channel per file (stable in side.json, nightly in sidenightly.json) |
Multiple apps: EeveeSpotify, X, YTLite, etc. |
| Do NOT | Add third-party apps to this file | Assume this relates to MiniStore self-updates |
| File | Line | Description | Priority |
|---|---|---|---|
AltStoreCore/Model/InstalledApp.swift |
129 | Version comparison when multiple tracks are independent (acknowledged limitation, inherited SideStore logic) | Medium |
AltStoreCore/Model/StoreApp.swift |
622 | Support multiple device types in screenshot filtering (inherited SideStore feature work) | Medium |
AltStoreCore/Model/Source.swift |
272 | Support alternate source URLs in recommended-source check (inherited SideStore feature work) | Medium |
AltStoreCore/Model/NewsItem.swift |
78 | Move app-specific news to appEntity — large inherited refactor, deferred |
Low |
- Broad bug audit complete — all major source files have been audited. See
.claude/update_log.mdfor the full audit history.MyAppsViewController,DatabaseManager,DatabaseManager+Async.swift,Sources/tab VCs, andAppDetailCollectionViewControllerwere audited and found clean (2026-05-10). - print() → Logger migration complete — ALL targets (2026-06-09): Main app, AltStoreCore, AltWidget, and SideStore/Utils/ use
Logger.main.*.SideStore/MinimuxerWrapper.swift(categoryMinimuxerBridge),SideStore/EMProxyWrapper.swift(categoryEMProxyBridge, simulator-only no-op stubs),Shared/Connections+Shared/Server Protocol(categoriesConnections/ServerProtocol), andAltBackup/(categoryAltBackup) use file-privateLogger(subsystem: "com.rileytestut.AltStore", category:)directly — these targets have no AltStoreCore dependency, so they share the hardcoded subsystem string for unified Console filtering. Only remaining prints:SideStore/Tests/(intentional). - Dead code + incorrect pattern cleanup complete (2026-05-31): All Settings VCs use cross-dissolve animation for OLED/accent reloadData. Retain cycles in
AdvancedSettingsViewControllerUIAlertAction closures fixed. Expiry notification cancel bug fixed (prefix-filter instead of wrong single ID). All 5 experimental features confirmed fully wired into production code. - RSTAsyncBlockOperation
operation.finish()audit complete (2026-06-03): Systemic missingfinish()on success/failure paths in all prefetch handlers. Fixed in:BrowseViewController,NewsViewController,SourcesViewController,SourceDetailContentViewController,AddSourceViewController(×2),AppCardCollectionViewCell,AppScreenshotsViewController,PreviewAppScreenshotsViewController,ErrorLogViewController(×4 paths including nil-context guard). All operations inMyAppsViewControllerandAppManageralready calledfinish()correctly. - Error-surfacing audit complete (2026-06-09): Systemic weak zone found at entry points (URL-scheme handlers, file-open handlers, document-picker imports) using
guard ... try? ... else { return }— failures vanished with no log or UI. Fixed: Sources "Refresh All" per-source failures (aggregate toast + log),.sideconfaccount import (toast + authenticate result handling), pairing-file open (toast + log), malformedappbackupresponsefailure deep links (now deliverOperationError.unknownResultinstead of letting BackupAppOperation time out), AppDelegatepairingdeep link"urlName"key bug (keys are lowercased — handler could never match), AltBackup eaten backup result (now fires local completion notification),KeychainItemgetter/setter silent error swallowing (do/catch + log, nil semantics kept),ConsoleLogsilent stderr fallback, beta-updates toggle revert toast, badge-count + prewarm fetch logging. Core operation pipeline confirmed clean (errors propagate tofinish(.failure)or log+toast). - Extended bug audit complete (2026-06-03):
AppPermission17To17_1MigrationPolicyleft_permissionnil for unknown type strings — added empty-string sentinel.StoreApp11To17_1andStoreApp17To17_1migration policies passed untypedAny?values directly to non-optional@NSManaged Stringproperties — addedas? String ?? ""coercion.OperationError.maximumAppIDLimitReachedforce-unwrappedDateComponentsFormatter.string(from:)which returns nil when expiry is past.UIColor+HexscanInt32overflowed on ARGB values with alpha ≥ 0x80.ProcessInfo+AltStoreBuildVersion.<nil-suffix comparison inverted (GM sorted before beta).super.init()missing inBackupControllerandActiveAppsTimelineProvider. CI build failure (Unicode curly quotes inReviewPermissionsViewController) fixed.AltWidget/AppsTimelineProviderallSatisfycondition inverted — healthy widgets got relevance 0.Countdown.swiftnumberOfDays < 0dead code — capsule shape never shown for expired apps.ChangelogViewControllerretain cycle — resolved by full SwiftUI rewrite (2026-06-11).
Active migration from UIKit to SwiftUI (started 2026-06-11). Goal: full departure from UIKit. Strategy:
- New screens: write in SwiftUI, host via
SettingsHostingController(settings stack) orUIHostingControllerelsewhere - Existing UIKit screens: rewrite incrementally, most contained first (Settings screens → tabs last)
Round 2 completed (2026-06-12): All five tabs (SourcesView, UpdatesView, MyAppsView+MyAppsViewModel, BrowseView, NewsView); AppIDsView (was AppIDsViewController — 30 s watchdog + generation counter preserved, orphaned Main.storyboard scene + dead unwindToMyAppsViewController IBAction removed); ReviewPermissionsView (was ReviewPermissionsViewController — same completionHandler contract with VerifyAppOperation); SourceDetailView (was SourceDetailViewController + SourceDetailContentViewController + SourceHeaderView/xib + NewsCollectionViewCell/xib — scroll-driven blur/nav-fade rebuilt in SwiftUI; nav chrome (icon+name title view, ADD/REMOVE PillButton, iOS 26 glass-capsule workaround) lives in SourceDetailHostingController; 4 Sources.storyboard scenes + showSourceDetails segue removed; News/Browse hosting controllers gained programmatic init(source:) for the View All pushes).
- Gotcha (round 2): SwiftUI
.toolbarinside a CHILDUIHostingController(addChild embed) never reaches the nav bar — the bar reads the parent VC'snavigationItem. Create bar buttons in UIKit on the VC that sits on the nav stack (MyAppsViewController sideload “+”). Direct hosting-controller subclasses on the stack (Browse/News/Sources/Updates) are unaffected. - Pattern (round 2): rows wrap
AppBannerViewviaUIViewRepresentablefor exact visual parity; a tap recognizer whose delegate returns!(touch.view is UIControl)lets the pill button receive taps while the row navigates.withObservationTrackingre-arm loop drives UIKit nav chrome from@Observablemodels. - Round 2 addendum:
AddSourceView(was AddSourceViewController — Combine debounce pipeline became a cancellable Task with 200 ms sleep; +/✓ staging keyed by source identifier; both sheet storyboard scenes removed,presentAddSourcebuildsForwardingNavigationController(rootViewController: AddSourceHostingController())programmatically; SourceComponents.swift + AddSourceTextFieldCell deleted). Sources.storyboard now contains only the two tab scenes. - Round 4 completed (2026-06-13): Authentication flow fully migrated.
AuthenticationView(+AuthenticationModel),SelectTeamView,InstructionsView,RefreshAltStoreView(+RefreshAltStoreModel) and their hosting controllers replaceAuthenticationViewController,SelectTeamViewController,InstructionsViewController,RefreshAltStoreViewControllerand the entireAuthentication.storyboard.AuthenticationOperationnow builds every screen — and the modal nav controller — programmatically (UINavigationController(navigationBarClass: NavigationBar.self, ...)reproducing the storyboard's opaque-SettingsBackground/white-title appearance). Contracts preserved verbatim:authenticationHandler/completionHandler, therequiresTwoFactorAuthenticationspinner-stop, the white "Failed to Log In" toast, SelectTeam's nil-handler-before-resume, RefreshAltStore's determinateProgress.fractionCompletedKVO → circularProgressViewand Try Again/Refresh Later alert.InsetGroupTableViewCell+SettingsHeaderFooterView(+ .xib) deleted — they were only used bySelectTeamViewController. - Remaining UIKit: LaunchViewController (322, bootstrap), TabBarController/nav controllers/ToastView (infrastructure — stays by design). The UIKit→SwiftUI screen migration is otherwise complete.
- Parity audit (2026-06-16): every migrated screen diffed against its git-historical UIKit original (Detail, all 5 tabs, auth flow, all Settings). Fixes shipped: App Detail
AppDetailHostingControllerwas missingnavigationItem.largeTitleDisplayMode = .never(large-title bar covered the back button + banner and showed a glass ghost — the reported "no back button / ghost +/frozen parallax" triple; see gotchas.md "largeTitleDisplayMode nav-bar parity landmine"); overscroll blur was inverted; back chevron nowapp.tintColor. Auth RefreshAltStore on-screen "Refresh Later" now always finishes.cancelled(alert keeps forwarding the error); Instructions step labels restoredminimumScaleFactor(0.5); sign-in field.emailAddress. My Apps expiry countdown refreshes on tab re-selection (model.refreshExpiryClock()inviewIsAppearing); installed-app screenshot prewarm restored. AddSource re-subscribes todidAddSource/didRemoveSource(button +/✓ state was stale) + typed-URL.altPrimarycolor. Settings Beta Testing icon back to.altPrimary; ErrorLog "Show More" now gated on measured render height, not char count. Audited-clean (no critical): Source Detail, Sources/Updates/Browse, ReviewPermissions (completionHandler contract), AppIDs (watchdog+generation), BetaTesting (toggle-order invariant), ExperimentalFeatures, TabOrder, NetworkDiagnostics, RefreshingApps, WhatsNew, RefreshAttempts, Licenses, AccentColor/Display/TechThings/Advanced/MiniStoreSigning/Developer/AltAppIcons. Known-accepted residuals (SwiftUI-equivalent / improvement, not regressions): News shows "No News" on empty-success (original blank); some sibling settings screens dropped now-inert OLED/accent crossfade observers; auth scroll-to-top-after-attempt relies on SwiftUI keyboard avoidance. - Round 3 completed (2026-06-12):
AppDetailView+AppDetailHostingController(was AppViewController, AppContentViewController, AppDetailCollectionViewController, AppContentViewControllerCells, AppScreenshotsViewController, PreviewAppScreenshotsViewController, dead HeaderContentViewController — ~1,800 lines → 905 lines). Parallax scroll rebuilt with GeometryReader+PreferenceKey; blur overlay driven by scroll fraction;UnevenRoundedRectanglecontent card corners flatten as nav bar reveals.CollapsingMarkdownViewwrapped viaUIViewRepresentablewithsizeThatFits+@Binding sizingTickfor correct dynamic height after collapse toggle.AppBannerViewwrapped asUIViewRepresentablewith Coordinator observing CoreData/foreground/OLED notifications. Screenshots carousel usesscrollPosition(id:)for scroll-to-initial. Nav chrome inAppDetailHostingController;withObservationTrackingre-arm loop drives frommodel.navBarFraction. iOS 26 glass-capsule workaround: nil outrightBarButtonItemwhen fraction < 0.01.StoryboardID.swiftdeleted (only held dead showApp segue ID). Four Main.storyboard App Detail scenes removed. AllmakeAppViewControllercall sites updated toAppDetailHostingController.makeAppViewController. - Completed:
WhatsNewView(was ChangelogViewController),RefreshAttemptsView(was RefreshAttemptsViewController + storyboard scene),LicensesView(was dead storyboard-only LicensesViewController — resurrected, now reachable from Tech Things),ExperimentalFeaturesView(was ExperimentalFeaturesSettingsViewController — Smart Refresh action sheet became a menu Picker),TabOrderView(was SettingsTabOrderViewController — permanent edit mode via.environment(\.editMode, .constant(.active))),NetworkDiagnosticsView(was NetworkDiagnosticsViewController — async/await ping test),BetaTestingView(was BetaTestingSettingsViewController — takesweak var navigationControllerfor in-stack pushes; toggle revert uses anisRevertingToggleflag because SwiftUIonChangefires on programmatic writes, unlike UIKit valueChanged),DeveloperView+DeveloperProfileStore(was DeveloperSettingsViewController — profile cache now an@Observablesingleton;DeveloperProfileStore.prefetch()replaces the old VC static prefetch in AppDelegate + SettingsViewController),DisplayView(was DisplaySettingsViewController — accent swatch + OLED/accent re-render via notification-driven.idbump),TechThingsView(was TechThingsSettingsViewController — clear-cache confirm + error alerts in SwiftUI, error-log export still presents UIActivityViewController via nav controller, Error Log push still storyboard-based until that screen migrates),RefreshingAppsView(was RefreshingAppsSettingsViewController — shortcut preview viaShortcutPreviewCoordinatorholding the UIDocumentInteractionController),AccentColorView(was AccentColorSettingsViewController — 5-column LazyVGrid, system UIColorPickerViewController kept for the custom picker viaColorPickerCoordinator),AdvancedSettingsView(was AdvancedSettingsViewController — hidden debug section behind 3-finger triple-swipe-up viaDebugGestureAttacherUIViewRepresentable that climbs the responder chain to attach the recognizer; mail viaMailComposeCoordinator; pairing-file done-alert intentionally has no dismiss button — UIAlertController kept for that),AltAppIconsView(was AltAppIconsViewController —UIApplication.didChangeAppIconNotificationextension moved with it; forced dark scheme replaces overrideUserInterfaceStyle),MiniStoreSigningView(was MiniStoreSigningSettingsViewController — document pickers +promptForPasswordpresented via nav controller, all toasts in nav view),ErrorLogView+ErrorDetailsView(was ErrorLogViewController/Cell/ErrorDetailsViewController + 3 storyboard scenes —@SwiftUI.SectionedFetchRequestoverlocalizedDateString, details + console-log as.sheetwith detents, minimuxer log viaLogQuickLookCoordinator; needs.environment(\.managedObjectContext, ...)at both push sites; needsimport CoreDatafor NSManagedObjectID),SettingsRootView+SettingsRootViewController(was SettingsViewController + static-cell storyboard scene — hosting controller subclassesUIHostingControllerwithinit?(coder:rootView:)so the storyboard scene stays; ALL nav-bar invariants preserved verbatim: bar-level mutations only in viewDidAppear, large-title collapse via transition coordinator, item-level appearances in viewWillAppear. The sharedUIViewControllerextension (applySettingsNavBar/settingsPinContentTop/promptForPassword) moved to SettingsHostingController.swift. InsetGroupTableViewCell + SettingsHeaderFooterView were kept for SelectTeamViewController, then deleted in Round 4 when it was migrated) - Infrastructure:
SettingsHostingController<Content>— generic host that setsnavigationItem.title, disables large titles, appliesapplySettingsNavBar(); use it for every SwiftUI settings screen - Pattern:
@MainActor @Observableview model,asyncfetch methods,.task {}+.refreshable {}in view; CoreData lists use@FetchRequestwith.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)injected at the call site - Navigation: UIKit nav stack owns the stack;
.navigationTitleis inert insideUIHostingController— title goes throughSettingsHostingController.init - Do NOT use
NavigationStackorNavigationViewinside a hosted view pushed onto the UIKit nav stack - Gotcha: app-local
final class Button: UIButton(Components/Button.swift) shadowsSwiftUI.Buttonin the main target — writeSwiftUI.Button/SwiftUI.Labelexplicitly until that class is renamed or removed - Gotcha: AltStoreCore exports
public typealias FetchRequest = NSFetchRequest<NSFetchRequestResult>(Protocols/Fetchable.swift) — collides with SwiftUI's property wrapper; write@SwiftUI.FetchRequestandSwiftUI.FetchedResultsexplicitly
Both files live in this repo and are served raw from develop. The separate
MiniStore-Public mirror was retired (2026-06-20) — deploy_public.yml and the
deploy_public.py helper were deleted. All JSON changes happen here now.
side.json and sidenightly.json are auto-stamped by CI on every stable/nightly
build (version, date, size, sha256, downloadURL all written automatically) by the stamp
step in stable.yml / nightly.yml, which commits the result back to this repo and
uploads the IPA to this repo's releases. Manual editing is not needed for release fields.
Existing installs that still point at the old MiniStore-Public URLs are migrated to the
in-repo URLs on launch — the old URLs are listed in resolveOrCreateMiniStoreSource
(legacy) and deleteOrphanedMiniStoreSources (known-bad) in DatabaseManager, plus
Source.fetchMiniStoreUpdateSource candidates.
Still inherited from SideStore:
alpha.yml+workflow.py deploy()push an alpha channel toSideStore/apps-v2.json(gated behind theCROSS_REPO_PUSH_KEYsecret). This is unrelated to MiniStore-Public and is dormant unless that secret is set. Left in place.
Asset note:
side.json/sidenightly.jsonscreenshot URLs point atMiniStore/develop/Screenshots/MiniStore-{1,2,3}.PNG. Pulled from the retired MiniStore-Public repo intoScreenshots/(2026-06-20) — live once merged todevelop.
Last updated: 2026-06-21
Recommended Claude Code plugins for this project. Install via the Claude Code marketplace or manually.
| Plugin | Repo | What it does |
|---|---|---|
| swift-ios-skills | dpearson2699/swift-ios-skills | 84 agent skills for iOS 26+ / Swift 6.3 — SwiftUI, CoreData, concurrency, hardware, AI/ML, and more |
| apple-platform-build-tools | kylehughes/apple-platform-build-tools-claude-code-plugin | Build/test/archive Xcode projects from Claude; includes xcrun/xcodebuild/simctl reference docs and a subagent for scheme discovery and simulator management |
| claude-mem | thedotmack/claude-mem | Persistent memory across sessions — captures what the agent does, compresses with AI, injects relevant context back into future sessions |
| claude-code-lsps | Piebald-AI/claude-code-lsps | LSP servers for 30+ languages — gives Claude go-to-definition, find-references, hover info, and symbol search |
| awesome-claude-code-subagents | VoltAgent/awesome-claude-code-subagents | 154+ specialized subagents across 10 categories (core dev, infra, security, data/AI, etc.) with isolated context windows |
| pointbreak-claude | withpointbreak/pointbreak-claude | AI-assisted debugging — set breakpoints, inspect variables, step through code via /debug, /step, /inspect slash commands |
| ui-ux-pro-max-skill | nextlevelbuilder/ui-ux-pro-max-skill | Design intelligence — 161 UI styles, 161 color palettes, 57 font pairings, accessibility checklists; supports SwiftUI |
| superpowers | obra/superpowers | Agentic dev methodology — TDD cycles, implementation planning, parallel subagent execution, spec refinement, and automated code review from brainstorm through deploy |
| claude-swift-engineering | johnrogers/claude-swift-engineering | 12 specialized Swift agents for iOS/macOS — architecture planning, implementation, testing, and review for Swift 6.2 / SwiftUI / TCA, plus 18 knowledge skills covering TCA patterns and iOS design principles |
| swift-lsp | claude.com/plugins/swift-lsp | Official Claude plugin — Swift Language Server Protocol integration for code intelligence (go-to-definition, autocomplete, symbol search) in Swift files |
| context7 | upstash/context7 | MCP server that injects up-to-date, version-specific library docs and code examples directly into context — eliminates hallucinated APIs and outdated patterns |
| Axiom | CharlesWiltgen/Axiom | 236 skills for Apple platform development (iOS/iPadOS/watchOS/tvOS) covering UI, data, concurrency, performance, networking, and accessibility, plus 39 agents for scanning common issues like memory leaks and data migration errors |
| swiftui-agent-skill | twostraws/swiftui-agent-skill | SwiftUI Pro skill by Paul Hudson — guidance on navigation, layouts, animations, state management, and accessibility; flags deprecated APIs and LLM-common mistakes; trigger with /swiftui-pro |
| caveman | juliusbrussee/caveman | Output-token compressor (/caveman lite|full|ultra|wenyan, /caveman-commit, /caveman-review, /caveman-stats). MiniStore's "Caveman Mode — Full" directive is modeled on it. Doc: .claude/plugins/caveman.md |
| headroom-desktop | gglucass/headroom-desktop | Desktop tray app that compresses prompts/tool-output over the wire (~50%). MiniStore's "Headroom Mode" directive is modeled on it. Doc: .claude/plugins/headroom.md |
| taste-skill | Leonxlnx/taste-skill | Web-frontend design skills (variance/motion/density dials). Web only — design-vocabulary reference for this native-iOS repo, not a code generator. Doc: .claude/plugins/taste.md |
| impeccable | pbakaus/impeccable | Web design skill — 23 commands + 44 detector rules (/impeccable init|audit|critique|polish). Web only — does not lint SwiftUI/UIKit. Doc: .claude/plugins/impeccable.md |
Caveman & Headroom are active conventions, not just recommendations — they are enforced as the "Caveman Mode — Full" and "Headroom Mode — Token Frugality" directives at the top of this file. The plugin docs under
.claude/plugins/describe the upstream tooling and its precedence relationship to those directives.
- Read the full file before touching it. No exceptions.
- Commit after every self-contained change. Never leave the tree dirty with multiple independent changes.
- No force-push without explicit instruction. No
--no-verify. Noreset --hardon a pushed branch without explicit instruction. - Never swallow errors. Log domain + code. Set a visible error state.
- Claude may freely edit
CLAUDE.mdand all.claude/files without confirmation. These are documentation — keeping them current is a standing instruction. - Follow Swift ISP and protocol-oriented design. New protocols must be narrow.
New
ResultOperationsubclasses must callfinish(_:)exactly once on every path. See.claude/swift-isp.mdfor the full checklist. - Use correct
ghflag syntax.gh release edituses--draft=falseand--prerelease=false— never--no-draft/--no-prerelease. See.claude/github-info.md. - Never add VPN-related code. No TunnelProv target, no NetworkExtension framework,
no packet tunnel, no VPN tab.
isVPNEnabledUserDefault exists only as a stub (always false) for key-space compatibility. Exception (sanctioned, 2026-06-10): the LocalDevVPN redirect flow is allowed — it is a URL-scheme hop to the external LocalDevVPN app, not in-app VPN code. Components:enableEMPforWireguardUserDefault, "LocalDevVPN Auto-Connect" toggle in Refreshing Apps settings,localdevvpninLSApplicationQueriesSchemes, andMyAppsViewController.checkMinimuxer(deep-link out → background-task return viasidestore://→ minimuxer retarget + retry). Do not remove these citing this rule.