Skip to content

refactor: remove all Vitruvian references — legal rebrand to Phoenix - #709

Closed
9thLevelSoftware wants to merge 9 commits into
mainfrom
rebrand/remove-vitruvian-references
Closed

refactor: remove all Vitruvian references — legal rebrand to Phoenix#709
9thLevelSoftware wants to merge 9 commits into
mainfrom
rebrand/remove-vitruvian-references

Conversation

@9thLevelSoftware

@9thLevelSoftware 9thLevelSoftware commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

Legal rebrand plus an upgrade-safe persisted-file migration. Existing Android and iOS installations keep their sandbox and local data while the active database and Android preference stores move to Phoenix filenames.

This supersedes the earlier “keep legacy persisted filenames” workaround. Fresh installs and successful upgrades use:

  • Database: phoenix.db
  • Database recovery: phoenix-recovery.db
  • Database staging/lock: phoenix.db.migrating, phoenix-db-migration.lock
  • Android preferences: phoenix_preferences, phoenix_secure_preferences
  • Preference recoveries: phoenix_preferences_recovery, phoenix_secure_preferences_recovery

Rebrand changes

  • iOS: VitruvianPhoenix → PhoenixApp (directory, project, scheme, Swift type, entitlements)
  • Android: VitruvianApp → PhoenixApp, VitruvianTheme → PhoenixTheme
  • SQLDelight API: VitruvianDatabase → PhoenixDatabase
  • Build/UI/docs: Phoenix project, theme, icons, documentation, workflows, and scripts

Persisted-file compatibility

  • A common pre-database state machine checkpoints WAL, validates SQLite, creates and flushes a neutral recovery copy, and performs same-directory no-replacement atomic cutover.
  • Interrupted migration resumes only from a verified legacy or recovery source.
  • If legacy and target databases coexist, startup blocks with DB_DUAL_DATABASES; neither candidate is opened, changed, selected, or merged automatically.
  • SQLDelight schema migration and reconciliation run only after the physical file is safely selected.
  • Recovery survives the migrated launch and is removed only after validation on the next real launch. Same-process Retry cannot consume it early.
  • Android plaintext and encrypted stores migrate through SharedPreferences APIs. Phoenix values win conflicts; encrypted XML bytes are never copied.
  • Database/preference failures render a shared preserved-data screen with a stable diagnostic and Retry where safe.
  • Application ID, bundle ID, signing configuration, SQLDelight schema contents, and migration files are unchanged.

Compatibility lookup remains indefinitely for skipped-release upgrades. Legacy filename literals are intentionally limited to:

  • DatabaseFileNames.LEGACY
  • AndroidPreferenceFileNames.LEGACY_PLAINTEXT and LEGACY_ENCRYPTED
  • Android backup/data-extraction exclusions
  • Migration tests and the disposable-emulator upgrade harness

Verification completed

  • Common coordinator state/failure-order tests: pass
  • Android host and unit tests: pass
  • Six Android instrumentation migration tests: pass
  • :shared:verifyCommonMainPhoenixDatabaseMigration: pass
  • :shared:compileKotlinIosArm64: pass
  • Android lint and debug assembly: pass
  • Android release assembly: pass
  • Release cue-resource verifier: pass
  • QA-release-boundary verifier: pass
  • iOS schema shell validator: 47 tables / 571 columns
  • Legacy-literal and schema-diff review: pass
  • Changed-file Spotless review and git diff --check: pass

The disposable API 35 upgrade matrix passed all six scenarios: Fresh, v0.9.6 Upgrade, InterruptedStaging, CorruptSource, DualDatabase, and LowStorage. The upgrade retained 3 profiles, 10 workouts, 1 routine, plaintext preferences, and a real committed-WAL payload while advancing schema 43 → 47. Low storage reached 372 KiB free, blocked with DB_RECOVERY_COPY_FAILED, preserved the legacy source, and completed through the rendered Retry action after recovery. Successful scenarios retained neutral recovery on the migrated launch and removed it on the next launch.

Detailed procedure and evidence contract: docs/qa/persisted-file-upgrade.md.

Remaining release gates — do not merge yet

  • Run a production-signed Android upgrade with the same signing certificate and a higher-version candidate APK. A local debug key cannot supply this evidence.
  • Run a true skipped-version upgrade from the oldest supported production build.
  • Run the upgrade and two-launch cleanup checks on a physical iOS device; this Windows host has no iOS device/simulator target and skips framework linking.
  • compileTestKotlinIosArm64 is still blocked by three pre-existing errors in unchanged tests (RoutineSetWeightResolverTest and WorkoutExecutionGuardRestoredRuntimeTest).
  • Repository-wide spotlessCheck still reports 168 unrelated pre-existing Kotlin formatting violations; all files touched here are clean.

Published implementation branch head: b98aa9f8 (rebrand/remove-vitruvian-references). Because the PR is closed, GitHub still exposes its historical pull ref at 25023179 until a maintainer reopens it.

PR remains closed and is not being reopened automatically. Reopen only after the external signed/device gates are attached.

