Skip to content

release: 0.0.103 — memory/Load-Anyway fix + on-device TTS/vision/tools hardening + promote tooling - #506

Merged
alichherawalla merged 38 commits into
mainfrom
release/0.0.103
Jul 9, 2026
Merged

alichherawalla merged 38 commits into
mainfrom
release/0.0.103

Conversation

@alichherawalla

@alichherawalla alichherawalla commented Jul 9, 2026 •

Copy link
Copy Markdown
Collaborator

0.0.103 — memory/Load-Anyway fix + release tooling + on-device hardening

Consolidated release branch. Every change is red-first tested; on-device items are
called out as UNVERIFIED (TestFlight) where a device run is still owed.

Core fixes

  • Load Anyway memory gate — makeRoomFor now evicts, then re-measures real free RAM against the override survival floor (was a predictive pre-eviction estimate that crediting 0 for clean evictions → false refusals). + an end-to-end override-chain integration test.
  • Vision (mmproj) dropped on iOS retry — persist metadataJson on the download store row so a same-session retry/reconcile re-fetches the vision projector instead of re-issuing text-only.
  • OD3 — chat vs Home selector parity — chat model-select now routes through the MEASURED residency loader (drops the divergent predictive checkMemoryForModel pre-check); refusal comes from OverridableMemoryError → the shared "Load Anyway" affordance. A model loadable from Home is now loadable in chat.
  • OD7 — reasoning toggle detection — surface the thinking toggle for reasoning models via the chat-template signal (not a name regex), single-source with the runtime parser.
  • OD14 — cross-tool chain-of-thought — keep the pre-tool-call reasoning on the intermediate tool-call message so BOTH rounds of thinking persist in the transcript (was vanishing when the tool fired).
  • wednesday.is — "made with ♥" link points at plain https://wednesday.is; collapsed a DRY break (screen was hardcoding the URL).

Pro (audio/TTS) fixes — pro fix/tts-download-flag-reconcile

  • OD-TTS1 — play does nothing / "not downloaded" though on disk — reconcile the engine's volatile completeness signal to the durable persisted modelDownloaded flag, on BOTH checkDownloadStatus AND the speak() play gate. Extracted download actions to ttsDownloadActions.ts (no eslint-disable).
  • OD9 — TTS spoke tool-call content aloud — strip complete control-token blocks + withhold unclosed openers BEFORE sentence segmentation (was stripping per-sentence fragments, leaking split tool-call JSON).
  • OD8 — voice-mode thinking now streams live (was appearing all at once at completion).

Release tooling

  • promote.sh + fastlane promote lanes + scripts/lib/version.js (single-source version math) — promote a tested beta to production without rebuild.
  • ios-device.sh — build against a generic iOS destination (fixes the "developer disk image could not be mounted" build stall).

Test-infra fix (surfaced by the above)

  • Fixed a jest mock-hoisting/TDZ bug in ChatScreen.test.tsx — the jest.mock factory referenced a const mock before init → loadTextModel: undefined, which had been masking stale assertions. Delegating arrows + typed mocks; 155/155.

Gates (local)

  • tsc clean · eslint 0 errors · production bundle builds · full related-test suite green (4798 pass on push).

NOT yet verified — TestFlight (Release build) is the on-device gate

On-device confirmation is owed for: Load-Anyway on a fittable model, vision-retry, TTS play/stream, voice thinking, cross-tool CoT. Deferred device/perf items logged in docs/GAPS_BACKLOG.md (OD10 mid-speak stop, OD11 TTS co-residency, OD13 reasoning-format mismatch, threads=1 default).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added automated Android/iOS release promotion lanes to move already-tested builds to production without rebuilding.
  • Bug Fixes
    • Android UAT now embeds the plain next production version in binaries (beta suffix remains for tags/releases).
    • Improved “Load Anyway” memory-override/survival-floor handling.
    • Strengthened mmproj repair/retry recovery after iOS restarts.
  • UI/Content
    • Onboarding “made with” now uses the centralized Wednesday link and shows the Wednesday logo.
    • Vision repair downloads now show determinate progress with byte counts.
  • Tests
    • Expanded coverage for version/tag parsing and “Load Anyway” measured-loader override flows.

alichherawalla and others added 5 commits July 9, 2026 11:59
The override survival floor refused loads on a PREDICTION computed before
eviction (availMB + freedByEvict - incomingDirty), crediting 0 for evicting a
clean/mmap model - so it under-counted the RAM iOS reclaims on unload and
refused big models the device can actually hold. That is why "Load Anyway"
still failed and why "load a small model, wait, then load the big one" worked
(users defeating the stale estimate). Regression from 8034754 (2026-07-06).

Fix: under override, evict first, then refreshMemoryInfo() + re-read real free
RAM, and apply the 1200MB survival floor to the true post-eviction number. The
physics guard is preserved (still refuses if genuinely below floor after freeing
everything) - it just stops refusing loads that would succeed.

Test models real iOS reclaim (free RAM rises after the unload) so it fails on
the old predict-then-refuse code and passes on evict-then-measure - the static
mock the old tests used structurally could not catch this timing bug. 70/70 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fix Android versionName leak

The pipeline could CUT a beta (uat.sh) but had no way to BLESS a good cut to
production, and Android baked "-beta.N" into versionName — frozen into the AAB,
user-visible, blocking Play's promote-as-is (internal→production, same bytes).

- uat.sh: Android versionName = the plain production version (no -beta suffix),
  matching iOS's MARKETING_VERSION. The -beta.N label now lives only in the git
  tag / GitHub prerelease / store notes, never in the shipped binary. Play orders
  by versionCode (the timestamp), not versionName, so this is safe. Stale header
  comment corrected (the "-beta versionName" was a misconception, not a Play rule).
- scripts/promote.sh <beta-tag>: reuses the EXACT tested bytes — bumps+tags the
  repo, promotes the Play internal build to production (draft), attaches the
  TestFlight build to a new App Store version, and cuts a clean vX.Y.Z GitHub
  release from the beta's APK. No rebuild anywhere. Asserts the beta commit is on
  main first. Two final gates stay MANUAL by design (Play rollout %, ASC submit).
- fastlane: android promote (track_promote_to, no re-upload) + ios promote
  (skip_binary_upload, attach existing build) lanes.
- version math extracted to scripts/lib/version.js (single source of truth for
  cut + promote), typed via version.d.ts, 26 tests covering every derivation +
  rejection path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds shared release-promotion tooling, revises model-loading and residency override handling, updates onboarding and audio/reasoning flows, and extends mmproj repair/download metadata plus retry and progress behavior.

Changes

Release Promotion Tooling

Layer / File(s) Summary
Version and beta-tag helpers
scripts/lib/version.js, scripts/lib/version.d.ts, __tests__/unit/scripts/version.test.ts
Implements semantic version, beta-tag, and build-annotation helpers, a CLI entrypoint, TypeScript declarations, and unit tests.
Fastlane promote lanes
fastlane/Fastfile
Adds Android and iOS promote lanes that reuse existing builds for production and App Store release flows.
Promotion script
scripts/promote.sh
Derives target versions from beta tags, verifies ancestry on origin/main, bumps and tags the repo, runs fastlane promotion, and creates a GitHub release.
UAT and iOS device tooling
scripts/uat.sh, scripts/ios-device.sh
Updates beta-build version handling and changes iOS device detection and signing/build invocation in the device script.

Model Residency and Load Override Flow

