Skip to content

fix(messaging): reduce composer recomposition and typing jank#6318

Merged
jamesarich merged 2 commits into
meshtastic:mainfrom
jeremiah-k:bugfix/messaging-input-lifecycle-jank
Jul 23, 2026
Merged

fix(messaging): reduce composer recomposition and typing jank#6318
jamesarich merged 2 commits into
meshtastic:mainfrom
jeremiah-k:bugfix/messaging-input-lifecycle-jank

Conversation

@jeremiah-k

@jeremiah-k jeremiah-k commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Overview

This reduces message-composer recomposition and per-keystroke work, particularly on active meshes where telemetry changes frequently and inline Markdown is enabled.

The composer previously received full Node objects, so updates to telemetry, position, metrics, last-heard time, or other unrelated fields could invalidate the input subtree even when mention-visible identity data had not changed. The live editor also performed more parsing, matching, and draft-persistence work than necessary during rapid typing.

This keeps stored and transmitted message text unchanged while narrowing reactive inputs and bounding mention matching, live styling, and draft persistence.

Changes

Stable composer inputs

  • Project nodes into lightweight mention candidates containing only the ID and display names used by the composer.
  • Expose those candidates to MessageInput as a persistent ImmutableMap, giving Compose an explicit stability contract.
  • Derive the candidate map with structural equality so telemetry-only node updates can leave the input subtree skippable instead of recreating its output transformation or platform input connection.

Bounded typing work

  • Cache normalized mention fields once per candidate and normalize each query once.
  • Preserve source iteration order while stopping matching at the suggestion limit.
  • Fast-path empty queries and plain text.
  • Replace full GFM parsing in the live text field with lightweight detection for asterisk bold/italic, strikethrough, and inline code.
  • Intentionally leave underscore emphasis raw while editing; sent message bubbles continue to use the full Markdown renderer.
  • Debounce draft persistence to SavedStateHandle by 300 ms, cancel superseded writes, and keep send and explicit clear operations immediate.

Presentation-only mentions and Markdown

  • Parse Markdown ranges from the immutable source buffer before applying friendly-name substitutions.
  • Remap style boundaries through shorter or longer mention replacements.
  • Prevent Markdown delimiters in display names from introducing formatting absent from the stored text.
  • Ignore bold, italic, and strikethrough delimiters inside inline-code spans while preserving outer emphasis around nested code.
  • Preserve raw mention tokens and Markdown delimiters in stored and transmitted messages.

Review guide

The commits are intended to be reviewed independently and in order:

  1. fix(messaging): stabilize composer lifecycle and hot paths
    • stable mention projection;
    • bounded matching and live styling;
    • debounced, cancellation-safe draft persistence.
  2. fix(messaging): preserve markdown styling through mentions
    • source-first presentation planning;
    • style-range remapping through mention substitutions;
    • delimiter and inline-code isolation.

Regression coverage

Coverage includes:

  • case-insensitive mention matching across IDs and display names;
  • deterministic ordering and bounded early termination;
  • plain-text and incomplete-Markdown fast paths;
  • bold, italic, strikethrough, and inline-code styling;
  • bold delimiters not being double-counted as italic;
  • Markdown ranges before and after multiple mention replacements;
  • friendly names containing Markdown delimiters;
  • Markdown delimiters inside code spans;
  • emphasis surrounding nested code;
  • exact trailing-edge draft-persistence timing;
  • rapid edits replacing the pending debounce window;
  • clearing a draft cancelling pending persistence.

On-device validation

On-device testing was performed while connected to an active mesh with ongoing telemetry updates.

Normal typing is noticeably more responsive, and the earlier focus, keyboard, selection, mention-suggestion, and live-formatting instability was not reproduced during ordinary input.

Holding backspace can still produce some stutter. That appears to be a narrower sustained-edit performance issue and will be investigated separately rather than expanding this PR beyond its current scope.

Scope

This is limited to the message-composer hot path. It does not change transport, BLE scanning or reconnect behavior, database coordination, node identity, or application lifecycle behavior.

Summary by CodeRabbit

  • New Features

    • Improved message mentions with faster, more reliable autocomplete and clearer display names.
    • Mention matching now works case-insensitively across IDs and names.
    • Inline Markdown formatting is preserved when mentions are inserted or replaced.
  • Bug Fixes

    • Draft messages now save smoothly after brief editing pauses, reducing unnecessary updates.
    • Clearing a draft immediately prevents pending changes from being restored.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e786250d-47bf-4f0c-b414-b93413d6d7b6

📥 Commits

Reviewing files that changed from the base of the PR and between 847d4b2 and 830e096.

📒 Files selected for processing (5)
  • feature/messaging/build.gradle.kts
  • feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt
  • feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt
  • feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/MessageComposerBehaviorTest.kt
  • feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/MessageViewModelTest.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/MessageViewModelTest.kt
  • feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt
  • feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt

📝 Walkthrough

Walkthrough

Messaging mentions now use lightweight candidates with live markdown styling and replacement remapping. Draft persistence is debounced, with pending writes canceled when drafts are cleared. Tests cover composer behavior and draft timing.

Changes

Mention composer pipeline

Layer / File(s) Summary
Candidate-based autocomplete
feature/messaging/build.gradle.kts, feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt, feature/messaging/src/commonTest/.../MessageComposerBehaviorTest.kt
Mention candidates replace full nodes for matching, rendering, and candidate-ID insertion, with preview wiring and matching tests updated.
Mention and markdown transformation
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt, feature/messaging/src/commonTest/.../MessageComposerBehaviorTest.kt
Live inline markdown spans are detected and remapped after friendly-name mention replacements, with coverage for code spans, offsets, and delimiter handling.

Draft persistence

