Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📝 WalkthroughWalkthroughAdds native mobile tag management with complete-note tag summaries, search, filtering, rename, delete, optimistic updates, offline queueing, and synchronization. Adds typed bulk tag services and persistence handling. Adds a scroll-driven collapsible tab bar across mobile routes. ChangesMobile tag management
Collapsible mobile tab bar
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant TagsScreen
participant useTagManagementMutations
participant NoteService
participant MobileSyncService
TagsScreen->>useTagManagementMutations: submit rename or delete
useTagManagementMutations->>NoteService: persist bulk tag operation online
useTagManagementMutations->>MobileSyncService: queue operation offline or after retryable failure
MobileSyncService->>NoteService: replay queued operation
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR StatusUpdated for PR #188 at 📊 Allure Test ReportContributing Workflows
Catalog: All reports 🤖 Android Build PanelCheck a box below to trigger a release build:
|
Deploying everfreenote with
|
| Latest commit: |
a60de0b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a65d2e98.everfreenote.pages.dev |
| Branch Preview URL: | https://feature-mobile-tag-managemen.everfreenote.pages.dev |
Qodana for JSIt seems all right 👌 No new problems were found according to the checks applied @@ Code coverage @@
+ 91% total lines covered
11792 lines analyzed, 10799 lines covered
+ 94% fresh lines covered
365 lines analyzed, 346 lines covered
# Calculated according to the filters of your coverage tool💡 Qodana analysis was run in the pull request mode: only the changed files were checked Contact Qodana teamContact us at qodana-support@jetbrains.com
|
|
@codex review please |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review please |
|
To use Codex here, create a Codex account and connect to github. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
ui/mobile/app/(tabs)/tags.tsx (1)
75-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsolidate duplicate rename validation.
performRename(lines 75-81) andhandleRenameSubmit(lines 95-100) repeat the same!renameTargetand empty-replacementchecks.handleRenameSubmitre-validates before callingperformRename, which validates again. If validation rules change later, a future edit can update one function and miss the other, causing inconsistent behavior between the merge-confirmation path and the direct-save path.Extract the shared validation into one helper and call it from both call sites.
♻️ Proposed refactor to share validation
+ const validateRenameInput = useCallback((): string | null => { + if (!renameTarget) return null + const replacement = renameValue.trim() + if (!replacement) { + setRenameError('Tag name cannot be empty') + return null + } + return replacement + }, [renameTarget, renameValue]) + const performRename = useCallback(() => { - if (!renameTarget) return - const replacement = renameValue.trim() - if (!replacement) { - setRenameError('Tag name cannot be empty') - return - } + if (!renameTarget) return + const replacement = validateRenameInput() + if (!replacement) return renameTag.mutate( { tag: renameTarget.name, replacement }, { onSuccess: () => setRenameTarget(null), onError: (mutationError) => { Alert.alert('Rename failed', mutationError.message) }, } ) - }, [renameTarget, renameValue, renameTag]) + }, [renameTarget, renameTag, validateRenameInput]) const handleRenameSubmit = useCallback(() => { - if (!renameTarget) return - const replacement = renameValue.trim() - if (!replacement) { - setRenameError('Tag name cannot be empty') - return - } + if (!renameTarget) return + const replacement = validateRenameInput() + if (!replacement) return const mergesExistingTag = allTags.some((tag) => ( tag.name.trim().toLocaleLowerCase() === replacement.toLocaleLowerCase() && tag.name.trim().toLocaleLowerCase() !== renameTarget.name.trim().toLocaleLowerCase() )) ... - }, [allTags, performRename, renameTarget, renameValue]) + }, [allTags, performRename, renameTarget, validateRenameInput])🤖 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 `@ui/mobile/app/`(tabs)/tags.tsx around lines 75 - 120, Extract the shared rename-target and trimmed replacement validation from performRename and handleRenameSubmit into a single helper, preserving the existing empty-name error behavior and return value needed to stop submission. Call that helper from both functions, while keeping merge detection and confirmation in handleRenameSubmit and mutation execution in performRename.ui/mobile/components/tags/AlphabeticalIndex.tsx (1)
7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUnused-parameter lint noise on callback type signatures. Codacy repeatedly flags underscore-prefixed parameter names inside function type declarations (not runtime parameters) as unused. The shared root cause is one ESLint configuration gap: the
no-unused-varsrule does not recognize named parameters in type-only positions, so it flags names that exist purely for documentation.
ui/mobile/components/tags/AlphabeticalIndex.tsx#L7:onSelect: (_letter: string | null) => void— remove the parameter name or configure the rule to ignore type-signature parameters.ui/mobile/components/tags/TagManagementCard.tsx#L8-L9:onPress: (_tag: string) => voidandonActions: (_tag: MobileTagSummary) => void— same fix.ui/mobile/components/tags/TagSearchInput.tsx#L8-L8:onChangeText: (_value: string) => void— same fix.ui/mobile/app/(tabs)/tags.tsx#L292-L292:onChangeValue: (_value: string) => void— same fix.Fix the ESLint rule configuration once (e.g., disable
no-unused-varsfor type-only parameter positions, or switch to unnamed parameter types like(value: string) => voidwithout the underscore convention, since it isn't needed here) rather than patching each file individually.🤖 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 `@ui/mobile/components/tags/AlphabeticalIndex.tsx` at line 7, Update the shared ESLint no-unused-vars configuration to ignore named parameters used only in TypeScript function type signatures, preserving normal unused-variable checks. Leave the callback type declarations unchanged in ui/mobile/components/tags/AlphabeticalIndex.tsx:7 (onSelect), ui/mobile/components/tags/TagManagementCard.tsx:8-9 (onPress and onActions), ui/mobile/components/tags/TagSearchInput.tsx:8 (onChangeText), and ui/mobile/app/(tabs)/tags.tsx:292 (onChangeValue); these sites require no direct changes.Source: Linters/SAST tools
🤖 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 `@core/services/notes.ts`:
- Around line 171-195: Extract the duplicated changed-note persistence logic
from renameTag and deleteTag into a shared helper that invokes updateNote
concurrently for all changed notes, using Promise.all or an appropriate
bounded-concurrency approach. Update both methods to reuse this helper while
preserving their existing tag transformation and return behavior.
In `@docs/ai/implementation/feature-mobile-tag-management.md`:
- Line 9: Update the placeholder status statement in the
feature-mobile-tag-management implementation guide to reflect the current
implemented structure and validation evidence already documented in the
surrounding sections. Remove the stale Phase 1/requirements-review wording while
preserving the existing implementation details.
In `@docs/ai/planning/feature-mobile-tag-management.md`:
- Line 89: Confirm the single verified full mobile regression total from the
final CI or Allure result, then use that same number in all affected documents:
replace the total in docs/ai/planning/feature-mobile-tag-management.md at lines
89-89, docs/ai/implementation/feature-mobile-tag-management.md at lines 27-27,
and docs/ai/testing/feature-mobile-tag-management.md at lines 60-60.
In `@ui/mobile/hooks/useTagManagement.ts`:
- Around line 45-52: Update the successful remote-load path in the note-fetching
flow to replace or reconcile the user-scoped local snapshot on every getAllNotes
result, including an empty array; do not guard databaseService.saveNotes(notes)
behind notes.length. Add a regression test covering an existing non-empty local
cache followed by an empty remote result and verify subsequent local fallback
returns no stale notes.
In `@ui/mobile/hooks/useTagManagementMutations.ts`:
- Around line 101-118: Update the catch handling in the tag mutation flow around
NoteService.renameTag/deleteTag to log the caught error and only call
queueBulkMutation() for genuinely retryable connectivity failures. Propagate
permanent server-side failures to the caller instead of treating them as
successfully queued, while preserving offline queuing for transient network
errors.
---
Nitpick comments:
In `@ui/mobile/app/`(tabs)/tags.tsx:
- Around line 75-120: Extract the shared rename-target and trimmed replacement
validation from performRename and handleRenameSubmit into a single helper,
preserving the existing empty-name error behavior and return value needed to
stop submission. Call that helper from both functions, while keeping merge
detection and confirmation in handleRenameSubmit and mutation execution in
performRename.
In `@ui/mobile/components/tags/AlphabeticalIndex.tsx`:
- Line 7: Update the shared ESLint no-unused-vars configuration to ignore named
parameters used only in TypeScript function type signatures, preserving normal
unused-variable checks. Leave the callback type declarations unchanged in
ui/mobile/components/tags/AlphabeticalIndex.tsx:7 (onSelect),
ui/mobile/components/tags/TagManagementCard.tsx:8-9 (onPress and onActions),
ui/mobile/components/tags/TagSearchInput.tsx:8 (onChangeText), and
ui/mobile/app/(tabs)/tags.tsx:292 (onChangeValue); these sites require no direct
changes.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f1f29b43-08e5-4fa6-854b-8eecf2f9b1c5
📒 Files selected for processing (50)
core/services/notes.tscore/tests/services/tag-management-notes.test.tscore/tests/unit/tag-mutation-queue.test.tscore/types/offline.tscore/utils/compactQueue.tsdocs/ai/deployment/feature-mobile-tag-management.mddocs/ai/design/feature-mobile-tag-management.mddocs/ai/implementation/feature-mobile-tag-management.mddocs/ai/monitoring/feature-mobile-tag-management.mddocs/ai/planning/feature-mobile-tag-management.mddocs/ai/requirements/feature-mobile-tag-management.mddocs/ai/testing/feature-mobile-tag-management.mdui/mobile/app/(tabs)/_layout.tsxui/mobile/app/(tabs)/index.tsxui/mobile/app/(tabs)/search.tsxui/mobile/app/(tabs)/settings.tsxui/mobile/app/(tabs)/tags.tsxui/mobile/components/search/SearchResultsList.tsxui/mobile/components/settings/AIIndexPanel.tsxui/mobile/components/tags/AlphabeticalIndex.tsxui/mobile/components/tags/TagManagementCard.tsxui/mobile/components/tags/TagSearchInput.tsxui/mobile/components/tags/index.tsxui/mobile/eslint.config.mjsui/mobile/hooks/index.tsui/mobile/hooks/useTagManagement.tsui/mobile/hooks/useTagManagementMutations.tsui/mobile/providers/CollapsibleTabBarProvider.tsxui/mobile/providers/index.tsui/mobile/services/sync.tsui/mobile/tests/component/aiIndexPanel.test.tsxui/mobile/tests/component/alphabeticalIndex.test.tsxui/mobile/tests/component/collapsibleTabBarProvider.test.tsxui/mobile/tests/component/tagSearchInput.test.tsxui/mobile/tests/component/useTagManagement.test.tsxui/mobile/tests/component/useTagManagementMutations.test.tsxui/mobile/tests/integration/bulkSelection.test.tsxui/mobile/tests/integration/notesScreen.test.tsxui/mobile/tests/integration/searchScreen.test.tsxui/mobile/tests/integration/searchScreenAI.test.tsxui/mobile/tests/integration/settingsScreen.test.tsxui/mobile/tests/integration/tagsScreen.test.tsxui/mobile/tests/tsconfig.jsonui/mobile/tests/unit/collapsibleTabBar.test.tsui/mobile/tests/unit/syncService.test.tsui/mobile/tests/unit/tagManagement.test.tsui/mobile/tsconfig.jsonui/mobile/types/lucide-react-native.d.tsui/mobile/utils/collapsibleTabBar.tsui/mobile/utils/tagManagement.ts
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
ui/mobile/services/sync.ts (1)
92-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an exhaustiveness check to
performSync's switch.The
switchonitem.operationhas nodefaultcase. If a new operation value is added to theMutationQueueItem['operation']union later but this dispatcher isn't updated,performSyncwould resolve without syncing or throwing, andOfflineSyncManagerwould treat that as a successful sync and remove the item from the queue, silently dropping the mutation.Add a default branch that throws, using a
nevercheck for compile-time exhaustiveness.♻️ Proposed fix
const performSync = async (item: MutationQueueItem, noteService: NoteService): Promise<void> => { switch (item.operation) { case 'create': await syncCreate(item, noteService) return case 'update': await syncUpdate(item, noteService) return case 'renameTag': case 'deleteTag': await syncTagMutation(item, noteService) return case 'delete': await noteService.deleteNote(item.noteId) return + default: { + const exhaustiveCheck: never = item.operation + throw new Error(`Unhandled mutation operation: ${exhaustiveCheck}`) + } } }🤖 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 `@ui/mobile/services/sync.ts` around lines 92 - 108, Update the switch in performSync to add a default branch that passes item.operation to a never-typed exhaustiveness check and throws for unsupported operations, ensuring unhandled MutationQueueItem operation values cannot resolve as successful syncs.
🤖 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 `@core/services/offlineSyncManager.ts`:
- Around line 136-145: Wrap the new this.queue.removeItems(discardedIds) call in
drainQueue with a try/catch, matching the failure-tolerant cleanup pattern
already used in processSyncItem. Log cleanup failures with console.warn and
allow the surrounding drainQueue flow to continue into the subsequent online
sync loop.
In `@core/utils/compactQueue.ts`:
- Around line 42-73: Update withPendingStatus to reset both lastError and
attempts when changing an item to pending, while preserving the existing status
reset. Ensure all callers, including the tag-mutation bypass and
create-consolidation branch in compactNoteOperations, produce a fresh retry
state without stale failure details.
In `@docs/ai/testing/feature-mobile-tag-management.md`:
- Around line 62-63: Update the testing evidence for tags.tsx to resolve the
92.3% changed-file coverage gap: either add tests covering the uncovered
executable lines or explicitly document those remaining lines and gaps in this
feature document before marking the evidence complete. Keep the reported
coverage and test results accurate.
In `@ui/mobile/hooks/useTagManagementMutations.ts`:
- Around line 23-45: The isRetryableBulkMutationError check must recognize
transient PostgrestError SQLSTATE codes, not only numeric HTTP statuses and Node
network codes. Update its string-code handling to classify connection/sqlclient
failures using the appropriate transient prefix such as 08, and include other
established transient PostgREST codes as needed while preserving the existing
checks.
In `@ui/mobile/services/database.ts`:
- Around line 170-189: Update saveNotesInTransaction to provide non-null
timestamp values before the notes INSERT: use a safe fallback for
note.created_at and fall back from note.updated_at to the resolved created_at
value when missing. Pass these resolved values to db.runAsync while preserving
the existing transaction and note-processing behavior.
---
Nitpick comments:
In `@ui/mobile/services/sync.ts`:
- Around line 92-108: Update the switch in performSync to add a default branch
that passes item.operation to a never-typed exhaustiveness check and throws for
unsupported operations, ensuring unhandled MutationQueueItem operation values
cannot resolve as successful syncs.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 09234b70-f766-47df-89a6-eecb58b807de
📒 Files selected for processing (22)
core/services/notes.tscore/services/offlineSyncManager.tscore/tests/unit/offline-sync-manager.test.tscore/utils/compactQueue.tsdocs/ai/design/feature-mobile-tag-management.mddocs/ai/implementation/feature-mobile-tag-management.mddocs/ai/planning/feature-mobile-tag-management.mddocs/ai/testing/feature-mobile-tag-management.mdui/mobile/app/(tabs)/_layout.tsxui/mobile/app/(tabs)/tags.tsxui/mobile/components/tags/AlphabeticalIndex.tsxui/mobile/components/tags/TagManagementCard.tsxui/mobile/components/tags/TagSearchInput.tsxui/mobile/hooks/useTagManagement.tsui/mobile/hooks/useTagManagementMutations.tsui/mobile/providers/CollapsibleTabBarProvider.tsxui/mobile/services/database.tsui/mobile/services/sync.tsui/mobile/tests/component/useTagManagement.test.tsxui/mobile/tests/component/useTagManagementMutations.test.tsxui/mobile/tests/unit/databaseService.test.tsui/mobile/tests/unit/syncService.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- docs/ai/planning/feature-mobile-tag-management.md
- ui/mobile/app/(tabs)/_layout.tsx
- ui/mobile/hooks/useTagManagement.ts
- docs/ai/implementation/feature-mobile-tag-management.md
- ui/mobile/tests/unit/syncService.test.ts
- ui/mobile/providers/CollapsibleTabBarProvider.tsx
- core/services/notes.ts
- ui/mobile/tests/component/useTagManagement.test.tsx
- docs/ai/design/feature-mobile-tag-management.md
There was a problem hiding this comment.
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 `@core/tests/unit/offline-sync-additional-branches.test.ts`:
- Around line 144-165: Update the “continues draining when compacted queue
cleanup fails” test fixture so DeterministicStorage.upsertQueue preserves
existing queue items and applies item-level upsert semantics instead of
replacing the entire queue. Strengthen the assertions to verify only the latest
item is passed to performSync, the superseded stale item is never synchronized,
and getPendingBatch is called the expected number of times rather than merely
once.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 94ab07c8-bf92-48d6-828d-ad5d88122417
📒 Files selected for processing (11)
AGENTS.mdcore/services/offlineSyncManager.tscore/tests/unit/compact-queue-additional-branches.test.tscore/tests/unit/offline-sync-additional-branches.test.tscore/tests/unit/tag-mutation-queue.test.tscore/utils/compactQueue.tsdocs/ai/testing/feature-mobile-tag-management.mdui/mobile/hooks/useTagManagementMutations.tsui/mobile/services/database.tsui/mobile/tests/component/useTagManagementMutations.test.tsxui/mobile/tests/unit/databaseService.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- ui/mobile/tests/unit/databaseService.test.ts
- ui/mobile/hooks/useTagManagementMutations.ts
- core/services/offlineSyncManager.ts
- core/utils/compactQueue.ts
- ui/mobile/services/database.ts
| it("continues draining when compacted queue cleanup fails", async () => { | ||
| const storage = new DeterministicStorage() | ||
| storage.queue = [ | ||
| makeItem("stale", { | ||
| noteId: "same-note", | ||
| clientUpdatedAt: "2026-01-01T00:00:01Z", | ||
| }), | ||
| makeItem("latest", { | ||
| noteId: "same-note", | ||
| clientUpdatedAt: "2026-01-01T00:00:02Z", | ||
| }), | ||
| ] | ||
| storage.removeQueueItems.mockRejectedValueOnce(new Error("cleanup failed")) | ||
| const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined) | ||
| const performSync = jest.fn().mockResolvedValue(undefined) | ||
| const manager = new OfflineSyncManager(storage, performSync, makeNetwork(false)) | ||
|
|
||
| await expect(manager.handleOnline()).resolves.toBeUndefined() | ||
|
|
||
| expect(warn).toHaveBeenCalledWith("Failed to remove compacted queue items:", expect.any(Error)) | ||
| expect(performSync).toHaveBeenCalledWith(expect.objectContaining({ id: "latest" })) | ||
| expect(storage.getPendingBatch).toHaveBeenCalled() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Make this cleanup-failure test model the queue contract.
OfflineSyncManager.drainQueue() uses item-level upsertQueue and then removes discarded IDs separately. However, DeterministicStorage.upsertQueue at Lines 24-26 replaces the entire queue. When removeQueueItems fails at Line 156, stale is already gone, so this test cannot detect replay of the superseded item with the real adapter behavior.
The assertions are also too weak. toHaveBeenCalled() does not prove a second pending-batch read, and toHaveBeenCalledWith(...) does not prove that stale was not synchronized. Use item-level upsert semantics in the fixture, assert that only latest is synchronized, and assert the expected batch-read count.
Proposed test hardening
- readonly upsertQueue = jest.fn(async (items: MutationQueueItem[]) => {
- this.queue = [...items]
- })
+ readonly upsertQueue = jest.fn(async (items: MutationQueueItem[]) => {
+ for (const item of items) {
+ const existingIndex = this.queue.findIndex((entry) => entry.id === item.id)
+ if (existingIndex === -1) {
+ this.queue.push(item)
+ } else {
+ this.queue[existingIndex] = item
+ }
+ }
+ })
- expect(performSync).toHaveBeenCalledWith(expect.objectContaining({ id: "latest" }))
- expect(storage.getPendingBatch).toHaveBeenCalled()
+ expect(performSync).toHaveBeenCalledTimes(1)
+ expect(performSync).toHaveBeenCalledWith(expect.objectContaining({ id: "latest" }))
+ expect(performSync).not.toHaveBeenCalledWith(expect.objectContaining({ id: "stale" }))
+ expect(storage.getPendingBatch).toHaveBeenCalledTimes(2)🤖 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 `@core/tests/unit/offline-sync-additional-branches.test.ts` around lines 144 -
165, Update the “continues draining when compacted queue cleanup fails” test
fixture so DeterministicStorage.upsertQueue preserves existing queue items and
applies item-level upsert semantics instead of replacing the entire queue.
Strengthen the assertions to verify only the latest item is passed to
performSync, the superseded stale item is never synchronized, and
getPendingBatch is called the expected number of times rather than merely once.
When removing compaction-discarded rows failed, item-level adapters kept the superseded row pending; syncing its superseding item anyway left the stale row free to replay in a later drain and silently revert newer edits. Hold back every note touched by an unremoved superseded row for the current drain (unaffected notes keep syncing) and let the next drain re-compact and retry the removal. The cleanup-failure test now mirrors the real adapters' item-level upsert semantics and asserts the full contract, addressing the remaining CodeRabbit review finding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workflow never set APP_VARIANT and exported only NEXT_PUBLIC_* web vars with placeholder fallbacks, so Expo resolved the dev variant with an empty Supabase config during the Gradle build and the release APK crashed on startup. - select the variant via APP_VARIANT/EXPO_PUBLIC_APP_VARIANT - feed variant-specific EXPO_PUBLIC_SUPABASE_* from secrets (stage falls back to the existing NEXT_PUBLIC_* web secrets), plus the stage editor WebView URL and prod public web origin - derive the webview-bundle NEXT_PUBLIC_* from the same target so the bundled editor and the native side always share one Supabase project - enable the test-login button for stage builds - fail fast: validate inputs, probe Supabase /auth/v1/health, and verify the resolved Expo config before Gradle runs Documented the env contract in docs/ai/deployment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… builds The verification metadata was generated on Windows, so the macOS aapt2 artifact and several junit-bom Gradle module-metadata files were missing and local builds failed verification. Hashes verified against repo1.maven.org and dl.google.com. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New Tests in Allure Report Allure ReportShow 1 new test |
|




Summary
Validation
npx ai-devkit@latest lint --feature mobile-tag-managementpassedgit diff --checkpassed before commitAndroid/iOS device smoke testing remains unavailable in this environment.
Codacy static-analysis quality-gate findings remain intentionally out of scope for this PR fix.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests