fix(reticulum): Network PN mode, multi-step sync cascade, Auto discovered deposits, and .rsstack layout - #820
Conversation
…tack to .rsstack Propagation mode (Network + Auto owns Preferred): - Move Off/Auto/Manual mode selector from the App panel into Network -> Propagation nodes; remove the duplicated App controls (announce-only now). - Default mode is Off (MeshChatX parity). Auto picks the best remote PN at pick time (discovered union enabled-configured, lowest hops), soft-upserts + sets Preferred without a manual Add, and falls back to enabled local-prop. - Gate Set preferred / Add & prefer while Auto manages Preferred; Manual/Off never auto-write. Periodic auto-sync honors the mode (Off never ticks; local-prop still skipped). Mode help text + per-option tooltips added. - PN-island diagnostics: sidecar deposit-Completes log carries cascade_step + delivery_method; diagnostic snapshot gains a propagationClient slice; developer bundles always include mesh_client_stack.json + lxmf-outbound.log with expanded island log patterns. Docs + troubleshooting (PN-island section + repro matrix). Relocate Ratspeak overlay stack to repo-local .rsstack workspace: - Move rns-stack path deps and overlay checkouts from sibling ../../rs* to a gitignored .rsstack/ workspace so a standalone rsReticulum mirror stays clean. - Update Cargo.toml, reticulum-sidecar-path.ts, clone/ensure/apply/check-rsnomad scripts, update.sh, build-reticulum-sidecar-release.mjs, all sidecar CI workflows, contract tests, patches/README, and docs to the new layout. - Exclude .rsstack/ from repo-wide linters (yamllint, markdownlint, prettier) so the cloned upstream repos are not scanned by mesh-client tooling.
…eachable Auto tries discovered then configured then local; Manual can Prefer local-only and falls back to local on remote failure. Delete/supersede cancels no longer surface as "node unreachable," and the leftover App Reticulum section is gone.
Stop soft-upserting discovered nodes and rewriting Preferred on Auto; sync configured remotes then local, and surface Sync start/failure feedback.
Auto no longer adds nodes or sets Preferred. Sidecar sync accepts a destination_hash for a one-time remote sync without mutating the configured list or Preferred; renderer cascade tries best Discovered by hash, then configured remotes, then local-prop, and settles local when no interfaces are enabled.
PR CI already runs the full suite; pre-commit keeps staged tests.
Manual now syncs Preferred, or picks the closest added node for that sync only (no Preferred write), then falls back through the remaining added nodes to the local inbox, so a user who never pinned a Preferred node still gets store-and-forward. Off previously only stopped periodic sync while Preferred kept carrying offline DMs. It now means no propagation support end to end: the renderer blocks every sync path and reports no cascade capacity, and the sidecar persists propagation_mode, disarms the outbound PN, and builds an empty cascade so nothing is deposited on a remote PN or the local inbox.
Selecting Auto on a fresh stack could toast "Propagation sync failed — the node may be unreachable" without contacting anything: no PN announces had arrived yet, no remotes were added, and the local inbox reports enabled=false until its deferred messagestore load finishes. The sidecar now exposes that load as status "loading" on the local-prop row, the cascade writes syncLocalLoading / syncNoTarget when it contacted no node (never overwriting a real per-node error), and the auto-sync tick re-reads the sidecar while no cascade candidate exists so Auto recovers on its own once an announce lands or the local store finishes loading.
Quitting waited on a graceful Reticulum stack teardown before app:quit even
started: the renderer awaited reticulum.stop(), which ran the prepare-stop BLE
detach drain (1s cap) plus a 5s SIGTERM grace, adding roughly 2s to every exit.
Give quit a single owner. Main stops the sidecar with stop({ forQuit: true }),
skipping prepare-stop and SIGKILLing after 750ms, and escalates an in-flight
graceful stop instead of waiting on it. The connected Disconnect & Quit path no
longer calls onDisconnect, since main already tears down BLE, TCP, and the
sidecar. Stay-in-app Stop / Disconnect keeps the graceful drain.
Auto claimed to use discovered propagation nodes, but the sidecar built its outbound cascade from persisted rows only, so the Chat "no propagation node" banner was telling the truth and offline DMs had nowhere to go until the user added a node by hand. Auto now cascades onto heard PNs (cap 3, hop-sorted, nothing persisted) after the nodes the user added and before the local inbox, and the announce handler shares the rebuild helper so a newly heard PN is eligible without a restart. The renderer counts discovered nodes in Auto to match, which hides the banner and holds off the link-timeout failure bridge while the sidecar is still cascading. The banner also gains a dismiss control backed by a Network toggle, and every sync attempt records its target so progress, errors, and toasts name the node they reached -- unattributed when the cascade contacted nobody.
Wait for each sync attempt to settle before trying the next node so a discovered PN that accepts then times out no longer ends Auto/Manual cascades before remotes or local-prop are tried.
Omit recently failed remotes, cap each cascade attempt at ~60s, and refresh sidecar nodes before local fallback so Auto does not stall on dead discovered PNs or skip an enabled host inbox.
Treat OUTBOUND_BUSY as a soft defer (no 15-minute backoff), keep remote errors when local is still loading, fix stale modeHelpAuto/syncLocalLoading locales, align docs, dedupe cascade helpers, and add i18n quality guards.
|
Warning Review limit reached
Next review available in: 33 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: QUIET Plan: Pro Plus Run ID: 📒 Files selected for processing (22)
📝 WalkthroughWalkthroughThe PR adds persisted Off, Auto, and Manual propagation modes, discovered-node cascade synchronization, target-aware sync state, diagnostics, and quit-specific sidecar shutdown. It also moves Rust stack checkouts to ChangesReticulum propagation and workspace update
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ReticulumPropagationSection
participant reticulumPropagationStore
participant reticulumPropagationAutoApply
participant ReticulumSidecar
User->>ReticulumPropagationSection: Select mode or start sync
ReticulumPropagationSection->>reticulumPropagationStore: Set mode or sync target
reticulumPropagationStore->>ReticulumSidecar: Send mode or destination-hash request
ReticulumPropagationSection->>reticulumPropagationAutoApply: Start propagation cascade
reticulumPropagationAutoApply->>ReticulumSidecar: Attempt discovered, configured, or local target
ReticulumSidecar-->>reticulumPropagationAutoApply: Settlement or failure
reticulumPropagationAutoApply-->>reticulumPropagationSection: Target-aware result
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (4)
scripts/check-i18n-quality.mjs-1400-1408 (1)
1400-1408: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDetect non-Latin loading-status errors.
Require the pronoun check to split on punctuation instead of punctuation plus whitespace; no-whitespace clauses in Japanese and Chinese are currently skipped. Match sync terms without ASCII
\bboundaries, and add regression coverage for Japanese/Chinese no-whitespace cases. Add or update behavioral tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check-i18n-quality.mjs` around lines 1400 - 1408, Update the clause splitting in the loading-status detection near mentionsLoading and mentionsSyncOnly to split on sentence punctuation alone, preserving Japanese and Chinese clauses without whitespace. Replace the ASCII word-boundary requirement in the sync-term regex with Unicode-safe matching that detects non-Latin terms, and add behavioral regression tests covering Japanese and Chinese no-whitespace loading-status errors.Source: Path instructions
src/renderer/components/ReticulumPropagationNotice.tsx-25-50 (1)
25-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake propagation mode reactive.
readReticulumPropagationMode()only reads localStorage during render. Selecting Off writes localStorage but does not update state observed by this component. The notice can remain visible until an unrelated render occurs.Store the mode in reactive state, or subscribe through a mode hook. Add a transition test for selecting Off while the notice is visible.
As per coding guidelines, use Zustand persistence for localStorage-backed renderer state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/ReticulumPropagationNotice.tsx` around lines 25 - 50, Replace the one-time read from readReticulumPropagationMode() in ReticulumPropagationNotice with reactive Zustand-backed mode state, using the existing persistence pattern for localStorage. Ensure selecting Off updates the observed mode immediately so the notice unmounts without an unrelated render, and add a transition test covering that interaction while the notice is visible.Source: Coding guidelines
src/renderer/components/ReticulumPropagationSection.tsx-231-235 (1)
231-235: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winGate the Auto kick on an available cascade candidate.
resolvePropagationSyncTargetId('auto', ...)returnsnullwhen no discovered node, no configured remote, and no enabled local node exists. The?? 'local-prop'fallback then starts a cascade against a node that cannot serve. The user sees a failure toast only because the mode was switched. The bottom Sync button already useshasPropagationCascadeCandidatefor the same decision. Apply the same check here.🐛 Proposed fix
if (next !== 'auto') return; // Auto: kick discovered hash sync → configured → local (no Add, no Preferred). - const target = - resolvePropagationSyncTargetId('auto', nodes, preferredId, discovered) ?? 'local-prop'; - handleSyncNow(target); + if (!hasPropagationCascadeCandidate('auto', nodes, discovered)) return; + const target = + resolvePropagationSyncTargetId('auto', nodes, preferredId, discovered) ?? 'local-prop'; + handleSyncNow(target);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/components/ReticulumPropagationSection.tsx` around lines 231 - 235, Update the Auto branch near resolvePropagationSyncTargetId and handleSyncNow to first require hasPropagationCascadeCandidate with the current nodes, preferredId, and discovered state; return without starting synchronization when no cascade candidate is available. Remove the ?? 'local-prop' fallback so handleSyncNow is called only with a valid resolved target.src/renderer/lib/reticulum/reticulumPropagationMode.ts-216-231 (1)
216-231: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExclude a loading local node from the resolved sync target.
hasEnabledLocalPropagationNodeonly checksenabled. It ignoresstatus: 'loading'. When the local inbox is enabled but the sidecar is still reading its messagestore, both Manual and Auto resolve to'local-prop', andhasPropagationCascadeCandidatereports a candidate. The bottom Sync button inReticulumPropagationSection.tsxthen stays enabled and starts a cascade against a node that cannot settle.The per-row Sync button already guards this case at
ReticulumPropagationSection.tsxLine 447 with the comment "Local inbox cannot settle until its messagestore finishes loading". This module exportsisLocalPropagationLoadingfor the same purpose but does not apply it here.Apply the loading check in the local fallback of both branches, and in
hasPropagationCascadeCandidate. Add a test that covers an enabled local node withstatus: 'loading'and no remotes; the existing test atReticulumPropagationSection.test.tsxLine 525 usesenabled: false, so it does not reach this path.🐛 Proposed fix
export function hasEnabledLocalPropagationNode(nodes: PropagationNodeRow[]): boolean { return nodes.some((n) => n.id === 'local-prop' && n.enabled); } + +/** Local inbox that is enabled and has finished loading its messagestore. */ +export function hasSyncableLocalPropagationNode(nodes: PropagationNodeRow[]): boolean { + return nodes.some((n) => n.id === 'local-prop' && n.enabled && n.status !== 'loading'); +}if (mode === 'manual') { if (preferredId != null && preferredId.length > 0) return preferredId; const configuredBest = listConfiguredRemotePropagationIds(nodes).at(0); if (configuredBest != null) return configuredBest; - return hasEnabledLocalPropagationNode(nodes) ? 'local-prop' : null; + return hasSyncableLocalPropagationNode(nodes) ? 'local-prop' : null; } const discoveredBest = listDiscoveredPropagationTargets(nodes, discovered).at(0); if (discoveredBest != null) { return discoveredBest.destinationHash.toLowerCase(); } const configured = listConfiguredRemotePropagationIds(nodes).at(0); if (configured != null) return configured; - if (hasEnabledLocalPropagationNode(nodes)) return 'local-prop'; + if (hasSyncableLocalPropagationNode(nodes)) return 'local-prop'; return null;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/lib/reticulum/reticulumPropagationMode.ts` around lines 216 - 231, Update the local fallback logic in the propagation target resolver’s manual and automatic branches to exclude nodes for which isLocalPropagationLoading indicates loading, while preserving remote and ready-local selection. Apply the same loading exclusion in hasPropagationCascadeCandidate so it cannot report a loading local node as a candidate, and add coverage for an enabled loading local node with no remotes.
🧹 Nitpick comments (3)
docs/ci-cd.md (1)
79-79: 🔒 Security & Privacy | 🔵 TrivialRecord immutable upstream revisions for release builds.
This documents floating all five dependencies to
origin/main. The release-sidecar helper inscripts/build-reticulum-sidecar-release.mjsalso invokesscripts/clone-ratspeak-stack.sh. Rebuilding the same release tag can therefore use different Rust sources, and an upstream change can alter or block a release.Keep the floating default for development if required. Pin release refs or record resolved commit SHAs in CI artifacts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/ci-cd.md` at line 79, Update the release build flow around build-reticulum-sidecar-release.mjs and clone-ratspeak-stack.sh so release builds use immutable refs or record the resolved commit SHAs for all five Rust dependencies. Preserve the existing origin/main floating behavior for development builds and ensure release artifacts retain the exact source revisions used.src/renderer/lib/reticulum/reticulumPropagationAutoApply.test.ts (1)
540-553: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestore the
Date.nowspy in a teardown hook.
nowSpy.mockRestore()runs only after the assertions. If eitherexpecton Lines 550-551 fails, the spy stays installed.Date.nowthen returns the frozennowMsfor every later test in this file, and the backoff window checks inreticulumPropagationSyncBackoffstop advancing. Move the restore intoafterEach(or usevi.restoreAllMocks()there) so a failing assertion cannot leak the mock.♻️ Proposed teardown change
afterEach(() => { vi.unstubAllGlobals(); + vi.restoreAllMocks(); });await expect(startPropagationSyncCascade({ hasEnabledInterfaces: true })).resolves.toBe(true); expect(slowStartSync.mock.calls.map((c) => c[0])).toEqual([near, 'local-prop']); - nowSpy.mockRestore(); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/lib/reticulum/reticulumPropagationAutoApply.test.ts` around lines 540 - 553, Move Date.now cleanup for the test in the “settles the local inbox once the remote budget is spent” case into an afterEach teardown, using nowSpy.mockRestore() or vi.restoreAllMocks(), and remove the assertion-dependent restoration so failures cannot leak the mock.src/renderer/lib/reticulum/reticulumPropagationSyncBackoff.ts (1)
10-39: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueSweep expired entries when the
failuresmap grows.
failuresonly drops a key when that same key is queried again after the backoff window. A target that fails once and then stops announcing is never re-queried, so its entry stays for the whole session. On a mesh with many announced propagation nodes the map grows without an upper bound.Add a size-triggered sweep in
noteReticulumPropagationSyncFailure.♻️ Proposed sweep
/** Sync target id (row id, `local-prop`, or destination hash) to last failure time. */ const failures = new Map<string, number>(); +/** Sweep threshold — entries for targets that never return are otherwise never re-queried. */ +const FAILURES_SWEEP_AT = 256; function backoffKey(id: string): string { return id.toLowerCase(); } export function noteReticulumPropagationSyncFailure(id: string, atMs = Date.now()): void { if (id.length === 0) return; + if (failures.size >= FAILURES_SWEEP_AT) { + for (const [key, at] of failures) { + if (atMs - at >= RETICULUM_PROPAGATION_SYNC_FAILURE_BACKOFF_MS) failures.delete(key); + } + } failures.set(backoffKey(id), atMs); }As per coding guidelines: "Avoid hot-path O(n) work and perform lazy cleanup when collections become large."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/lib/reticulum/reticulumPropagationSyncBackoff.ts` around lines 10 - 39, Add a size-triggered lazy cleanup in noteReticulumPropagationSyncFailure: when failures reaches the configured large-map threshold, iterate entries and remove those whose failure timestamps are outside RETICULUM_PROPAGATION_SYNC_FAILURE_BACKOFF_MS relative to the current timestamp. Keep normal failure recording unchanged and avoid sweeping on every call.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reticulum.md`:
- Line 423: Update the workspace-free stub-build statement in the documented
build description to clarify that Cargo still requires the .rsstack/ checkouts,
matching reticulum-sidecar/README.md and the manifest path dependencies.
Preserve the distinction between the stub stack and the real mesh-I/O stack.
In `@reticulum-sidecar/Cargo.toml`:
- Around line 66-129: Ensure the full-build flow keeps the Reticulum-sidecar
path dependencies rooted at the repository-local ../.rsstack checkout that
scripts/clone-ratspeak-stack.sh updates. Either materialize custom
WORKSPACE_ROOT contents at that location before invoking rns-stack, or
consistently propagate WORKSPACE_ROOT into every path dependency in the
Cargo.toml dependency declarations, including rns-runtime, rns-identity,
rns-wire, rns-ratkey, rns-transport, lxmf-core, nomad-core, lxst-telephony,
lxst-core, lrgp, rns-interface, rns-link, and rns-crypto.
In `@reticulum-sidecar/src/stack/mod.rs`:
- Around line 1399-1413: Update set_propagation_mode to snapshot the existing
propagation mode before calling inner.set_propagation_mode(mode), then restore
that prior mode when inner.save(&self.config_dir, &self.storage_dir) fails.
Follow the snapshot-and-restore pattern used by set_pn_hosting_policy and
set_path_medium_preference, while preserving the current successful-save
behavior.
In `@reticulum-sidecar/src/stack/persistence.rs`:
- Around line 409-411: Update StackHandle::set_propagation_mode to snapshot the
previous propagation mode before mutation and restore it when inner.save fails,
keeping PersistedState consistent with the live bridge on error. Add a test
covering save failure and asserting the prior mode is retained.
In `@src/renderer/components/ReticulumPropagationSection.tsx`:
- Around line 226-230: Both sidecar-update promise chains lack rejection
handling, allowing unhandled promise rejections. In
src/renderer/components/ReticulumPropagationSection.tsx lines 226-230, append a
catch to the setModeOnSidecar(next) chain that warns with the rejection error;
do the same in lines 565-572 for setAutoSyncIntervalOnSidecar(sec), using the
specified contextual warning messages and unknown error type.
In `@src/renderer/lib/reticulum/reticulumPropagationAutoApply.ts`:
- Around line 116-125: Update tryLocalSettleIfEnabled so refreshFromSidecar is
wrapped in error handling: log the refresh failure, retain the nodes already
read from the store, and continue evaluating hasEnabledLocalPropagationNode
instead of rejecting. Preserve the existing refresh-and-recheck behavior when
the call succeeds.
In `@src/renderer/lib/reticulum/reticulumPropagationEffective.test.ts`:
- Around line 71-82: Update auto-discovery candidate handling in
hasEffectiveReticulumPropagationTarget and its candidate-building flow so
disabled configured propagation rows still reserve their destination_hash in the
discovered-node seen set, even though they are excluded from effective
configured candidates. Ensure matching announces are not appended as discovered
cascade targets, preserving the false result for disabled-only configurations.
In `@src/renderer/lib/reticulum/reticulumPropagationSync.ts`:
- Around line 298-306: Preserve supersession as a terminal non-success outcome
in the state update within the payload.active === false branch, using the
existing supersession marker rather than clearing lastSyncError to null. Update
awaitPropagationSyncSettled() to classify that marker as cancelled or
superseded, and add coverage that awaits a sync, applies
PROPAGATION_SYNC_SUPERSEDED, and verifies it does not resolve as success.
In `@src/renderer/lib/reticulum/useReticulumPropagationAutoSync.ts`:
- Around line 76-79: Attach rejection handlers to both mount-time calls in the
initialization flow: update refreshFromSidecar and setModeOnSidecar to catch
failures and log them with console.warn, while preserving their existing
fire-and-forget behavior. Do not alter the handled tick path through runTick.
---
Other comments:
In `@scripts/check-i18n-quality.mjs`:
- Around line 1400-1408: Update the clause splitting in the loading-status
detection near mentionsLoading and mentionsSyncOnly to split on sentence
punctuation alone, preserving Japanese and Chinese clauses without whitespace.
Replace the ASCII word-boundary requirement in the sync-term regex with
Unicode-safe matching that detects non-Latin terms, and add behavioral
regression tests covering Japanese and Chinese no-whitespace loading-status
errors.
In `@src/renderer/components/ReticulumPropagationNotice.tsx`:
- Around line 25-50: Replace the one-time read from
readReticulumPropagationMode() in ReticulumPropagationNotice with reactive
Zustand-backed mode state, using the existing persistence pattern for
localStorage. Ensure selecting Off updates the observed mode immediately so the
notice unmounts without an unrelated render, and add a transition test covering
that interaction while the notice is visible.
In `@src/renderer/components/ReticulumPropagationSection.tsx`:
- Around line 231-235: Update the Auto branch near
resolvePropagationSyncTargetId and handleSyncNow to first require
hasPropagationCascadeCandidate with the current nodes, preferredId, and
discovered state; return without starting synchronization when no cascade
candidate is available. Remove the ?? 'local-prop' fallback so handleSyncNow is
called only with a valid resolved target.
In `@src/renderer/lib/reticulum/reticulumPropagationMode.ts`:
- Around line 216-231: Update the local fallback logic in the propagation target
resolver’s manual and automatic branches to exclude nodes for which
isLocalPropagationLoading indicates loading, while preserving remote and
ready-local selection. Apply the same loading exclusion in
hasPropagationCascadeCandidate so it cannot report a loading local node as a
candidate, and add coverage for an enabled loading local node with no remotes.
---
Nitpick comments:
In `@docs/ci-cd.md`:
- Line 79: Update the release build flow around
build-reticulum-sidecar-release.mjs and clone-ratspeak-stack.sh so release
builds use immutable refs or record the resolved commit SHAs for all five Rust
dependencies. Preserve the existing origin/main floating behavior for
development builds and ensure release artifacts retain the exact source
revisions used.
In `@src/renderer/lib/reticulum/reticulumPropagationAutoApply.test.ts`:
- Around line 540-553: Move Date.now cleanup for the test in the “settles the
local inbox once the remote budget is spent” case into an afterEach teardown,
using nowSpy.mockRestore() or vi.restoreAllMocks(), and remove the
assertion-dependent restoration so failures cannot leak the mock.
In `@src/renderer/lib/reticulum/reticulumPropagationSyncBackoff.ts`:
- Around line 10-39: Add a size-triggered lazy cleanup in
noteReticulumPropagationSyncFailure: when failures reaches the configured
large-map threshold, iterate entries and remove those whose failure timestamps
are outside RETICULUM_PROPAGATION_SYNC_FAILURE_BACKOFF_MS relative to the
current timestamp. Keep normal failure recording unchanged and avoid sweeping on
every call.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: QUIET
Plan: Pro Plus
Run ID: 07cf470f-c83c-4940-9483-9a9a7d3e9d7b
⛔ Files ignored due to path filters (18)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!**/pnpm-lock.yamlreticulum-sidecar/patches/README.mdis excluded by!reticulum-sidecar/patches/**src/renderer/locales/cs/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/de/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/en/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/es/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/fr/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/id/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/it/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/ja/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/ko/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/nl/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/pl/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/pt-BR/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/ru/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/tr/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/uk/translation.jsonis excluded by!src/renderer/locales/**src/renderer/locales/zh/translation.jsonis excluded by!src/renderer/locales/**
📒 Files selected for processing (96)
.githooks/pre-push.github/workflows/build.yaml.github/workflows/flatpak.yaml.github/workflows/release.yaml.github/workflows/reticulum-sidecar.yaml.github/workflows/tests.yaml.gitignore.markdownlint-cli2.jsonc.prettierignore.yamllintAGENTS.mdREADME.mddocs/ci-cd.mddocs/development-environment.mddocs/nomad-hosting-interop.mddocs/reticulum.mddocs/troubleshooting.mdreticulum-sidecar/Cargo.tomlreticulum-sidecar/README.mdreticulum-sidecar/src/api/mod.rsreticulum-sidecar/src/api/propagation.rsreticulum-sidecar/src/stack/live.rsreticulum-sidecar/src/stack/lxmf_outbound.rsreticulum-sidecar/src/stack/mod.rsreticulum-sidecar/src/stack/persistence.rsreticulum-sidecar/src/stack/pn_cascade.rsreticulum-sidecar/src/stack/propagation_bridge.rsreticulum-sidecar/src/stack/propagation_mode.rsscripts/apply-rsLXMF-link-delivery-has-pending-to.shscripts/apply-rsLXMF-propagation-node-deferred-messagestore-load.shscripts/apply-rsLXMF-propagation-node-policy-setters.shscripts/apply-rsLXMF-propagation-sync-peering.shscripts/apply-rsReticulum-auto-beacon-utun.shscripts/apply-rsReticulum-ble-rnode-bond-desync.shscripts/apply-rsReticulum-ble-rnode-pairing-transition-debounce.shscripts/apply-rsReticulum-discovery-announce-egress.shscripts/apply-rsReticulum-inbound-raw-saturation-log.shscripts/apply-rsReticulum-link-client-nomad.shscripts/apply-rsReticulum-link-client-proof-budget.shscripts/apply-rsReticulum-packet-tap.shscripts/apply-rsReticulum-path-medium-slots.shscripts/build-reticulum-sidecar-release.mjsscripts/check-i18n-quality.mjsscripts/check-i18n-quality.test.mjsscripts/check-reticulum-sidecar.shscripts/check-rsnomad-fmt.shscripts/check-rsnomad.shscripts/clone-ratspeak-stack.shscripts/clone-ratspeak-stack.test.mjsscripts/ensure-rsReticulum-patches.shscripts/update.shscripts/update.test.mjssrc/main/index.tssrc/main/reticulum-sidecar-manager.test.tssrc/main/reticulum-sidecar-manager.tssrc/main/reticulum-sidecar-path.test.tssrc/main/reticulum-sidecar-path.tssrc/main/support-bundle.test.tssrc/main/support-bundle.tssrc/renderer/App.tsxsrc/renderer/components/AppPanel.tsxsrc/renderer/components/ConnectionPanel.test.tsxsrc/renderer/components/ConnectionPanel.tsxsrc/renderer/components/ReticulumAppPanelSection.tsxsrc/renderer/components/ReticulumPropagationControls.test.tsxsrc/renderer/components/ReticulumPropagationControls.tsxsrc/renderer/components/ReticulumPropagationNotice.test.tsxsrc/renderer/components/ReticulumPropagationNotice.tsxsrc/renderer/components/ReticulumPropagationSection.test.tsxsrc/renderer/components/ReticulumPropagationSection.tsxsrc/renderer/components/ReticulumPropagationSyncProgress.test.tsxsrc/renderer/components/ReticulumPropagationSyncProgress.tsxsrc/renderer/lib/reticulum/reticulumDiagnosticSnapshot.test.tssrc/renderer/lib/reticulum/reticulumDiagnosticSnapshot.tssrc/renderer/lib/reticulum/reticulumDiscoveryAnnounceEgress.contract.test.tssrc/renderer/lib/reticulum/reticulumLinkDeliveryHasPendingTo.contract.test.tssrc/renderer/lib/reticulum/reticulumLxmfOutboundBackchannel.contract.test.tssrc/renderer/lib/reticulum/reticulumOutboundFailureBridge.test.tssrc/renderer/lib/reticulum/reticulumOutboundFailureBridge.tssrc/renderer/lib/reticulum/reticulumPnHostingPolicySetters.contract.test.tssrc/renderer/lib/reticulum/reticulumPropagationAutoApply.test.tssrc/renderer/lib/reticulum/reticulumPropagationAutoApply.tssrc/renderer/lib/reticulum/reticulumPropagationEffective.test.tssrc/renderer/lib/reticulum/reticulumPropagationEffective.tssrc/renderer/lib/reticulum/reticulumPropagationMode.test.tssrc/renderer/lib/reticulum/reticulumPropagationMode.tssrc/renderer/lib/reticulum/reticulumPropagationSync.test.tssrc/renderer/lib/reticulum/reticulumPropagationSync.tssrc/renderer/lib/reticulum/reticulumPropagationSyncBackoff.test.tssrc/renderer/lib/reticulum/reticulumPropagationSyncBackoff.tssrc/renderer/lib/reticulum/useReticulumPropagationAutoSync.test.tssrc/renderer/lib/reticulum/useReticulumPropagationAutoSync.tssrc/renderer/runtime/useReticulumRuntime.reconnect-hardening.test.tssrc/renderer/runtime/useReticulumRuntime.tssrc/renderer/stores/reticulumPropagationStore.test.tssrc/renderer/stores/reticulumPropagationStore.ts
💤 Files with no reviewable changes (6)
- .githooks/pre-push
- src/renderer/components/ReticulumPropagationControls.tsx
- src/renderer/components/ReticulumAppPanelSection.tsx
- src/renderer/components/ReticulumPropagationControls.test.tsx
- src/renderer/App.tsx
- src/renderer/components/AppPanel.tsx
| path = "../.rsstack/rsReticulum/crates/rns-runtime" | ||
| optional = true | ||
| features = ["serial"] | ||
|
|
||
| [dependencies.rns-identity] | ||
| package = "rns-identity" | ||
| path = "../../rsReticulum/crates/rns-identity" | ||
| path = "../.rsstack/rsReticulum/crates/rns-identity" | ||
| optional = true | ||
|
|
||
| [dependencies.rns-wire] | ||
| package = "rns-wire" | ||
| path = "../../rsReticulum/crates/rns-wire" | ||
| path = "../.rsstack/rsReticulum/crates/rns-wire" | ||
| optional = true | ||
| features = ["std"] | ||
|
|
||
| [dependencies.rns-ratkey] | ||
| package = "rns-ratkey" | ||
| path = "../../rsReticulum/crates/rns-ratkey" | ||
| path = "../.rsstack/rsReticulum/crates/rns-ratkey" | ||
| optional = true | ||
|
|
||
| [dependencies.rns-transport] | ||
| package = "rns-transport" | ||
| path = "../../rsReticulum/crates/rns-transport" | ||
| path = "../.rsstack/rsReticulum/crates/rns-transport" | ||
| optional = true | ||
|
|
||
| [dependencies.lxmf-core] | ||
| package = "lxmf-core" | ||
| path = "../../rsLXMF/crates/lxmf-core" | ||
| path = "../.rsstack/rsLXMF/crates/lxmf-core" | ||
| optional = true | ||
|
|
||
| [dependencies.nomad-core] | ||
| package = "nomad-core" | ||
| path = "../../rsNomad/crates/nomad-core" | ||
| path = "../.rsstack/rsNomad/crates/nomad-core" | ||
| optional = true | ||
|
|
||
| [dependencies.lxst-telephony] | ||
| package = "lxst-telephony" | ||
| path = "../../rsLXST/crates/lxst-telephony" | ||
| path = "../.rsstack/rsLXST/crates/lxst-telephony" | ||
| optional = true | ||
|
|
||
| [dependencies.lxst-core] | ||
| package = "lxst-core" | ||
| path = "../../rsLXST/crates/lxst-core" | ||
| path = "../.rsstack/rsLXST/crates/lxst-core" | ||
| optional = true | ||
|
|
||
| [dependencies.lrgp] | ||
| package = "lrgp" | ||
| path = "../../lrgp-rs" | ||
| path = "../.rsstack/lrgp-rs" | ||
| optional = true | ||
|
|
||
| [dependencies.rns-interface] | ||
| package = "rns-interface" | ||
| path = "../../rsReticulum/crates/rns-interface" | ||
| path = "../.rsstack/rsReticulum/crates/rns-interface" | ||
| optional = true | ||
| features = ["ble"] | ||
|
|
||
| [dependencies.rns-link] | ||
| package = "rns-link" | ||
| path = "../../rsReticulum/crates/rns-link" | ||
| path = "../.rsstack/rsReticulum/crates/rns-link" | ||
| optional = true | ||
|
|
||
| [dependencies.rns-crypto] | ||
| package = "rns-crypto" | ||
| path = "../../rsReticulum/crates/rns-crypto" | ||
| path = "../.rsstack/rsReticulum/crates/rns-crypto" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^scripts/clone-ratspeak-stack\.sh$|Cargo\.toml$|AGENTS\.md$|.*\.sh$)' | sed -n '1,200p'
echo "== clone-ratspeak-stack outline/search =="
if [ -f scripts/clone-ratspeak-stack.sh ]; then
wc -l scripts/clone-ratspeak-stack.sh
sed -n '1,260p' scripts/clone-ratspeak-stack.sh
else
fd -a 'clone-ratspeak-stack\.sh$' .
fi
echo "== relevant RS_*_DIR / .rsstack references =="
rg -n "WORKSPACE_ROOT|RS_(RETICULUM|LXMF|NOMAD|LXST)_DIR|rns-|workspace|full-build|build\.rs|\.rsstack|cargo" -S . \
--glob '!node_modules' --glob '!dist' --glob '!build' --glob '!target' | sed -n '1,260p'
echo "== Cargo.toml context =="
if [ -f reticulum-sidecar/Cargo.toml ]; then
sed -n '50,145p' reticulum-sidecar/Cargo.toml
fiRepository: Colorado-Mesh/mesh-client
Length of output: 50381
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== scripts/apply-ratspeak-overlay-apply-list.sh =="
sed -n '1,180p' scripts/lib/apply-ratspeak-overlay-apply-list.sh
echo "== overlay scripts RS dir defaults =="
for f in scripts/apply-rsReticulum-*.sh scripts/apply-rsLXMF-*.sh; do
echo "-- $f"
sed -n '1,25p' "$f"
done
echo "== Cargo path references in reticulum-sidecar =="
rg -n "path\s*=|features\s*=|rns-stack|rns-ble|rns-rnode-tcp" reticulum-sidecar/Cargo.toml reticulum-sidecar/rust-toolchain.tomlRepository: Colorado-Mesh/mesh-client
Length of output: 306
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate lib =="
fd -a 'apply-ratspeak-overlay-apply-list\.sh$|apply-ratspeak-overlay.*\.sh$' .
echo "== lib content if present =="
if [ -f scripts/lib/apply-ratspeak-overlay-apply-list.sh ]; then
wc -l scripts/lib/apply-ratspeak-overlay-apply-list.sh
sed -n '1,220p' scripts/lib/apply-ratspeak-overlay-apply-list.sh
else
fd -a 'apply-ratspeak-overlay-apply-list\.sh$' . | sed -n '1,50p'
fi
echo "== overlay scripts default values =="
for f in $(fd -a 'apply-rsReticulum.*\.sh$|apply-rsLXMF.*\.sh$' scripts | sort); do
printf '\n-- %s\n' "$f"
sed -n '1,28p' "$f"
done
echo "== clone script relevant default/export refs =="
python3 - <<'PY'
from pathlib import Path
p=Path('scripts/clone-ratspeak-stack.sh')
lines=p.read_text().splitlines()
for i,l in enumerate(lines,1):
if 'WORKSPACE_ROOT=' in l or 'RS_RETICULUM_DIR' in l or 'RS_LXMF_DIR' in l or 'source "${SCRIPT_DIR}/lib/ratspeak-overlay-apply-list.sh"' in l:
print(f'{i}: {l}')
PY
echo "== Cargo workspace/path comment =="
sed -n '1,45p' reticulum-sidecar/Cargo.toml
sed -n '60,132p' reticulum-sidecar/Cargo.tomlRepository: Colorado-Mesh/mesh-client
Length of output: 18845
Keep Cargo paths fixed to the repo-local .rsstack/ workspace.
scripts/clone-ratspeak-stack.sh accepts WORKSPACE_ROOT outside the repository, but every Reticulum-sidecar path dependency is relative to ../.rsstack. If a user runs the clone script with a custom root and then builds the sidecar with rns-stack, Cargo may source patchable files from a different checkout than the script updated. Make the full-build flow materialize at ../.rsstack, or update every path dependency to honor the injected root using an environment variable/resolved path instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@reticulum-sidecar/Cargo.toml` around lines 66 - 129, Ensure the full-build
flow keeps the Reticulum-sidecar path dependencies rooted at the
repository-local ../.rsstack checkout that scripts/clone-ratspeak-stack.sh
updates. Either materialize custom WORKSPACE_ROOT contents at that location
before invoking rns-stack, or consistently propagate WORKSPACE_ROOT into every
path dependency in the Cargo.toml dependency declarations, including
rns-runtime, rns-identity, rns-wire, rns-ratkey, rns-transport, lxmf-core,
nomad-core, lxst-telephony, lxst-core, lrgp, rns-interface, rns-link, and
rns-crypto.
Source: Path instructions
| pub fn set_propagation_mode(&mut self, mode: PropagationMode) { | ||
| self.propagation_mode = mode; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Restore the prior mode if persistence fails.
StackHandle::set_propagation_mode calls this mutator before inner.save(...). If the save fails, PersistedState retains the new mode, but the live bridge keeps the old mode because the method returns early. Subsequent reads report behavior that the live bridge does not use.
Snapshot and restore the prior state when the save fails. Add a save-failure test.
As per coding guidelines, “Stateful or I/O code must preserve integrity on failure.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@reticulum-sidecar/src/stack/persistence.rs` around lines 409 - 411, Update
StackHandle::set_propagation_mode to snapshot the previous propagation mode
before mutation and restore it when inner.save fails, keeping PersistedState
consistent with the live bridge on error. Add a test covering save failure and
asserting the prior mode is retained.
Source: Coding guidelines
| it('returns false in auto when the discovered node is already added but disabled', () => { | ||
| const disabledConfigured: PropagationNodeRow = { | ||
| ...remoteNode, | ||
| enabled: false, | ||
| destination_hash: activeDiscovered.destination_hash, | ||
| }; | ||
| expect( | ||
| hasEffectiveReticulumPropagationTarget([disabledConfigured], null, 'auto', [ | ||
| activeDiscovered, | ||
| ]), | ||
| ).toBe(false); | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Compare renderer and sidecar handling of discovered nodes whose configured row is disabled.
set -euo pipefail
# Renderer filter: does it exclude configured hashes regardless of `enabled`?
rg -nP --type=ts -C15 'function listDiscoveredPropagationTargets'
# Sidecar: seed of the dedup set and the disabled-row skip.
rg -nP --type=rust -C10 'fn auto_discovered_candidates|fn candidates_from_propagation_rows' \
reticulum-sidecar/src/stack/pn_cascade.rsRepository: Colorado-Mesh/mesh-client
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files matching reticulumPropagationEffective:"
fd -a 'reticulumPropagationEffective' . || true
echo "Files matching pn_cascade.rs:"
fd -a 'pn_cascade\.rs' . || true
echo "Search listDiscoveredPropagationTargets / hasEffectiveReticulumPropagationTarget:"
rg -n --type=ts --type=t 'listDiscoveredPropagationTargets|hasEffectiveReticulumPropagationTarget|candidates_from_propagation_rows|auto_discovered_candidates' . || true
echo "Search node enabled handling in renderer:"
rg -n --type=ts --type=t 'destination_hash|enabled|discovered' src/renderer -C 3 || true
echo "Search disabled propagation row handling in sidecar:"
rg -n --type=rust 'enabled|destination_hash|seen|auto_discovered|candidates_from_propagation_rows' reticulum-sidecar/src/stack/pn_cascade.rs -C 8 || true
echo "Diff stat:"
git diff --stat || trueRepository: Colorado-Mesh/mesh-client
Length of output: 12996
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Renderer effective files:"
cat -n src/renderer/lib/reticulum/reticulumPropagationEffective.ts
echo "---- test ----"
cat -n src/renderer/lib/reticulum/reticulumPropagationEffective.test.ts | sed -n '1,140p'
echo "Sidecar relevant impl:"
cat -n reticulum-sidecar/src/stack/pn_cascade.rs | sed -n '118,202p'
echo "Call sites for auto_discovered_candidates:"
rg -n --type=rust 'auto_discovered_candidates|candidates_for_propagation_mode|candidates_from_propagation_rows' . -C 5 || true
echo "Search call sites for hasEffectiveReticulumPropagationTarget:"
rg -n --type=ts --type=tsx 'hasEffectiveReticulumPropagationTarget|listDiscoveredPropagationTargets' src -C 8 || true
echo "Behavioural probe sidecar dedup semantics (read-only Rust text analysis):"
python3 - <<'PY'
from pathlib import Path
p = Path('reticulum-sidecar/src/stack/pn_cascade.rs')
s = p.read_text()
start = s.index('pub fn auto_discovered_candidates')
end = s.index('\n}\n', start) + 4
print(s[start:end])
PYRepository: Colorado-Mesh/mesh-client
Length of output: 29650
Align auto_discovered_candidates with disabled configured rows.
In Auto mode, candidates_from_propagation_rows skips rows where enabled == false, then auto_discovered_candidates initializes its seen set from the remaining candidates. A discovered announce matching a disabled configured hash is not seen, so it can be appended as a discovered cascade target and receive a deposit even though the renderer reports no effective target.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/lib/reticulum/reticulumPropagationEffective.test.ts` around
lines 71 - 82, Update auto-discovery candidate handling in
hasEffectiveReticulumPropagationTarget and its candidate-building flow so
disabled configured propagation rows still reserve their destination_hash in the
discovered-node seen set, even though they are excluded from effective
configured candidates. Ensure matching announces are not appended as discovered
cascade targets, preserving the false result for disabled-only configurations.
Roll back propagation mode on sidecar save failure, keep supersede as a non-success settle marker, make Chat notice mode reactive, and harden cascade/local-loading/i18n edge cases from PR review.
Summary
Moves Reticulum Propagation mode into Network → Propagation nodes and rebuilds Auto/Manual/Off sync + outbound PN cascade end-to-end (renderer + sidecar). Also relocates the Ratspeak overlay stack to a gitignored
.rsstack/workspace, speeds Disconnect & Quit, and hardens cascade deferral / i18n / docs.Propagation mode UI & semantics
ReticulumPropagationControls/ReticulumAppPanelSection).auto) are honored.propagation_mode, disarms the outbound PN, and builds an empty cascade.POST /api/v1/propagation/mode) on change and on sidecar-ready; diagnostic snapshot exposespropagationClient(mode / preferred / autoTarget / resolvedSyncTargetId).Sync cascade settlement & backoff
awaitPropagationSyncSettled) —startSynconly means sidecar acceptance, not success.startSyncreturnsaccepted | deferred | failed(not a boolean).reticulumPropagationSyncBackoff, session memory); remote half of a cascade is budgeted (~5 min) with ~60s per-remote attempt cap before falling through to local.PROPAGATION_SYNC_OUTBOUND_BUSYadvances without 15‑minute backoff so an in-flight outbound deposit does not black-hole the best PN.syncNoTarget/syncLocalLoading(never overwriting a real per-node error).status: "loading"; auto-sync tick refreshes from sidecar while no cascade candidate exists so a fresh stack recovers on its own.syncTargetId+resolveReticulumPropagationTargetLabel(unprefixed when nobody was contacted).Auto deposits on Discovered PNs + Chat notice
auto_discovered_candidates/ shared rebuild helper on refresh and PN announce).hasEffectiveReticulumPropagationTarget/ cascade capacity count discovered rows in Auto — Chat “no PN” notice hides and the link-timeout Failed bridge holds off while cascade capacity remains.Disconnect & Quit performance
stop({ forQuit: true })skips prepare-stop BLE drain and SIGKILLs after ~750ms (instead of ~1s + 5s graceful wait).onDisconnect(main already tears down BLE/TCP/sidecar); stay-in-app Stop / Disconnect keep the graceful path.Ratspeak stack layout (
.rsstack)../../rs*to gitignored.rsstack/so a standalone rsReticulum mirror stays clean.update.sh, release build, CI workflows, contract tests, patches README, and docs..rsstack/from repo-wide linters (yamllint / markdownlint / prettier).Docs, i18n, tooling
AGENTS.md,docs/reticulum.md,docs/troubleshooting.mdwith Auto one-time Discovered sync, cascade order (including Auto discovered deposits), settlement/backoff, and Off = no PN.modeHelpAutoandsyncLocalLoadingin all 15 non-en locales; CzechmodeHelpManualPříručka:→Ručně:.check-i18n-qualityguards so Preferred-managed Auto help, syncLocalLoading pronoun errors, and Czech handbook false friends fail CI.pnpm-lock.yamlviapnpm run update.Commits (oldest → newest)
f1b5a0e1— move propagation mode to Network; relocate Ratspeak stack to.rsstacka807bd11— PN sync cascade with local Preferred; stop cancel→unreachable41e05958— Auto sync selects without adding PNs or Preferredb5716504— Auto one-time syncs best Discovered PN by hash54518cc5— remove local pre-push Vitest gate076e7274— Manual PN sync cascade; Off as no-PN supporta9df6b1b— stop reporting “unreachable” when PN sync has no target19ab81db— make Disconnect & Quit exit promptly1a67459a— Auto deposits on discovered PNs; dismissible Chat notice7fd25e69— advance PN sync cascade after async attempt failures8e61142a— reach local PN after failed Auto sync attempts231bb92e— harden cascade deferral, i18n, and helpers752eac54— refresh pnpm-lock after dependency updateTest plan
syncNoTargetorsyncLocalLoading(not “unreachable”); recovers after announce or messagestore loadOUTBOUND_BUSY) and does not omit that PN for 15 minutesmesh_client_stack.json+lxmf-outbound.log/ PN-island diagnostics as documented.rsstack/clones/overlays apply; sidecar full-feature build workspnpm run check:prgreen (incl. new i18n quality guards)Summary by CodeRabbit
New Features
Documentation