Layer / File(s) Summary
Residency implementation
src/services/modelResidency/index.ts
Changes override survival-floor handling in makeRoomFor to re-read memory after eviction and compute post-load free RAM from real available memory; related formatting changes follow the same paths.
Chat model loading
src/screens/ChatScreen/useChatModelActions.ts
Routes chat model selection through the shared override loader and removes predictive memory gating.
Residency and load-override tests
__tests__/unit/services/modelResidency.test.ts, __tests__/integration/models/loadAnywayOverrideChain.test.ts, __tests__/integration/models/chatHomeSelectorParity.test.ts, __tests__/rntl/screens/ChatScreen.test.tsx, __tests__/unit/hooks/useChatModelActions.test.ts
Updates residency unit tests and adds integration and screen coverage for the measured-loader and Load Anyway flows.

Reasoning and Streaming Support

Layer / File(s) Summary
Reasoning template detection
src/utils/messageContent.ts, src/services/llmHelpers.ts, src/stores/remoteModelCapabilities.ts, __tests__/unit/utils/messageContent.test.ts, __tests__/unit/services/llmHelpers.test.ts
Adds reasoning-marker detection and updates native-thinking capability checks and tests.
Reasoning transcript retention
src/services/generationToolLoop.ts, __tests__/unit/services/generationToolLoop.test.ts
Persists reasoning content on tool-call assistant messages and verifies it in the tool loop tests.
Audio and thinking stream tests
__tests__/unit/audio/streamingSpeech.test.ts, __tests__/pro/audio/ui/MessageAudioModeThinkingStream.test.tsx
Adds coverage for incremental voice thinking output and tool-call content suppression during streaming.

Mmproj Repair Download Flow

Layer / File(s) Summary
Download row metadata
src/services/modelManager/download.ts, __tests__/unit/services/parallelMmproj.test.ts
Writes metadataJson into the download store row and adds tests that assert the stored mmproj sidecar URL is present.
Text download retry
src/screens/DownloadManagerScreen/items.tsx, __tests__/unit/services/textDownloadProvider.test.ts
Reconstructs mmproj retry arguments from stored metadata and renders repair progress for completed downloads.
Repair vision progress
src/services/modelManager/download.ts, src/services/modelManager/index.ts, src/screens/DownloadManagerScreen/items.tsx, __tests__/unit/services/repairVisionProgress.test.ts, __tests__/rntl/components/CompletedDownloadCardRepair.test.tsx, __tests__/unit/stores/ttsStore.test.ts
Adds the transient mmproj repair download helper, delegates model repair orchestration to it, and renders determinate repair progress with matching tests.

Onboarding Wednesday Link Update

Layer / File(s) Summary
Wednesday URL and slide constants
src/constants/index.ts
Changes the Wednesday URL constant and reformats onboarding slide text and nearby exported constants.
Onboarding screen footer and layout
src/screens/OnboardingScreen.tsx
Uses the Wednesday URL constant in the screen, adds the Wednesday logo row styling and image, and preserves the onboarding and LAN discovery flow.
Onboarding screen tests
__tests__/rntl/screens/OnboardingScreen.test.tsx
Updates onboarding screen tests to use the Wednesday URL constant, adjust mocked slide data, and reformat render and LAN-discovery setup.

Repository Metadata and Docs

Layer / File(s) Summary
Repository notes
pro, docs/GAPS_BACKLOG.md
Changes the pro submodule reference and appends backlog documentation for the on-device test session and follow-up findings.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the main changes, but it omits required template sections like Type of Change, screenshots, checklist, related issues, and notes. Add the missing template sections and complete the checklist, screenshots for UI changes, related issues, and any additional notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main changes: release tooling, memory/load-anyway fix, and on-device hardening.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/0.0.103

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Release 0.0.103: fix override RAM check + add promote-to-production tooling

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Fix “Load Anyway” to re-check real free RAM after evicting residents.
• Add promote.sh + fastlane lanes to promote tested beta bytes without rebuild.
• Centralize version/tag derivation and align Android versionName with production.
Diagram

graph TD
  UI["UI: Load Anyway"] --> MRM["ModelResidencyManager"] --> HW["HardwareService"]
  UAT["scripts/uat.sh"] --> VER["scripts/lib/version.js"] --> PRO["scripts/promote.sh"] --> FL["fastlane promote"] --> CH["Play / ASC / GitHub"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Predictive survival-floor accounting (no refresh)
  • ➕ No extra refresh/read call after eviction (potentially faster).
  • ➕ Keeps logic purely arithmetic and easier to unit-test deterministically.
  • ➖ Still an estimate: cannot capture iOS reclaim timing/behavior accurately.
  • ➖ Risk of repeating the same class of false refusals under mmap/clean unloads.
2. CI-driven promotion workflow (GitHub Actions)
  • ➕ More reproducible, fewer local tool dependencies (gh/node/bundle).
  • ➕ Centralized secrets + audit trail for promotions.
  • ➖ More upfront infra and secret management complexity.
  • ➖ Harder to keep “two manual gates” ergonomics without over-automation.

Recommendation: The chosen approach for the memory fix (evict first, then refresh/re-read real free RAM before applying the survival floor) is the most robust because it measures the post-unload state instead of guessing reclaim. For release promotion, the lightweight script + fastlane promote-as-is lanes are appropriate provided the preflight guards remain strict (tag-on-main check, dirty working tree check) and the manual console gates stay explicit.

Files changed (8) +1065 / -169

Enhancement (3) +148 / -0
FastfileAdd Android/iOS promote lanes that reuse existing builds +41/-0

Add Android/iOS promote lanes that reuse existing builds

• Adds an Android lane to promote an internal-track build to production as a draft without uploading new binaries. Adds an iOS lane to attach an existing processed TestFlight build to a new App Store version without rebuilding or re-uploading, leaving submission manual.

fastlane/Fastfile

version.d.tsAdd TypeScript declarations for version.js +16/-0

Add TypeScript declarations for version.js

• Defines the types for the CommonJS version helpers so Jest/TypeScript consumers can import the module without enabling allowJs. Documents parsed version and beta-tag shapes.

scripts/lib/version.d.ts

version.jsIntroduce shared version/tag derivation utilities with CLI +91/-0

Introduce shared version/tag derivation utilities with CLI

• Adds a single-source-of-truth module for parsing versions, computing next patch, parsing beta tags, deriving target production versions, and building beta tags. Exposes a small CLI used by shell scripts and provides strict validation with clear errors.

scripts/lib/version.js

Bug fix (1) +123 / -41
index.tsFix override survival floor to use post-eviction real free RAM +123/-41

Fix override survival floor to use post-eviction real free RAM

• Moves the override survival-floor check to after actual evictions, then refreshes and re-reads available memory to compute true post-load headroom (minus incoming dirty footprint). Preserves the physics guard (still refuses below floor) while avoiding false refusals caused by pre-eviction snapshots and clean/mmap unload reclaim; includes minor logging/formatting refactors.

src/services/modelResidency/index.ts

Tests (2) +654 / -120
version.test.tsAdd unit tests for shared version math module +95/-0

Add unit tests for shared version math module

• Introduces comprehensive tests for parsing semver strings, beta tags, patch increments, and rejection paths. Ensures cut and promote scripts derive identical versions/tags and fail loudly on malformed inputs.

tests/unit/scripts/version.test.ts

modelResidency.test.tsAdd regression coverage for override RAM re-measure after eviction +559/-120

Add regression coverage for override RAM re-measure after eviction

• Adds a regression test modeling iOS-like RAM reclaim where available memory increases only after unload, verifying override succeeds when post-eviction headroom is sufficient. Also adds a companion test that the survival floor still blocks truly unsafe loads; includes minor formatting/clarity updates across existing tests.

tests/unit/services/modelResidency.test.ts

Other (2) +140 / -8
promote.shAdd promote script to bless a tested beta to production without rebuild +123/-0

Add promote script to bless a tested beta to production without rebuild

• Adds an end-to-end promotion script that derives the target version from a beta tag, verifies the beta commit is on main, bumps/tags the repo, and promotes Android/iOS/GitHub releases using the already-tested artifacts. Keeps rollout/submit steps manual and supports per-channel flags and idempotent re-runs.

scripts/promote.sh

uat.shAlign Android versionName with production version for promote-as-is +17/-8

Align Android versionName with production version for promote-as-is

• Updates release notes/comments and changes Android versionName to the plain target version (no -beta suffix) to avoid user-visible suffix leakage and to enable Play promote-as-is behavior. Clarifies that stores order builds by versionCode/build number, not versionName.

scripts/uat.sh

@qodo-code-review

qodo-code-review Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 36 rules

Grey Divider


Action required

1. promote.sh uses em dashes ✓ Resolved 📘 Rule violation ✧ Quality
Description
New user-visible output strings in scripts/promote.sh, Fastlane success messages, and
human-readable logs in ModelResidencyManager.makeRoomFor include Unicode em dashes (—). This
violates Compliance requirement PR Compliance ID 1570610 to use ASCII hyphens (-) instead of em
dashes in user-facing text (including CLI/tooling output and diagnostic logs).
Code

scripts/promote.sh[68]

+  warn "Tag v${TARGET_VERSION} already exists — skipping the version bump/tag (idempotent re-run)."
Relevance

⭐⭐⭐ High

Em-dash removals previously accepted for compliance/style in docs (PR503, PR468).

PR-#503
PR-#468

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1570610 forbids em dashes (—) in user-facing text, and the cited changes
introduce multiple such strings across the codebase: scripts/promote.sh includes messages like
already exists — skipping, Play done — ..., and App Store done — ...; Fastlane’s
UI.success(...) messages at fastlane/Fastfile[246-246] and fastlane/Fastfile[377-377] include
draft — confirm rollout in console and not submitted — submit in App Store Connect; and
src/services/modelResidency/index.ts adds log lines such as OVERRIDE — forcing load ... and
REFUSED even under override — real post-evict free ..., each containing the Unicode em dash
character.

Rule 1570610: Disallow em dashes in user-facing text; use hyphens instead
scripts/promote.sh[68-68]
scripts/promote.sh[84-84]
scripts/promote.sh[91-91]
scripts/promote.sh[109-109]
scripts/promote.sh[114-114]
fastlane/Fastfile[377-377]
fastlane/Fastfile[246-246]
src/services/modelResidency/index.ts[370-370]
src/services/modelResidency/index.ts[400-400]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-visible strings across CLI tooling (`scripts/promote.sh`), Fastlane success messages, and human-readable diagnostic logs contain Unicode em dashes (`—`), which are disallowed by compliance and must be replaced with ASCII hyphens (`-`).

## Issue Context
PR Compliance ID 1570610 forbids em dashes (`—`) in user-facing text; this applies not only to end-user UI strings but also to CLI/tooling output and human-readable logs (including diagnostic logs). Several newly added/updated messages currently use `—` (e.g., `already exists — skipping`, `Play done — ...`, `App Store done — ...`, `draft — confirm rollout in console`, `not submitted — submit in App Store Connect`, `OVERRIDE — forcing load ...`, `REFUSED even under override — real post-evict free ...`).

## Fix Focus Areas
- scripts/promote.sh[68-68]
- scripts/promote.sh[84-84]
- scripts/promote.sh[91-91]
- scripts/promote.sh[109-109]
- scripts/promote.sh[114-114]
- fastlane/Fastfile[246-246]
- fastlane/Fastfile[377-377]
- src/services/modelResidency/index.ts[370-370]
- src/services/modelResidency/index.ts[400-400]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Release tag may mispoint ✓ Resolved 🐞 Bug ☼ Reliability
Description
scripts/promote.sh verifies the beta tag’s commit is *an ancestor* of origin/main but never
checks out that commit (or even main) before committing/tagging v<TARGET_VERSION> and pushing.
As a result, the production tag/release can point at unrelated commits depending on the operator’s
current branch/HEAD.
Code

scripts/promote.sh[R57-78]

+# ── assert we are promoting what was tested: the beta tag's commit is on main ──
+git fetch --tags --quiet origin || error "Could not fetch tags/refs"
+git rev-parse -q --verify "refs/tags/${BETA_TAG}" >/dev/null || error "Tag ${BETA_TAG} not found locally (git fetch --tags)"
+BETA_SHA=$(git rev-list -n1 "$BETA_TAG")
+if ! git merge-base --is-ancestor "$BETA_SHA" origin/main; then
+  error "The commit for ${BETA_TAG} (${BETA_SHA:0:8}) is NOT on origin/main. Refusing to promote a build that isn't on main."
+fi
+info "Verified ${BETA_TAG} (${BETA_SHA:0:8}) is on origin/main."
+
+# ── 1. reconcile the repo: bump package.json to the target, commit, tag, push ──
+if git rev-parse -q --verify "refs/tags/v${TARGET_VERSION}" >/dev/null; then
+  warn "Tag v${TARGET_VERSION} already exists — skipping the version bump/tag (idempotent re-run)."
+else
+  info "Bumping package.json → ${TARGET_VERSION}, tagging v${TARGET_VERSION}"
+  node -e "const fs=require('fs'),p=require('./package.json');p.version='${TARGET_VERSION}';fs.writeFileSync('./package.json',JSON.stringify(p,null,2)+'\n')"
+  # Keep package-lock's top-level version in step if present (no full reinstall).
+  [ -f package-lock.json ] && node -e "const fs=require('fs'),l=require('./package-lock.json');l.version='${TARGET_VERSION}';if(l.packages&&l.packages['']){l.packages[''].version='${TARGET_VERSION}';}fs.writeFileSync('./package-lock.json',JSON.stringify(l,null,2)+'\n')" || true
+  git add package.json package-lock.json 2>/dev/null || git add package.json
+  git commit -m "chore(release): ${TARGET_VERSION}"
+  git tag "v${TARGET_VERSION}"
+  git push origin HEAD --tags
+fi
Relevance

⭐⭐ Medium

No prior precedent on ensuring checkout before tagging; team does accept script reliability fixes
(PR178).

PR-#178

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The script computes BETA_SHA and checks it’s on origin/main, but then bumps package.json,
tags, and pushes HEAD without checking out BETA_SHA/main or verifying HEAD matches the
intended commit.

scripts/promote.sh[57-65]
scripts/promote.sh[66-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`promote.sh` uses the beta tag SHA only for an ancestry check, then creates a release commit/tag from the current working copy `HEAD`. This can produce a `vX.Y.Z` tag that doesn’t correspond to the tested beta’s code lineage tip (or even to `main`), undermining traceability and “tested bytes” guarantees.

## Issue Context
Even with the ancestry check, `HEAD` may be:
- ahead of the beta tag on `main` (extra commits not in the tested build)
- on a different local branch that happens to contain the beta commit

## Fix Focus Areas
- scripts/promote.sh[57-78]

## Implementation direction
- After fetching, explicitly move to a known base before bumping/tagging, e.g.:
 - `git fetch origin main --tags`
 - `git checkout -B promote-base "$BETA_SHA"` (or `origin/main` if you require the beta to be the tip)
 - (optional) assert `git rev-parse HEAD == "$BETA_SHA"` if you want tags to map exactly
- Ensure pushes go to the intended branch:
 - push the bump commit to `origin/main` explicitly (`git push origin HEAD:main --tags`) or refuse if not on `main`.
- Add a preflight assertion that the working branch is correct (e.g., `main`) and up to date, or run in a detached checkout from the beta SHA to eliminate operator state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Promotion not beta-pinned ✓ Resolved 🐞 Bug ≡ Correctness
Description
scripts/promote.sh takes a specific beta tag but doesn’t pass any build identifier to the
Play/TestFlight promote steps, so the promoted store artifact can be the latest internal/TestFlight
build rather than the requested beta tag’s build. This can ship the wrong (potentially untested)
binary when multiple betas exist for the same target version.
Code

scripts/promote.sh[R80-92]

+# ── 2. Play: promote the existing internal build to production (draft) ──
+if [ "$DO_ANDROID" = 1 ]; then
+  info "Play: promoting the tested internal build to production (draft)…"
+  bundle exec fastlane android promote
+  info "Play done — open Play Console → Production and confirm the rollout %."
+fi
+
+# ── 3. App Store: attach the existing TestFlight build to a new version ──
+if [ "$DO_IOS" = 1 ]; then
+  info "App Store: attaching the tested TestFlight build to version ${TARGET_VERSION}…"
+  bundle exec fastlane ios promote app_version:"${TARGET_VERSION}"
+  info "App Store done — open App Store Connect and hit Submit for Review."
+fi
Relevance

⭐⭐ Medium

No historical evidence on pinning promote to specific build_number/versionCode; promote lanes just
introduced (PR503).

PR-#503

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The promote script only derives TARGET_VERSION from the beta tag, then runs store promotions
without passing the beta’s build number. The iOS fastlane lane explicitly states that omitting
build_number selects the latest processed build, which can differ from the beta tag requested.

scripts/promote.sh[52-55]
scripts/promote.sh[80-92]
fastlane/Fastfile[228-247]
fastlane/Fastfile[360-370]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scripts/promote.sh` accepts a beta tag (e.g. `v0.0.103-beta.1`) but the Play/TestFlight promotion commands are not tied to that tag’s specific build. iOS promotion explicitly documents that omitting `build_number` promotes the “latest processed” build, which may not be the requested beta.

## Issue Context
The release process goal is “promote-as-is” (reuse the exact tested bytes). If multiple betas for the same target version exist, promoting “latest” can drift from the user-specified tag.

## Fix Focus Areas
- scripts/promote.sh[80-92]
- fastlane/Fastfile[228-247]
- fastlane/Fastfile[360-376]

## Implementation direction
- Change `scripts/promote.sh` to either:
 - **require** an explicit build identifier (Android versionCode + iOS build_number) as inputs, or
 - **derive** them from a single source of truth attached during `uat.sh` (e.g., annotate the beta git tag with the build number(s), or store them in the GitHub prerelease metadata and query via `gh release view`).
- Thread those identifiers into fastlane:
 - iOS: call `fastlane ios promote app_version:"${TARGET_VERSION}" build_number:"${IOS_BUILD_NUMBER}"`.
 - Android: update the promote lane to promote a **specific** internal-track version code (if supported by your fastlane action), or fail fast if it cannot be pinned.
- Add a safety check: validate the promoted build’s version/build number matches the beta tag’s recorded build before promoting.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread scripts/promote.sh Outdated
Comment thread scripts/promote.sh Outdated
Comment thread scripts/promote.sh

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fastlane/Fastfile`:
- Around line 227-247: The promote lane currently promotes the entire internal
track without pinning a specific build, so it may ship a newer internal artifact
than the one tested. Update the Fastfile’s :promote lane to accept and use the
tested build’s version_code, and pass that value into upload_to_play_store
alongside track_promote_to so the promotion targets the exact build. Wire the
version_code through from the existing build-capture flow (for example the
cut-time metadata used by uat.sh/beta tagging) and keep the current draft
release behavior intact.

In `@scripts/promote.sh`:
- Around line 105-107: The NOTES_FILE assignment in promote.sh is using a
different path than the one mktemp actually creates, so the script writes and
cleans up the wrong file. Update the NOTES_FILE setup and the release-notes
write path so they reference the same actual temporary file created by mktemp,
and ensure the later rm cleanup targets that exact file.
- Around line 67-78: The idempotency check in promote.sh only looks for the
local v${TARGET_VERSION} tag, so a failed git push can leave the commit/tag
unpushed while later reruns skip the release steps. Update the logic around the
tag/commit block to verify the tag exists on origin as well, or make the flow
always attempt to push the existing commit/tag before continuing, using the same
promote.sh tag creation and git push path. Ensure the check distinguishes
“already released remotely” from “tag exists only locally” so reruns don’t
bypass the push.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bbc2c64-8396-4978-9b15-8dff7b7dfeeb

📥 Commits

Reviewing files that changed from the base of the PR and between 15ea3c4 and de25231.

📒 Files selected for processing (8)
  • __tests__/unit/scripts/version.test.ts
  • __tests__/unit/services/modelResidency.test.ts
  • fastlane/Fastfile
  • scripts/lib/version.d.ts
  • scripts/lib/version.js
  • scripts/promote.sh
  • scripts/uat.sh
  • src/services/modelResidency/index.ts

Comment thread fastlane/Fastfile
Comment thread scripts/promote.sh Outdated
Comment thread scripts/promote.sh Outdated
Drop the /hire-ai-native-mobile-squad path (and UTM params) so the
"made with ♥ by Wednesday" link just goes to https://wednesday.is.

Also collapses the DRY break: OnboardingScreen hardcoded its own copy
of the URL instead of the WEDNESDAY_URL constant — now imports and uses
the single source of truth. Test asserts against the imported constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@__tests__/rntl/screens/OnboardingScreen.test.tsx`:
- Around line 184-214: The onboarding scroll test in OnboardingScreen.test.tsx
no longer asserts the real state change, so restore coverage for the
`onMomentumScrollEnd` -> `setCurrentIndex` flow in `OnboardingScreen`. Use the
existing render setup and the `onboarding-screen` testID to trigger a scroll
event, then verify the expected current slide/index change with a meaningful
expectation instead of just grabbing the FlatList child reference. Keep the
existing `show onboarding-skip` and `show onboarding-next` checks separate, and
make sure this test would fail if the scroll/index update logic were broken.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c0e3742-6fff-4284-a773-d46090ea06c1

📥 Commits

Reviewing files that changed from the base of the PR and between de25231 and e516e26.

📒 Files selected for processing (3)
  • __tests__/rntl/screens/OnboardingScreen.test.tsx
  • src/constants/index.ts
  • src/screens/OnboardingScreen.tsx

Comment thread __tests__/rntl/screens/OnboardingScreen.test.tsx Outdated
Drives the real seam end to end — loadModelWithOverride (UI helper) →
activeModelService.loadTextModel({override}) → makeRoomFor — mocking only
the native LLM engine and the hardware RAM sensor. Asserts the outcome the
user feels (model loaded / refused, which dialog shows), not gate calls.

The headline case reproduces the shipped bug via a DYNAMIC RAM mock: free
RAM is below the survival floor before the clean victim's eviction and
above it after (modelling iOS reclaiming the unloaded mmap pages). It goes
RED on origin/main (predictive floor credits 0 for a clean eviction →
false refusal → user sees "Error") and GREEN on the fix (evict-then-
measure → loads). Verified red on main by swapping in main's makeRoomFor.

Corners covered: overridable first-refusal surfaces the Load-Anyway prompt
without evicting; the genuine hard limit refuses once (no infinite offer);
the session override skips the gate on reload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
__tests__/integration/models/loadAnywayOverrideChain.test.ts (1)

48-51: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Update the setup note to match the mocked boundaries
The comment says only llm and hardware are mocked, but this test also mocks litert and localDreamGenerator. Keep the description aligned with the actual boundary set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/integration/models/loadAnywayOverrideChain.test.ts` around lines 48
- 51, The setup note is out of sync with the test’s mocked dependencies, since
loadAnywayOverrideChain.test.ts also mocks litert and localDreamGenerator in
addition to llm and hardware. Update the surrounding description/comment to
reflect the full mocked boundary set used by the test so it matches the actual
imports being stubbed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@__tests__/integration/models/loadAnywayOverrideChain.test.ts`:
- Around line 48-51: The setup note is out of sync with the test’s mocked
dependencies, since loadAnywayOverrideChain.test.ts also mocks litert and
localDreamGenerator in addition to llm and hardware. Update the surrounding
description/comment to reflect the full mocked boundary set used by the test so
it matches the actual imports being stubbed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 19987dd3-ace9-4723-8fb5-24fc710a5ea9

📥 Commits

Reviewing files that changed from the base of the PR and between e516e26 and 715c82b.

📒 Files selected for processing (1)
  • __tests__/integration/models/loadAnywayOverrideChain.test.ts

alichherawalla and others added 16 commits July 9, 2026 14:40
Retrying a failed vision-model download on iOS re-fetched only the base
GGUF and silently dropped the mmproj (vision projector), leaving a
vision model registered as text-only.

Cause: the download's mmProjDownloadUrl lives in metadataJson, which was
handed to the native downloader but never written to the local store row
(add() persisted mmProjFileName/Size but not metadataJson). metadataJson
only reached the row via a hydrate from native — i.e. after an app
restart — so a same-session retry/reconcile (restartIosTextDownload)
found meta=null and reconstructed the job without the mmproj sidecar.
iOS-only: it can't resume a foreground download so it rebuilds from
scratch; Android resumes the existing mmproj row by id.

Fix: persist metadataJson on the store row at add() time, exactly as it
is sent to native. One field, no behaviour change elsewhere.

Tests (red-first): parallelMmproj asserts the added row carries
metadataJson.mmProjDownloadUrl (fails before the fix); textDownloadProvider
guards that iOS retry rebuilds the main + mmproj sidecar from it, and that
a plain text model rebuilds without a mmProjFile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives the REAL chatStore with a dynamic sequence of reasoning-token appends,
builds the in-progress streaming message via the REAL getDisplayMessages, and
renders the REAL MessageAudioMode at each step. Asserts the displayed thinking
text reflects each increment ('' -> partial -> more), not only the final string.
Fails on the pre-fix pro code (thinking-block absent while streaming), passes
with the fix. Bumps the pro submodule pointer to the matching pro fix commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Repair Vision" action re-downloads a model's missing mmproj (~900MB)
but showed only an indeterminate "Repairing" spinner, so a large file looked
frozen. Route the repair download through the SAME download-store rows the
normal download writes (add/updateProgress/setCompleted/remove), keyed on the
model's modelKey, so the existing determinate progress bar lights up:

- Extract performMmProjRepairDownload into download.ts (the owner of
  background-download orchestration). repairMmProj now delegates + persists.
- CompletedDownloadCard renders the shared progress-bar markup from the live
  store row while a repair is in flight, reusing the same styles/tokens as
  ActiveDownloadCard (no fork). The "Vision Repaired" alert is unchanged.

Tests (red-first, dynamic mock at the boundary):
- repairVisionProgress: drives the real useDownloadStore, asserts progress
  advances 0 -> mid -> complete (not terminal-only) and fails through the
  store on error. Verified red when updateProgress is removed.
- CompletedDownloadCardRepair (RNTL): the determinate bar renders and its
  bytes advance as the store advances; spinner-only before the row is seeded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chat pre-checked with the predictive checkMemoryForModel (fileSize×1.5),
gating models the MEASURED residency loader (makeRoomFor) accepts — so a
model loadable from Home was blocked in chat. Remove the predictive gate;
the measured loader is the single authoritative gate and its
OverridableMemoryError drives the SAME Load-Anyway affordance via the
shared loadModelWithOverride helper. Parity with Home.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On-device test session (Qwythos-9B vision + memory, 12GB iPhone/Android)
surfaced OD1-OD8 (vision-drop-on-retry, repair-no-progress, chat/Home
selector split, forced-load freeze, Android retry stall, Kokoro loop,
reasoning-toggle detection, voice-mode thinking). Logged with evidence.
Also lands the manual-signing ios-device.sh helper used to run + pull
device logs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mplate signal (OD7)

The in-chat thinking/reasoning toggle is gated by supportsNativeThinking(context),
which returned context.isJinjaSupported() = "the model ships a parseable jinja
template". A community reasoning model like empero-ai/Qwythos-9B-Claude-Mythos
emits <think> reasoning the app already renders, but its merged chat template
minja cannot flag, so isJinjaSupported() was false and the toggle never appeared -
even though the runtime parseThinkingContent renders its reasoning fine. Reasoning
was detected two disagreeing ways.

Make the load-time capability derive from the SAME reasoning signal the runtime
parser uses: the reasoning delimiters (<think>, Gemma <|channel>thought, Qwen
<|channel|>analysis) in the model's own chat_template metadata. When the jinja
signal is false, fall back to templateEmitsReasoning(chat_template) so a
reasoning-capable model surfaces the toggle. No model-name hardcoding; the markers
live once in messageContent.ts as the single source of truth.

Tests (red-first): reasoning template with isJinjaSupported()=false -> true
(reproduces OD7 Qwythos), plain template -> false, plus Gemma/Qwen/alt-key cases
and direct templateEmitsReasoning coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The OD3 agent died on a session limit before its test file was green (16
failures on full-file run, though each passed in isolation). Two harness
bugs, no source change:
- The refactor moved 'await waitForRenderFrame()' (a real 350ms frame) onto
  the refusal path, so fake-timer Load-Anyway tests hung awaiting a timer
  they never advanced. Run those on real timers + a flush helper.
- InteractionManager.runAfterInteractions doesn't flush under jest fake
  timers → cross-test pollution timed out later real-timer tests. Stub it
  synchronously for every test; hard-reset timers + spies in afterEach;
  mockReset the loader so a stray *Once queue can't leak.

32/32 in this file; 184/184 across the OD-affected suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Core: red-first ttsStore test proving checkDownloadStatus re-hydrates a
desynced engine from the persisted truth (and does NOT fabricate downloaded
when the flag is false, nor re-hydrate mid-download). Bumps pro pointer to
the reconcile fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Red-first regression tests for the streaming speak path (fix lives in the
pro submodule, branch fix/tts-strip-tool-calls). Feeds a <tool_call> block
that spans multiple sentence boundaries, interleaved with real answer text,
token-by-token, and asserts the OBSERVABLE SPOKEN OUTPUT (what reaches
engine.speak) never contains a tool-call fragment at ANY streaming step —
only the real prose. Covers: block across sentence boundaries; an unclosed
opener mid-stream (withheld, not spoken); a still-forming opener tag; a plain
answer with no tool call (unchanged); and the trailing finishStreamingText
flush. Fails against the pre-fix code (fragment leaks `{"name": …`), passes
after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
alichherawalla and others added 10 commits July 9, 2026 17:17
…/regression); threads fix makes 9B fast on both platforms
…(OD14)

A tool-using turn reasons, then emits a tool call. That first round's thinking
was dropped — the intermediate tool-call assistant message was built with no
reasoningContent (generationToolLoop.ts) and the live stream was cleared — so
the thinking visibly vanished when the tool fired and only the post-tool-result
round's thinking survived on the final answer. On device: 'for a moment it's
just the tool calls and the thinking disappears'.

Attach this round's accumulated state.reasoningContent to the tool-call
assistant message, so each round's chain-of-thought persists on its own message
and BOTH rounds are kept in the transcript (the cross-tool CoT demo).

Red-first: new test drives round-1 reasoning via onStream and asserts the
intermediate tool-call message carries it; proven to fail without the fix line,
pass with it. 311 generation/tool-loop tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Targeting id=$DEVICE_ID made xcodebuild block on the device being fully
'available' and fail with 'developer disk image could not be mounted' whenever
the phone locked or the tunnel hiccupped mid-build. Compiling needs only the
arm64 device slice; install/launch still reach the device via devicectl.
…er behavior

OD3 removed the predictive checkMemoryForModel pre-check from chat model
selection; it now routes through the measured residency loader (loadTextModel),
whose OverridableMemoryError drives the shared "Insufficient Memory / Load
Anyway" affordance. Updated the 6 stale tests (and 2 adjacent ones that were
only passing because a latent mock bug made the load throw "not a function" →
an Error alert) to assert the new behavior: measured loader invoked, refusal
surfaces the Insufficient Memory alert + Load Anyway, override retries with
{override:true}.

Also fixes a pre-existing latent mock-infra bug: the activeModelService mock
factory references mockLoadModel/mockUnloadModel, which are declared after the
hoisted jest.mock, so the load/unload keys held `undefined` until a manual
runtime patch. Bind them in beforeEach so every test routes load/unload through
the mocks (order-independent).

155/155 ChatScreen tests pass; eslint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… behavior

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two dual-fork commits fixed the runtime mock-hoisting/TDZ bug and the OD3
assertions, but left mockLoadModel/mockUnloadModel typed as zero-arg jest.fn,
so the delegating-arrow factory (...args) and the keyed mockImplementation
refusal cases (id,_t,opts) failed tsc. Type both as (...args: any[]) — runtime
unchanged, 155/155 still green, tsc clean.
@alichherawalla alichherawalla changed the title release: 0.0.103 — Load Anyway memory fix + promote-to-production tooling release: 0.0.103 — memory/Load-Anyway fix + on-device TTS/vision/tools hardening + promote tooling Jul 9, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
__tests__/rntl/screens/ChatScreen.test.tsx (1)

104-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Two overlapping fixes for the same TDZ problem — clarify which is actually needed.

The delegating-arrow factory (104-113) is documented as solving the TDZ issue by deferring the mockLoadModel/mockUnloadModel lookup to call time. The re-binding at 553-562 cites the identical TDZ reasoning as its own justification. If the arrows already work, this reassignment is redundant; if it's still required, the arrows aren't actually fixing the problem and the comment at 104-109 is stale/misleading. Worth consolidating to one mechanism.

Also applies to: 553-562

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/rntl/screens/ChatScreen.test.tsx` around lines 104 - 113, The TDZ
workaround is duplicated and the two fixes conflict in intent; keep only one
mechanism and make the other unnecessary or remove it. Update the
`ChatScreen.test.tsx` mock setup around the delegating-arrow factory and the
later re-binding so there is a single source of truth for
`mockLoadModel`/`mockUnloadModel`, and ensure the remaining code clearly defers
lookup until call time without stale commentary.
__tests__/integration/models/chatHomeSelectorParity.test.ts (1)

48-49: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Misleading requestAnimationFrame stub; timing buffer thinner than sibling convention.

The comment claims stubbing requestAnimationFrame "flushes" waitForRenderFrame fast, but the sibling test file (useChatModelActions.test.ts) documents that this helper actually resolves via InteractionManager.runAfterInteractions(() => setTimeout(resolve, 350)) and stubs InteractionManager.runAfterInteractions specifically for that reason — not requestAnimationFrame. As written here, the stub likely does nothing useful, and the test instead relies entirely on the flat setTimeout(resolve, 400) padding at line 203, which is also thinner than the 450ms buffer used elsewhere in this PR for the same 350ms wait.

Consider aligning with the sibling file's pattern: stub InteractionManager.runAfterInteractions and bump the buffer to 450ms for consistency and reduced flakiness risk.

Also applies to: 201-203

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/integration/models/chatHomeSelectorParity.test.ts` around lines 48
- 49, The timing setup in chatHomeSelectorParity.test.ts is misleading because
waitForRenderFrame resolves through InteractionManager.runAfterInteractions and
a 350ms timeout, not requestAnimationFrame. Update the test to stub
InteractionManager.runAfterInteractions like the sibling
useChatModelActions.test.ts does, and increase the surrounding wait buffer from
400ms to 450ms for consistency and less flakiness. Use the existing
waitForRenderFrame helper and the test’s InteractionManager usage as the key
touchpoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@__tests__/unit/hooks/useChatModelActions.test.ts`:
- Around line 468-484: The test in useChatModelActions should assert the actual
finally cleanup, not just the loading-start callback. Update the failure-path
assertion around initiateModelLoad, makeDeps, and loadAnywayBtn.onPress so it
verifies setIsModelLoading(false) was called after the forced load rejection,
and ideally that the final loading state is cleared even when mockLoadTextModel
fails again. Replace the current toHaveBeenCalledWith(true) check with an
assertion that proves the cleanup path ran.

In `@src/screens/ChatScreen/useChatModelActions.ts`:
- Around line 96-101: The `initiateModelLoad` catch block is re-implementing the
“Load Anyway” override flow instead of reusing `loadModelWithOverride`, creating
duplicate UX and behavior. Update `useChatModelActions` so this path delegates
to the shared helper used by `proceedWithModelLoadFn`, and extend that helper’s
options if needed to support the `onLoadedResume` callback chain. Keep the alert
copy, retry action, and memory-gating logic centralized in the shared
implementation so future changes stay consistent.

---

Nitpick comments:
In `@__tests__/integration/models/chatHomeSelectorParity.test.ts`:
- Around line 48-49: The timing setup in chatHomeSelectorParity.test.ts is
misleading because waitForRenderFrame resolves through
InteractionManager.runAfterInteractions and a 350ms timeout, not
requestAnimationFrame. Update the test to stub
InteractionManager.runAfterInteractions like the sibling
useChatModelActions.test.ts does, and increase the surrounding wait buffer from
400ms to 450ms for consistency and less flakiness. Use the existing
waitForRenderFrame helper and the test’s InteractionManager usage as the key
touchpoints.

In `@__tests__/rntl/screens/ChatScreen.test.tsx`:
- Around line 104-113: The TDZ workaround is duplicated and the two fixes
conflict in intent; keep only one mechanism and make the other unnecessary or
remove it. Update the `ChatScreen.test.tsx` mock setup around the
delegating-arrow factory and the later re-binding so there is a single source of
truth for `mockLoadModel`/`mockUnloadModel`, and ensure the remaining code
clearly defers lookup until call time without stale commentary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5c1efb0-914d-434b-ab6d-3d1b30015852

📥 Commits

Reviewing files that changed from the base of the PR and between 28b914a and 15a1159.

📒 Files selected for processing (21)
  • __tests__/integration/models/chatHomeSelectorParity.test.ts
  • __tests__/pro/audio/ui/MessageAudioModeThinkingStream.test.tsx
  • __tests__/rntl/components/CompletedDownloadCardRepair.test.tsx
  • __tests__/rntl/screens/ChatScreen.test.tsx
  • __tests__/unit/audio/streamingSpeech.test.ts
  • __tests__/unit/hooks/useChatModelActions.test.ts
  • __tests__/unit/services/generationToolLoop.test.ts
  • __tests__/unit/services/llmHelpers.test.ts
  • __tests__/unit/services/repairVisionProgress.test.ts
  • __tests__/unit/stores/ttsStore.test.ts
  • __tests__/unit/utils/messageContent.test.ts
  • docs/GAPS_BACKLOG.md
  • pro
  • scripts/ios-device.sh
  • src/screens/ChatScreen/useChatModelActions.ts
  • src/screens/DownloadManagerScreen/items.tsx
  • src/services/generationToolLoop.ts
  • src/services/llmHelpers.ts
  • src/services/modelManager/download.ts
  • src/services/modelManager/index.ts
  • src/utils/messageContent.ts

Comment thread __tests__/unit/hooks/useChatModelActions.test.ts
Comment thread src/screens/ChatScreen/useChatModelActions.ts
alichherawalla and others added 5 commits July 9, 2026 18:27
…predicate (OD7 §C)

The §C audit caught it: the OD7 marker list carried a 'SINGLE SOURCE OF TRUTH'
comment but was a 4th hand-maintained copy that OMITTED enable_thinking, so it
diverged from remoteModelCapabilities — a model exposing only the enable_thinking
kwarg in its template was detected reasoning-capable on the gateway but NOT
on-device. Add enable_thinking to the marker set, route remoteModelCapabilities'
template detection through templateEmitsReasoning (one predicate, local + remote),
and correct the comment to state exactly what it owns (template-capability
detection) vs what stays in parseThinkingContent (output-delimiter extraction).

Red-first: enable_thinking-only template now detects true (was false).
161 tests green (messageContent + llmHelpers + remoteModelCapabilities).
…otency

Fixes the release-tooling defects flagged on PR #506:

1. Tag mispoint: promote.sh now bases the v<version> commit/tag on the tested
   BETA tag's commit (BETA_SHA), never arbitrary HEAD, and pushes to an explicit
   branch (PROMOTE_BRANCH, default main) fast-forward-only.
2. Partial-push idempotency: the repo step distinguishes "released on origin"
   (git ls-remote) from "tag only local" (a prior push failed) and re-attempts
   the push instead of skipping.
3. Build-pin (single source of truth): uat.sh records the tested store build id
   (Android versionCode / iOS CURRENT_PROJECT_VERSION) in the beta tag annotation
   ("Build: <n>", format owned by scripts/lib/version.js) and the GH prerelease
   body; promote.sh recovers it and pins Play (upload_to_play_store version_code:)
   + TestFlight (upload_to_app_store build_number:). Fails fast if unrecoverable.
   The Android promote lane FAILS FAST without version_code rather than promoting
   "latest on internal".
4. NOTES_FILE mktemp: drop the ".md" that orphaned the real temp file.

Also: em-dash compliance sweep (rule 1570610) across promote.sh, uat.sh, Fastfile.
Adds red-first tests for the new build-id helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…6 (remote-caps flakiness / fixture hardening)
…EM-SM] logs

- OnboardingScreen 'updates currentIndex on scroll end' was gutted to zero
  assertions (CodeRabbit Major) — rewrite it to drive onMomentumScrollEnd and
  assert the footer flips Next→Get Started (isLastSlide = currentIndex===last),
  a direct guard on onMomentumScrollEnd→setCurrentIndex. 15/15 green.
- Replace 26 Unicode em dashes with ASCII hyphens in modelResidency [MEM-SM]
  diagnostic log lines (compliance 1570610). 58 residency tests still green.
…dempotency + em-dash sweep

Closes the CodeRabbit/Qodo Major findings on promote.sh: build-pin (single-source
build id via uat.sh→tag annotation→promote.sh→fastlane version_code/build_number,
fail-fast if unrecoverable), tag-from-beta-SHA (not HEAD), remote-aware idempotency,
NOTES_FILE mktemp, and the em-dash sweep across promote.sh/uat.sh/Fastfile.
@alichherawalla

Copy link
Copy Markdown
Collaborator Author

Review findings addressed (pushed in 6271328)

All actionable CodeRabbit + Qodo findings fixed, red-first where code:

  • promote.sh em-dashes (rule 1570610) — swept —→- across promote.sh, uat.sh, fastlane/Fastfile (grep count now 0 in all three). Also cleared 26 em-dashes in modelResidency [MEM-SM] diagnostic logs. — 09eb60be
  • promote.sh not beta-pinned (Major) — single-source build id: uat.sh records the tested BUILD_NUMBER into the beta tag annotation (+ GH prerelease body); promote.sh recovers it and threads it into fastlane (android promote version_code:, ios promote build_number:); fails fast if unrecoverable rather than promoting "latest". upload_to_play_store version_code: confirmed against the action's documented contract. — 09eb60be
  • promote.sh release tag may mispoint (Major) — the release commit/tag is now built from the tested BETA_SHA (detached checkout), not the operator's HEAD, and pushed ff-only to an explicit PROMOTE_BRANCH. — 09eb60be
  • promote.sh partial-push idempotency (Major) — idempotency now checks the remote tag (git ls-remote) and distinguishes "already released" from "local-only tag", re-attempting the push on re-run. — 09eb60be
  • promote.sh NOTES_FILE mktemp mismatch (Minor) — dropped the stray .md so the created and cleaned-up paths match. — 09eb60be
  • OnboardingScreen.test.tsx scroll test had no assertions (Major) — rewritten to drive onMomentumScrollEnd and assert the footer flips Next→Get Started (isLastSlide = currentIndex === last), a direct guard on onMomentumScrollEnd → setCurrentIndex. — latest push

Also fixed (from the internal hygiene audit): OD7 reasoning-detection was a false "single source of truth" that diverged from remoteModelCapabilities (omitted enable_thinking) — now a genuinely shared templateEmitsReasoning predicate, local + remote.

Two separate pre-existing bugs surfaced during review (Jinja tool-parser role-alternation error; remote-capabilities response-shape flakiness) are logged in docs/GAPS_BACKLOG.md (OD15/OD16) as fast-follows — confirmed not regressions from this branch.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

@alichherawalla Thanks for the detailed rundown of the fixes. I'll re-review the PR now, including the promote.sh build-id pinning/idempotency changes, the em-dash cleanup, and the OnboardingScreen test assertions.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/lib/version.js (1)

89-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stray em dash left in error message.

Line 90 still uses an em dash (—) inside the thrown error string. The PR's review follow-ups explicitly replaced em dashes with hyphens across promote.sh, uat.sh, fastlane/Fastfile, and diagnostic logs for brand-voice consistency, but this occurrence in version.js appears to have been missed.

✏️ Proposed fix
-      'No "Build: <n>" line in the tag annotation — cannot recover the tested build id. ' +
+      'No "Build: <n>" line in the tag annotation - cannot recover the tested build id. ' +
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lib/version.js` around lines 89 - 93, The thrown error string in
version.js still contains a stray em dash, so update the message in the build-id
recovery path to use a hyphen instead for consistency with the other reviewed
files. Check the error text inside the version-recovery logic that throws when
the tag annotation lacks a Build line, and replace the em dash in that message
while keeping the rest of the wording unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/GAPS_BACKLOG.md`:
- Line 198: The note in the backlog entry has a Markdown lint issue because the
inline code snippet in the OD14 description includes a trailing space. Update
the OD14 text in GAPS_BACKLOG.md so the code span around content:'', has no
extra space before the closing backtick, keeping the rest of the entry
unchanged.

---

Nitpick comments:
In `@scripts/lib/version.js`:
- Around line 89-93: The thrown error string in version.js still contains a
stray em dash, so update the message in the build-id recovery path to use a
hyphen instead for consistency with the other reviewed files. Check the error
text inside the version-recovery logic that throws when the tag annotation lacks
a Build line, and replace the em dash in that message while keeping the rest of
the wording unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 224691ec-1024-4a0c-bde8-e2b505b016ba

📥 Commits

Reviewing files that changed from the base of the PR and between 28b914a and 6271328.

📒 Files selected for processing (30)
  • __tests__/integration/models/chatHomeSelectorParity.test.ts
  • __tests__/pro/audio/ui/MessageAudioModeThinkingStream.test.tsx
  • __tests__/rntl/components/CompletedDownloadCardRepair.test.tsx
  • __tests__/rntl/screens/ChatScreen.test.tsx
  • __tests__/rntl/screens/OnboardingScreen.test.tsx
  • __tests__/unit/audio/streamingSpeech.test.ts
  • __tests__/unit/hooks/useChatModelActions.test.ts
  • __tests__/unit/scripts/version.test.ts
  • __tests__/unit/services/generationToolLoop.test.ts
  • __tests__/unit/services/llmHelpers.test.ts
  • __tests__/unit/services/repairVisionProgress.test.ts
  • __tests__/unit/stores/ttsStore.test.ts
  • __tests__/unit/utils/messageContent.test.ts
  • docs/GAPS_BACKLOG.md
  • fastlane/Fastfile
  • pro
  • scripts/ios-device.sh
  • scripts/lib/version.d.ts
  • scripts/lib/version.js
  • scripts/promote.sh
  • scripts/uat.sh
  • src/screens/ChatScreen/useChatModelActions.ts
  • src/screens/DownloadManagerScreen/items.tsx
  • src/services/generationToolLoop.ts
  • src/services/llmHelpers.ts
  • src/services/modelManager/download.ts
  • src/services/modelManager/index.ts
  • src/services/modelResidency/index.ts
  • src/stores/remoteModelCapabilities.ts
  • src/utils/messageContent.ts
✅ Files skipped from review due to trivial changes (1)
  • pro
🚧 Files skipped from review as they are similar to previous changes (22)
  • tests/unit/utils/messageContent.test.ts
  • scripts/lib/version.d.ts
  • tests/unit/services/generationToolLoop.test.ts
  • tests/unit/services/repairVisionProgress.test.ts
  • tests/rntl/components/CompletedDownloadCardRepair.test.tsx
  • tests/unit/services/llmHelpers.test.ts
  • src/services/generationToolLoop.ts
  • tests/pro/audio/ui/MessageAudioModeThinkingStream.test.tsx
  • src/services/modelManager/index.ts
  • tests/unit/stores/ttsStore.test.ts
  • tests/unit/audio/streamingSpeech.test.ts
  • src/screens/DownloadManagerScreen/items.tsx
  • scripts/ios-device.sh
  • src/services/modelManager/download.ts
  • fastlane/Fastfile
  • tests/integration/models/chatHomeSelectorParity.test.ts
  • src/services/llmHelpers.ts
  • scripts/promote.sh
  • src/screens/ChatScreen/useChatModelActions.ts
  • src/services/modelResidency/index.ts
  • tests/rntl/screens/ChatScreen.test.tsx
  • tests/unit/hooks/useChatModelActions.test.ts

Comment thread docs/GAPS_BACKLOG.md
CPU-with-proper-threads is the path. The devicectl "developer disk image could not be mounted"
is a Mac-side tooling wedge (iOS 26.5.1), independent of the app — reconnect to clear.

| OD14 | **Pre-tool-call reasoning not persisted (thinking "disappears" when a tool call starts)** | RESOLVED | In a tool-using turn the model reasons, then emits a tool call; that FIRST round's live thinking is cleared (clearStreamingMessage between rounds) and the intermediate tool-call assistant message is built with `content:'', ` NO reasoningContent (generationToolLoop.ts:447-448). The post-tool-result round's thinking IS shown on the final answer (confirmed on device: "Thought process: The knowledge base search returned no results…"). So thinking visibly vanishes at tool-call start then reappears on the final message — the pre-tool-call reasoning is lost from the transcript. Minor fidelity gap, feature works end-to-end. Fix: attach the accumulated reasoningBuffer to the intermediate tool-call assistant message. NOTE (good): iOS ran this 9B on Metal (99 layers GPU offloaded), 2.0 tok/s, TTFT 10.79s — iOS Metal works well; Android Adreno OpenCL (1GB max-alloc) falls back to CPU. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Trim the trailing space inside the code span.

content:'', triggers MD038 because the inline code includes a trailing space. Remove it so the note renders and lints cleanly.

Suggested fix
- ... built with `content:'', ` NO reasoningContent ...
+ ... built with `content:'',` NO reasoningContent ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| OD14 | **Pre-tool-call reasoning not persisted (thinking "disappears" when a tool call starts)** | RESOLVED | In a tool-using turn the model reasons, then emits a tool call; that FIRST round's live thinking is cleared (clearStreamingMessage between rounds) and the intermediate tool-call assistant message is built with `content:'', ` NO reasoningContent (generationToolLoop.ts:447-448). The post-tool-result round's thinking IS shown on the final answer (confirmed on device: "Thought process: The knowledge base search returned no results…"). So thinking visibly vanishes at tool-call start then reappears on the final message — the pre-tool-call reasoning is lost from the transcript. Minor fidelity gap, feature works end-to-end. Fix: attach the accumulated reasoningBuffer to the intermediate tool-call assistant message. NOTE (good): iOS ran this 9B on Metal (99 layers GPU offloaded), 2.0 tok/s, TTFT 10.79s — iOS Metal works well; Android Adreno OpenCL (1GB max-alloc) falls back to CPU. |
| OD14 | **Pre-tool-call reasoning not persisted (thinking "disappears" when a tool call starts)** | RESOLVED | In a tool-using turn the model reasons, then emits a tool call; that FIRST round's live thinking is cleared (clearStreamingMessage between rounds) and the intermediate tool-call assistant message is built with `content:'',` NO reasoningContent (generationToolLoop.ts:447-448). The post-tool-result round's thinking IS shown on the final answer (confirmed on device: "Thought process: The knowledge base search returned no results…"). So thinking visibly vanishes at tool-call start then reappears on the final message — the pre-tool-call reasoning is lost from the transcript. Minor fidelity gap, feature works end-to-end. Fix: attach the accumulated reasoningBuffer to the intermediate tool-call assistant message. NOTE (good): iOS ran this 9B on Metal (99 layers GPU offloaded), 2.0 tok/s, TTFT 10.79s — iOS Metal works well; Android Adreno OpenCL (1GB max-alloc) falls back to CPU. |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 198-198: Spaces inside code span elements

(MD038, no-space-in-code)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/GAPS_BACKLOG.md` at line 198, The note in the backlog entry has a
Markdown lint issue because the inline code snippet in the OD14 description
includes a trailing space. Update the OD14 text in GAPS_BACKLOG.md so the code
span around content:'', has no extra space before the closing backtick, keeping
the rest of the entry unchanged.

Source: Linters/SAST tools

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.

1 participant