Context
A triage pass compared all 124 then-open issues against master (cc1023ac), reading the current code and the fixing commits, and driving a Pixel_9 emulator where the flow was observable. 13 issues turned out to be already fixed or stale and were closed; regression pins for those landed in #1274.
This issue collects what is left, split by how safely it can be handed to a coding agent running with little or no supervision. Each row names the exact file and the shape of the change, so a task can be cut from it directly. Nothing here is a new bug report — every item links an existing issue.
The point of the split is that "small diff" and "safe for an agent" are not the same thing. Tier A items have one obvious correct implementation. Tier B items are still bounded but have a failure mode worth a human look. Everything in the last section should not go to an unsupervised agent, and the reason is stated.
Tier A — mechanical, one obvious implementation
Self-contained, unit-testable, no product or design decision left open. Each mirrors a pattern that already exists in the repo.
| Issue |
Change |
Where |
| #809 |
Transfer-to-savings success returns to the Spending details screen instead of Home. Replace popBackStack<Routes.TransferRoot>(inclusive = true) with navigateToHome() — the sibling external-node flow already does this (a13842243), and iOS calls navigation.reset(). |
ContentView.kt:940-947 |
| #1110 |
Channel details hardcodes 24h time. DatePattern.CHANNEL_DETAILS = "MMM d, yyyy, HH:mm"; route it through the existing UiDateStyle.pattern(is24Hour) + LocalIs24HourFormat machinery that the activity list already uses (the #677 fix). |
ChannelDetailScreen.kt:588-604, ext/DateTime.kt:264 |
| #1042 |
A successful retry keeps the failed attempt's amount. The existing-row branch copies only updatedAt/status/contact; add value, fee, preimage, message, which are already on payment/kind. |
CoreService.kt:748-753 |
| #1167 |
Three Core restore slices share one runCatching, so one failure silently drops the rest. Split into three runSuspendCatching blocks with per-slice Logger.error and an aggregate failure result. |
ActivityRepo.kt:985-989 |
| #1139 |
Contact activity cross-filters boost ids between wallets: getTxIdsInBoostTxIds() is called once with the default wallet. ActivityListViewModel.kt:238-242 already does the per-wallet version — copy it. (The wallet-scope half of this issue is already fixed by c95160a6b.) |
ActivityRepo.kt:462 |
| #622 |
Pull-to-refresh does not re-fetch FX rates. Add currencyRepo.triggerRefresh() to the existing onRefresh lambda; CurrencyRepo.refresh() already guards re-entrancy with isRefreshing. |
HomeScreen.kt:286-290 |
| #558 |
The number pad stores the grouped text as raw input, so "100 000 000" is 11 chars against a 10-char cap and further digits are refused; deletes also waste presses on separators. Strip SATS_GROUPING_SEPARATOR when assigning rawInputText at the three sites. |
AmountInputViewModel.kt:147-152, 170-176, 193 |
| #638 |
The "also required for payments" label runs under the Switch in longer locales. Add Modifier.weight(1f) to the BodyMSB, keep the Switch intrinsic. |
PinResultScreen.kt:100-117 |
| #630 |
Tag length is uncapped, so arbitrarily long tags reach metadata and backups. Add a TAG_MAX_LENGTH constant and .take() in the tag inputs, mirroring DEVICE_LABEL_MAX_LENGTH = 50 and BIO_MAX_LENGTH = 160. (The chip rendering half is already fixed by a78783249.) |
AddTagScreen.kt:120-136, components/AddTagSheet.kt:76 |
| #1177 |
Electrum connection failures discard the probe's specific cause. Branch on the ElectrumProbeError subclass, add the strings alphabetically, keep the generic fallback. Moving the mapping into the ViewModel would also make it unit-testable. |
ElectrumConfigScreen.kt:70-77 |
| #894 |
Design-confirmed: the Coin selection frame has no Auto row — the sheet goes nav bar → divider → UTXO rows. Autopilot vs manual is the existing Settings > Advanced > Coin selection preference, and the frame's own note says the sheet is only shown when that preference is manual. Remove auto_select_row. |
SendCoinSelectionScreen.kt:115-133; Figma 46920:148767 |
| #422 |
Design-confirmed: three fixed ordered suggestion sets, one per wallet state, 4 cards max, and "when a card is removed the next in the set is promoted". Empty wallet: Buy, Spend, Support, Back Up, Secure, Profile, Hardware, Invite. On-chain: Back Up, Secure, Spend, Support, Profile, Hardware, Invite, Buy. Spending: Quickpay, Get Paid, Hardware, Shop, Profile, Support, Invite, Buy. Implement the sets verbatim. |
SuggestionsRepo.kt:32-49, 72-105; Figma 46920:147832/147833/147834 |
| #634 |
Design-confirmed: the Incorrect-word note reads "To correct for a mistake, the user can simply tap either the word in red or the button for that word to toggle the button back to the off-state." So: tapping the red word in the list must also clear it, and there is no overwrite semantics. Today only re-tapping the same chip works, and any other tap hits a bare return. |
ConfirmMnemonicScreen.kt:74-100; Figma 46920:148017, note 46920:145482 |
Tier B — bounded, but check the failure mode
Small and localized, but each has a way to be subtly wrong. Fine for an agent that writes the test first; worth a human skim on review.
| Issue |
Change |
Watch out for |
| #1251 |
Trezor powered off shows a blank "Unknown error". handleFailure has branches for cancellation, passphrase, device-busy, firmware, connectivity, timeout — but none for device-unavailable, so the raw AppError falls to else -> ToastEventBus.send(error). Add the branch in both the send and transfer when blocks, and give TrezorRepo a typed error instead of the bare AppError. |
Two call sites (HwSendViewModel.kt:269-306 and TransferViewModel.kt:1290); missing one leaves half the bug. |
| #901 |
Self-payment shows "Insufficient Spending". Add a guard comparing the decoded invoice's payee pubkey against lightningRepo.getNodeId() before the existing checks, plus one string. |
Must run before the canSend check or the wrong toast still wins. |
| #896 |
Pasting a partial mnemonic fills field 1 and then blocks editing: handlePastedWords only acts on exactly 12 or 24 words, so a fragment is dropped while the text field keeps it locally. Spread N words from the focused index. |
The field holds its own TextFieldValue; fixing only the ViewModel leaves the stuck-text symptom. |
| #797 |
A tx that confirms without being seen in the mempool produces no received-payment sheet — Command.from maps only PaymentReceived and OnchainTransactionReceived. Extend it to the confirmed event and route handleOnchainTransactionConfirmed through notifyPaymentReceived. |
Must not double-notify after a normal mempool-first receive; the existing seen/claim dedupe in the handler is what prevents it, so test both orders. |
| #804 |
The critical-update check is emitted on a no-replay SharedFlow whose only collector lives under walletExists, so it is dropped during onboarding. Give it replay = 1 (or hold it in a StateFlow) and collect above the onboarding/main split. |
Env.isDebug short-circuits the check, so it cannot be exercised on a dev build. |
| #724 |
PIN is only required at process start; there is no lifecycle re-lock anywhere. Add a background hook calling resetIsAuthenticatedState(). |
Must not re-lock while an in-app biometric prompt, the system share sheet, or the Trezor USB permission dialog is foregrounded — that is the whole risk of this one. |
| #631 |
Rapid month taps in the activity date-range sheet cancel each other's Animatables and can strand the grid at alpha = 0 / offsetX = ±1000f. Serialize through a Mutex and clear isAnimating in a finally after joining the children. |
The current isAnimating guard does not serialize because the children are unjoined — re-adding the same guard shape fixes nothing. |
| #614 |
The transfer banner can stick when an order is fulfilled-then-closed: syncTransferStates only settles on channel-ready or EXPIRED. Widen the settle condition. |
Widening too far hides genuinely pending transfers; pair with the DeriveBalanceStateUseCaseTest case. |
| #616 |
"Failed to load UTXOs" with no retry — one listSpendableOutputs() call, and executeWhenNodeRunning means a not-yet-running node lands here too. Add a bounded retry and an error/retry state. |
Needs a real retry affordance, not just a longer wait. |
| #845 |
LightningRepo.start wraps the whole startup in runCatching, so a CancellationException after start() returns is swallowed and latches ErrorStarting. Switch to a rethrowing form and rework getStatus(). |
Repro tests already exist on origin/fix/stuck-node-state-845 (7699a98e5) — port them rather than writing new ones. |
| #1125 |
restartNode() calls start(shouldRetry = false), so the first transient failure is terminal. Allow the bounded retry on the restart path. |
Touches node lifecycle; keep the retry bounded and review toast suppression. |
| #1093 |
Residual from the Paykit hardening: PubkyRepo.initialize returns early on an ensureServiceInitialized() throw before _sessionRestorationFailed is set, so identity failures produce no toast and no re-auth prompt. Set the flag on that path. |
Only the early-return half is agent-sized. Widening canDeferStaleSession to other error contexts needs SDK-owner input — leave it. |
| #869 |
Wiping a wallet leaves RN_PENDING_BLOCKTANK_ORDER_IDS_KEY behind, so the retry loop keeps flashing the status icon. WipeWalletUseCase calls only markMigrationChecked(); expose and call the existing clearPersistedMigrationData(). Also chunk the un-chunked blocktank.orders(orderIds = …) call. |
The icon-debounce half of this issue changes user-visible health semantics — do not bundle it. |
| #876 |
The receive sheet's starting tab is correct now (ef9067646), but LaunchedEffect(canCreateLightningInvoice, …) still animates to AUTO when that flag flips true asynchronously on open. Guard the effect with an "already on default" flag. |
Needs an on-device look to confirm; the dumps do not show the animation. |
| #715 |
22 Bitrefill gift-card categories are hardcoded English literals. Add the strings alphabetically and change the enum field to @StringRes Int, resolving at the 2 call sites. |
Mechanical, but do not localize acronyms (repo rule). |
| #417 |
The LNURL-pay comment is sent to the server and then dropped — savePreActivityMetadata has no comment parameter. Add a nullable field through the repo + Room entity and render it in the existing note section. |
Needs a Room migration; that is the only reason this is not Tier A. |
| #633 |
Design-confirmed spec: screen 375 wide, card at x=32 width 311, inner padding 32 all round, two 107.5-wide columns with a 32 gutter, rows 22 high on a 30px pitch. Apply the spec so long words stop wrapping. |
The file states row height but no explicit overflow rule, so truncate-vs-shrink is still a judgement call — ask before picking. Figma 46920:147925. |
Tier C — test and dependency work only
| Issue |
Change |
| #1193 |
Flaky unit tests: QuickPayRepoTest builds a real file-backed CacheStore, so virtual-time timeouts race real IO. Extract an interface (or inject DataStore<AppCacheData>) and supply an in-memory fake. Test-only, no production behaviour change; failure mode is a red build. |
| #843 |
Branch-and-Bound coin selection fails on a fragmented wallet. Fixed upstream in ldk-node 348197b (PR #109), which is not in the pinned 0.7.0-rc.66. The change here is a version bump — but it needs a published artifact and a full on-chain/Lightning regression run, so treat the bump as the easy part. |
Do NOT hand these to an unsupervised agent
Listed so nobody has to re-derive why.
Blocked upstream or cross-repo — the fix is not in this repo:
Duplicates unmerged work — landing the existing PR is the action, not a second implementation:
Money-movement or funds-safety decisions:
Root cause not yet identified — an agent would be guessing:
Design decision still open:
Scope, not difficulty:
Suggested first batch
If you want to start small, Tier A #809, #1110, #622 and #638 are each a handful of lines with an obvious test, touch no money paths, and between them cover navigation, formatting, data refresh and layout — enough to see whether the pipeline works before handing over anything larger.
🤖 Generated with Claude Code
Context
A triage pass compared all 124 then-open issues against
master(cc1023ac), reading the current code and the fixing commits, and driving a Pixel_9 emulator where the flow was observable. 13 issues turned out to be already fixed or stale and were closed; regression pins for those landed in #1274.This issue collects what is left, split by how safely it can be handed to a coding agent running with little or no supervision. Each row names the exact file and the shape of the change, so a task can be cut from it directly. Nothing here is a new bug report — every item links an existing issue.
The point of the split is that "small diff" and "safe for an agent" are not the same thing. Tier A items have one obvious correct implementation. Tier B items are still bounded but have a failure mode worth a human look. Everything in the last section should not go to an unsupervised agent, and the reason is stated.
Tier A — mechanical, one obvious implementation
Self-contained, unit-testable, no product or design decision left open. Each mirrors a pattern that already exists in the repo.
popBackStack<Routes.TransferRoot>(inclusive = true)withnavigateToHome()— the sibling external-node flow already does this (a13842243), and iOS callsnavigation.reset().ContentView.kt:940-947DatePattern.CHANNEL_DETAILS = "MMM d, yyyy, HH:mm"; route it through the existingUiDateStyle.pattern(is24Hour)+LocalIs24HourFormatmachinery that the activity list already uses (the #677 fix).ChannelDetailScreen.kt:588-604,ext/DateTime.kt:264updatedAt/status/contact; addvalue,fee,preimage,message, which are already onpayment/kind.CoreService.kt:748-753runCatching, so one failure silently drops the rest. Split into threerunSuspendCatchingblocks with per-sliceLogger.errorand an aggregate failure result.ActivityRepo.kt:985-989getTxIdsInBoostTxIds()is called once with the default wallet.ActivityListViewModel.kt:238-242already does the per-wallet version — copy it. (The wallet-scope half of this issue is already fixed byc95160a6b.)ActivityRepo.kt:462currencyRepo.triggerRefresh()to the existingonRefreshlambda;CurrencyRepo.refresh()already guards re-entrancy withisRefreshing.HomeScreen.kt:286-290"100 000 000"is 11 chars against a 10-char cap and further digits are refused; deletes also waste presses on separators. StripSATS_GROUPING_SEPARATORwhen assigningrawInputTextat the three sites.AmountInputViewModel.kt:147-152, 170-176, 193Modifier.weight(1f)to theBodyMSB, keep the Switch intrinsic.PinResultScreen.kt:100-117TAG_MAX_LENGTHconstant and.take()in the tag inputs, mirroringDEVICE_LABEL_MAX_LENGTH = 50andBIO_MAX_LENGTH = 160. (The chip rendering half is already fixed bya78783249.)AddTagScreen.kt:120-136,components/AddTagSheet.kt:76ElectrumProbeErrorsubclass, add the strings alphabetically, keep the generic fallback. Moving the mapping into the ViewModel would also make it unit-testable.ElectrumConfigScreen.kt:70-77Settings > Advanced > Coin selectionpreference, and the frame's own note says the sheet is only shown when that preference is manual. Removeauto_select_row.SendCoinSelectionScreen.kt:115-133; Figma46920:148767SuggestionsRepo.kt:32-49, 72-105; Figma46920:147832/147833/147834return.ConfirmMnemonicScreen.kt:74-100; Figma46920:148017, note46920:145482Tier B — bounded, but check the failure mode
Small and localized, but each has a way to be subtly wrong. Fine for an agent that writes the test first; worth a human skim on review.
handleFailurehas branches for cancellation, passphrase, device-busy, firmware, connectivity, timeout — but none for device-unavailable, so the rawAppErrorfalls toelse -> ToastEventBus.send(error). Add the branch in both the send and transferwhenblocks, and giveTrezorRepoa typed error instead of the bareAppError.HwSendViewModel.kt:269-306andTransferViewModel.kt:1290); missing one leaves half the bug.lightningRepo.getNodeId()before the existing checks, plus one string.canSendcheck or the wrong toast still wins.handlePastedWordsonly acts on exactly 12 or 24 words, so a fragment is dropped while the text field keeps it locally. Spread N words from the focused index.TextFieldValue; fixing only the ViewModel leaves the stuck-text symptom.Command.frommaps onlyPaymentReceivedandOnchainTransactionReceived. Extend it to the confirmed event and routehandleOnchainTransactionConfirmedthroughnotifyPaymentReceived.SharedFlowwhose only collector lives underwalletExists, so it is dropped during onboarding. Give itreplay = 1(or hold it in aStateFlow) and collect above the onboarding/main split.Env.isDebugshort-circuits the check, so it cannot be exercised on a dev build.resetIsAuthenticatedState().Animatables and can strand the grid atalpha = 0/offsetX = ±1000f. Serialize through aMutexand clearisAnimatingin afinallyafter joining the children.isAnimatingguard does not serialize because the children are unjoined — re-adding the same guard shape fixes nothing.syncTransferStatesonly settles on channel-ready orEXPIRED. Widen the settle condition.DeriveBalanceStateUseCaseTestcase.listSpendableOutputs()call, andexecuteWhenNodeRunningmeans a not-yet-running node lands here too. Add a bounded retry and an error/retry state.LightningRepo.startwraps the whole startup inrunCatching, so aCancellationExceptionafterstart()returns is swallowed and latchesErrorStarting. Switch to a rethrowing form and reworkgetStatus().origin/fix/stuck-node-state-845(7699a98e5) — port them rather than writing new ones.restartNode()callsstart(shouldRetry = false), so the first transient failure is terminal. Allow the bounded retry on the restart path.PubkyRepo.initializereturns early on anensureServiceInitialized()throw before_sessionRestorationFailedis set, so identity failures produce no toast and no re-auth prompt. Set the flag on that path.canDeferStaleSessionto other error contexts needs SDK-owner input — leave it.RN_PENDING_BLOCKTANK_ORDER_IDS_KEYbehind, so the retry loop keeps flashing the status icon.WipeWalletUseCasecalls onlymarkMigrationChecked(); expose and call the existingclearPersistedMigrationData(). Also chunk the un-chunkedblocktank.orders(orderIds = …)call.ef9067646), butLaunchedEffect(canCreateLightningInvoice, …)still animates to AUTO when that flag flips true asynchronously on open. Guard the effect with an "already on default" flag.@StringRes Int, resolving at the 2 call sites.savePreActivityMetadatahas no comment parameter. Add a nullable field through the repo + Room entity and render it in the existing note section.46920:147925.Tier C — test and dependency work only
QuickPayRepoTestbuilds a real file-backedCacheStore, so virtual-time timeouts race real IO. Extract an interface (or injectDataStore<AppCacheData>) and supply an in-memory fake. Test-only, no production behaviour change; failure mode is a red build.348197b(PR #109), which is not in the pinned0.7.0-rc.66. The change here is a version bump — but it needs a published artifact and a full on-chain/Lightning regression run, so treat the bump as the easy part.Do NOT hand these to an unsupervised agent
Listed so nobody has to re-derive why.
Blocked upstream or cross-repo — the fix is not in this repo:
sendToAddressreturnsOk(txid)unconditionally today, so a rejection has no representation to assert against.no_routeerror on funding lightning address #364 — the 10% in-flight cap is an ldk-node default for non-LSP inbound channels.@onboarding_2fails randomly #507 — the failing test lives inbitkit-e2e-tests.Duplicates unmerged work — landing the existing PR is the action, not a second implementation:
spar/issue-1144). Extra LDK round trip on every max-send confirm #1150's symbols (shouldDrainOnchain,estimateMaxSendOnchain) do not exist onmasterat all; it is a review note against that PR and should probably be re-targeted onto it or closed.Money-movement or funds-safety decisions:
Root cause not yet identified — an agent would be guessing:
syncStatesuspending touches ~15 call sites and can reorder LDK events), Transition to Confirm screen is often low-FPS #503 (frame timing, needs profiling on a real device).Design decision still open:
46920:149655) does show a SERVICE FEES tile reading ₿ 0, in a 2×2 grid with NETWORK FEES / TO SPENDING / TOTAL. So Android currently matches the design, and this issue is a request to change the design for third-party LSPs, not a build error. The frame's note also describes an unbuilt "discount modal" on tapping a fee tile.Scope, not difficulty:
pull_requesttrigger is the trivial part.Suggested first batch
If you want to start small, Tier A #809, #1110, #622 and #638 are each a handful of lines with an obvious test, touch no money paths, and between them cover navigation, formatting, data refresh and layout — enough to see whether the pipeline works before handing over anything larger.
🤖 Generated with Claude Code