- Rename iOS project: VitruvianPhoenix → PhoenixApp (directory, xcodeproj, scheme, swift, entitlements)
- Rename Android: VitruvianApp → PhoenixApp, VitruvianTheme → PhoenixTheme
- Rename SQLDelight: VitruvianDatabase.sq → PhoenixDatabase.sq
- Update settings.gradle.kts root project name: ProjectPhoenix
- Update AndroidManifest: Theme.PhoenixProject, .PhoenixApp
- Replace all icon assets with new Phoenix logo
- Scrub all Vitruvian/vitphoe references from source, tests, docs, CI workflows
- Verified: grep -ri vitruvian = 0, grep -ri vitphoe = 0
- Builds verified: SQLDelight interface gen, Kotlin Android/iOS, Xcode device build
Copilot AI lite review requested due to automatic review settings August 21, 2026 16:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97235f036e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

companion object {
private const val TAG = "DriverFactory"
private const val DATABASE_NAME = "vitruvian.db"
private const val DATABASE_NAME = "phoenix.db"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the existing database filename during rebrand

On an upgrade from any released build, user data remains in vitruvian.db, but the unchanged application/bundle ID now opens phoenix.db; because there is no file migration or fallback lookup, SQLDelight creates a fresh empty database and all profiles, routines, and workout history appear lost. The same filename change is present in the iOS driver, so both platforms need to keep the on-disk name or rename/copy the existing database before opening it.

Useful? React with 👍 / 👎.

Comment on lines +35 to +36
private const val ENCRYPTED_PREFS_FILE = "phoenix_secure_preferences"
private const val PLAINTEXT_PREFS_FILE = "phoenix_preferences"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Migrate the existing Android preference stores

For users upgrading an existing Android installation, these new filenames create empty preference stores while vitruvian_preferences and vitruvian_secure_preferences retain all previous settings and encrypted portal credentials. Nothing copies from those legacy files—the token migration below only reads the newly named plaintext store—so users are logged out and lose units, theme, safety, and other persisted choices even if the database-name regression is fixed.

Useful? React with 👍 / 👎.

name.startsWith("Vitruvian", ignoreCase = true)
if (isVitruvian) {
log.i { "Found Vitruvian by name: $name" }
name.startsWith("Phoenix", ignoreCase = true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep accepting the legacy BLE advertised-name prefix

When a compatible machine advertises a non-null name beginning with Vitruvian rather than Vee_ or VIT, replacing the previously accepted prefix with Phoenix makes this filter return false immediately. Because the named-device branch exits before checking the known service UUID/service data, those physical machines disappear from the scan results and cannot be selected; branding can change in UI text without changing identifiers emitted by existing firmware.

Useful? React with 👍 / 👎.

@@ -282,7 +282,7 @@ private fun EulaContent() {
content = null,
)
Text(
text = "Project Phoenix is an independent, community-developed project. IT IS NOT AFFILIATED WITH, ENDORSED BY, AUTHORIZED BY, OR SUPPORTED BY VITRUVIAN INVESTMENTS PTY LTD (IN LIQUIDATION), MANAGED BY MERCHANTS ADVISORY, OR ANY OTHER EQUIPMENT MANUFACTURER.",
text = "Project Phoenix is an independent, community-developed project. IT IS NOT AFFILIATED WITH, ENDORSED BY, AUTHORIZED BY, OR SUPPORTED BY PHOENIX INVESTMENTS PTY LTD (IN LIQUIDATION), MANAGED BY MERCHANTS ADVISORY, OR ANY OTHER EQUIPMENT MANUFACTURER.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the actual company name in the legal disclaimer

The app's rebrand cannot rename the third-party legal entity: changing VITRUVIAN INVESTMENTS PTY LTD (IN LIQUIDATION) to PHOENIX INVESTMENTS PTY LTD makes the no-affiliation clause refer to a different or nonexistent company. In every accepted EULA this now fails to disclaim affiliation with the actual equipment manufacturer, undermining the stated legal purpose of the change.

Useful? React with 👍 / 👎.

type = "application/json"
putExtra(Intent.EXTRA_STREAM, uri)
putExtra(Intent.EXTRA_SUBJECT, "Vitruvian Phoenix Backup")
putExtra(Intent.EXTRA_SUBJECT, "Phoenix Phoenix Backup")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: A blind global replace of VitruvianPhoenix turned "Vitruvian Phoenix Backup" into "Phoenix Phoenix Backup", and now your share-sheet subject line reads like a nervous stutter. Find-and-replace without grep is how you ship "Project Phoenix Phoenix Project".

🩹 The Fix:

Suggested change
putExtra(Intent.EXTRA_SUBJECT, "Phoenix Phoenix Backup")
putExtra(Intent.EXTRA_SUBJECT, "Phoenix Backup")

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@@ -43,7 +43,7 @@ private val DarkSlate = Color(0xFF0F172A)
private val DeepNavy = Color(0xFF1E293B)

/**
* Animated splash screen with the Vitruvian Phoenix logo.
* Animated splash screen with the Phoenix Phoenix logo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: Same find-and-replace felony as the backup subject — your splash screen's own KDoc now brags about "the Phoenix Phoenix logo", which is either a meditation on reincarnation or a typo. Either way, doc comments shouldn't develop a stammer.

🩹 The Fix:

Suggested change
* Animated splash screen with the Phoenix Phoenix logo.
* Animated splash screen with the Phoenix logo.

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


companion object {
const val CHANNEL_ID = "vitruvian_workout_channel"
const val CHANNEL_ID = "phoenix_workout_channel"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: Renaming the notification CHANNEL_ID from vitruvian_workout_channel to phoenix_workout_channel looks harmless until you remember Android persists channel-level user preferences (importance override, sound, DND bypass) keyed by that string forever. Existing users keep a ghost channel they can no longer reach from app settings, and their overrides quietly stop applying. This is the kind of change that turns into a "weird, my workout notifications changed" 1-star review six months later.

🩹 The Fix: Keep the existing vitruvian_workout_channel ID and only rename the user-visible channel name. If you really want the ID to match the brand, write a one-shot migration that copies the old channel's overrides onto the new one before deleting the old channel. Don't drop the old ID silently.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@@ -272,7 +272,7 @@ class IosDataBackupManager(
.replace("-", "") + "_" +
KmpUtils.formatTimestamp(KmpUtils.currentTimeMillis(), "HH:mm:ss")
.replace(":", "")
val fileName = "vitruvian_backup_$timestamp.json"
val fileName = "phoenix_backup_$timestamp.json"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: Renaming the backup filename prefix from vitruvian_backup_*.json to phoenix_backup_*.json means every user who emailed themselves a backup, dropped one in iCloud Drive, or pulled one off Files now has files the new app won't recognize as recent imports. They aren't corrupted — they're just abandoned with a different first name. Survives uninstall, dies on rebrand.

🩹 The Fix: Either keep vitruvian_backup_*.json (the file name is internal, the brand is in the JSON contents / UI label), or accept BOTH prefixes when listing/importing backups so existing files still surface. Don't make users redo work the rebrand didn't actually need to invalidate.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

<exclude domain="database" path="vitruvian.db-shm" />
<exclude domain="sharedpref" path="vitruvian_preferences.xml" />
<exclude domain="sharedpref" path="vitruvian_secure_preferences.xml" />
<exclude domain="database" path="phoenix.db" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: The auto-backup exclude paths were renamed from vitruvian.db/vitruvian_preferences.xml to phoenix.db/phoenix_preferences.xml, but the old files still live inside Google Drive backups of every user who installed v1. When they restore on a new device, Android dutifully puts vitruvian.db back — and the new app (looking for phoenix.db) sees an empty database, same data-loss outcome as the local rename. Two paths to the same cliff.

🩹 The Fix: Add the legacy paths alongside the new ones so old backups still skip what they used to skip, and pair this with a first-launch migration that renames any pre-existing vitruvian.db to phoenix.db before SQLDelight opens the driver (the right place for the DB rename, not here).

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

else -> VitruvianModel.Unknown
fun detectModel(deviceName: String): PhoenixModel = when {
deviceName.startsWith("Vee_", ignoreCase = true) -> PhoenixModel.VFormTrainer
deviceName.startsWith("VIT", ignoreCase = true) -> PhoenixModel.TrainerPlus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: fun getDeviceDisplayInfo(deviceName: String): String = "Phoenix Trainer ($deviceName)" — there is no such thing as a "Phoenix Trainer" sold on this planet. The hardware manufacturer did not rebrand; only your app did. Every telemetry payload, every analytics export, every diagnostic report now ships with manufacturer="Phoenix" for a device that was built, branded, and boxed as a Vitruvian. That is a misrepresentation baked into data your own EULA insists you don't misrepresent the manufacturer of.

🩹 The Fix: Keep the actual manufacturer string (Vitruvian) in the equipment/manufacturer fields used for telemetry and protocol logging, and only render "Phoenix" in UI text. If the goal is just a safer display, use "Trainer ($deviceName)" or pull from a localized UI resource. Don't lie into your own logs.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Roast 🔥

Verdict: Request changes | Recommendation: One wrong diagnostic-code label in the new migration coordinator would mis-route every staging-cleanup failure to support.

Overview

Severity Count
🚨 critical 0
⚠️ warning 1
💡 suggestion 1
🤏 nitpick 1
Issue Details (click to expand)
File Line Roast
shared/src/commonMain/.../data/local/DatabaseFileMigration.kt 284 ⚠️ deleteIncompleteStaging() throws RECOVERY_COPY_FAILED when its real action is deleting a stuck staging file from any of three unrelated call sites. Diagnostic codes lie about which path failed.
shared/src/commonMain/.../di/AppModule.kt 16 💡 PersistedFileStartupPrerequisite data object plus three get<>() calls re-implements Koin's own resolution order. The "boundary" is a no-op ceremony.
androidApp/build.gradle.kts 322 🤏 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" is the default — the default of the default.

🏆 Best part: The new DatabaseFileMigrationCoordinator is genuinely well-built for the data-loss-prevention problem it solves — fingerprint-checked copies, atomic move with staleness guards, post-validation cleanup, and a per-process lock that prevents two startups from fighting over the same file. That's the kind of boring, careful plumbing that nobody notices because it never breaks. Whoever wrote this understands that "rename a database file" is a four-step surgical procedure, not a one-liner.

💀 Worst part: Filing a staging-cleanup failure under RECOVERY_COPY_FAILED means the very first time a staging file gets stuck (which the design explicitly anticipates with the dual-existence check), the user sees a diagnostic code that points support at the wrong root cause. The next person to debug "why did the migration fail twice in a row" will waste hours chasing the wrong subsystem.

📊 Overall: A real upgrade path for once — readable, testable, and conservative about data loss — wrapped in enough scaffolding to file a planning application. The Ponytail pass leaves alone what it must (locks, fingerprints, recovery-then-cutover) and trims the rest. One bug in the diagnostic taxonomy, three ceremonies to delete, and this can ship.

Files Reviewed (incremental diff — 31 files, ~4000 LOC)
  • shared/.../data/local/DatabaseFileMigration.kt — new migration coordinator ⚠️
  • shared/.../data/local/AndroidDatabaseFileOperations.kt — new Android file ops
  • shared/.../data/local/IosDatabaseFileOperations.kt — new iOS file ops
  • shared/.../data/preferences/AndroidPreferenceFileMigrator.kt — new preference migrator
  • shared/.../data/local/DriverFactory.android.kt — gating via coordinator ✅ restores DB filename
  • shared/.../data/local/DriverFactory.ios.kt — gating via coordinator ✅ restores DB filename
  • shared/.../di/PlatformModule.android.kt — wires migrators, retains target filenames ✅
  • shared/.../di/AppModule.ktPersistedFileStartupPrerequisite marker 💡
  • shared/.../di/KoinInit.ktrunMigrations() removed ✅
  • shared/.../StartupDependencyResolution.kt — new diagnostic resolution
  • shared/.../App.ktPersistedFileStartupFailureScreen (replaces CrashErrorScreen)
  • shared/.../AndroidAppHost.kt, shared/.../IosAppHost.kt, shared/.../MainViewController.kt — retry gate
  • androidApp/.../MainActivity.kt, androidApp/.../PhoenixApp.kt — migration now gated behind app host
  • androidApp/build.gradle.kts — test runner + instrumentation deps 🤏
  • androidApp/src/main/res/xml/backup_rules.xml, data_extraction_rules.xml — expanded exclusions (legacy + target + staging + recovery)
  • iosApp/.../PhoenixApp.swiftrunMigrations() call removed
  • docs/... design + evidence + harness script + 7 test files (binary-instrumentation tests, coordinator tests, harness tests) — reviewed for shape, not line-by-line
  • binary: iosApp AppIcon1024 variants, new_phoenix_icon.png — not reviewable

Previously open P1s on unchanged files (BLE filter prefix, EULA legal entity name, notification channel ID, iOS backup filename, AndroidBackupRules vitruvian exclude paths, HardwareDetection "Phoenix Trainer" telemetry, Splash/Eula "Phoenix Phoenix") are out of incremental scope and intentionally not re-flagged.

Fix these issues in Kilo Cloud

Previous Review Summaries (2 snapshots, latest commit 2502317)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 2502317)

Verdict: Incremental commit resolves 2 of 4 P1s (DB filename + SharedPreferences filenames on both platforms); 2 P1s and 6 lower-severity items remain open on unchanged files. Recommendation: Address remaining P1s (BLE filter, EULA legal entity) before merge.

Overview

Severity Count
🚨 critical 0
⚠️ warning 2
💡 suggestion 0
🤏 nitpick 0
Resolved since previous review (click to expand)
File Resolution
shared/src/androidMain/.../DriverFactory.android.kt DATABASE_NAME restored from phoenix.dbvitruvian.db. Upgrading users keep their database. ✅
shared/src/iosMain/.../DriverFactory.ios.kt DATABASE_NAME restored to vitruvian.db. ✅
shared/src/androidMain/.../di/PlatformModule.android.kt ENCRYPTED_PREFS_FILE / PLAINTEXT_PREFS_FILE restored to vitruvian_secure_preferences / vitruvian_preferences. Users keep login + settings. ✅
New issue on changed lines
File Line Roast
shared/src/androidHostTest/.../SqlDelightUserProfileRepositoryTest.kt 1460–1462 ⚠️ Hand-typed Int query-identifier constants tied to SQLDelight's internal hash; this PR proved the hazard by having to regenerate them after the rebrand. See inline comment.
Carried-forward, still open on unchanged files (not re-commented per incremental scope)
File Severity Issue
shared/.../data/ble/KableBleConnectionManager.kt ⚠️ P1 BLE filter on line 310 only accepts Vee_ / VIT / Phoenix; legacy firmware still advertises with the Vitruvian prefix and disappears from scan results.
shared/.../presentation/screen/EulaScreen.kt ⚠️ P1 Line 285 says PHOENIX INVESTMENTS PTY LTD (IN LIQUIDATION) — a fabricated entity. The legal-disclaimer purpose requires naming the real (third-party) hardware manufacturer.
androidApp/.../service/WorkoutForegroundService.kt ⚠️ warning CHANNEL_ID = "phoenix_workout_channel" orphans users' per-channel preferences (importance/sound/DND overrides) keyed by the old vitruvian_workout_channel ID.
shared/src/iosMain/.../DataBackupManager.ios.kt ⚠️ warning phoenix_backup_*.json prefix abandons any user-saved backup from a prior build.
androidApp/src/main/res/xml/backup_rules.xml ⚠️ warning Exclude paths renamed to phoenix.db / phoenix_preferences.xml; legacy Google Drive backups won't be excluded consistently, and the on-disk path still needs to match what the driver opens.
androidApp/src/main/res/xml/data_extraction_rules.xml ⚠️ warning Same regression as backup_rules.xml (newly noticed in this pass).
shared/src/commonMain/.../util/HardwareDetection.kt ⚠️ warning getDeviceDisplayInfo reports "Phoenix Trainer" for hardware the OEM still brands as Vitruvian. Telemetry lies about manufacturer.
shared/.../presentation/screen/SplashScreen.kt 🤏 nitpick KDoc says "the Phoenix Phoenix logo".
shared/src/androidMain/.../DataBackupManager.android.kt 🤏 nitpick Share subject "Phoenix Phoenix Backup".

🏆 Best part: The incremental fix is the right shape — keep the on-disk identifier stable, change the brand in UI strings. That's the textbook answer to "rebrand without orphaning users", and the author chose it twice (Android + iOS). Whoever wrote this commit clearly read the previous review and shipped the surgical version, not a refactor.

💀 Worst part: The fix is half the surgery. Same class of regression (rename without backward-compat) is still present in BLE filter, EULA, notification channel, and backup filenames, just on files this commit didn't touch. The author's instinct was right; the coverage was incomplete.

📊 Overall: Two bleeding wounds sutured, four still open. The patient can walk, but they shouldn't run the marathon yet.

Files Reviewed (incremental diff — 7 files changed)
  • shared/src/androidMain/.../data/local/DriverFactory.android.kt — DB filename restored ✅
  • shared/src/iosMain/.../data/local/DriverFactory.ios.kt — DB filename restored ✅
  • shared/src/androidMain/.../di/PlatformModule.android.kt — Prefs filenames restored ✅
  • shared/src/androidHostTest/.../SqlDelightUserProfileRepositoryTest.kt — new magic-number fragility ⚠️
  • iosApp/AppIcon1024.png — binary, not reviewable
  • iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon1024.png — binary, not reviewable
  • new_phoenix_icon.png — removed (binary)

Ponytail Review

Incremental diff is purely a restoration of original identifiers plus a binary icon swap. No new abstractions, no new helpers, no speculative config. The new test-constant fragility is the only deletion candidate: replace the three magic numbers with an sql-prefix match (or import the generated query type) and the private companion object collapses into the surrounding fault-injection block. About 5 lines.

shared/src/androidHostTest/kotlin/com/devil/phoenixproject/data/repository/SqlDelightUserProfileRepositoryTest.kt:L1459: test-shrink the three hand-typed query identifier constants and their private companion object. Replace with an inline sql.startsWith(...) check (the execute method already receives the SQL string). -5 lines.

Ponytail net: -5 lines.

Suggested Minimal Patch

  1. Inline test fix (this diff): drop the magic-number identifiers and the companion object; switch FaultInjectingSqlDriver to match on sql prefix.
  2. Next commits (unchanged files): restore Vitruvian legacy in BLE filter (line 310); restore the real third-party legal entity name in EulaScreen.kt (line 285). Those two close the remaining P1s.

Final Merge Guidance

Do not merge until the BLE filter prefix and EULA legal entity are fixed on unchanged files. After that, the rebrand is genuinely upgrade-safe and Ponytail is clean. The test-constant fragility on the changed line is a recommended cleanup but not merge-blocking — it's the same hazard the code already had.

Fix these issues in Kilo Cloud

Previous review (commit 97235f0)

Verdict: 6 New Issues Found (10 total with existing P1s) | Recommendation: Address before merge

Overview

Severity Count
🚨 critical 0
⚠️ warning 4
💡 suggestion 0
🤏 nitpick 2

Existing bot comments already flagged the 4 P1 data-loss / legal hazards:

  • DriverFactory.android.kt:15 + iOS counterpart — vitruvian.dbphoenix.db orphans every existing user's database
  • PlatformModule.android.kt:36 — SharedPreferences filenames renamed, no migration, users logged out + lose settings
  • KableBleConnectionManager.kt:310 — BLE advertised-name filter dropped Vitruvian prefix
  • EulaScreen.kt:285 — EULA now disclaims affiliation with a company that doesn't exist

My 6 new findings (this pass):

File Line Severity Roast
shared/src/iosMain/.../DataBackupManager.ios.kt 275 ⚠️ warning Backup file prefix rename orphans user-saved backups
androidApp/.../service/WorkoutForegroundService.kt 26 ⚠️ warning Notification channel ID rename orphans user channel settings
androidApp/src/main/res/xml/backup_rules.xml 5 ⚠️ warning Auto-backup exclude paths renamed; old cloud backups inert on restore
shared/src/commonMain/.../HardwareDetection.kt 27 ⚠️ warning Telemetry manufacturer string now lies ("Phoenix") about real hardware
shared/src/commonMain/.../SplashScreen.kt 46 🤏 nitpick KDoc: "Phoenix Phoenix logo" stutter
shared/src/androidMain/.../DataBackupManager.android.kt 460 🤏 nitpick Share subject: "Phoenix Phoenix Backup" stutter

The same find-and-replace sloppiness also stuttered Phoenix Phoenix across ANDROID_INSTALL.md (6×), iOS_INSTALL.md (5×), and .github/ISSUE_TEMPLATE/bug_report.yml — flagged here only because the CFBundleDisplayName is actually Project Phoenix, so user-facing docs that say Settings > Phoenix Phoenix > Bluetooth are simply wrong.


🏆 Best part: The rebrand is honestly thorough — 465 files, generated SQLDelight interface, scheme/xcodeproj, Compose resource package, localization strings, CI scripts, all scrubbed. That's a real piece of work.

💀 Worst part: Four P1 data-loss regressions shipped in a PR whose only checks were grep -ri vitruvian for the word "Vitruvian". The string is gone; the user data it pointed to is still where it always was, just under a name the app no longer knows. A rebrand is exactly the moment backward-compat deserves more than a static analyzer.

📊 Overall: Like pulling off a heist and leaving the getaway driver's keys in the vault — the brand is gone, but the cars (databases, prefs, notification channels, cloud backups) are still parked under the old name. One first-launch migration pass would turn this from a data-loss incident into a true 1.0.0.

Files Reviewed (highlights; 465 changed)

  • shared/src/androidMain/.../DriverFactory.android.kt — DB filename regression (existing P1)
  • shared/src/iosMain/.../DriverFactory.ios.kt — same DB filename regression on iOS (existing P1 mentioned iOS too)
  • shared/src/androidMain/.../PlatformModule.android.kt — prefs filename regression (existing P1)
  • shared/src/commonMain/.../KableBleConnectionManager.kt — BLE prefix removed (existing P1)
  • shared/src/commonMain/.../EulaScreen.kt — legal entity renamed (existing P1)
  • shared/src/androidMain/.../DataBackupManager.android.kt — stutter typo
  • shared/src/commonMain/.../SplashScreen.kt — stutter typo in KDoc
  • androidApp/.../service/WorkoutForegroundService.kt — notification channel ID rename
  • shared/src/iosMain/.../DataBackupManager.ios.kt — backup filename rename
  • androidApp/src/main/res/xml/backup_rules.xml + data_extraction_rules.xml — exclude paths orphaned legacy cloud backups
  • shared/src/commonMain/.../HardwareDetection.kt — telemetry lies about manufacturer

Final Merge Guidance

Do not merge until the 4 existing P1s (DB filename, prefs filename, BLE filter, EULA legal entity) are fixed with a first-launch migration that renames vitruvian.dbphoenix.db on both platforms, migrates the SharedPreferences files, restores the Vitruvian BLE prefix, and corrects the EULA to name the actual manufacturer. The 6 new findings should be fixed in the same patch — they're all the same root cause class (rename without data-migration). After that, Ponytail has no further complaints; the rebrand itself is appropriately thorough.


Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 59.5K · Output: 12.3K · Cached: 793.9K

Review guidance: REVIEW.md from base branch main

- Revert DATABASE_NAME to 'vitruvian.db' (Android + iOS) to preserve existing user data on upgrade
- Revert Android preference filenames to 'vitruvian_secure_preferences' / 'vitruvian_preferences'
- Update SQLDelight fault injection statement IDs to match regenerated PhoenixDatabase identifiers
- Add opaque black background to iOS AppIcon (Apple requires no transparent corners)
- Remove accidentally committed HTML file (failed Google Drive download)

@kilo-code-bot kilo-code-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incremental review on commit 2502317 (fix: address PR #709 review — preserve upgrade paths and fix icon). Two of the four P1s (DB filename, SharedPreferences filenames) are resolved; two P1s (BLE filter, EULA legal entity) remain open on unchanged files. One new test-shrink finding on the changed test constants.

const val SET_ACTIVE_PROFILE_IDENTIFIER = 373_348_112
const val DELETE_PROFILE_IDENTIFIER = 787_673_935
const val CLEAR_RECOVERY_JOURNAL_IDENTIFIER = 1_230_173_044
const val SET_ACTIVE_PROFILE_IDENTIFIER = 415_176_795

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: Three hand-typed magic numbers (415_176_795, -1_674_170_908, 1_812_830_207) are supposed to match SQLDelight's internal Int hash of setActiveProfile:, deleteProfile:, and the recovery-journal clear query. The fact that this PR had to regenerate them — because the original rebrand renamed the database file and quietly re-hashed every query identifier — is the bug report writing itself. If a future engineer reorders a single query, renames the .sq file, or upgrades the SQLDelight Gradle plugin, every InjectedTransitionFailure test silently no-ops and the suite turns green while exercising nothing. This is "passing tests" as a hazard.

🩹 The Fix: Stop using opaque identifier integers. Either match on the sql text (the execute signature already passes it — if (sql.startsWith("UPDATE UserProfile SET isActive")) is human-readable and survives identifier churn), or import the generated query object from PhoenixDatabase and call setActiveProfileQueries directly with a wrapping driver. Either approach removes the silent-breakage class entirely and removes the need to ever re-derive these constants after a database rename.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 250231795b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -73,7 +73,7 @@ class ThemeViewModel(private val settings: Settings) : ViewModel() {

companion object {
const val THEME_MODE_KEY = "theme_mode"
const val THEME_PREFS_FILE = "vitruvian_preferences"
const val THEME_PREFS_FILE = "phoenix_preferences"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep startup reads on the restored preference store

On Android, PlatformModule.android.kt now correctly restores the live Settings store to vitruvian_preferences, but MainActivity and applyPersistedApplicationNightMode use this constant to read the theme and language before Compose starts. They therefore read an empty phoenix_preferences file, so upgraded users get the default system theme and locale during cold-start initialization instead of their saved choices. Fresh evidence after the earlier preference-store finding is this mismatch between the corrected DI store and the still-renamed startup constant.

Useful? React with 👍 / 👎.

Comment on lines +6 to +10
<exclude domain="database" path="phoenix.db" />
<exclude domain="database" path="phoenix.db-journal" />
<exclude domain="database" path="phoenix.db-wal" />
<exclude domain="database" path="phoenix.db-shm" />
<exclude domain="sharedpref" path="phoenix_preferences.xml" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude the filenames the app actually persists

On Android 12+ device-transfer paths that apply these extraction rules, the exclusions no longer match the files opened by this revision: both drivers retain vitruvian.db, and PlatformModule retains vitruvian_preferences.xml and vitruvian_secure_preferences.xml. Consequently the database and sensitive preference stores are eligible for transfer despite this file's explicit exclusion intent; the API 23–30 backup rules have the same mismatch and should likewise retain the legacy paths.

Useful? React with 👍 / 👎.


companion object {
const val CHANNEL_ID = "vitruvian_workout_channel"
const val CHANNEL_ID = "phoenix_workout_channel"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the installed notification channel ID

For Android users upgrading an existing installation, changing the channel ID makes createNotificationChannel() create a separate channel rather than renaming the existing workout channel. Any user-selected importance, sound, vibration, or disabled state attached to vitruvian_workout_channel is lost, and users who disabled that channel can begin receiving workout notifications again; keep the stable ID and change only the channel's display name.

Useful? React with 👍 / 👎.

"\$migrationReady = \$false",
"do {",
" \$migrationLine = & \$adb shell run-as \$package grep -F profile_preferences_legacy_migration_complete_v1 shared_prefs/vitruvian_preferences.xml 2>\$null",
" \$migrationLine = & \$adb shell run-as \$package grep -F profile_preferences_legacy_migration_complete_v1 shared_prefs/phoenix_preferences.xml 2>\$null",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point the schema fixture check at the retained stores

When the documented schema-42 upgrade workflow runs against this revision, this polling command checks phoenix_preferences.xml, while the application still stores the migration marker in vitruvian_preferences.xml; the workflow therefore times out even after a successful migration. The same test later requires databases/phoenix.db, so it currently enforces a guide that cannot inspect the database opened by DriverFactory rather than validating the real upgrade path.

Useful? React with 👍 / 👎.

@9thLevelSoftware
9thLevelSoftware deleted the rebrand/remove-vitruvian-references branch August 21, 2026 17:51
operations.delete(DatabaseArtifact.STAGING)
} catch (failure: Throwable) {
throw DatabaseFileMigrationException(
DatabaseMigrationFailureCode.RECOVERY_COPY_FAILED,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: This deleteIncompleteStaging() helper throws RECOVERY_COPY_FAILED when the action is "delete a stuck staging file from a completely different codepath." Three call sites funnel unrelated failures (post-migration cleanup, recovery reconstruction, plain target preparation) into a diagnostic code that screams "recovery copy is busted." When the user sees DB_RECOVERY_COPY_FAILED on the failure screen, support will hunt for a bad recovery copy that has nothing to do with the actual problem. The naming lies harder than a politician before election day.

🩹 The Fix:

Suggested change
DatabaseMigrationFailureCode.RECOVERY_COPY_FAILED,
DatabaseMigrationFailureCode.STAGING_CLEANUP_FAILED,

…and add a matching STAGING_CLEANUP_FAILED entry to DatabaseMigrationFailureCode (line ~36). The other call sites that genuinely mean "recovery copy failed" are correct.

📏 Severity: warning


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// Resolving this singleton is the persisted-file startup boundary. The
// values are intentionally resolved in this order so no feature dependency
// can observe a partially prepared database or preference store.
single {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: PersistedFileStartupPrerequisite is a data object whose entire purpose is "wait for these singletons to resolve." This is the dependency-injection equivalent of installing a revolving door in a hallway that's already a one-way corridor — PhoenixDatabase and both Settings are already single { ... }, so the moment ANY consumer asks Koin for them, Koin resolves them in the right order anyway. The "boundary" ceremony adds a token class plus three get<>() lines that just re-invoke what Koin already does for free.

🩹 The Fix: Replace the whole block with single<Unit> { Unit } named "persistedFileStartupPrerequisite", or — more idiomatically — drop the marker entirely and have AndroidAppHost / IosAppHost resolve a single composed single { PersistedFileReadiness(get(), get(), get()) } directly. Then data object PersistedFileStartupPrerequisite and its content delete.

📏 Severity: suggestion


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

applicationId = "com.devil.phoenixproject"
minSdk = 26
targetSdk = 37
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤏 The Roast: testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" is the default. Configuring the default to be the default is a tattoo that reads "I am here."

🩹 The Fix:

Suggested change
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

📏 Severity: nitpick


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b98aa9f89b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -73,7 +73,7 @@ class ThemeViewModel(private val settings: Settings) : ViewModel() {

companion object {
const val THEME_MODE_KEY = "theme_mode"
const val THEME_PREFS_FILE = "vitruvian_preferences"
const val THEME_PREFS_FILE = "phoenix_preferences"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Migrate preferences before startup theme reads

On the first Android launch after upgrading, PhoenixApp.attachBaseContext() and MainActivity read this newly named store before AndroidAppHost resolves PersistedFileStartupPrerequisite, which is what actually runs AndroidPreferenceFileMigrator; they therefore see an empty store and apply the system theme instead of the saved explicit mode. The fresh evidence beyond the earlier store-mismatch comment is that the store is now migrated correctly but only after these cold-start reads, leaving the application night-mode qualifier incorrect until the user changes the setting or restarts.

Useful? React with 👍 / 👎.

Comment on lines +26 to 29
private val PHOENIX_DEVICE = Device(
manufacturer = "Phoenix",
model = "Trainer",
type = Device.TYPE_UNKNOWN,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the hardware manufacturer in Health Connect

When Android users export newly completed workouts, this value is written into Metadata.activelyRecorded for both exercise and calorie records, so Health Connect now records a nonexistent Phoenix device manufacturer. Phoenix is the app's new brand, not the manufacturer of the Trainer hardware; keep the actual hardware manufacturer here or omit the device attribution so exported health records retain accurate provenance.

Useful? React with 👍 / 👎.

9thLevelSoftware added a commit that referenced this pull request Aug 22, 2026
…#711)

* refactor: remove all Vitruvian references — legal rebrand to Phoenix

- Rename iOS project: VitruvianPhoenix → PhoenixApp (directory, xcodeproj, scheme, swift, entitlements)
- Rename Android: VitruvianApp → PhoenixApp, VitruvianTheme → PhoenixTheme
- Rename SQLDelight: VitruvianDatabase.sq → PhoenixDatabase.sq
- Update settings.gradle.kts root project name: ProjectPhoenix
- Update AndroidManifest: Theme.PhoenixProject, .PhoenixApp
- Replace all icon assets with new Phoenix logo
- Scrub all Vitruvian/vitphoe references from source, tests, docs, CI workflows
- Verified: grep -ri vitruvian = 0, grep -ri vitphoe = 0
- Builds verified: SQLDelight interface gen, Kotlin Android/iOS, Xcode device build

* fix: address PR #709 review — preserve upgrade paths and fix icon

- Revert DATABASE_NAME to 'vitruvian.db' (Android + iOS) to preserve existing user data on upgrade
- Revert Android preference filenames to 'vitruvian_secure_preferences' / 'vitruvian_preferences'
- Update SQLDelight fault injection statement IDs to match regenerated PhoenixDatabase identifiers
- Add opaque black background to iOS AppIcon (Apple requires no transparent corners)
- Remove accidentally committed HTML file (failed Google Drive download)

* test: specify persisted database filename migration

* feat(android): migrate persisted Phoenix database filename safely

* feat(ios): migrate persisted Phoenix database filename safely

* feat(android): migrate legacy preference filenames

* feat: block startup until persisted-file migration is safe

* test: verify Phoenix persisted-file upgrade path

* docs: record PR 709 migration evidence

---------

Co-authored-by: Devil <dasblueyeddevil@gmail.com>
Co-authored-by: Codex <codex@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants