Skip to content

fix: offer "Load Anyway" memory override for all model types (image parity)#498

Merged
alichherawalla merged 27 commits into
mainfrom
fix/load-anyway-all-model-types
Jul 8, 2026
Merged

fix: offer "Load Anyway" memory override for all model types (image parity)#498
alichherawalla merged 27 commits into
mainfrom
fix/load-anyway-all-model-types

Conversation

@alichherawalla

@alichherawalla alichherawalla commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

An image-generation reliability + UX pass, triggered by an r/off_grid_ai report ("Failed to load image model, not enough memory" on a Galaxy S23 FE with no "Load Anyway" option) and extended through live on-device debugging on a OnePlus Nord (Snapdragon 765G, GPU-only). Ships several independent fixes; the TTS piece rides as a pro submodule bump (off-grid-ai/mobile-pro#18).

Everything is fails-before/passes-after tested. Full suite green: 330 suites, 6960 tests. Verified on-device throughout.

What's in here

1. Load Anyway for image models (the original report)

  • Image loads preserve the typed OverridableMemoryError instead of stringifying it, so the shared failure card offers Load Anyway (retry with { override: true }) — parity with text. 6b3e7981, 94c554ff, 9b6a6bce
  • A refusal that persists under override is a hard limit → non-overridable, so the card stops re-offering a no-op. Text + image copy differentiated (first refusal vs post-eviction). f13323ad, 18e36eb5
  • Shared ActionButton extracted (Retry / Load Anyway) — DRY. f8310e5a; design-spec reference added 5a0b18f5

2. Image model extraction integrity (the real S23-class bug)

  • Root cause found on-device: a partial unzip left the model missing pos_emb.bin + clip_v2.mnn.weight, yet it was marked _ready; the native server then died and reported a misleading "your device may not support this backend" (the SoC was blamed for a missing-file bug).
  • New imageModelIntegrity util: backend-agnostic completeness rule (base files + primary unet + *.mnn*.mnn.weight pairing). Wired into the primary download finalize + resume (both via one shared ensureImageExtractionComplete seam), so a partial extraction is never marked ready. ef960ed2, 49439e9c
  • Load-time gate throws typed ImageModelIncompleteError → UI says "re-download" (not "Load Anyway" — missing files can't be forced). ef960ed2

3. Image-gen UX / quality (GPU-only devices)

  • Progress label says "Generating image (N/steps)" while steps advance, not "GPU optimization in progress". 988c5e7e
  • Never generate at a garbage resolution: SD1.5 below 256 is incoherent (not "smaller"). Size slider floor 128 → 256; generation floors width/height to 256 so a stale sub-256 setting can't reach the pipeline. 3ea60c12
  • GPU-path advice card: on the mnn (no-NPU) path, guides the user — raise steps (<20 = muddy), lower size (512 = ~18 min on mid-tier), or raise size (<256 = garbage). Hidden on NPU/CoreML and at the 256/20 sweet spot. 3ea60c12, 8a26aaff

4. TTS streaming stop (pro submodule bump — off-grid-ai/mobile-pro#18)

  • Pausing a streaming auto-speak wedged + released the Kokoro engine (killing all later playback). Kokoro can't pause mid-synthesis, so a fileless synth now shows a STOP control that aborts cleanly (engine settled, not released). Core carries the submodule bump + tests. 141835cb, ddacfad6

5. Layout + logged gaps

  • Evicted "tap to continue" snackbar renders below the rounded failure card. 4736866c
  • Logged (deferred, with evidence): QNN over-recommendation on non-flagship Snapdragons (SM7250 offered NPU it can't run), and first-run inline-preview native emission. 560207d0

On-device verification (OnePlus Nord, SM7250, GPU-only)

  • 512×22 = good but ~18 min; 256×22 = coherent + good, ~2m48s (the sweet spot the card steers to); 128 = garbage (now floored to 256).
  • Incomplete-model fix: restoring the 2 missing files → GPU model loads + generates.
  • TTS STOP control verified.
  • NPU: confirmed hardware dead-end on this SoC (QNN HTP error 4000) — separate deferred gap.

Merge order

Merge off-grid-ai/mobile-pro#18 first, re-bump this PR's pro pointer to pro-main's merge commit, then merge this. Merge commits only (no squash).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an in-chat image generation advice card with suggestions for improving quality or size settings.
    • Added a Load Anyway option for certain image model loading errors, letting users retry after a memory-related failure.
  • Bug Fixes

    • Improved handling of incomplete image model downloads and extraction, reducing failed or partial model loads.
    • Refined audio playback and streaming controls for more reliable stop, pause, and resume behavior.

alichherawalla and others added 3 commits July 8, 2026 02:51
…or any model type

The "Load Anyway" memory override was wired only in the text-model load path
(loadModelWithOverride / useChatModelActions). The shared failure surface that
every non-text modality reports through (reportModelFailure -> ModelFailureCard)
had no override affordance at all - so image/tts/stt/embedding could only ever
show "Retry", never force past the budget.

Make the override a property of the shared surface instead of the text path:
- ModelFailure gains `overridable` + `onLoadAnyway`.
- reportModelFailure derives `overridable` ONCE from the typed OverridableMemoryError
  discriminant (never re-sniffed from the message) and only keeps onLoadAnyway when
  the cause is genuinely overridable and the failure is a hard error (not a warning).
- ModelFailureCard renders a "Load Anyway" action next to Retry when overridable.

Any modality that reports an overridable gate now gets the same override, uniformly.

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

_ensureImageModelLoaded caught the typed OverridableMemoryError and immediately
downgraded it to a plain string (`error?.message`) before reporting the failure.
That destroyed the `overridable` discriminant one layer too early, so the shared
failure surface could never know the gate was overridable - image models on a
memory-tight device (e.g. an 8GB S23 FE) got a dead-end "Free memory & Retry"
that re-ran into the same wall, with no way to force the load past the budget.

Route the image path onto the same override seam the text path uses:
- _fail carries the original thrown error as `opts.cause` (not just its message).
- When the cause is an OverridableMemoryError, _fail builds an onLoadAnyway that
  re-runs the last request with { override: true }; reportModelFailure drops it
  for any non-overridable cause, so other failures are unaffected.
- generateImage / _ensureImageModelLoaded thread `override` down to
  loadImageModel({ override: true }) (which already evicts + forces the load).

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

Fails-before / passes-after guards for the override parity fix:
- modelFailureHandler: overridable is set from the TYPED error; a non-overridable
  error (and a soft warning with a memory cause) drops onLoadAnyway - the false
  branches, so a caller can't offer override where it doesn't apply.
- ModelFailureCard: the "Load Anyway" button renders only when overridable AND a
  handler is present (incl. a non-image type), and pressing it runs the handler.
- imageGenerationFlow (integration): an OverridableMemoryError on image load now
  surfaces overridable + onLoadAnyway, and invoking it re-runs the load with
  { override: true }; a plain load error offers no override. Drives the REAL
  imageGenerationService + REAL failure store (only native boundaries mocked), so
  deleting the fix fails the test.

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

coderabbitai Bot commented Jul 7, 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

Adds image-model integrity and override handling, expands audio stop/playback coverage, adds Pro MCP transport and UI tests, and updates repository coverage and submodule configuration.

Changes

Load Anyway Override Flow

Layer / File(s) Summary
Integrity and advice utilities
src/utils/imageModelIntegrity.ts, src/utils/imageGenAdvice.ts, src/types/index.ts, __tests__/unit/utils/*, __tests__/unit/services/modelLoadErrors.test.ts
Adds image-model integrity checks, image advice thresholds, image-related message fields, and tests for the new error and utility behavior.
Override-aware loading and failures
src/services/activeModelService/index.ts, src/services/imageGenerationService.ts, src/services/modelFailureHandler.ts, src/stores/modelFailureStore.ts, src/components/ModelFailureCard.tsx, src/components/ImageGenAdviceCard.tsx, src/components/index.ts, src/screens/ChatScreen/ChatMessageArea.tsx, src/components/GenerationSettingsModal/ImageQualitySliders.tsx, __tests__/integration/generation/*, __tests__/integration/models/*, __tests__/unit/services/*, __tests__/unit/components/ModelFailureCard.test.tsx, __tests__/rntl/components/ImageGenAdviceCard.test.tsx, __tests__/rntl/onboarding/ChatScreenSpotlight.test.tsx, __tests__/rntl/screens/ChatScreen.test.tsx
Adds override-aware image-model loading, typed failure propagation, retry-card actions, image advice UI, and the supporting integration/unit/RNTL coverage.
Image model download/resume wiring
src/screens/ModelsScreen/imageDownloadActions.ts, src/screens/ModelsScreen/imageDownloadResume.ts, __tests__/unit/screens/ModelsScreen/*, __tests__/integration/models/*
Adds extraction-completeness checks to download and resume paths and updates the related tests to stub the new integrity gate.

Audio streaming and playback controls

Layer / File(s) Summary
Streaming stop behavior
__tests__/unit/audio/streamingSpeech.test.ts, __tests__/integration/audio/streamingStateMachine.test.ts
Adds stopStreamingSpeechForTurn coverage for suppressing the current turn, preventing release/advancement, and clearing suppression on reset.
Playback control states
__tests__/rntl/components/PlaybackControls.test.tsx, __tests__/pro/audio/*
Expands playback and TTS UI coverage for touchability, glyph selection, synth-state derivation, engine bridging, voice pickers, and related store transitions.

Pro MCP services and UI

Layer / File(s) Summary
MCP transport and OAuth
__tests__/pro/mcp/*, __tests__/pro/demo/demoMode.test.ts, __tests__/pro/audio/engines/*, __tests__/pro/audio/tts*
Adds comprehensive Pro MCP transport, OAuth, schema-trimming, store, demo-mode, and audio-engine coverage.
MCP Pro screens
__tests__/pro/ui/*, __tests__/pro/audio/ui/*
Adds Pro screen and modal coverage for MCP setup, tool selection, settings, and audio UI components.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed but doesn't follow the required template and omits the Type of Change and screenshots sections. Add the template sections, especially Type of Change and UI screenshots/recordings, plus checklist items, related issues, and additional notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: adding a Load Anyway override for image-model parity.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/load-anyway-all-model-types

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Enable "Load Anyway" override on shared model failure card for all model types

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add a shared "Load Anyway" action for overridable memory-gate failures on the failure card.
• Preserve typed OverridableMemoryError through image load failures and retry with { override: true
 }.
• Add unit + integration tests to lock override visibility and retry behavior.
Diagram

graph TD
  IGS["ImageGenerationService"] --> MFH["modelFailureHandler"] --> MFS[("modelFailureStore")] --> MFC["ModelFailureCard"]
  IGS --> AMS["activeModelService"] --> NLD["Native model load"]
  IGS --> MLE["OverridableMemoryError"]
  MFH --> MLE
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Per-modality override UI (image-specific button)
  • ➕ Limits shared-surface changes; smaller blast radius to ModelFailureCard/handler.
  • ➕ Allows modality-specific copy/behavior without shared contracts.
  • ➖ Duplicates logic across modalities and risks parity regressions (this bug’s root issue).
  • ➖ Harder to ensure consistent gating semantics across model types.
2. Store original error object on ModelFailure and derive overridable in UI
  • ➕ Keeps handler simpler; UI can decide which actions to show.
  • ➕ Enables richer UI (stack traces, codes) without expanding handler API.
  • ➖ Leakes error-type coupling into UI layer (React components now depend on service error types).
  • ➖ Harder to guarantee consistent derivation; encourages repeated “sniffing” in multiple places.
3. Message-based override detection (regex on error.message)
  • ➕ No need to propagate typed errors through layers.
  • ➕ Works even if native layer cannot throw typed errors consistently.
  • ➖ Brittle and localization/copy-sensitive; prone to false positives/negatives.
  • ➖ Reintroduces the abstraction leak this PR fixes (typed discriminant discarded).

Recommendation: Keep the PR’s approach: make overridable-ness a single derived verdict in reportModelFailure, and require callers to preserve/pass the typed cause. It centralizes the policy at the shared seam, avoids message sniffing, and ensures all modalities using the failure card automatically gain parity without duplicating UI logic.

Files changed (7) +247 / -27

Enhancement (3) +54 / -14
ModelFailureCard.tsxRender "Load Anyway" action for overridable failures +34/-14

Render "Load Anyway" action for overridable failures

• Refactors the action area into an actions row that can show both Retry and Load Anyway. Adds conditional rendering for a "Load Anyway" button (zap icon) when failure.overridable && failure.onLoadAnyway, alongside existing Retry behavior and memory-pressure retry copy.

src/components/ModelFailureCard.tsx

modelFailureHandler.tsAdd overridable + onLoadAnyway to shared failure reporting +13/-0

Add overridable + onLoadAnyway to shared failure reporting

• Extends ReportFailureContext with onLoadAnyway and derives an overridable boolean once using isOverridableMemoryError (only for severity==='error'). Persists overridable on the stored ModelFailure and drops onLoadAnyway unless the failure is genuinely overridable, preventing override actions for warnings or non-memory failures.

src/services/modelFailureHandler.ts

modelFailureStore.tsExtend ModelFailure contract with overridable and onLoadAnyway +7/-0

Extend ModelFailure contract with overridable and onLoadAnyway

• Adds optional overridable and onLoadAnyway fields to the ModelFailure interface so the shared card can expose a typed memory override action. Documents that overridable is derived from OverridableMemoryError in reportModelFailure (single source of truth).

src/stores/modelFailureStore.ts

Bug fix (1) +29 / -10
imageGenerationService.tsPreserve typed load error cause and thread override through image model load +29/-10

Preserve typed load error cause and thread override through image model load

• Threads an optional override flag from generateImage through _ensureImageModelLoaded to activeModelService.loadImageModel via { override: true }. Changes _fail to accept an original cause, builds an onLoadAnyway retry only when the cause is an overridable memory gate, and reports failures with the typed cause so the shared failure surface can expose the override action.

src/services/imageGenerationService.ts

Tests (3) +164 / -3
imageGenerationFlow.test.tsAdd integration coverage for image "Load Anyway" override retry +61/-3

Add integration coverage for image "Load Anyway" override retry

• Updates image model auto-load expectation to include override options parameters. Adds an integration suite asserting that an OverridableMemoryError during image model load surfaces an overridable failure with onLoadAnyway, and that invoking it retries loadImageModel with { override: true }. Updates _ensureImageModelLoaded test call shape to use the new opts object.

tests/integration/generation/imageGenerationFlow.test.ts

ModelFailureCard.test.tsxAdd unit tests for "Load Anyway" button rendering and behavior +69/-0

Add unit tests for "Load Anyway" button rendering and behavior

• Introduces a new test suite that verifies the "Load Anyway" action appears only when overridable && onLoadAnyway are present, across model types, and that pressing it invokes the handler. Also asserts the button is absent for non-overridable failures or when no handler is provided.

tests/unit/components/ModelFailureCard.test.tsx

modelFailureHandler.test.tsTest overridable derivation and onLoadAnyway gating in reportModelFailure +34/-0

Test overridable derivation and onLoadAnyway gating in reportModelFailure

• Adds unit tests ensuring overridable is derived from the typed OverridableMemoryError and that onLoadAnyway is retained only for hard errors with an overridable cause. Verifies false branches: non-overridable errors and warnings do not surface Load Anyway even if a handler is passed.

tests/unit/services/modelFailureHandler.test.ts

@qodo-code-review

qodo-code-review Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 33 rules

Grey Divider


Action required

1. ModelFailureCard missing design spec ✓ Resolved 📘 Rule violation § Compliance
Description
src/components/ModelFailureCard.tsx introduces new UI actions (including a new "Load Anyway"
button) but does not include any nearby reference to the required design specification documents.
This violates the UI-change requirement and makes it harder to audit design-system compliance.
Code

src/components/ModelFailureCard.tsx[R41-64]

+      {(failure.onRetry || (failure.overridable && failure.onLoadAnyway)) && (
+        <View style={styles.actionsRow}>
+          {failure.onRetry && (
+            <AnimatedPressable
+              onPress={() => { onDismiss(); failure.onRetry?.(); }}
+              style={styles.actionButton}
+              accessibilityLabel="Retry"
+              testID={`model-failure-retry-${failure.modelType}`}
+            >
+              <Icon name="refresh-cw" size={12} color={colors.primary} style={styles.actionIcon} />
+              <Text style={styles.actionText}>{failure.memoryPressure ? 'Free memory & Retry' : 'Retry'}</Text>
+            </AnimatedPressable>
+          )}
+          {failure.overridable && failure.onLoadAnyway && (
+            <AnimatedPressable
+              onPress={() => { onDismiss(); failure.onLoadAnyway?.(); }}
+              style={styles.actionButton}
+              accessibilityLabel="Load Anyway"
+              testID={`model-failure-load-anyway-${failure.modelType}`}
+            >
+              <Icon name="zap" size={12} color={colors.error} style={styles.actionIcon} />
+              <Text style={[styles.actionText, { color: colors.error }]}>Load Anyway</Text>
+            </AnimatedPressable>
+          )}
Relevance

⭐⭐ Medium

No historical suggestions found enforcing “design spec reference” comments for UI changes in
src/components.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1781168 requires UI-affecting changes to include a nearby reference to an approved
design specification document. The updated ModelFailureCard adds new UI elements and copy (the
actions row and "Load Anyway"), but the file header and the modified UI block contain no reference
to any of the required design spec paths.

Rule 1781168: UI changes must reference documented design specifications
src/components/ModelFailureCard.tsx[1-23]
src/components/ModelFailureCard.tsx[41-64]

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

## Issue description
`ModelFailureCard` UI behavior was changed (new actions row + "Load Anyway" button), but the file/directory contains no reference to the required design specification docs. The compliance rule requires an explicit reference near the UI change.

## Issue Context
Add a short comment near the component definition (or near the modified UI block) referencing at least one of:
- `../brand/DESIGN_PHILOSOPHY.md`
- `docs/design/DESIGN_PHILOSOPHY_SYSTEM.md`
- `docs/design/VISUAL_HIERARCHY_STANDARD.md`

## Fix Focus Areas
- src/components/ModelFailureCard.tsx[1-23]
- src/components/ModelFailureCard.tsx[41-64]

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



Remediation recommended

2. Repeatable Load Anyway prompt ✓ Resolved 🐞 Bug ☼ Reliability
Description
If an image model load is rejected even during an override attempt (e.g., residency survival-floor
refusal), the failure will still be treated as overridable and keep re-offering “Load Anyway”,
leading to a repeated no-op action for the user. This happens because overridability is derived
solely from the thrown error type and the image retry path doesn’t suppress the action after an
override has already been tried.
Code

src/services/imageGenerationService.ts[R175-184]

+    // Load Anyway: only when the cause is the overridable memory gate. Re-run the last
+    // request forcing the image-model load past the budget (override evicts every
+    // evictable resident, then loads). reportModelFailure ignores onLoadAnyway unless
+    // the cause is actually overridable, so passing it here is safe for other errors.
+    const onLoadAnyway = isOverridableMemoryError(opts?.cause) && this._lastParams
+      ? () => { void this.generateImage(this._lastParams as GenerateImageParams, { override: true }); }
+      : undefined;
+    // Report with the typed cause (not the wrapped string) so the overridable
+    // discriminant survives; `message` keeps the user-facing wrapped text.
+    reportModelFailure('image', opts?.cause ?? error, { message: error, onRetry, onLoadAnyway });
Relevance

⭐⭐ Medium

Team often accepts reliability/race-condition hardening in imageGenerationService, but no prior
“disable repeated override CTA” precedent.

PR-#460
PR-#109
PR-#170

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR adds a Load Anyway retry that re-runs generation with { override: true } whenever the
failure cause is an OverridableMemoryError. But modelResidencyManager.makeRoomFor explicitly has
a path where it returns fits:false even with override (survival-floor refusal), and
activeModelService.loadImageModel throws OverridableMemoryError whenever a load is marked
overridable, regardless of whether override was already attempted; therefore the failure surface
will continue to classify it as overridable and keep offering Load Anyway on subsequent failures.

src/services/imageGenerationService.ts[161-185]
src/services/activeModelService/index.ts[241-321]
src/services/modelResidency/index.ts[276-336]

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

### Issue description
After the user presses **Load Anyway**, `imageGenerationService` retries with `{ override: true }`. If the load still fails (notably when `modelResidencyManager.makeRoomFor(..., {override:true})` refuses due to the override survival floor), `activeModelService.loadImageModel` can still throw `OverridableMemoryError`, which makes the failure surface keep showing **Load Anyway** again. This can repeatedly present an action that is unlikely to change the outcome.

### Issue Context
- `ModelResidencyManager.makeRoomFor` can return `fits: false` even under override when post-load free RAM would drop below the survival floor.
- `activeModelService.checkImageModelCanLoad` currently returns `overridable: true` whenever `fits` is false, regardless of whether `opts.override` was already true, and `loadImageModel` throws `OverridableMemoryError` whenever `check.overridable` is true.
- `imageGenerationService._fail` offers `onLoadAnyway` whenever the cause is an `OverridableMemoryError`, without accounting for whether this attempt already used override.

### Fix Focus Areas
- src/services/imageGenerationService.ts[161-185]
- src/services/imageGenerationService.ts[316-334]
- src/services/activeModelService/index.ts[241-321]
- src/services/modelResidency/index.ts[276-336]

### Suggested fix approaches (pick one)
1) **Service-level (preferred, central):** In `activeModelService.checkImageModelCanLoad`, if `opts?.override === true` and `fits === false`, return `overridable: false` (and/or a different error message), so `loadImageModel` throws a non-overridable `Error` and the UI won’t re-offer Load Anyway.

2) **Caller-level (localized):** Thread an `overrideAttempted` flag into `imageGenerationService._fail(...)` from `_ensureImageModelLoaded(...)` and only set `onLoadAnyway` when `overrideAttempted` is false. This prevents repeated Load Anyway prompts after a forced attempt already failed.

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


Grey Divider

Qodo Logo

Comment thread src/components/ModelFailureCard.tsx
Comment thread src/services/imageGenerationService.ts

@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)
src/components/ModelFailureCard.tsx (1)

41-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting a shared action-button helper.

The Retry and Load Anyway blocks are structurally identical (AnimatedPressable + Icon + Text, dismiss-then-callback). A small parameterized sub-render (icon name, color, label, handler, testID) would remove the duplication and make adding a third action later straightforward.

As per coding guidelines, "extend close matches with props rather than forking, and only build new when nothing fits."

♻️ Sketch of a shared action renderer
+function ActionButton({ icon, color, label, onPress, accessibilityLabel, testID, styles }: {
+  icon: string; color: string; label: string; onPress: () => void;
+  accessibilityLabel: string; testID: string; styles: ReturnType<typeof createStyles>;
+}) {
+  return (
+    <AnimatedPressable onPress={onPress} style={styles.actionButton} accessibilityLabel={accessibilityLabel} testID={testID}>
+      <Icon name={icon} size={12} color={color} style={styles.actionIcon} />
+      <Text style={[styles.actionText, { color }]}>{label}</Text>
+    </AnimatedPressable>
+  );
+}
🤖 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 `@src/components/ModelFailureCard.tsx` around lines 41 - 65, The Retry and Load
Anyway action blocks in ModelFailureCard are duplicated, so extract a small
shared action renderer/helper to handle the common AnimatedPressable, Icon, and
Text layout plus dismiss-then-callback behavior. Parameterize it with the
action’s icon name, color, label, handler, and testID, and then use that helper
for both failure.onRetry and failure.onLoadAnyway so adding future actions stays
simple.

Source: Coding guidelines

🤖 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 `@src/components/ModelFailureCard.tsx`:
- Around line 41-65: The Retry and Load Anyway action blocks in ModelFailureCard
are duplicated, so extract a small shared action renderer/helper to handle the
common AnimatedPressable, Icon, and Text layout plus dismiss-then-callback
behavior. Parameterize it with the action’s icon name, color, label, handler,
and testID, and then use that helper for both failure.onRetry and
failure.onLoadAnyway so adding future actions stays simple.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 19f6f709-8060-47ee-be34-debb476aeba4

📥 Commits

Reviewing files that changed from the base of the PR and between b226246 and 9b6a6bc.

📒 Files selected for processing (7)
  • __tests__/integration/generation/imageGenerationFlow.test.ts
  • __tests__/unit/components/ModelFailureCard.test.tsx
  • __tests__/unit/services/modelFailureHandler.test.ts
  • src/components/ModelFailureCard.tsx
  • src/services/imageGenerationService.ts
  • src/services/modelFailureHandler.ts
  • src/stores/modelFailureStore.ts

alichherawalla and others added 3 commits July 8, 2026 09:05
… overridable

Addresses PR review (qodo): after "Load Anyway" retried with { override: true }, a
residency survival-floor refusal still threw OverridableMemoryError, so the failure
card kept re-offering "Load Anyway" as a repeated no-op (the image path re-runs the
same request, so it could loop).

Decide overridability at the owning service, once: checkImageModelCanLoad now returns
overridable:false when the refusal persists under override (the copy also says "even
after freeing other models"). The text loadTextModel throw is aligned to the same
invariant — a first refusal is overridable, a refusal under override is a plain
non-overridable Error — so the rule is single-sourced across both modalities.

Tests: unit (checkImageModelCanLoad flips overridable with the override flag, incl. the
false branch) + integration (pressing Load Anyway, then the forced retry failing as a
hard limit, leaves the card with no onLoadAnyway — the user-facing outcome).

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

Addresses PR review (qodo): a UI change must reference the design specification. Adds
a header note pointing to DESIGN_PHILOSOPHY / DESIGN_PHILOSOPHY_SYSTEM /
VISUAL_HIERARCHY_STANDARD and restating the token/weight/vector-icon rules the actions
row follows.

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

Addresses PR review (CodeRabbit): the two action blocks were structurally identical
(AnimatedPressable + Icon + Text, dismiss-then-callback). Extract one parameterized
ActionButton (icon/color/label/testID/onPress) so they can't drift and a third action
is a one-liner - per the reuse-before-building guideline. Behavior + testIDs unchanged;
card tests pass.

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

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Addressed the nitpick in f8310e5 — extracted a shared ActionButton (icon/color/label/testID/onPress) and routed both Retry and Load Anyway through it, so the two blocks can't drift and a third action stays a one-liner. Behavior and testIDs are unchanged; the card unit tests still pass.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cd /repo 2>/dev/null || true
git fetch origin f8310e5a --depth=1 2>&1 | tail -5
git show f8310e5a --stat 2>&1 | head -20
git show f8310e5a -- src/components/ModelFailureCard.tsx 2>&1 | head -200

Length of output: 5076


@alichherawalla Confirmed — I reviewed commit f8310e5 and the refactor looks solid. The new ActionButton cleanly consolidates the icon/label/color/testID/onPress pattern, and both call sites now correctly preserve:

  • Original testIDs (model-failure-retry-${failure.modelType}, model-failure-load-anyway-${failure.modelType})
  • The dismiss-then-callback ordering
  • Conditional retry label ("Free memory & Retry" vs "Retry")
  • Distinct colors per action (primary vs error)

This is exactly the kind of consolidation I was hoping for — nicely done. Thanks for addressing it.

1 similar comment
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cd /repo 2>/dev/null || true
git fetch origin f8310e5a --depth=1 2>&1 | tail -5
git show f8310e5a --stat 2>&1 | head -20
git show f8310e5a -- src/components/ModelFailureCard.tsx 2>&1 | head -200

Length of output: 5076


@alichherawalla Confirmed — I reviewed commit f8310e5 and the refactor looks solid. The new ActionButton cleanly consolidates the icon/label/color/testID/onPress pattern, and both call sites now correctly preserve:

  • Original testIDs (model-failure-retry-${failure.modelType}, model-failure-load-anyway-${failure.modelType})
  • The dismiss-then-callback ordering
  • Conditional retry label ("Free memory & Retry" vs "Retry")
  • Distinct colors per action (primary vs error)

This is exactly the kind of consolidation I was hoping for — nicely done. Thanks for addressing it.

@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 (2)
src/services/activeModelService/index.ts (1)

166-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a text-model equivalent of the image overridable test.

Only the image path (checkImageModelCanLoad) has dedicated unit tests for the first-refusal-vs-override-retry Error-type branching. The text path's equivalent branch (Lines 166-171) currently has no matching coverage in the supplied files.

Want me to draft a doLoadTextModelLocked override test mirroring activeModelService.imageOverridable.test.ts?

🤖 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 `@src/services/activeModelService/index.ts` around lines 166 - 171, Add unit
coverage for the override-vs-first-refusal branching in doLoadTextModelLocked,
matching the existing image-path behavior tested for checkImageModelCanLoad.
Create a text-model test that verifies the initial insufficient-memory refusal
throws OverridableMemoryError, then a retry with opts.override set throws a
plain Error from the same room.fits failure path. Mirror the structure and
assertions used in activeModelService.imageOverridable.test.ts so the text path
is covered with the same first-refusal/override semantics.
__tests__/unit/services/activeModelService.imageOverridable.test.ts (1)

39-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: add an override: true + fits: true case.

Only override: false is exercised for the success path; adding an override: true + fits: true case would confirm overriding never blocks a load that already fits.

🤖 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__/unit/services/activeModelService.imageOverridable.test.ts` around
lines 39 - 43, The success path in activeModelService.imageOverridable tests
only covers check({ override: false }) with makeRoomFor returning fits: true, so
add a complementary case in activeModelService.imageOverridable.test.ts that
calls check({ override: true }) and asserts canLoad stays true when the gate
already fits. Reuse the existing check and makeRoomFor mocks in the same
describe block so the new test verifies override does not block a load that
already fits.
🤖 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 `@src/services/activeModelService/index.ts`:
- Around line 166-171: The first-refusal message in activeModelService’s memory
check is using the retry wording for both branches. Update the `!room.fits` path
so the non-override case throws the shorter initial refusal copy, and keep the
existing “even after freeing other models” text only for the `opts?.override`
branch in the `OverridableMemoryError`/`Error` split.

---

Nitpick comments:
In `@__tests__/unit/services/activeModelService.imageOverridable.test.ts`:
- Around line 39-43: The success path in activeModelService.imageOverridable
tests only covers check({ override: false }) with makeRoomFor returning fits:
true, so add a complementary case in activeModelService.imageOverridable.test.ts
that calls check({ override: true }) and asserts canLoad stays true when the
gate already fits. Reuse the existing check and makeRoomFor mocks in the same
describe block so the new test verifies override does not block a load that
already fits.

In `@src/services/activeModelService/index.ts`:
- Around line 166-171: Add unit coverage for the override-vs-first-refusal
branching in doLoadTextModelLocked, matching the existing image-path behavior
tested for checkImageModelCanLoad. Create a text-model test that verifies the
initial insufficient-memory refusal throws OverridableMemoryError, then a retry
with opts.override set throws a plain Error from the same room.fits failure
path. Mirror the structure and assertions used in
activeModelService.imageOverridable.test.ts so the text path is covered with the
same first-refusal/override semantics.
🪄 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: bae4d47d-b5b4-4932-85c4-0385361b27f0

📥 Commits

Reviewing files that changed from the base of the PR and between 9b6a6bc and 5a0b18f.

📒 Files selected for processing (4)
  • __tests__/integration/generation/imageGenerationFlow.test.ts
  • __tests__/unit/services/activeModelService.imageOverridable.test.ts
  • src/components/ModelFailureCard.tsx
  • src/services/activeModelService/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/ModelFailureCard.tsx
  • tests/integration/generation/imageGenerationFlow.test.ts

Comment thread src/services/activeModelService/index.ts Outdated
alichherawalla and others added 2 commits July 8, 2026 10:13
…dge, stop control

Tests for the pro streaming-TTS stop fix (off-grid-ai/mobile-pro#18), which live here so
they can import both core and pro:
- streamingSpeech unit: stopStreamingSpeechForTurn suppresses re-engagement for the turn;
  isStreaming projects true/false; a new turn re-enables; and stopping mid-segment never
  advances to another segment or releases the engine (fails-before/passes-after of the
  on-device wedge).
- streamingStateMachine integration: the user-stop [TTS-SM] trace is a clean abort with no
  "engine wedged → release" and release() never called.
- PlaybackControls (RNTL): STOP glyph for a fileless synth (streaming/re-synth/preparing),
  pause/play for file-backed, always-controllable, and the usePlaybackState.isThisSynth
  derivation matrix (streaming / re-synth / paused / file-backed / other-message / idle).

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

Pauses on a streaming/fileless-synth TTS now STOP cleanly instead of wedging + releasing
the Kokoro engine (which bricked all later playback). See off-grid-ai/mobile-pro#18.

Co-Authored-By: Claude Opus 4.8 (1M context) <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__/unit/audio/streamingSpeech.test.ts (1)

163-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Timeout override and hung mock implementation aren't restored after the test.

_setSpeakTimeoutForTest(20) and mockEngine.speak.mockImplementation(() => new Promise(() => {})) mutate shared module/mock state without any afterEach/cleanup. If further tests are appended after this suite in the same file, they'd inherit the 20ms timeout and the never-resolving speak mock, causing confusing intermittent failures.

♻️ Suggested cleanup
   it('stopping mid-segment does NOT advance to another segment or release the engine (no wedge)', async () => {
     _setSpeakTimeoutForTest(20); // fail fast instead of the real 15s
     // Engine can't complete (as if paused) — speak hangs, then times out.
     mockEngine.speak.mockImplementation(() => new Promise<void>(() => { /* never settles */ }));
     ...
     expect(isStreamingSpeechActive()).toBe(false);
+    _setSpeakTimeoutForTest(15000); // restore default to avoid leaking into later tests
   });
🤖 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__/unit/audio/streamingSpeech.test.ts` around lines 163 - 165, The
test in streamingSpeech.test.ts mutates shared state by overriding the speak
timeout via _setSpeakTimeoutForTest and replacing mockEngine.speak with a
never-resolving implementation, but never restores either setting. Update the
affected test block around the speak timeout and mockEngine.speak setup to use
cleanup (for example in afterEach or a local restore pattern) so the original
timeout and mock behavior are reset after this test, preventing later tests in
the same suite from inheriting the 20ms timeout or hung mock.
🤖 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__/unit/audio/streamingSpeech.test.ts`:
- Around line 163-165: The test in streamingSpeech.test.ts mutates shared state
by overriding the speak timeout via _setSpeakTimeoutForTest and replacing
mockEngine.speak with a never-resolving implementation, but never restores
either setting. Update the affected test block around the speak timeout and
mockEngine.speak setup to use cleanup (for example in afterEach or a local
restore pattern) so the original timeout and mock behavior are reset after this
test, preventing later tests in the same suite from inheriting the 20ms timeout
or hung mock.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ede74ba-ce04-483f-94d9-7c8ab83d120b

📥 Commits

Reviewing files that changed from the base of the PR and between f8310e5 and 141835c.

📒 Files selected for processing (4)
  • __tests__/integration/audio/streamingStateMachine.test.ts
  • __tests__/rntl/components/PlaybackControls.test.tsx
  • __tests__/unit/audio/streamingSpeech.test.ts
  • pro

…" (text path)

Addresses PR review (CodeRabbit): the text loadTextModel refusal used the retry
wording ("even after freeing other models") for BOTH branches, so the FIRST refusal
read like a post-eviction message. Differentiate to match the image path: the initial
(overridable) refusal is the shorter copy; only the override-retry (hard-limit) refusal
keeps "even after freeing other models".

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

Copy link
Copy Markdown
Collaborator Author

@coderabbitai addressed in 18e36eb — the text loadTextModel refusal now differentiates the two branches (first/overridable refusal = shorter copy; override-retry hard-limit = "even after freeing other models"), matching the image path. lint + tsc clean, 106 related tests green.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cd /repo 2>/dev/null || true
git fetch origin 18e36eb5 --depth=1 2>&1 | tail -5
git show 18e36eb5 --stat 2>&1 | head -20
git show 18e36eb5 -- src/services/activeModelService/index.ts 2>&1 | head -150

Length of output: 3002


@alichherawalla Confirmed — verified commit 18e36eb. The loadTextModel refusal now correctly differentiates:

  • First refusal (no override): OverridableMemoryError with the shorter "Not enough free memory to load this model. Close other apps or choose a smaller model." — no false claim of prior eviction attempts.
  • Override-retry refusal (hard limit hit): plain Error with "Not enough free memory to load this model, even after freeing other models. Close other apps or choose a smaller model." — accurately reflects that freeing was already attempted.

This now matches the image path's messaging semantics. Nice catch and clean fix.

(๑˃̵ᴗ˂̵)و

alichherawalla and others added 9 commits July 8, 2026 11:59
…ilure card

The flat "Model unloaded to free memory — tap to continue" bar was rendered ABOVE the
rounded ModelFailureCard, so a flat full-width bar capped a rounded card — looks wrong.
Swap the order in ChatMessageArea: the rounded failure card sits higher, the flat
snackbar drops directly above the composer. Pure render-order change, no logic.

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

Observed live on SM7250 (OnePlus Nord): app recommends a QNN model then the native
process crashes "may not support this model's backend". Root cause: classifySmNumber's
'min' catch-all marks every Snapdragon NPU-capable, so getQualcommImageRec recommends
qnn for SoCs that can't run it. Deferred (needs a curated SoC allowlist + device rounds),
tracked with evidence + fix direction rather than a rushed capability change.

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

On-device (OnePlus Nord) an image model failed to load with a MISLEADING "your device
(SM7250) may not support this model's backend" error. Root cause was NOT the backend: the
downloaded mnn model was missing pos_emb.bin + clip_v2.mnn.weight — a partial unzip that
was still marked `_ready` because nothing validated the extraction. The native server then
died (code 1) and the generic fallback message blamed the SoC.

A — never mark a partial extraction `_ready`:
- New src/utils/imageModelIntegrity.ts: backend-agnostic completeness rule (required base
  files pos_emb.bin/token_emb.bin/tokenizer.json + primary unet + MNN split-weight pairing:
  every *.mnn must have its non-zero *.mnn.weight — this catches the dropped 156MB
  clip_v2.mnn.weight). Pure `checkImageModelFiles` + IO `validateImageModelDir` +
  `ensureImageExtractionComplete` (re-unzip once, else throw).
- Wired into the primary zip download finalize and the resume path (whose old
  `validateModelDir` only checked "dir non-empty", so it passed a 9-of-11-file model).

B — honest failure for an already-broken model:
- New ImageModelIncompleteError (typed, NOT overridable). activeModelService.loadImageModel
  validates the model dir before the native load and throws it, so the failure card says
  "re-download" instead of the misleading "Load Anyway"/SoC message. Missing files can't be
  fixed by forcing the load, so Load Anyway is correctly withheld.

Verified on device: restoring the two files let the GPU model load + generate.

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

On the first run the progress card read "GPU optimization in progress... (6/8)" even as
real diffusion steps advanced — it looked like generation hadn't started. Once steps are
advancing it IS generating: show "Generating image (N/steps)" with a one-time "(optimizing
GPU, one-time)" aside on the first run, instead of leading with "optimization". The initial
pre-step first-run notice (~120s one-time) is unchanged.

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

- imageModelIntegrity unit: reproduces the exact on-device bug (pos_emb.bin +
  clip_v2.mnn.weight dropped → incomplete), split-weight pairing, zero-byte-as-missing,
  qnn unet.bin, coreml non-empty-only.
- modelFailureHandler: an ImageModelIncompleteError is NOT overridable (no Load Anyway →
  re-download), vs the memory-gate OverridableMemoryError which IS (the OP repro).
- imageGenerationFlow: first-run mid-step label is "Generating image (6/20)" with the
  one-time optimize aside, never "GPU optimization in progress".
- gaps: log the first-run inline-preview native-emission behaviour to revisit.

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

Self-review follow-up: resume had its own assertResumeExtractionComplete (validate→throw)
duplicating ensureImageExtractionComplete from the integrity module — and inconsistently
(no re-unzip retry). Route both resume unzip sites through the single shared seam so the
completeness gate is defined once and resume also gets the re-unzip-once resilience. Also
moved a stray mid-file logger import to the top.

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

On a non-NPU device (GPU/mnn path) a full SD1.5 model is a hard trade the user must steer,
and the old UI let them fall into both traps: 8 steps → muddy; 512x512 → ~18 min on a
mid-tier GPU; 128x128 → 43s but GARBAGE (SD1.5 is incoherent below ~256 — it's not a
"smaller image", it's broken).

- New utils/imageGenAdvice: pure, data-driven rule (branch on backend as data) — advises
  only on the mnn path: raise steps (<20), lower size (>256, for speed), or raise size
  (<256, which is garbage not smaller). NPU/CoreML get no advice (fast + fixed-shape).
- New ImageGenAdviceCard in the image settings: reads settings live, renders the tips as
  the user moves the Steps/Size sliders. Design tokens + Feather icons + brand voice.
- Image Size slider floor raised 128 → 256 (128/192 only ever produced garbage for these
  models); description states the 256-fast / 512-detailed-but-slow trade.
- Generation floors width/height to 256 (Math.max) so a STALE persisted sub-256 setting
  can't reach the pipeline — the user never sees garbage, only "reduced size".

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

- imageGenAdvice unit: mnn-only; raiseSteps (<20), lowerSize (>256), raiseSize (<256 =
  garbage, the 128 case), quiet at the 256/20 sweet spot, no false raiseSize at width 0.
- ImageGenAdviceCard (RNTL): hidden off-mnn + at sweet spot; shows the right tip per
  setting, driving the real store + real rule.
- imageGenerationFlow: a stale 128 setting is floored to 256 before the native call; a
  valid 512 passes through unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new extraction-completeness + load-time integrity checks run real RNFS validation.
Four pre-existing suites (imageDownloadActions/resume, imageDownloadRecovery,
activeModelService) mock RNFS/services and never lay a complete model dir on disk, so the
new gate threw ImageModelIncompleteError where they expected success. Those suites cover
download/residency ORCHESTRATION, not the completeness rule (which has its own dedicated
tests in imageModelIntegrity.test + imageGenerationFlow), so mock the integrity boundary to
"complete" in each. Full suite green (330 suites, 6960 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alichherawalla and others added 5 commits July 8, 2026 14:58
…new modules

- jest.config: collectCoverageFrom now includes pro/** (measured, gated on proExists);
  pro carved into its own aggregate threshold group (its authoritative gate is the pro
  repo CI — core only runs pro-dependent suites, ~18%); new standalone modules
  (imageModelIntegrity, imageGenAdvice, modelLoadErrors, ImageGenAdviceCard) held to 100%.
- Move ImageGenAdviceCard out of the settings modal INTO the chat (ChatMessageArea, beside
  ModelFailureCard) so a user hitting slow/garbled generations actually sees the tips;
  add a session dismiss. Discoverability fix.
- Tests to 100% on the new modules: imageModelIntegrity IO wrappers (RNFS/unzip boundaries
  only), ImageModelIncompleteError + guard branches, advice-card dismiss + nullish, and a
  B false-branch wiring test (loadImageModel REFUSES an incomplete model — asserts the
  outcome: throws + native load never happens). Stub the new barrel export in the two
  chat-screen suites' component mocks.
- CLAUDE.md: document the coverage direction (measure pro; new code 100%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c (18% → 60%)

Phase 1 of the pro-coverage push (release blocked until pro is robust). 11 real,
minimal-mock test suites (286 cases) driving the actual pro logic — mocks ONLY at genuine
boundaries (network transport, OAuth/secure-storage, native audio-api, calendar), asserting
observable OUTCOMES/state and both branches (deleting the impl fails them):
- mcp: schemaTrim (pure, 0 mocks), mcpClient (real JSON-RPC over a scripted XHR fake),
  mcpService (real stores + service, McpClient/oauth boundaries stubbed as dumb data),
  oauthClient/pkce/tokenStore, mcpStore (real zustand), adapters, McpToolExtension.extra.
- audio: EngineRegistry (DI stub engines implementing the interface — no native), audioFilePlayer
  (AudioContext boundary only). demo: demoMode.

Config:
- jest: collectCoverageFrom now includes pro/** (excl index/d.ts/tests); anchored the
  testPathIgnorePatterns '/pro/' → '<rootDir>/pro/' so it ignores only the submodule's own
  suite, NOT this repo's __tests__/pro/** (the bare pattern swallowed both). pro carved into
  its own coverage group, floor ratcheted 15→55/45/45/55 (measured ~60/50/52/61).
- tsconfig: add @offgrid/pro/* path so tsc resolves the alias (jest/metro already did).

EmailCalendarExtension: the agent's file was a redundant duplicate of the existing suite
(already 93%/100%) and leaked a jest.spyOn(Linking) into the next worker file — removed it.

Full suite green: 342 suites, 7264 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 of the pro-coverage push. 8 real, minimal-mock suites driving the actual engine
+ audio logic — the ONLY mocked boundary is the native runtime each engine wraps
(react-native-executorch / llama.rn / react-native-audio-api / RNFS / background-download),
stubbed as dumb recorders. The real state machines, event emitters, size-weighted progress
math, session-abort, and error cascades all run; assertions check phase transitions,
emitted event payloads, and thrown errors — both branches — never "was called" alone:
- engines: Qwen3TTSEngine (100% stmts), OuteTTSEngine, KokoroEngine.extra (uncovered branches).
- audio logic: ttsPlayback.extra (file path + pause/resume/stop routing via the REAL
  playback machine), ttsStore.extra (store actions + residency canEvict veto),
  ttsEngineSubscription.extra, OnDeviceEngineEmitter, useAudioProgressCaption.

Verified: full suite green (349 suites / 7434), no cross-suite pollution (spies restored,
no leaked globals), tsc clean, eslint clean. Mutation-checked (e.g. breaking Qwen3's
completeness ratio fails its tests). pro floor ratcheted 55→70/58/60/72 (measured
74/61/64/76).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o fields; pro bump

Phase 3 of the pro-coverage push — the release-blocking robustness pass is done: pro is
now 90% stmts / 83% branch / 85% func / 91% lines (from 18% at the start).

13 real RNTL suites rendering the ACTUAL pro UI against the REAL stores (useMcpStore /
useTTSStore driven by setState), asserting what the user sees + the resulting store STATE
after presses — never "a fn was called". Mocks only genuine shims (vector-icons,
Animated/Sheet wrappers, theme, navigation). Both branches of each conditional render +
handler covered; no cross-suite pollution (spies restored, stores reset in afterEach):
- MCP: McpServerModal (100%), McpToolsScreen, McpGuideScreen, McpToolPickerSheet (100%),
  McpSettingsSection.
- audio UI: EngineBridge, AudioModeLayout, MessageAudioMode, TTSButton, TTSSection,
  VoicePickerPopover, QuickSettingsAudioRow, AudioEmptyState.

Type gaps the new tests exposed (pre-existing; pro UI was never tsc-checked from core):
- core Message was missing audioPath / waveformData / audioDurationSeconds — genuinely
  read + set for audio-mode messages (voice notes). Added them (all optional, additive).
- pro estimateClipDuration transcript param widened to optional (fixed on the pro branch;
  submodule bumped 103cd2ee → 9035ec50).

pro coverage floor ratcheted 70→88/80/82/89. Full suite green: 362 suites / 7577 tests.

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

The pre-push --findRelatedTests run fans hundreds of Message-importing suites across all
workers; under that contention RNTL's 1s default waitFor timeout starves and a couple
async-hook suites (e.g. useVoiceDownloadItems) flake with a spurious timeout despite
passing in isolation (1.6-2.9s). Raise the global asyncUtilTimeout to 5s — load-tolerant,
still well under the 10s jest testTimeout, no assertion changed. Removes the class of
load flakiness surfaced when src/types entered the changed-file set.

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

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review please — 10+ new commits since the last pass (pro test-coverage 18%→90%, Message type fields, jest/tsconfig coverage config, asyncUtilTimeout flake fix).

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

@alichherawalla Got it — kicking off a full review of the PR to cover all the new commits since the last pass, including the pro submodule test-coverage bump (18%→90%), the Message type field additions, the jest/tsconfig coverage configuration changes, and the asyncUtilTimeout flake fix.

✅ Action performed

Full review finished.

…e audio comment

Review follow-ups (SOLID/DRY pass):
- Import SWEET_SPOT_SIZE from imageGenAdvice into the size-slider min + the generation
  Math.max floor instead of re-hardcoding 256 in three places, so the guard value and the
  advice rule share one source of truth.
- Clarify the Message audio-fields comment: audioDurationSeconds on Message is distinct
  from the same-named field on MediaAttachment (not a duplicate — different interfaces).

Co-Authored-By: Claude Opus 4.8 (1M context) <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: 6

🧹 Nitpick comments (5)
__tests__/unit/audio/streamingSpeech.test.ts (1)

162-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Real-timer wait after short timeout risks flakiness under CI load.

_setSpeakTimeoutForTest(20) followed by a real setTimeout(..., 40) gives only a 20ms margin for the timeout to fire and the drain to settle. Under CI contention this window can be too tight, causing an intermittent failure where mockEngine.speak hasn't yet been called a second time or the timeout hasn't fired.

Consider using Jest fake timers (jest.useFakeTimers() + jest.advanceTimersByTime) for deterministic timing instead of a wall-clock sleep.

🤖 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__/unit/audio/streamingSpeech.test.ts` around lines 162 - 181, The
streamingSpeech timeout test is using a real wall-clock sleep after
`_setSpeakTimeoutForTest(20)`, which makes the timing-dependent assertion flaky
under CI load. Update the `stopping mid-segment does NOT advance to another
segment or release the engine (no wedge)` test to use Jest fake timers instead
of `setTimeout`, and advance time deterministically so the hung
`mockEngine.speak`, timeout handling, and `stopStreamingSpeechForTurn` behavior
are exercised without relying on a narrow real-time window.
__tests__/pro/audio/engines/OuteTTSEngine.test.ts (1)

279-291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tautological assertion doesn't verify audio resource cleanup.

expect(mockAudioApi.closed).toBeGreaterThanOrEqual(0) is always true (a non-negative counter starting at 0), so it never actually confirms whether the native AudioContext gets closed on release(). This leaves the audio resource-cleanup path effectively unasserted in this test.

♻️ Proposed fix: assert an exact expected value
-    expect(mockAudioApi.closed).toBeGreaterThanOrEqual(0);
+    // If no AudioContext is created until the first speak(), assert it stays untouched:
+    expect(mockAudioApi.closed).toBe(0);
+    // OR, if release() is expected to close an existing context, assert it was closed once:
+    // expect(mockAudioApi.closed).toBe(1);
🤖 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__/pro/audio/engines/OuteTTSEngine.test.ts` around lines 279 - 291,
The test for OuteTTSEngine.release is currently using a tautological check on
mockAudioApi.closed, so it never proves the AudioContext was actually closed.
Update the assertion in the release path test to verify the exact expected
closed count after calling e.release(), using the existing OuteTTSEngine,
release, and mockAudioApi symbols to confirm native audio cleanup really
happened.
src/services/activeModelService/index.ts (1)

259-270: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a shared memory-refusal helper.

The text path (lines 165-168) and this image path now duplicate the same override/non-override message-construction shape (opts?.override ? "...even after freeing..." : "...short copy..."). This exact duplication is what caused the wording-drift bug flagged in the earlier review round. Consolidating into one small helper (parameterized by subject text and throw-vs-return usage) would keep both paths in sync automatically the next time either message needs to change.

♻️ Example shared helper
+function memoryRefusalMessage(subject: string, override: boolean | undefined): string {
+  return override
+    ? `Not enough memory to load ${subject}, even after freeing other models. Close other apps or choose a smaller model.`
+    : `Not enough memory to load ${subject}. Close other apps or choose a smaller model.`;
+}

Then both doLoadTextModelLocked and checkImageModelCanLoad build their message via memoryRefusalMessage(...) instead of inlining the ternary independently.

🤖 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 `@src/services/activeModelService/index.ts` around lines 259 - 270, Extract the
duplicated override/non-override memory refusal wording into a shared helper so
the text and image paths stay in sync. Add a small utility (for example, a
memoryRefusalMessage-style helper) and use it from both doLoadTextModelLocked
and checkImageModelCanLoad instead of inlining the same ternary in each place.
Keep the helper parameterized by the model/subject text and whether the caller
should throw or return an error so both paths produce identical messaging.
src/services/imageGenerationService.ts (1)

161-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication between onRetry and onLoadAnyway closures.

Both closures do the same thing — guard on this._lastParams and re-invoke this.generateImage(this._lastParams, ...) — differing only in the opts payload. Could be factored into a small retryLastParams(opts?) helper for a bit less repetition, but it's not blocking.

🤖 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 `@src/services/imageGenerationService.ts` around lines 161 - 186, The `_fail`
method in `imageGenerationService` repeats the same “retry last params” logic in
both `onRetry` and `onLoadAnyway`. Extract that shared `this._lastParams` guard
and `this.generateImage(...)` re-invocation into a small local helper inside
`_fail`, then have `onRetry` and `onLoadAnyway` call it with their respective
options so the behavior stays identical but duplication is removed.
src/types/index.ts (1)

232-238: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the stale parenthetical audioDurationSeconds is already defined on MediaAttachment, so this note reads like a duplicate-field warning in Message.

🤖 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 `@src/types/index.ts` around lines 232 - 238, Remove the stale parenthetical
note from the Message audio-mode field comment so it no longer implies
audioDurationSeconds is a duplicate or separately declared field. Update the
documentation around audioPath, waveformData, and audioDurationSeconds in the
Message type to describe the audio-mode payload clearly, and keep the comment
aligned with the existing MediaAttachment definition.
🤖 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__/pro/audio/ui/TTSButton.test.tsx`:
- Around line 142-157: The TTSButton press test is triggering an async store
update outside React’s testing boundary, which can make the assertion run before
all microtasks finish. Update the TTSButton test case to wrap the
`fireEvent.press` call in the same `act(async () => { ... })` pattern used
elsewhere in `TTSButton.test.tsx`, using the `handlePress`/`speak()` path as the
target behavior so the test waits for the full transition before asserting store
state.

In `@__tests__/pro/ui/McpSettingsSection.test.tsx`:
- Around line 29-48: The test setup in McpSettingsSection.test.tsx still
evaluates mods!.section and mods!.store even when describe.skip is used, so move
the ProToolsSettingsSection/useMcpStore destructuring behind an early if (!mods)
return guard or into the test body. Also, the type-only import aliases for
SectionModule and StoreModule force resolution of pro modules during tsc checks,
so add this test path to the exclusion list or provide a stub type declaration
for the pro package in open-core CI.

In `@__tests__/pro/ui/McpToolPickerSheet.test.tsx`:
- Around line 16-31: The theme mock in McpToolPickerSheet.test.tsx closes over a
non-mock-prefixed COLORS binding, which will break Jest transform. Rename COLORS
to mockColors or move the object directly inside the `@offgrid/core/theme`
jest.mock factory, and make sure useTheme returns that mock-prefixed value. Keep
the Feather icon shim unchanged; the fix is specifically in the mock data
referenced by useTheme.

In `@__tests__/pro/ui/McpToolsScreen.test.tsx`:
- Around line 43-58: The test setup in McpToolsScreen.test.tsx still uses a
direct relative type import for ScreenModule, which makes tsc resolve the
private pro/ module during public builds even though load() is wrapped in
try/catch. Change the type source so it does not reference the private submodule
path directly, or move this suite to the same pro-only type path pattern used by
the other private tests, keeping the existing load()/mods/describe.skip
structure intact.

In `@src/components/GenerationSettingsModal/ImageQualitySliders.tsx`:
- Around line 45-47: The ImageQualitySliders slider is receiving persisted
imageWidth values that can fall below the allowed minimum, so normalize or clamp
imageWidth before it is passed into the slider. Update the settings flow that
feeds ImageQualitySliders so stale values are coerced into the 256–512 range
before rendering, using the imageWidth field and its parent settings
loader/initializer to keep the slider handle within min={256}.

In `@src/utils/imageModelIntegrity.ts`:
- Around line 47-76: The completeness check in checkImageModelFiles only
validates MNN .weight companions, so missing graph files like clip_v2.mnn,
vae_decoder.mnn, and vae_encoder.mnn can still slip through. Update
checkImageModelFiles to explicitly require the MNN graph files as part of the
BASE_REQUIRED/requirePresent validation, alongside the existing split-weight
pairing logic, so both the graph and its matching .weight file are enforced.

---

Nitpick comments:
In `@__tests__/pro/audio/engines/OuteTTSEngine.test.ts`:
- Around line 279-291: The test for OuteTTSEngine.release is currently using a
tautological check on mockAudioApi.closed, so it never proves the AudioContext
was actually closed. Update the assertion in the release path test to verify the
exact expected closed count after calling e.release(), using the existing
OuteTTSEngine, release, and mockAudioApi symbols to confirm native audio cleanup
really happened.

In `@__tests__/unit/audio/streamingSpeech.test.ts`:
- Around line 162-181: The streamingSpeech timeout test is using a real
wall-clock sleep after `_setSpeakTimeoutForTest(20)`, which makes the
timing-dependent assertion flaky under CI load. Update the `stopping mid-segment
does NOT advance to another segment or release the engine (no wedge)` test to
use Jest fake timers instead of `setTimeout`, and advance time deterministically
so the hung `mockEngine.speak`, timeout handling, and
`stopStreamingSpeechForTurn` behavior are exercised without relying on a narrow
real-time window.

In `@src/services/activeModelService/index.ts`:
- Around line 259-270: Extract the duplicated override/non-override memory
refusal wording into a shared helper so the text and image paths stay in sync.
Add a small utility (for example, a memoryRefusalMessage-style helper) and use
it from both doLoadTextModelLocked and checkImageModelCanLoad instead of
inlining the same ternary in each place. Keep the helper parameterized by the
model/subject text and whether the caller should throw or return an error so
both paths produce identical messaging.

In `@src/services/imageGenerationService.ts`:
- Around line 161-186: The `_fail` method in `imageGenerationService` repeats
the same “retry last params” logic in both `onRetry` and `onLoadAnyway`. Extract
that shared `this._lastParams` guard and `this.generateImage(...)` re-invocation
into a small local helper inside `_fail`, then have `onRetry` and `onLoadAnyway`
call it with their respective options so the behavior stays identical but
duplication is removed.

In `@src/types/index.ts`:
- Around line 232-238: Remove the stale parenthetical note from the Message
audio-mode field comment so it no longer implies audioDurationSeconds is a
duplicate or separately declared field. Update the documentation around
audioPath, waveformData, and audioDurationSeconds in the Message type to
describe the audio-mode payload clearly, and keep the comment aligned with the
existing MediaAttachment definition.
🪄 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: 33f77333-df34-41a1-b8e7-25b34fd0b1aa

📥 Commits

Reviewing files that changed from the base of the PR and between b226246 and ee5774d.

📒 Files selected for processing (70)
  • CLAUDE.md
  • __tests__/integration/audio/streamingStateMachine.test.ts
  • __tests__/integration/generation/imageGenerationFlow.test.ts
  • __tests__/integration/models/activeModelService.test.ts
  • __tests__/integration/models/imageDownloadRecovery.test.ts
  • __tests__/pro/audio/EngineRegistry.test.ts
  • __tests__/pro/audio/OnDeviceEngineEmitter.test.ts
  • __tests__/pro/audio/audioFilePlayer.test.ts
  • __tests__/pro/audio/engines/KokoroEngine.extra.test.ts
  • __tests__/pro/audio/engines/OuteTTSEngine.test.ts
  • __tests__/pro/audio/engines/Qwen3TTSEngine.test.ts
  • __tests__/pro/audio/ttsEngineSubscription.extra.test.ts
  • __tests__/pro/audio/ttsPlayback.extra.test.ts
  • __tests__/pro/audio/ttsStore.extra.test.ts
  • __tests__/pro/audio/ui/AudioEmptyState.test.tsx
  • __tests__/pro/audio/ui/AudioModeLayout.test.tsx
  • __tests__/pro/audio/ui/EngineBridge.test.tsx
  • __tests__/pro/audio/ui/MessageAudioMode.test.tsx
  • __tests__/pro/audio/ui/QuickSettingsAudioRow.test.tsx
  • __tests__/pro/audio/ui/TTSButton.test.tsx
  • __tests__/pro/audio/ui/TTSSection.test.tsx
  • __tests__/pro/audio/ui/VoicePickerPopover.test.tsx
  • __tests__/pro/audio/useAudioProgressCaption.test.ts
  • __tests__/pro/demo/demoMode.test.ts
  • __tests__/pro/mcp/McpToolExtension.extra.test.ts
  • __tests__/pro/mcp/adapters.test.ts
  • __tests__/pro/mcp/mcpClient.test.ts
  • __tests__/pro/mcp/mcpService.test.ts
  • __tests__/pro/mcp/mcpStore.test.ts
  • __tests__/pro/mcp/oauthClient.test.ts
  • __tests__/pro/mcp/schemaTrim.test.ts
  • __tests__/pro/ui/McpGuideScreen.test.tsx
  • __tests__/pro/ui/McpServerModal.test.tsx
  • __tests__/pro/ui/McpSettingsSection.test.tsx
  • __tests__/pro/ui/McpToolPickerSheet.test.tsx
  • __tests__/pro/ui/McpToolsScreen.test.tsx
  • __tests__/rntl/components/ImageGenAdviceCard.test.tsx
  • __tests__/rntl/components/PlaybackControls.test.tsx
  • __tests__/rntl/onboarding/ChatScreenSpotlight.test.tsx
  • __tests__/rntl/screens/ChatScreen.test.tsx
  • __tests__/unit/audio/streamingSpeech.test.ts
  • __tests__/unit/components/ModelFailureCard.test.tsx
  • __tests__/unit/screens/ModelsScreen/imageDownloadActions.test.ts
  • __tests__/unit/screens/ModelsScreen/imageDownloadResume.test.ts
  • __tests__/unit/services/activeModelService.imageOverridable.test.ts
  • __tests__/unit/services/modelFailureHandler.test.ts
  • __tests__/unit/services/modelLoadErrors.test.ts
  • __tests__/unit/utils/imageGenAdvice.test.ts
  • __tests__/unit/utils/imageModelIntegrity.io.test.ts
  • __tests__/unit/utils/imageModelIntegrity.test.ts
  • docs/GAPS_BACKLOG.md
  • jest.config.js
  • jest.setup.ts
  • pro
  • src/components/GenerationSettingsModal/ImageQualitySliders.tsx
  • src/components/ImageGenAdviceCard.tsx
  • src/components/ModelFailureCard.tsx
  • src/components/index.ts
  • src/screens/ChatScreen/ChatMessageArea.tsx
  • src/screens/ModelsScreen/imageDownloadActions.ts
  • src/screens/ModelsScreen/imageDownloadResume.ts
  • src/services/activeModelService/index.ts
  • src/services/imageGenerationService.ts
  • src/services/modelFailureHandler.ts
  • src/services/modelLoadErrors.ts
  • src/stores/modelFailureStore.ts
  • src/types/index.ts
  • src/utils/imageGenAdvice.ts
  • src/utils/imageModelIntegrity.ts
  • tsconfig.json

Comment thread __tests__/pro/audio/ui/TTSButton.test.tsx
Comment on lines +29 to +48
type SectionModule = typeof import('@offgrid/pro/ui/McpSettingsSection');
type StoreModule = typeof import('@offgrid/pro/mcp/mcpStore');

function load(): { section: SectionModule; store: StoreModule } | null {
try {
return {
section: require(['..', '..', '..', 'pro', 'ui', 'McpSettingsSection'].join('/')),
store: require(['..', '..', '..', 'pro', 'mcp', 'mcpStore'].join('/')),
};
} catch {
return null;
}
}

const mods = load();
const maybe = mods ? describe : describe.skip;

maybe('ProToolsSettingsSection', () => {
const { ProToolsSettingsSection } = mods!.section;
const { useMcpStore } = mods!.store;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat .gitmodules 2>/dev/null
fd -t d '^pro$' -d 1
rg -n '"pro"|pro/\*\*|exclude' tsconfig.json

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## __tests__/pro/ui/McpSettingsSection.test.tsx\n'
cat -n __tests__/pro/ui/McpSettingsSection.test.tsx | sed -n '1,120p'

printf '\n## tsconfig.json\n'
cat -n tsconfig.json | sed -n '1,120p'

printf '\n## jest config files\n'
fd -a 'jest.config.*|jest.*config.*|package.json' .

printf '\n## package.json excerpt\n'
cat -n package.json | sed -n '1,220p'

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 12663


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## jest.config.js\n'
cat -n jest.config.js | sed -n '1,240p'

printf '\n## search for __tests__/pro or pro path exclusions\n'
rg -n "__tests__/pro|pro/\*\*|`@offgrid/pro`|describe\.skip" -S . --glob '!pro/**'

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 21176


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## __tests__/unit/tools/mcpPresets.test.ts\n'
cat -n __tests__/unit/tools/mcpPresets.test.ts | sed -n '1,120p'

printf '\n## __tests__/pro/ui/McpGuideScreen.test.tsx\n'
cat -n __tests__/pro/ui/McpGuideScreen.test.tsx | sed -n '1,120p'

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 10372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## declarations and stubs for `@offgrid/pro`\n'
rg -n "declare module '`@offgrid/pro`|`@offgrid/pro`" src __tests__ *.d.ts *.ts *.tsx --glob '!pro/**' --glob '!node_modules/**'

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 11393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## tsconfig files\n'
fd -a 'tsconfig*.json' .

printf '\n## search for pro-dependent files excluded from tsc\n'
rg -n "__tests__/pro/|proDependentTestPaths|exclude" tsconfig*.json .github package.json jest.config.js -S

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 767


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## github workflows\n'
fd -a '.github/workflows' .

printf '\n## search for tsc, pro checkout, and submodule handling in CI\n'
rg -n "tsc --noEmit|noEmit|submodule|checkout.*submodules|pro/package.json|proExists|git submodule|offgrid-pro" .github . -S --glob '!pro/**' --glob '!node_modules/**'

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 563


Guard the pro-only setup before the skipped describe

  • describe.skip still runs the callback, so mods!.section / mods!.store will throw when pro/ is missing. Move the destructuring behind if (!mods) return; or into each test.
  • typeof import('@offgrid/pro/...') still forces module resolution under tsc --noEmit; tsconfig.json excludes pro/** but not __tests__/pro/**, so this file also needs exclusion or a type stub in open-core CI.
🤖 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__/pro/ui/McpSettingsSection.test.tsx` around lines 29 - 48, The test
setup in McpSettingsSection.test.tsx still evaluates mods!.section and
mods!.store even when describe.skip is used, so move the
ProToolsSettingsSection/useMcpStore destructuring behind an early if (!mods)
return guard or into the test body. Also, the type-only import aliases for
SectionModule and StoreModule force resolution of pro modules during tsc checks,
so add this test path to the exclusion list or provide a stub type declaration
for the pro package in open-core CI.

Comment thread __tests__/pro/ui/McpToolPickerSheet.test.tsx Outdated
Comment thread __tests__/pro/ui/McpToolsScreen.test.tsx Outdated
Comment thread src/components/GenerationSettingsModal/ImageQualitySliders.tsx Outdated
Comment thread src/utils/imageModelIntegrity.ts
alichherawalla and others added 2 commits July 8, 2026 17:29
…ix theme consts

Addresses the CodeRabbit review pass:
- (Major) checkImageModelFiles only enforced the .weight side of split MNN assets, so a
  partial extraction that dropped a .mnn GRAPH itself (clip_v2.mnn / vae_decoder.mnn) but
  kept its .weight passed the completeness gate. Now explicitly require the clip graph
  (clip_v2.mnn OR clip.mnn) + vae_decoder.mnn for mnn — the graphs the native server always
  loads. vae_encoder stays optional (native adds --vae_encoder only if present). +5 tests
  (dropped-clip-graph, base-clip accepted, dropped-vae_decoder, vae_encoder-optional);
  module still 100% coverage.
- (Convention) rename the theme `COLORS` const → `mockColors` in the 3 UI test files that
  reference it inside a jest.mock factory. It passed (lazy access) but relying on a
  non-mock-prefixed binding in a hoisted factory is fragile; align with the repo convention.

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

Final CodeRabbit review nits:
- ImageQualitySliders: clamp the displayed value to >= SWEET_SPOT_SIZE so a stale persisted
  imageWidth (<256) doesn't render below the slider's own min. Matches the generation floor.
- TTSButton test: wrap the async speak() press in act() so store updates settle cleanly.
- McpToolsScreen test: type imports use the @offgrid/pro/* alias (consistent with siblings;
  the skippable runtime require stays relative). McpSettingsSection already guards via
  mods!/describe.skip — no change needed.

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

Copy link
Copy Markdown
Collaborator Author

Addressed the review pass (5187e11, plus earlier fixes):

  • (Major) imageModelIntegrity graph files — now require the clip graph (clip_v2.mnn/clip.mnn) + vae_decoder.mnn for mnn, not just their .weight (a dropped graph slipped the pairing loop). vae_encoder stays optional (native adds it only if present). +5 tests, module still 100%.
  • (Critical) COLORS in jest.mock factory — renamed → mockColors in the 3 theme-mocking UI tests (passed via lazy access, but aligned to the mock-prefix convention).
  • ImageQualitySliders — clamp displayed size to ≥256 for stale persisted values.
  • TTSButton test — async press wrapped in act().
  • McpToolsScreen test — type imports use the @offgrid/pro/* alias; runtime require stays skippable. McpSettingsSection already guards via mods!/describe.skip.

Full suite green (362 suites); tsc + lint clean.

Point the pro submodule at pro-main's merge commit for #18 (streaming-TTS STOP +
estimateClipDuration) instead of the pre-merge branch tip, so #498 references a commit
that lives on pro/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alichherawalla
alichherawalla merged commit 79e5e44 into main Jul 8, 2026
1 check passed
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