Layer / File(s) Summary
Debounced draft persistence
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt, feature/messaging/src/commonTest/kotlin/org/meshtastic/feature/messaging/MessageViewModelTest.kt
Draft edits update memory immediately and persist after 300 ms; clearing cancels pending persistence and is covered by tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MessageScreen
  participant MessageInput
  participant MentionCandidateMatcher
  participant MentionOutputPlan
  participant LiveMarkdownParser
  MessageScreen->>MessageInput: provide mention candidates
  MessageInput->>MentionCandidateMatcher: match mention query
  MentionCandidateMatcher-->>MessageInput: return ordered suggestions
  MessageInput->>MentionOutputPlan: replace mention tokens
  MentionOutputPlan->>LiveMarkdownParser: remap markdown spans
  LiveMarkdownParser-->>MessageInput: return styled composer output
Loading

Possibly related PRs

Suggested labels: refactor, ui

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main goal of the changes: reducing composer recomposition and typing jank in messaging.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
feature/messaging/build.gradle.kts

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bugfix PR tag label Jul 19, 2026
@jeremiah-k
jeremiah-k force-pushed the bugfix/messaging-input-lifecycle-jank branch 2 times, most recently from dad6f71 to 847d4b2 Compare July 22, 2026 22:19
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 22, 2026 23:42
@jamesarich

Copy link
Copy Markdown
Collaborator

Went through this in detail — the correctness story is solid: raw text in the buffer is what's stored and transmitted (OutputTransformation is display-only), so the wire format and iOS parity are untouched; the live-styling regexes are index-safe on unterminated/nested/multibyte input; rename propagation works through the derived candidate map; the span remapping math checks out through short/long replacements; and parsing before replacement closes the delimiter-injection hole. The cached normalization and bounded lazy matching do what the description says, with deterministic ordering. Nice work.

Four smaller items, none blocking:

  1. The headline recomposition claim is only partially realized. MessageScreen still reads nodes directly, so it recomposes on every telemetry tick — and the candidate map is a plain Map, which is unstable to the Compose compiler, so MessageInput is compared by reference and re-executes anyway (cheap re-execution thanks to the inner memoization, but not the skipping the description implies). Passing a kotlinx.collections.immutable.ImmutableMap (or an @Immutable wrapper) would make the claim fully true for one line of change.

  2. Device validation. The Validation section lists unit coverage only — for a jank PR I'd like the usual on-device pass (busy mesh, telemetry churn, inline Markdown on) before merge, even though the risk is low with the wire format untouched.

  3. PR body nit: "Removed the ineffective onCleared() draft write" — there's no onCleared at the merge base or on main, and nothing of the sort in this diff. Worth correcting so the changelog doesn't record a phantom change.

  4. Cosmetic, worth a KDoc note: the live detectors cover *, **, ~~, and backticks but not underscore emphasis, so _x_/__x__ previews unstyled in the composer while styling correctly in the sent bubble. Fine as a trade — just document it at liveInlineMarkdownStyleRanges so it doesn't come back as a bug report. (Also: MessageComposerPerformanceTest asserts correctness, not performance — a rename would keep expectations honest.)

With #2 done (and ideally #1), this is ready.

@jeremiah-k
jeremiah-k marked this pull request as draft July 22, 2026 23:56
@jeremiah-k
jeremiah-k force-pushed the bugfix/messaging-input-lifecycle-jank branch from 847d4b2 to ec68328 Compare July 23, 2026 01:03
Project nodes into a persistent immutable map of lightweight mention candidates whose equality depends only on identifiers and display names. Keep the composer parameter stable when telemetry, position, last-heard, or other unrelated node fields change so Compose can skip the input subtree without recreating its output transformation or churning the platform input connection.

Keep live editing bounded with a lightweight inline-markdown scanner, cached normalized candidate fields, one query normalization, limited lazy matching, and an empty-query fast path. Document the intentionally narrower live-preview dialect while leaving sent-message rendering unchanged.

Drive debounced draft persistence from text snapshots, cancel superseded writes, and clear sent drafts synchronously. Cover candidate ordering and limits, supported inline styling, debounce replacement, and immediate draft clearing in behavior-focused tests.
Build mention replacements and inline-style spans from the immutable source buffer before applying presentation edits. Remap style boundaries through length-changing friendly-name substitutions so replacing @!<hex> tokens cannot shift or discard surrounding markdown.

Keep presentation-only transformations from changing the wire text, and prevent display names or delimiters inside code spans from introducing unintended bold, italic, or strikethrough styling. Use deterministic span ordering and lint-clean boundary traversal for overlapping syntax cases.

Cover mentions nested within markdown, markdown contained by mention substitutions, delimiter-bearing display names, code-span isolation, and replacement length changes. Keep candidate terminology aligned throughout the transformation pipeline.
@jeremiah-k
jeremiah-k force-pushed the bugfix/messaging-input-lifecycle-jank branch from ec68328 to 830e096 Compare July 23, 2026 01:15
@jeremiah-k

Copy link
Copy Markdown
Contributor Author

@jamesarich I’ve addressed the earlier review feedback and completed another on-device pass. Normal typing is noticeably improved. There is still some stutter when holding backspace, which I plan to investigate further as part of follow-up lifecycle work, but I don’t think that should block this PR in its current scope.

During final testing I also encountered BLE reconnect and scan-lifecycle failures. Those issues are unrelated to the composer changes and are addressed separately in #6384, so there is no code or merge-order dependency between the two PRs.

@jeremiah-k
jeremiah-k marked this pull request as ready for review July 23, 2026 14:01

@jamesarich jamesarich left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate it - thank you!

@jamesarich
jamesarich added this pull request to the merge queue Jul 23, 2026
Merged via the queue into meshtastic:main with commit bc5cf83 Jul 23, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants