Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix backup tags on activity edit - #479

Merged
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit
Nov 24, 2025
Merged

Fix backup tags on activity edit#479
ovitrif merged 1 commit into
use-preactivity-metadatafrom
fix/backup-tags-on-edit

Conversation

@ben-kaufman

Copy link
Copy Markdown
Contributor

Fixes backup of tags when editing activity details.

Test: Go to an actitivy, edit its tags (add and remove), then back up the wallet seed, wipe the wallet, and restore it.

@ben-kaufman
ben-kaufmanforce-pushed the fix/backup-tags-on-edit branch from cec084b to 98c2620CompareNovember 24, 2025 14:44
@claude

claudeBot commented Nov 24, 2025

Copy link
Copy Markdown
Contributor

Code Review: Fix backup tags on activity edit

Thanks for this fix! This PR addresses an important issue with tag backup when editing activity details. Overall, the changes follow good architectural patterns and improve the codebase. Here's my detailed review:


Strengths

  1. Excellent Architectural Improvement

    • Properly moves business logic from ViewModels to the Repository layer
    • Follows the Repository pattern correctly as outlined in the project conventions
    • ActivityRepo now properly encapsulates tag-related operations with methods like getActivityTags(), addTagsToActivity(), and removeTagsFromActivity()
  2. Improved Error Handling

    • Consistent use of Result<T> type throughout
    • Proper use of .onSuccess {} and .onFailure {} patterns
    • Better error logging with context
  3. Consistent Patterns

    • All async operations properly use viewModelScope.launch(bgDispatcher) (not GlobalScope)
    • StateFlow patterns maintained correctly
    • Error messages are informative and include context
  4. Repository Encapsulation

    • ViewModels no longer directly access coreService.activity - they go through ActivityRepo instead
    • This ensures that tag changes trigger notifyActivitiesChanged() which is critical for backup sync

🔍 Code Quality Observations

ActivityDetailViewModel.kt (app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt:48-87)

  • ✅ Good: Simplified error handling with Result types
  • ✅ Good: Removed manual try-catch in favor of Result's onSuccess/onFailure
  • ✅ Good: Consistent pattern across all three methods (loadTags, removeTag, addTag)
  • 💡 Note: The addTag method calls settingsStore.addLastUsedTag(tag) on success - this is good for UX but consider if this should also be tracked in case of failures

SendCoinSelectionViewModel.kt (app/src/main/java/to/bitkit/ui/screens/wallets/send/SendCoinSelectionViewModel.kt:74-92)

  • ✅ Good: Changed from direct coreService access to activityRepo.getActivityTags()
  • ✅ Good: Proper error logging added
  • ✅ Good: Removed unnecessary outer runCatching block - the repository method already returns a Result
  • 💡 Minor: The empty check if (tags.isNotEmpty()) is appropriate for this use case

ActivityRepo.kt - New methods are well-designed:

  • getActivityTags(): Simple, focused method with proper error handling
  • addTagsToActivity(): Includes deduplication logic and calls notifyActivitiesChanged()
  • removeTagsFromActivity(): Validates activity exists and calls notifyActivitiesChanged()

🐛 Potential Issues & Concerns

Critical: Backup Sync Issue (The fix this PR addresses)

Root cause identified correctly: The ViewModels were calling coreService.activity methods directly, bypassing the repository layer. This meant notifyActivitiesChanged() wasn't called, so the backup system didn't know tags had changed.

Fix verification: ✅ All tag operations now go through ActivityRepo methods that properly call notifyActivitiesChanged() after modifications.

Minor: Error Handling Edge Case

In SendCoinSelectionViewModel.kt:74-92, the code handles the case where tags fail to load by logging an error, but the map entry is never added. This means subsequent calls to loadTagsByTxId will retry loading the same tags. This is probably acceptable behavior, but consider if you want to add an empty list entry to prevent repeated failures.

.onFailure { e ->Logger.error("Failed to load tags for utxo $txId", e)
// Consider: _tagsByTxId.update { currentMap -> currentMap + (txId to emptyList()) }
}

🔒 Security Considerations

✅ No security concerns identified:

  • Tag operations validate activity existence before modification
  • Proper use of coroutine scopes prevents memory leaks
  • No SQL injection risks (using type-safe repository methods)
  • Input validation exists in repository layer (e.g., filter { it.isNotBlank() })

Performance Considerations

✅ Performance looks good:

  • Repository methods properly use bgDispatcher for background work
  • Tag loading is done lazily in SendCoinSelectionViewModel (only when needed)
  • No unnecessary database operations
  • Proper use of StateFlow for reactive updates

🧪 Test Coverage

Current state: Test file updated minimally (just mock change from CoreService to ActivityRepo)

Recommendation: Consider adding tests for the tag-related functionality:

@Test
fun`loadTags updates tags state on success`() = test {
val activityId ="test-activity-id"val expectedTags =listOf("tag1", "tag2")
val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.getActivityTags(activityId))
.thenReturn(Result.success(expectedTags))
sut.setActivity(mockActivity)
sut.loadTags()
assertEquals(expectedTags, sut.tags.value)
}
@Test
fun`addTag calls settingsStore and reloads tags on success`() = test {
val activityId ="test-activity-id"val tag ="new-tag"val mockActivity = mock<Activity.Onchain>()
whenever(activityRepo.addTagsToActivity(activityId, listOf(tag)))
.thenReturn(Result.success(Unit))
sut.setActivity(mockActivity)
sut.addTag(tag)
verify(settingsStore).addLastUsedTag(tag)
verify(activityRepo).getActivityTags(activityId) // loadTags called
}

However, these additions are not blocking - the current fix is solid and the existing test infrastructure proves the refactoring maintains compatibility.


📝 Summary & Recommendation

Verdict: ✅ Approve with minor suggestions

This PR successfully fixes the tag backup issue by properly routing tag operations through the repository layer. The architectural improvements are significant and align perfectly with the project's conventions.

Key improvements:

  1. Tags are now properly backed up because notifyActivitiesChanged() is called
  2. Better separation of concerns (ViewModels → Repository → CoreService)
  3. Consistent error handling throughout
  4. More maintainable code structure

Minor suggestions (non-blocking):

  1. Consider retry behavior for tag loading failures in SendCoinSelectionViewModel
  2. Consider adding unit tests for tag operations (can be done in a follow-up PR)

Test plan verification: The described test (edit tags → backup → wipe → restore) should now work correctly since all tag modifications trigger the activitiesChanged notification that the backup system monitors.

Great work! 🎉

@synonymdevsynonymdev deleted a comment from claudeBotNov 24, 2025
@ovitrif
ovitrif merged commit 5680517 into use-preactivity-metadataNov 24, 2025
15 checks passed
@ovitrif
ovitrif deleted the fix/backup-tags-on-edit branch November 24, 2025 15:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ben-kaufman@ovitrif@jvsena42