Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Requirements for this bug fix milestone. Each maps to roadmap phases.
- [ ] **RELAY-01**: Relay auto-selection does not loop (add/remove/add cycle)
- [ ] **RELAY-02**: Root cause of automatic relay unset identified and fixed

### Clear Announces (#365)

- [x] **ANNOUNCE-01**: Clear All Announces preserves contacts in My Contacts

## v2 Requirements

Deferred bug fixes to address in a future milestone.
Expand Down Expand Up @@ -50,12 +54,13 @@ Which phases cover which requirements.
| PERF-03 | Phase 1 | Pending |
| RELAY-01 | Phase 2 | Pending |
| RELAY-02 | Phase 2 | Pending |
| ANNOUNCE-01 | Phase 2.1 | Complete |

**Coverage:**
- v1 requirements: 5 total
- Mapped to phases: 5
- v1 requirements: 6 total
- Mapped to phases: 6
- Unmapped: 0

---
*Requirements defined: 2026-01-24*
*Last updated: 2026-01-24 after roadmap creation*
*Last updated: 2026-01-27 after phase 2.1 completion*
18 changes: 18 additions & 0 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This milestone addresses two high-priority bugs reported after the 0.7.2 pre-rel

- [x] **Phase 1: Performance Fix** - Investigate and fix UI stuttering and progressive degradation
- [ ] **Phase 2: Relay Loop Fix** - Investigate and fix the relay auto-selection loop
- [x] **Phase 2.1: Clear Announces Preserves Contacts** - Fix Clear All Announces to exempt My Contacts (#365) (INSERTED)

## Phase Details

Expand Down Expand Up @@ -46,6 +47,22 @@ Plans:
- [ ] 02-02-PLAN.md - Add loop detection, backoff, and Sentry diagnostics (depends on 02-01)
- [ ] 02-03-PLAN.md - Add unit tests for state machine (depends on 02-01)

### Phase 2.1: Clear Announces Preserves Contacts (INSERTED)
**Goal**: "Clear All Announces" in the Network tab deletes all announces *except* those belonging to contacts in My Contacts, preserving the ability to open new conversations with saved contacts
**Depends on**: Nothing (independent fix)
**Requirements**: ANNOUNCE-01
**Issue**: [#365](https://github.com/torlando-tech/columba/issues/365)
**Success Criteria** (what must be TRUE):
1. User can tap "Clear All Announces" and all non-contact announces are removed
2. Announces belonging to contacts in My Contacts are preserved after clearing
3. User can still open a new conversation with any saved contact after clearing announces
4. "Node not found" error no longer appears when tapping a contact after clearing announces
**Plans**: 2 plans in 2 waves

Plans:
- [x] 02.1-01-PLAN.md — Fix DAO, Repository, ViewModel, and UI to preserve contact announces
- [x] 02.1-02-PLAN.md — Add DAO and ViewModel tests for contact-preserving deletion (depends on 02.1-01)

## Progress

**Execution Order:**
Expand All @@ -55,3 +72,4 @@ Phases 1 and 2 are independent and can be worked in any order.
|-------|----------------|--------|-----------|
| 1. Performance Fix | 3/3 | ✓ Complete | 2026-01-25 |
| 2. Relay Loop Fix | 0/3 | Not started | - |
| 2.1. Clear Announces Preserves Contacts (INSERTED) | 2/2 | ✓ Complete | 2026-01-27 |
58 changes: 45 additions & 13 deletions .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,35 @@
See: .planning/PROJECT.md (updated 2026-01-24)

**Core value:** Fix the performance degradation and relay selection loop bugs so users have a stable, responsive app experience.
**Current focus:** Phase 2 - Relay Selection Loop Fixes
**Current focus:** Phase 2.1 - Clear Announces Preserves Contacts

## Current Position

Phase: 2 of 2 (Relay Selection Loop Fixes)
Plan: 3 of 3 complete
Phase: 2.1 (Clear Announces Preserves Contacts)
Plan: 2 of 2 complete
Status: Phase complete
Last activity: 2026-01-25 — Completed 02-03-PLAN.md (State machine and loop prevention tests)
Last activity: 2026-01-28 — Completed 02.1-02-PLAN.md (Test contact-preserving deletion)

Progress: [██████████] 100% (6/6 total plans across both phases)
Progress: [██████████] 100% (8/8 total plans: 6 from phases 1-2 + 2/2 from phase 2.1)

## Performance Metrics

**Velocity:**
- Total plans completed: 6
- Average duration: 5m 50s
- Total execution time: 35m 1s
- Total plans completed: 8
- Average duration: 5m 24s
- Total execution time: 43m 13s

**By Phase:**

| Phase | Plans | Total Time | Avg/Plan |
|-------|-------|------------|----------|
| 01-performance-fix | 3/3 | 18m 42s | 6m 14s |
| 02-relay-loop-fix | 3/3 | 16m 19s | 5m 26s |
| 02.1-clear-announces | 2/2 | 8m 12s | 4m 6s |

**Recent Trend:**
- Last 3 plans: 3m 3s (02-01), 5m 5s (02-02), 27m 11s (02-03)
- Trend: Variable (02-03 was comprehensive test addition - 9 new tests)
- Last 3 plans: 27m 11s (02-03), 2m 58s (02.1-01), 5m 14s (02.1-02)
- Trend: Fast execution for focused bug fixes and tests

*Updated after each plan completion*

Expand All @@ -57,6 +58,15 @@ Recent decisions affecting current work:
- Send Sentry warning events when relay loop detected for diagnostics (02-02)
- Use MutableSharedFlow to simulate reactive announce updates in state machine tests (02-03)
- Test debounce with rapid emissions (100ms intervals) to verify batching (02-03)
- Use SQL subquery (NOT IN) for contact-aware filtering instead of joins (02.1-01)
- Preserve original deleteAllAnnounces() for backward compatibility and testing (02.1-01)
- Fall back to deleteAllAnnounces() if no active identity (02.1-01)

### Roadmap Evolution

- Phase 2.1 inserted after Phase 2: Clear Announces Preserves Contacts — #365 (URGENT)
- "Clear All Announces" deletes contact announces, breaking ability to open new conversations
- Fix: exempt My Contacts announces from the bulk delete

### Pending Todos

Expand All @@ -81,10 +91,10 @@ Also pending from plans:

## Session Continuity

Last session: 2026-01-25
Stopped at: Completed 02-03-PLAN.md - State machine and loop prevention tests
Last session: 2026-01-28
Stopped at: Completed 02.1-02-PLAN.md - Test contact-preserving deletion
Resume file: None
Next: Phase 2 complete - All relay loop fix plans executed
Next: Phase 2.1 complete - all roadmap items finished

## Phase 2 Completion Summary

Expand All @@ -108,3 +118,25 @@ All 3 plans executed successfully:
- Ready for merge and release
- Sentry monitoring in place (from 01-03) will track relay selection events
- No pending blockers for this phase

## Phase 2.1 Completion Summary

**Phase 02.1 - Clear Announces Preserves Contacts: COMPLETE**

All 2 plans executed successfully:
- 02.1-01: Identity-aware announce deletion (2m 58s) ✓
- 02.1-02: Test contact-preserving deletion (5m 14s) ✓

**Key outcomes:**
- Issue #365 (Clear All deletes contacts) fixed via SQL subquery
- deleteAllAnnouncesExceptContacts preserves contact announces for active identity
- ViewModel routes to identity-aware delete, falls back to old method if no identity
- SQL behavior validated with 6 DAO tests using real Room database
- Identity-aware routing verified with 3 ViewModel tests using MockK

**Testing confidence:** High - All tests pass (DAO + ViewModel)

**Production readiness:**
- Ready for merge and release
- Fixes critical UX bug preventing users from opening conversations with saved contacts
- No pending blockers for this phase
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
---
phase: 02.1-clear-announces-preserves-contacts
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- data/src/main/java/com/lxmf/messenger/data/db/dao/AnnounceDao.kt
- data/src/main/java/com/lxmf/messenger/data/repository/AnnounceRepository.kt
- app/src/main/java/com/lxmf/messenger/viewmodel/AnnounceStreamViewModel.kt
- app/src/main/java/com/lxmf/messenger/ui/screens/AnnounceStreamScreen.kt
autonomous: true

must_haves:
truths:
- "Clear All Announces deletes non-contact announces"
- "Clear All Announces preserves announces belonging to contacts in My Contacts"
- "Dialog text reflects new behavior (mentions contacts are preserved)"
- "Falls back to delete-all if no active identity (backward compatible)"
artifacts:
- path: "data/src/main/java/com/lxmf/messenger/data/db/dao/AnnounceDao.kt"
provides: "deleteAllAnnouncesExceptContacts(identityHash) SQL query"
contains: "DELETE FROM announces WHERE destinationHash NOT IN"
- path: "data/src/main/java/com/lxmf/messenger/data/repository/AnnounceRepository.kt"
provides: "deleteAllAnnouncesExceptContacts(identityHash) passthrough"
contains: "deleteAllAnnouncesExceptContacts"
- path: "app/src/main/java/com/lxmf/messenger/viewmodel/AnnounceStreamViewModel.kt"
provides: "Updated deleteAllAnnounces() with identity-aware routing"
contains: "identityRepository.getActiveIdentitySync"
- path: "app/src/main/java/com/lxmf/messenger/ui/screens/AnnounceStreamScreen.kt"
provides: "Updated dialog text mentioning contact preservation"
contains: "except those saved in My Contacts"
key_links:
- from: "AnnounceStreamViewModel.deleteAllAnnounces()"
to: "AnnounceRepository.deleteAllAnnouncesExceptContacts()"
via: "identityRepository.getActiveIdentitySync() provides identityHash"
pattern: "deleteAllAnnouncesExceptContacts.*identityHash"
- from: "AnnounceRepository.deleteAllAnnouncesExceptContacts()"
to: "AnnounceDao.deleteAllAnnouncesExceptContacts()"
via: "Direct delegation"
pattern: "announceDao.deleteAllAnnouncesExceptContacts"
---

<objective>
Fix "Clear All Announces" to preserve announces belonging to contacts in My Contacts.

Purpose: Users who tap "Clear All Announces" currently lose all announce data including contacts, which breaks the ability to open new conversations with saved contacts (causing "Node not found" errors). This fix exempts contact announces from the bulk delete.

Output: Updated DAO query, repository method, ViewModel logic, and dialog text across 4 files.
</objective>

<execution_context>
@/home/tyler/.claude/get-shit-done/workflows/execute-plan.md
@/home/tyler/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02.1-clear-announces-preserves-contacts/02.1-RESEARCH.md
@data/src/main/java/com/lxmf/messenger/data/db/dao/AnnounceDao.kt
@data/src/main/java/com/lxmf/messenger/data/repository/AnnounceRepository.kt
@app/src/main/java/com/lxmf/messenger/viewmodel/AnnounceStreamViewModel.kt
@app/src/main/java/com/lxmf/messenger/ui/screens/AnnounceStreamScreen.kt
</context>

<tasks>

<task type="auto">
<name>Task 1: Add identity-aware delete to DAO and Repository layers</name>
<files>
data/src/main/java/com/lxmf/messenger/data/db/dao/AnnounceDao.kt
data/src/main/java/com/lxmf/messenger/data/repository/AnnounceRepository.kt
</files>
<action>
**AnnounceDao.kt:** Add a new method `deleteAllAnnouncesExceptContacts(identityHash: String)` immediately AFTER the existing `deleteAllAnnounces()` method (after line 132). Keep the original `deleteAllAnnounces()` unchanged (used for tests and fallback). The new method uses a SQL subquery:

```kotlin
/**
* Delete all announces except those belonging to contacts of the specified identity.
* Preserves contact announces so users can still open conversations with saved contacts.
*
* @param identityHash The identity hash to filter contacts by
*/
@Query("""
DELETE FROM announces
WHERE destinationHash NOT IN (
SELECT destinationHash
FROM contacts
WHERE identityHash = :identityHash
)
""")
suspend fun deleteAllAnnouncesExceptContacts(identityHash: String)
```

IMPORTANT: The subquery MUST include `WHERE identityHash = :identityHash` to scope by the active identity. Without this, contacts from OTHER identities would also be preserved, which is incorrect.

**AnnounceRepository.kt:** Add a new method `deleteAllAnnouncesExceptContacts(identityHash: String)` immediately AFTER the existing `deleteAllAnnounces()` method (after line 399). It simply delegates to the DAO:

```kotlin
/**
* Delete all announces except those belonging to contacts of the specified identity.
* Preserves contact announces so users can still open conversations with saved contacts.
*
* @param identityHash The identity hash to filter contacts by
*/
suspend fun deleteAllAnnouncesExceptContacts(identityHash: String) {
announceDao.deleteAllAnnouncesExceptContacts(identityHash)
}
```
</action>
<verify>
Build the data module to verify Room compiles the new SQL query:
```bash
JAVA_HOME=/home/tyler/android-studio/jbr ./gradlew :data:compileDebugKotlin
```
The build should succeed without Room SQL compilation errors.
</verify>
<done>
AnnounceDao has `deleteAllAnnouncesExceptContacts(identityHash)` with correct SQL subquery.
AnnounceRepository has matching passthrough method.
Original `deleteAllAnnounces()` is preserved in both files for backward compatibility.
</done>
</task>

<task type="auto">
<name>Task 2: Update ViewModel and UI dialog to use identity-aware delete</name>
<files>
app/src/main/java/com/lxmf/messenger/viewmodel/AnnounceStreamViewModel.kt
app/src/main/java/com/lxmf/messenger/ui/screens/AnnounceStreamScreen.kt
</files>
<action>
**AnnounceStreamViewModel.kt:** Replace the `deleteAllAnnounces()` method (lines 476-485) with an identity-aware version. The ViewModel already has `identityRepository` injected (line 47). The new implementation:

1. Gets the active identity via `identityRepository.getActiveIdentitySync()`
2. If identity exists, calls `announceRepository.deleteAllAnnouncesExceptContacts(identityHash)`
3. If identity is null (edge case during identity switching), falls back to `announceRepository.deleteAllAnnounces()` for backward compatibility
4. Updates log messages to reflect the new behavior

Replace the method body:
```kotlin
/**
* Delete all announces from the database, except those belonging to saved contacts.
* Preserves announces for contacts so users can still open conversations.
* Nodes will reappear when they announce again.
*/
fun deleteAllAnnounces() {
viewModelScope.launch {
try {
val activeIdentity = identityRepository.getActiveIdentitySync()
if (activeIdentity != null) {
announceRepository.deleteAllAnnouncesExceptContacts(activeIdentity.identityHash)
Log.d(TAG, "Deleted non-contact announces for identity: ${activeIdentity.identityHash}")
} else {
// Fallback: delete all if no active identity (shouldn't happen in normal use)
announceRepository.deleteAllAnnounces()
Log.d(TAG, "Deleted all announces (no active identity)")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to delete announces", e)
}
}
}
```

**AnnounceStreamScreen.kt:** Update the dialog text (around line 856) to reflect the new behavior. Change:
```
"This will remove all discovered nodes from the list. They will reappear when they announce again."
```
To:
```
"This will remove all discovered nodes from the list, except those saved in My Contacts. Nodes will reappear when they announce again."
```
This is a single string change in the `text` lambda of the AlertDialog.
</action>
<verify>
Build the full app to verify everything compiles:
```bash
JAVA_HOME=/home/tyler/android-studio/jbr ./gradlew :app:compileDebugKotlin
```
The build should succeed.
</verify>
<done>
ViewModel's `deleteAllAnnounces()` now routes through `deleteAllAnnouncesExceptContacts()` when an active identity exists.
Falls back to original `deleteAllAnnounces()` when no active identity.
Dialog text updated to inform users that contacts are preserved.
</done>
</task>

</tasks>

<verification>
1. Full project build passes: `JAVA_HOME=/home/tyler/android-studio/jbr ./gradlew assembleDebug`
2. Existing tests pass: `JAVA_HOME=/home/tyler/android-studio/jbr ./gradlew testDebugUnitTest`
3. Code quality checks pass: `JAVA_HOME=/home/tyler/android-studio/jbr ./gradlew detektCheck`
</verification>

<success_criteria>
- AnnounceDao has `deleteAllAnnouncesExceptContacts(identityHash)` with SQL subquery that excludes contacts for the given identity
- AnnounceRepository exposes matching method
- AnnounceStreamViewModel.deleteAllAnnounces() uses identity-aware method with null-identity fallback
- Dialog text mentions contact preservation
- All existing tests still pass
- Project builds successfully
</success_criteria>

<output>
After completion, create `.planning/phases/02.1-clear-announces-preserves-contacts/02.1-01-SUMMARY.md`
</output>
Loading