Skip to content

Render rich message content and Nostr mentions#659

Closed
erskingardner wants to merge 6 commits into
masterfrom
codex/parse-message-links-and-mentions
Closed

Render rich message content and Nostr mentions#659
erskingardner wants to merge 6 commits into
masterfrom
codex/parse-message-links-and-mentions

Conversation

@erskingardner
Copy link
Copy Markdown
Member

@erskingardner erskingardner commented May 14, 2026

Summary

  • Add reusable rich message rendering for parsed message content, including tappable URLs and Nostr mention tokens.
  • Render inline code and fenced code blocks with monospace styling, full-width block backgrounds, copy buttons, and copied feedback.
  • Display known Nostr mentions as friendly @name text in chat bubbles and chat-list previews while preserving the underlying nostr:npub... content for sending/tapping.
  • Add @ mention suggestions in the composer for other chat members, with rich display text in the input and nostr:npub... stored/sent in the actual message.
  • Keep mention-aware draft restoration and message sending wired through the chat input flow.

Implementation Notes

  • MessageContentText handles token-aware rendering for bubbles without taking ownership of chat-member lookup.
  • Chat screens build the known mention display map from group members and pass it down to message bubbles.
  • Chat-list previews do a small metadata-backed replacement pass because ChatMessageSummary currently contains raw content but not parsed content tokens.
CleanShot.2026-05-14.at.10.52.14.mp4
CleanShot 2026-05-14 at 11 06 16

Testing

  • flutter test test/widgets/chat_list_tile_test.dart
  • just lint
  • just test-flutter-quiet
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Rich message formatting: clickable links, inline & fenced code blocks with copy-to-clipboard and “Copied” feedback
    • Group chat @mentions: suggestions, friendly display names in messages/listings, and tap-to-open profile/navigation
    • Composer: mention-aware input that inserts/restores friendly mentions while preserving underlying identifiers for sending/drafts
  • Documentation

    • Added localization strings for "Copy code" and "Copied" across languages
  • Tests

    • Extensive widget tests for links, mentions, code blocks, copy UX, and draft persistence

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds MessageContentText for tokenized rendering (URLs, Nostr mentions, inline/fenced code, highlights); refactors WnMessageBubble/ChatMessageBubble to propagate contentTokens and Nostr tap/display wiring; implements MentionTextEditingController and composer mention suggestions/insertion; replaces npub URIs with @displayName in previews; adds tests and platform mocks.

Changes

Mentions and Tokenized Message Rendering

Layer / File(s) Summary
MentionTextEditingController implementation
lib/utils/mention_text_editing_controller.dart
Adds MentionTextEditingController and MentionTextTarget to insert/track/display friendly mentions, restore stored Nostr URIs, expose messageText, shift/validate mention ranges, and custom buildTextSpan rendering.
MessageContentText widget and parsing
lib/widgets/message_content_text.dart
Adds MessageContentText HookWidget: token segmentation, inline and fenced code parsing, highlight-range conversion, span construction with TapGestureRecognizers, URL/Nostr tap handlers (with fallback launches), and fenced-code copy UI.
WnMessageBubble + ChatMessageBubble wiring
lib/widgets/wn_message_bubble.dart, lib/widgets/chat_message_bubble.dart
Refactors WnMessageBubble to use MessageContentText, adds contentTokens, nostrDisplayNamesByUri, and onNostrTap public surfaces; ChatMessageBubble forwards message.contentTokens and provides default Nostr-profile navigation.
ChatScreen mention wiring and composer UI
lib/screens/chat_screen.dart
Loads group members, builds mention candidate list and nostr:<npub>@displayName map, converts _ChatInput to a HookWidget, configures mention targets on the controller, and inserts selected mentions into the composer.
ChatListTile preview replacement
lib/widgets/chat_list_tile.dart
Adds RegExp and async helpers to resolve nostr:npub... URIs in subtitles to @displayName using user metadata, and memoizes results via useFuture.
Hook updates
lib/hooks/use_chat_input.dart, lib/hooks/use_group_members.dart
Switches chat input to MentionTextEditingController and reads messageText for drafts; useGroupMembers gains enabled flag to short-circuit fetching.
Localization
lib/l10n/*.arb
Adds copyCode and copied localization keys across language ARB files.
Tests: rendering, URL launches, code blocks, mentions, controller
test/widgets/*, test/screens/*, test/utils/*, test/hooks/*
Adds and updates tests: MessageContentText (URLs, Nostr taps, inline/fenced code, copy), WnMessageBubble/ChatMessageBubble URL and Nostr tap tests with UrlLauncherPlatform mocks, MentionTextEditingController unit tests, ChatScreen mention UX tests (render/insert/draft restore), ChatListTile preview test, and helper/mocks for clipboard and url launcher.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mubarakcoded
  • jgmontoya
  • dannym-arx
  • pepina-dev
  • cypherpinkdev
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/parse-message-links-and-mentions

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@lib/widgets/message_content_text.dart`:
- Around line 238-243: The loop over candidates currently returns the first
candidate that matches (based on candidate order) which can pick a later
occurrence; instead scan all candidates to find the earliest match position
(minimum start) and return a single _TokenMatch with that start and
corresponding end; update the code using the existing variables candidates,
content, cursor, start, end and construct/return the _TokenMatch for the minimum
start found (or null/no match if none).
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: 33fe1007-c8ed-427a-863c-ff0a0afa2d97

📥 Commits

Reviewing files that changed from the base of the PR and between 08d5c44 and 5589b9b.

📒 Files selected for processing (6)
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • test/widgets/wn_message_bubble_test.dart
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Check
  • GitHub Check: Test (2/4)
  • GitHub Check: Test (4/4)
  • GitHub Check: Test (3/4)
  • GitHub Check: Test (1/4)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use single quotes for strings in Dart/Flutter code
Enable and respect prefer_const_constructors lint rule in Dart/Flutter code
Enable and respect prefer_final_locals lint rule in Dart/Flutter code
Set line width to 100 characters in Dart/Flutter code
Preserve trailing commas in Dart/Flutter code
Maintain minimum test coverage of 99%; never submit a PR that reduces test coverage
Avoid using // coverage:ignore, // coverage:ignore-line, // coverage:ignore-start, or // coverage:ignore-end to bypass coverage requirements; write tests instead (only exception: truly unreachable code)

Files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
lib/widgets/!(wn_)*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Complex reusable widgets should not have wn_/Wn prefix and may contain translations, hooks with Rust API calls, or other complex logic

Files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
lib/widgets/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Screen-scoped widgets extracted from a single screen should be prefixed with the screen name and only used in that one screen

Files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
**/*.{dart,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Do not add comments except for code that is really complex or hard to understand

Files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/**/*.dart: Use flutter_screenutil for all size values (padding, margins, gaps, icon sizes, font sizes, border radius, container dimensions) to ensure responsive layouts. Use .w for width, .h for height, .sp for font size/letter spacing, and .r for radius
Avoid StatefulWidget; prefer providers (for shared app-wide state) or hooks (for widget-local state) instead

Files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
lib/widgets/**

⚙️ CodeRabbit configuration file

lib/widgets/**: This is a reusable widget.
There are two kinds of reusable widgets:

  1. Design system widgets — simple, presentational widgets that match
    the Figma design system in name and structure. They have Widgetbook
    stories, contain only presentational logic, and do NOT have
    translations or Rust API calls.

    • File MUST be prefixed with wn_ (e.g. wn_filled_button.dart)
    • Class MUST be prefixed with Wn (e.g. WnFilledButton)
  2. Complex reusable widgets — used across multiple screens but contain
    translations, hooks with Rust API calls, or other complex logic
    that makes them harder to display in Widgetbook.

    • These do NOT use the wn_/Wn prefix
    • Example: OnboardingCarousel (used in multiple screens, has
      translations and a page controller inside)

General rules for all widgets in this directory:

  • Use const constructors where possible
  • Use flutter_screenutil (.w, .h, .sp, .r) for all dimensions
  • Avoid StatefulWidget — prefer hooks for local state
  • No comments except for truly complex logic

Files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
test/**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

test/**/*_test.dart: Test files should mirror source structure with _test.dart suffix
Use helpers from test/test_helpers.dart in tests: setUpTestView(), mountTestApp(), mountHook(), mountWidget(), mountStackedWidget()
Mock Rust API by extending MockWnApi from test/mocks/mock_wn_api.dart instead of implementing RustLibApi directly
Prefer find.byKey() over find.byIcon() in tests; add keys to icons in widgets and use find.byKey(const Key('icon_name')) in tests
Use valid 64-character hex strings for pubkeys in tests, not dummy values like 'abc' or 'test-pubkey'

Files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
test/**

⚙️ CodeRabbit configuration file

test/**: IMPORTANT: CI enforces coverage regression (coverage must never decrease). It does not enforce a fixed 95% minimum threshold.
Rules:

  • Test files mirror source structure with _test.dart suffix
  • Use helpers from test/test_helpers.dart (setUpTestView, mountTestApp, etc.)
  • Mock Rust API using RustLib.initMock(api: mockApi)
  • Always extend MockWnApi from test/mocks/mock_wn_api.dart
  • Prefer find.byKey() over find.byIcon() for widget testing
  • Use valid 64-char hex strings for pubkeys, not dummy values like 'abc'
  • Tests must be deterministic — no external service dependencies

Files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
lib/widgets/wn_*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/widgets/wn_*.dart: Design system widgets should be prefixed with wn_ in filename and Wn in class name (e.g., wn_filled_button.dart / WnFilledButton)
Design system widgets must have Widgetbook stories and should not contain translations or Rust API calls

Files:

  • lib/widgets/wn_message_bubble.dart
🧠 Learnings (26)
📚 Learning: 2026-01-05T20:05:32.918Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/screens/settings_screen.dart:76-88
Timestamp: 2026-01-05T20:05:32.918Z
Learning: In the Sloth project, tooltips should not be used for UI elements. During code reviews, verify that UI components do not include Tooltip widgets and replace them with accessible alternatives (e.g., long-press hints, labels, or interactive guidance). Apply this guidance across Dart UI files under lib (not just settings_screen.dart) to maintain a consistent design approach.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-09T13:25:18.531Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: lib/services/message_service.dart:14-26
Timestamp: 2026-01-09T13:25:18.531Z
Learning: When errors are presented to users in the Sloth codebase (UI layers such as snackbars, dialogs, toasts), show friendly, user-facing messages instead of raw Rust error messages. Implement a mapping from internal error types or codes to clear, non-technical messages, and surface actionable guidance for end users where appropriate. Avoid exposing internal stack traces or language runtime details; centralize common user-facing error wording in a dedicated utility or service and translate backend errors into concise, helpful UI messages.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-04-06T09:40:41.044Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 552
File: lib/screens/profile_keys_screen.dart:71-71
Timestamp: 2026-04-06T09:40:41.044Z
Learning: In this Flutter/Dart codebase, when using flutter_screenutil (e.g., ScreenUtil or ScreenUtil-based sizing like `w`, `h`), do not require scaled units for literal zero values. Specifically, in EdgeInsets (and similar sizing/padding/margin/gap APIs), bare numeric `0` should be allowed (e.g., `EdgeInsets.all(0)`, `EdgeInsets.symmetric(vertical: 0)`, `SizedBox(width: 0)`), because scaling `0` remains `0`. Only flag ScreenUtil violations when a non-zero literal needs to be expressed via `0.w`/`0.h` equivalents; do not flag bare `0`.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-05T22:40:27.888Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_image_picker.dart:127-143
Timestamp: 2026-01-05T22:40:27.888Z
Learning: In marmot-protocol's Sloth project (lib/widgets/...): image hosting URLs returned by blossom servers are always lowercase (http:// or https://). Do not implement or rely on case-insensitive URL protocol detection in WN image picker code; assume the protocol is lowercase. If URL protocol checks are needed, compare against the exact literals 'http://' and 'https://'. Add tests that assert URLs use lowercase protocols. This guideline applies to all Dart files under lib/widgets that handle image URLs.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-04T10:35:43.112Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 170
File: lib/widgets/wn_list_item.dart:232-234
Timestamp: 2026-02-04T10:35:43.112Z
Learning: In this repository ( marmot-protocol/sloth ), when writing widget build methods in Dart files under lib/widgets, only extract a local typography variable if context.typographyScaled or context.typography is used multiple times within the same build method. If typography is used only once, keep the inline access to avoid an unnecessary extra line. This guideline helps maintain readability and avoids premature refactoring.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-14T05:09:20.007Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 260
File: lib/widgets/wn_message_quote.dart:32-88
Timestamp: 2026-02-14T05:09:20.007Z
Learning: In Dart widget files under lib/widgets (matching lib/widgets/**/*.dart), spacing literals such as padding, gap values, and border radii should not be extracted into constants based on josefinalliende's preference. Apply this rule consistently across widget files to improve readability and avoid over-abstracting simple literals.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-06T01:08:41.552Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_warning_box.dart:28-79
Timestamp: 2026-01-06T01:08:41.552Z
Learning: In reviews for the marmot-protocol/sloth repository, avoid suggesting accessibility, design, or general improvements unless they are strictly relevant to the PR description and its stated goals. Focus feedback on the specific PR objectives and requirements; ignore unrelated stylistic or broad improvement suggestions.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-15T14:42:54.111Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 39
File: lib/hooks/use_user_search.dart:47-53
Timestamp: 2026-01-15T14:42:54.111Z
Learning: When using npubFromHex(String hexPubkey) from lib/utils/formatting.dart, it already handles errors internally and returns null on failure. Do not wrap calls to this function in try/catch blocks. Instead, check for a null return and handle accordingly (e.g., treat as invalid hex). This guideline applies to all Dart files in the repository that may call this function.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-16T17:35:32.431Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 43
File: lib/hooks/use_network_relays.dart:155-184
Timestamp: 2026-01-16T17:35:32.431Z
Learning: In Dart/Flutter code, prefer void as the return type for fire-and-forget async functions that are intended to run in the background without blocking the caller. Using Future or Future<void> would allow callers to accidentally await, which could block for long periods. This pattern makes the call fire-and-forget by design. Apply to functions that intentionally should not be awaited; document the intent where appropriate. (Example context: pollRelayStatus in lib/hooks/use_network_relays.dart uses void to prevent callers from awaiting a potentially long-running background task.)

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-19T15:50:56.684Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 52
File: lib/screens/app_settings_screen.dart:30-40
Timestamp: 2026-01-19T15:50:56.684Z
Learning: Target the Flutter SDK version >= 3.27 across the repo. Since features like Column.spacing and Row.spacing were added in Flutter 3.27, you can safely use them in Dart files (e.g., lib/screens/app_settings_screen.dart) as long as the pubspec.yaml environment specifies Flutter >= 3.27. If needed, enforce this by validating the environment constraint (e.g., flutter, sdk: flutter) in pubspec.yaml.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-29T03:02:34.290Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 100
File: lib/widgets/wn_dropdown_selector.dart:126-139
Timestamp: 2026-01-29T03:02:34.290Z
Learning: Global font-family usage guideline: Since Manrope is configured in lib/theme.dart for both light and dark themes, individual TextStyle declarations should not specify fontFamily: 'Manrope'. During reviews, verify that no TextStyle instances override fontFamily unnecessarily; rely on theme inheritance instead. If a TextStyle must specify a font for a specific case, ensure it is clearly justified and localized, and document why the override is needed. This guideline applies across Dart files in the project (e.g., lib/widgets/wn_dropdown_selector.dart and related components).

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-29T16:02:52.588Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 129
File: test/widgets/wn_overlay_test.dart:84-91
Timestamp: 2026-01-29T16:02:52.588Z
Learning: In Dart/Flutter code, when constructing widgets inside an already-const context (for example, within a const Scaffold with a child that is a collection of widgets), avoid adding explicit const keywords to the children. The Dart analyzer treats these as implicitly const, and adding explicit const can trigger the unnecessary_const lint. Review test files and general Dart files to ensure you do not redundantly prefix children with const in const contexts; rely on the implicit const behavior instead.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-07T03:58:22.587Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/services/android_signer_service.dart:13-13
Timestamp: 2026-02-07T03:58:22.587Z
Learning: In Dart files across the repository, retain NIP (Nostr Implementation Possibilities) and MIP (Marmot Implementation Possibilities) protocol reference comments that link code to their specifications. While general guidance discourages extraneous comments, these references improve traceability and maintainability by documenting the protocol context for related implementation details. Keep them in Dart sources (e.g., lib/services/android_signer_service.dart) and ensure they are kept up-to-date.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-11T17:29:43.985Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/screens/app_settings_screen.dart:64-69
Timestamp: 2026-02-11T17:29:43.985Z
Learning: In the whitenoise Flutter app, after deleting all data and resetting auth, use Routes.goToHome(context) instead of Routes.goToLogin(context) because the home screen is the app entry point and will manage authentication routing. Apply this change to navigation calls that should land on the home screen after a data reset or auth reset. Update lib/screens/app_settings_screen.dart and any similar navigation points accordingly.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-18T18:36:13.394Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 315
File: lib/screens/edit_group_screen.dart:45-47
Timestamp: 2026-02-18T18:36:13.394Z
Learning: Whitenoise uses automated code formatting through the precommit workflow (just precommit). Do not tweak line wrapping or formatting manually; follow the formatter's output. Run the precommit formatter locally and ensure the code matches its styling decisions, and only deviate if the formatter cannot express the intended style (in which case adjust the code to satisfy the formatter).

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-04-06T09:36:06.726Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 553
File: lib/widgets/wn_copyable_field.dart:38-40
Timestamp: 2026-04-06T09:36:06.726Z
Learning: In Dart, `String.operator*` is a valid built-in operator for repeating strings (e.g., `'⬤' * 16`). In code reviews of Dart (`.dart`) files, do not treat expressions like `'text' * n` as invalid syntax and do not recommend replacing them with alternatives such as `List.filled(...).join()`; the operator is supported by `dart:core`.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-05-13T13:46:32.612Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/hooks/use_block_actions.dart:3-3
Timestamp: 2026-05-13T13:46:32.612Z
Learning: In marmot-protocol/whitenoise code reviews, do not flag imports that reference FRB-generated bindings under `package:rust_lib_whitenoise/src/rust/...` as private-internals violations. In `rust_lib_whitenoise`, Dart bindings are intentionally generated into `lib/src/rust/`, and `analysis_options.yaml` sets `implementation_imports: false` to suppress the related Dart lint—so `package:rust_lib_whitenoise/src/rust/...` imports are the correct/only way to consume that FRB-generated API.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-05-13T13:46:39.250Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/services/foreground_service.dart:13-16
Timestamp: 2026-05-13T13:46:39.250Z
Learning: In this repo (marmot-protocol/whitenoise), `rust_lib_whitenoise` is an FRB-generated Flutter package where the generated Dart bindings are intentionally exposed under `lib/src/rust/` as the public API surface. Do not treat imports like `package:rust_lib_whitenoise/src/rust/...` as breaking package API boundaries in code reviews (the project also sets `implementation_imports: false` in `analysis_options.yaml` to opt into this). Never raise concerns about `src/` imports from `rust_lib_whitenoise`.

Applied to files:

  • lib/widgets/chat_message_bubble.dart
  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-05T21:28:05.652Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: test/screens/profile_keys_screen_test.dart:14-23
Timestamp: 2026-01-05T21:28:05.652Z
Learning: In test mocks for the marmot-protocol/sloth project, allow skipping bounds checking for substring operations when test inputs are controlled and have proven sufficient length (e.g., 'test_pubkey' is always 12 characters). Favor simpler, deterministic mock implementations in such controlled test contexts, but avoid relaxing bounds checks in production code or tests with variable inputs. Apply this guideline primarily to test files under the test/ directory.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-01-07T16:49:18.694Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: test/screens/chat_screen_test.dart:245-282
Timestamp: 2026-01-07T16:49:18.694Z
Learning: In the marmot-protocol/sloth repository, it is acceptable to use force-unwrapping (!) in Dart test files as an implicit assertion: if a value is unexpectedly null, the test will fail. This guideline applies only to test code under the test/ directory and should not be used in production code.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-01-11T22:43:09.610Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 36
File: test/screens/chat_list_screen_test.dart:74-76
Timestamp: 2026-01-11T22:43:09.610Z
Learning: In Dart tests (e.g., test/screens/chat_list_screen_test.dart and related files), use setUp() to call _api.reset() to clean up StreamController resources. Do not suggest or rely on tearDownAll for this cleanup.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-04-14T03:52:37.716Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 565
File: test/providers/offline_provider_test.dart:63-177
Timestamp: 2026-04-14T03:52:37.716Z
Learning: In this repository, it’s acceptable to use fixed-time waits for test timing using `Future.delayed` (e.g., `await Future.delayed(const Duration(milliseconds: 10))`) inside test files. During code reviews, don’t recommend replacing these fixed sleeps with event-based or poll-based synchronization when the change is localized to Dart test files under the `test/` directory.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-02-02T21:11:03.835Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 157
File: test/widgets/wn_middle_ellipsis_text_test.dart:11-20
Timestamp: 2026-02-02T21:11:03.835Z
Learning: In widget tests (files matching test/widgets/**/*_test.dart) that use the mountWidget helper from test/test_helpers.dart, you do not need to call setUpTestView(tester) separately before mounting. Rely on mountWidget for necessary setup to avoid redundant setUpTestView calls in these tests. If a test does not use mountWidget, continue using setUpTestView as before.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-02-02T21:11:17.594Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 157
File: test/widgets/wn_copy_card_test.dart:8-16
Timestamp: 2026-02-02T21:11:17.594Z
Learning: In test/widgets/**/*_test.dart, when using the mountWidget helper, you do not need to call setUpTestView(tester) separately because mountWidget performs the necessary setup for widget tests. Rely on mountWidget to prepare the test environment and avoid redundant setUpTestView calls.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-02-05T11:59:09.400Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 189
File: test/widgets/wn_filter_chip_test.dart:1-3
Timestamp: 2026-02-05T11:59:09.400Z
Learning: In Dart/Flutter test files, Offset is exported by package:flutter_test/flutter_test.dart. If you already import flutter_test, you do not need to import dart:ui for Offset. Ensure there are no conflicting Offset symbols when both imports occur.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
📚 Learning: 2026-04-28T17:39:39.941Z
Learnt from: pepina-dev
Repo: marmot-protocol/whitenoise PR: 600
File: test/hooks/use_leave_group_test.dart:67-78
Timestamp: 2026-04-28T17:39:39.941Z
Learning: In this repo’s Dart test files under `test/` (e.g., `test/hooks/use_leave_group_test.dart`), it’s acceptable to avoid calling `mockApi.reset()` at the start of `setUp()` when the test suite already manually resets all relevant individual mock fields in `setUp()`. Reviewers should not flag the absence of a `reset()` call as long as every field that could leak state between tests is explicitly reinitialized and the tests are passing.

Applied to files:

  • test/widgets/wn_message_bubble_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
🔇 Additional comments (6)
lib/widgets/chat_message_bubble.dart (1)

100-110: LGTM!

test/widgets/wn_message_bubble_test.dart (1)

3-6: LGTM!

Also applies to: 17-28, 61-85

test/widgets/chat_message_bubble_test.dart (1)

3-5: LGTM!

Also applies to: 23-34, 75-87, 116-141

test/widgets/message_content_text_test.dart (1)

1-71: LGTM!

lib/widgets/wn_message_bubble.dart (1)

8-9: LGTM!

Also applies to: 30-31, 44-45, 86-90, 155-162, 176-183, 195-203, 218-226, 350-351, 378-379, 455-456, 468-480, 674-675, 701-702, 756-757, 789-790

lib/widgets/message_content_text.dart (1)

1-237: LGTM!

Also applies to: 248-397

Comment thread lib/widgets/message_content_text.dart
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

✅ Coverage: 99.37% → 99.38% (+0.01%)

History
  • 8bb2879 2026-05-14 06:56:57 UTC — 99.26% (-0.11% vs base)
  • fce5ed0 2026-05-14 09:02:50 UTC — 99.03% (-0.34% vs base)
  • 440ff98 2026-05-14 09:29:13 UTC — 99.05% (-0.32% vs base)
  • a491430 2026-05-14 09:48:02 UTC — 99.06% (-0.31% vs base)
  • f2b8a9a 2026-05-14 10:17:39 UTC — 99.06% (-0.31% vs base)
  • 64d7893 2026-05-14 11:27:54 UTC — 99.38% (+0.01% vs base)

@erskingardner erskingardner marked this pull request as ready for review May 14, 2026 08:53
@erskingardner
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

✅ Actions performed

Full review triggered.

@erskingardner erskingardner changed the title Parse message link and mention tokens in chat bubbles Render rich message content and Nostr mentions May 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
lib/widgets/message_content_text.dart (1)

732-739: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Match token candidates by earliest occurrence.

This still returns the first matching candidate in list order, so a later .../ hit can win over an earlier ... hit. When that happens the segment map shifts and the wrong substring becomes tappable.

💡 Suggested fix
 _TokenMatch? _findTokenMatch(String content, SerializableToken token, int cursor) {
   final candidates = _displayCandidates(token);
   if (candidates.isEmpty) return _TokenMatch(start: cursor, end: cursor);

+  _TokenMatch? bestMatch;
   for (final candidate in candidates) {
     final start = content.indexOf(candidate, cursor);
     if (start >= 0) {
-      return _TokenMatch(start: start, end: start + candidate.length);
+      final match = _TokenMatch(start: start, end: start + candidate.length);
+      if (bestMatch == null ||
+          match.start < bestMatch.start ||
+          (match.start == bestMatch.start && match.end > bestMatch.end)) {
+        bestMatch = match;
+      }
     }
   }

-  return null;
+  return bestMatch;
 }
🤖 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 `@lib/widgets/message_content_text.dart` around lines 732 - 739,
_findTokenMatch currently returns the first candidate in candidates order rather
than the earliest occurrence in content, causing wrong substring matches; change
the logic in _findTokenMatch to iterate all results of
content.indexOf(candidate, cursor) for each candidate returned by
_displayCandidates, ignore negative indices, pick the candidate with the
smallest start index (earliest occurrence), and then return a _TokenMatch with
that start and start + candidate.length; if no candidate is found return the
existing _TokenMatch(start: cursor, end: cursor).
🤖 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 `@lib/widgets/wn_message_bubble.dart`:
- Around line 159-169: The timestamp/truncation logic currently measures raw
`content` and can misjudge width when `MessageContentText` renders transformed
tokens (e.g., `nostr:npub...` → `@Bob` or fenced code removal); update the
layout checks to measure the actual rich text used by `MessageContentText`
instead of the raw string: obtain the TextSpan/TextPainter that
`MessageContentText` will render (e.g., via a shared helper or
`MessageContentText`'s span-construction method) and use that TextPainter for
the single-line/truncation and timestamp placement logic (replace the
raw-content painter usage near the `MessageContentText` instantiation and apply
the same change to the other `MessageContentText` branches referenced). Ensure
`effectiveMaxLines`/`maxLines` and `overflow` behavior are preserved when
measuring the rich TextSpan.

---

Duplicate comments:
In `@lib/widgets/message_content_text.dart`:
- Around line 732-739: _findTokenMatch currently returns the first candidate in
candidates order rather than the earliest occurrence in content, causing wrong
substring matches; change the logic in _findTokenMatch to iterate all results of
content.indexOf(candidate, cursor) for each candidate returned by
_displayCandidates, ignore negative indices, pick the candidate with the
smallest start index (earliest occurrence), and then return a _TokenMatch with
that start and start + candidate.length; if no candidate is found return the
existing _TokenMatch(start: cursor, end: cursor).
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: b506cb63-3743-4294-b2d9-07d8813fd666

📥 Commits

Reviewing files that changed from the base of the PR and between 08d5c44 and d6afcc2.

📒 Files selected for processing (14)
  • lib/hooks/use_chat_input.dart
  • lib/screens/chat_screen.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
  • test/hooks/use_chat_input_test.dart
  • test/screens/chat_screen_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/message_content_text_test.dart
  • test/widgets/wn_message_bubble_test.dart
👮 Files not reviewed due to content moderation or server errors (7)
  • test/widgets/chat_message_bubble_test.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/hooks/use_chat_input.dart
  • test/hooks/use_chat_input_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • lib/screens/chat_screen.dart
  • test/screens/chat_screen_test.dart
📜 Review details
🧰 Additional context used
📓 Path-based instructions (12)
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use single quotes for strings in Dart/Flutter code
Enable and respect prefer_const_constructors lint rule in Dart/Flutter code
Enable and respect prefer_final_locals lint rule in Dart/Flutter code
Set line width to 100 characters in Dart/Flutter code
Preserve trailing commas in Dart/Flutter code
Maintain minimum test coverage of 99%; never submit a PR that reduces test coverage
Avoid using // coverage:ignore, // coverage:ignore-line, // coverage:ignore-start, or // coverage:ignore-end to bypass coverage requirements; write tests instead (only exception: truly unreachable code)

Files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
**/*.{dart,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Do not add comments except for code that is really complex or hard to understand

Files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
test/**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

test/**/*_test.dart: Test files should mirror source structure with _test.dart suffix
Use helpers from test/test_helpers.dart in tests: setUpTestView(), mountTestApp(), mountHook(), mountWidget(), mountStackedWidget()
Mock Rust API by extending MockWnApi from test/mocks/mock_wn_api.dart instead of implementing RustLibApi directly
Prefer find.byKey() over find.byIcon() in tests; add keys to icons in widgets and use find.byKey(const Key('icon_name')) in tests
Use valid 64-character hex strings for pubkeys in tests, not dummy values like 'abc' or 'test-pubkey'

Files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
test/**

⚙️ CodeRabbit configuration file

test/**: IMPORTANT: CI enforces coverage regression (coverage must never decrease). It does not enforce a fixed 95% minimum threshold.
Rules:

  • Test files mirror source structure with _test.dart suffix
  • Use helpers from test/test_helpers.dart (setUpTestView, mountTestApp, etc.)
  • Mock Rust API using RustLib.initMock(api: mockApi)
  • Always extend MockWnApi from test/mocks/mock_wn_api.dart
  • Prefer find.byKey() over find.byIcon() for widget testing
  • Use valid 64-char hex strings for pubkeys, not dummy values like 'abc'
  • Tests must be deterministic — no external service dependencies

Files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
lib/widgets/!(wn_)*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Complex reusable widgets should not have wn_/Wn prefix and may contain translations, hooks with Rust API calls, or other complex logic

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
lib/widgets/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Screen-scoped widgets extracted from a single screen should be prefixed with the screen name and only used in that one screen

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/**/*.dart: Use flutter_screenutil for all size values (padding, margins, gaps, icon sizes, font sizes, border radius, container dimensions) to ensure responsive layouts. Use .w for width, .h for height, .sp for font size/letter spacing, and .r for radius
Avoid StatefulWidget; prefer providers (for shared app-wide state) or hooks (for widget-local state) instead

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/hooks/use_chat_input.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
lib/widgets/**

⚙️ CodeRabbit configuration file

lib/widgets/**: This is a reusable widget.
There are two kinds of reusable widgets:

  1. Design system widgets — simple, presentational widgets that match
    the Figma design system in name and structure. They have Widgetbook
    stories, contain only presentational logic, and do NOT have
    translations or Rust API calls.

    • File MUST be prefixed with wn_ (e.g. wn_filled_button.dart)
    • Class MUST be prefixed with Wn (e.g. WnFilledButton)
  2. Complex reusable widgets — used across multiple screens but contain
    translations, hooks with Rust API calls, or other complex logic
    that makes them harder to display in Widgetbook.

    • These do NOT use the wn_/Wn prefix
    • Example: OnboardingCarousel (used in multiple screens, has
      translations and a page controller inside)

General rules for all widgets in this directory:

  • Use const constructors where possible
  • Use flutter_screenutil (.w, .h, .sp, .r) for all dimensions
  • Avoid StatefulWidget — prefer hooks for local state
  • No comments except for truly complex logic

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
lib/hooks/use_*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/hooks/use_*.dart: Hook files should be prefixed with use_ (e.g., use_chat_list.dart)
Hook functions should start with use (e.g., useChatList())

Files:

  • lib/hooks/use_chat_input.dart
lib/hooks/**

⚙️ CodeRabbit configuration file

lib/hooks/**: This is a flutter_hooks hook (ephemeral widget-local state).
Rules:

  • Files must be prefixed with use_ (e.g. use_chat_list.dart)
  • Hook functions must start with use (e.g. useChatList())
  • Hooks receive data as parameters, not widget refs
  • Ensure proper cleanup/dispose of subscriptions and resources

Files:

  • lib/hooks/use_chat_input.dart
lib/screens/**

⚙️ CodeRabbit configuration file

lib/screens/**: This is a Flutter screen (full-page widget).
Architecture rules:

  • Screens should WATCH Riverpod providers for shared state
  • Use flutter_hooks for ephemeral/local state (NOT StatefulWidget)
  • Pass data to hooks, not refs
  • Use flutter_screenutil (.w, .h, .sp, .r) for all size values
  • Widgets should use const constructors where possible
  • No comments except for truly complex logic
  • When a widget is extracted from a screen and only used in that one
    screen, it should be prefixed with the screen name (e.g.
    ChatListTile for a widget only used in the chat list screen).
    These are screen-scoped widgets and do NOT use the Wn prefix.

Files:

  • lib/screens/chat_screen.dart
lib/widgets/wn_*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/widgets/wn_*.dart: Design system widgets should be prefixed with wn_ in filename and Wn in class name (e.g., wn_filled_button.dart / WnFilledButton)
Design system widgets must have Widgetbook stories and should not contain translations or Rust API calls

Files:

  • lib/widgets/wn_message_bubble.dart
🧠 Learnings (38)
📚 Learning: 2026-01-05T21:28:05.652Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: test/screens/profile_keys_screen_test.dart:14-23
Timestamp: 2026-01-05T21:28:05.652Z
Learning: In test mocks for the marmot-protocol/sloth project, allow skipping bounds checking for substring operations when test inputs are controlled and have proven sufficient length (e.g., 'test_pubkey' is always 12 characters). Favor simpler, deterministic mock implementations in such controlled test contexts, but avoid relaxing bounds checks in production code or tests with variable inputs. Apply this guideline primarily to test files under the test/ directory.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-01-07T16:49:18.694Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: test/screens/chat_screen_test.dart:245-282
Timestamp: 2026-01-07T16:49:18.694Z
Learning: In the marmot-protocol/sloth repository, it is acceptable to use force-unwrapping (!) in Dart test files as an implicit assertion: if a value is unexpectedly null, the test will fail. This guideline applies only to test code under the test/ directory and should not be used in production code.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-01-11T22:43:09.610Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 36
File: test/screens/chat_list_screen_test.dart:74-76
Timestamp: 2026-01-11T22:43:09.610Z
Learning: In Dart tests (e.g., test/screens/chat_list_screen_test.dart and related files), use setUp() to call _api.reset() to clean up StreamController resources. Do not suggest or rely on tearDownAll for this cleanup.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-04-14T03:52:37.716Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 565
File: test/providers/offline_provider_test.dart:63-177
Timestamp: 2026-04-14T03:52:37.716Z
Learning: In this repository, it’s acceptable to use fixed-time waits for test timing using `Future.delayed` (e.g., `await Future.delayed(const Duration(milliseconds: 10))`) inside test files. During code reviews, don’t recommend replacing these fixed sleeps with event-based or poll-based synchronization when the change is localized to Dart test files under the `test/` directory.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-01-06T01:08:41.552Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_warning_box.dart:28-79
Timestamp: 2026-01-06T01:08:41.552Z
Learning: In reviews for the marmot-protocol/sloth repository, avoid suggesting accessibility, design, or general improvements unless they are strictly relevant to the PR description and its stated goals. Focus feedback on the specific PR objectives and requirements; ignore unrelated stylistic or broad improvement suggestions.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-15T14:42:54.111Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 39
File: lib/hooks/use_user_search.dart:47-53
Timestamp: 2026-01-15T14:42:54.111Z
Learning: When using npubFromHex(String hexPubkey) from lib/utils/formatting.dart, it already handles errors internally and returns null on failure. Do not wrap calls to this function in try/catch blocks. Instead, check for a null return and handle accordingly (e.g., treat as invalid hex). This guideline applies to all Dart files in the repository that may call this function.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-16T17:35:32.431Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 43
File: lib/hooks/use_network_relays.dart:155-184
Timestamp: 2026-01-16T17:35:32.431Z
Learning: In Dart/Flutter code, prefer void as the return type for fire-and-forget async functions that are intended to run in the background without blocking the caller. Using Future or Future<void> would allow callers to accidentally await, which could block for long periods. This pattern makes the call fire-and-forget by design. Apply to functions that intentionally should not be awaited; document the intent where appropriate. (Example context: pollRelayStatus in lib/hooks/use_network_relays.dart uses void to prevent callers from awaiting a potentially long-running background task.)

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-19T15:50:56.684Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 52
File: lib/screens/app_settings_screen.dart:30-40
Timestamp: 2026-01-19T15:50:56.684Z
Learning: Target the Flutter SDK version >= 3.27 across the repo. Since features like Column.spacing and Row.spacing were added in Flutter 3.27, you can safely use them in Dart files (e.g., lib/screens/app_settings_screen.dart) as long as the pubspec.yaml environment specifies Flutter >= 3.27. If needed, enforce this by validating the environment constraint (e.g., flutter, sdk: flutter) in pubspec.yaml.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-29T03:02:34.290Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 100
File: lib/widgets/wn_dropdown_selector.dart:126-139
Timestamp: 2026-01-29T03:02:34.290Z
Learning: Global font-family usage guideline: Since Manrope is configured in lib/theme.dart for both light and dark themes, individual TextStyle declarations should not specify fontFamily: 'Manrope'. During reviews, verify that no TextStyle instances override fontFamily unnecessarily; rely on theme inheritance instead. If a TextStyle must specify a font for a specific case, ensure it is clearly justified and localized, and document why the override is needed. This guideline applies across Dart files in the project (e.g., lib/widgets/wn_dropdown_selector.dart and related components).

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-29T16:02:52.588Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 129
File: test/widgets/wn_overlay_test.dart:84-91
Timestamp: 2026-01-29T16:02:52.588Z
Learning: In Dart/Flutter code, when constructing widgets inside an already-const context (for example, within a const Scaffold with a child that is a collection of widgets), avoid adding explicit const keywords to the children. The Dart analyzer treats these as implicitly const, and adding explicit const can trigger the unnecessary_const lint. Review test files and general Dart files to ensure you do not redundantly prefix children with const in const contexts; rely on the implicit const behavior instead.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-07T03:58:22.587Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/services/android_signer_service.dart:13-13
Timestamp: 2026-02-07T03:58:22.587Z
Learning: In Dart files across the repository, retain NIP (Nostr Implementation Possibilities) and MIP (Marmot Implementation Possibilities) protocol reference comments that link code to their specifications. While general guidance discourages extraneous comments, these references improve traceability and maintainability by documenting the protocol context for related implementation details. Keep them in Dart sources (e.g., lib/services/android_signer_service.dart) and ensure they are kept up-to-date.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-11T17:29:43.985Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/screens/app_settings_screen.dart:64-69
Timestamp: 2026-02-11T17:29:43.985Z
Learning: In the whitenoise Flutter app, after deleting all data and resetting auth, use Routes.goToHome(context) instead of Routes.goToLogin(context) because the home screen is the app entry point and will manage authentication routing. Apply this change to navigation calls that should land on the home screen after a data reset or auth reset. Update lib/screens/app_settings_screen.dart and any similar navigation points accordingly.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-18T18:36:13.394Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 315
File: lib/screens/edit_group_screen.dart:45-47
Timestamp: 2026-02-18T18:36:13.394Z
Learning: Whitenoise uses automated code formatting through the precommit workflow (just precommit). Do not tweak line wrapping or formatting manually; follow the formatter's output. Run the precommit formatter locally and ensure the code matches its styling decisions, and only deviate if the formatter cannot express the intended style (in which case adjust the code to satisfy the formatter).

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-04-06T09:36:06.726Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 553
File: lib/widgets/wn_copyable_field.dart:38-40
Timestamp: 2026-04-06T09:36:06.726Z
Learning: In Dart, `String.operator*` is a valid built-in operator for repeating strings (e.g., `'⬤' * 16`). In code reviews of Dart (`.dart`) files, do not treat expressions like `'text' * n` as invalid syntax and do not recommend replacing them with alternatives such as `List.filled(...).join()`; the operator is supported by `dart:core`.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-05-13T13:46:32.612Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/hooks/use_block_actions.dart:3-3
Timestamp: 2026-05-13T13:46:32.612Z
Learning: In marmot-protocol/whitenoise code reviews, do not flag imports that reference FRB-generated bindings under `package:rust_lib_whitenoise/src/rust/...` as private-internals violations. In `rust_lib_whitenoise`, Dart bindings are intentionally generated into `lib/src/rust/`, and `analysis_options.yaml` sets `implementation_imports: false` to suppress the related Dart lint—so `package:rust_lib_whitenoise/src/rust/...` imports are the correct/only way to consume that FRB-generated API.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-05-13T13:46:39.250Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/services/foreground_service.dart:13-16
Timestamp: 2026-05-13T13:46:39.250Z
Learning: In this repo (marmot-protocol/whitenoise), `rust_lib_whitenoise` is an FRB-generated Flutter package where the generated Dart bindings are intentionally exposed under `lib/src/rust/` as the public API surface. Do not treat imports like `package:rust_lib_whitenoise/src/rust/...` as breaking package API boundaries in code reviews (the project also sets `implementation_imports: false` in `analysis_options.yaml` to opt into this). Never raise concerns about `src/` imports from `rust_lib_whitenoise`.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • lib/widgets/chat_list_tile.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • lib/hooks/use_chat_input.dart
  • test/screens/chat_screen_test.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • test/widgets/wn_message_bubble_test.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-05T11:59:09.400Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 189
File: test/widgets/wn_filter_chip_test.dart:1-3
Timestamp: 2026-02-05T11:59:09.400Z
Learning: In Dart/Flutter test files, Offset is exported by package:flutter_test/flutter_test.dart. If you already import flutter_test, you do not need to import dart:ui for Offset. Ensure there are no conflicting Offset symbols when both imports occur.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-04-28T17:39:39.941Z
Learnt from: pepina-dev
Repo: marmot-protocol/whitenoise PR: 600
File: test/hooks/use_leave_group_test.dart:67-78
Timestamp: 2026-04-28T17:39:39.941Z
Learning: In this repo’s Dart test files under `test/` (e.g., `test/hooks/use_leave_group_test.dart`), it’s acceptable to avoid calling `mockApi.reset()` at the start of `setUp()` when the test suite already manually resets all relevant individual mock fields in `setUp()`. Reviewers should not flag the absence of a `reset()` call as long as every field that could leak state between tests is explicitly reinitialized and the tests are passing.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/hooks/use_chat_input_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/screens/chat_screen_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-02-02T21:11:03.835Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 157
File: test/widgets/wn_middle_ellipsis_text_test.dart:11-20
Timestamp: 2026-02-02T21:11:03.835Z
Learning: In widget tests (files matching test/widgets/**/*_test.dart) that use the mountWidget helper from test/test_helpers.dart, you do not need to call setUpTestView(tester) separately before mounting. Rely on mountWidget for necessary setup to avoid redundant setUpTestView calls in these tests. If a test does not use mountWidget, continue using setUpTestView as before.

Applied to files:

  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-02-02T21:11:17.594Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 157
File: test/widgets/wn_copy_card_test.dart:8-16
Timestamp: 2026-02-02T21:11:17.594Z
Learning: In test/widgets/**/*_test.dart, when using the mountWidget helper, you do not need to call setUpTestView(tester) separately because mountWidget performs the necessary setup for widget tests. Rely on mountWidget to prepare the test environment and avoid redundant setUpTestView calls.

Applied to files:

  • test/widgets/chat_list_tile_test.dart
  • test/widgets/message_content_text_test.dart
  • test/widgets/chat_message_bubble_test.dart
  • test/widgets/wn_message_bubble_test.dart
📚 Learning: 2026-01-05T20:05:32.918Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/screens/settings_screen.dart:76-88
Timestamp: 2026-01-05T20:05:32.918Z
Learning: In the Sloth project, tooltips should not be used for UI elements. During code reviews, verify that UI components do not include Tooltip widgets and replace them with accessible alternatives (e.g., long-press hints, labels, or interactive guidance). Apply this guidance across Dart UI files under lib (not just settings_screen.dart) to maintain a consistent design approach.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/hooks/use_chat_input.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-09T13:25:18.531Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: lib/services/message_service.dart:14-26
Timestamp: 2026-01-09T13:25:18.531Z
Learning: When errors are presented to users in the Sloth codebase (UI layers such as snackbars, dialogs, toasts), show friendly, user-facing messages instead of raw Rust error messages. Implement a mapping from internal error types or codes to clear, non-technical messages, and surface actionable guidance for end users where appropriate. Avoid exposing internal stack traces or language runtime details; centralize common user-facing error wording in a dedicated utility or service and translate backend errors into concise, helpful UI messages.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/hooks/use_chat_input.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-04-06T09:40:41.044Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 552
File: lib/screens/profile_keys_screen.dart:71-71
Timestamp: 2026-04-06T09:40:41.044Z
Learning: In this Flutter/Dart codebase, when using flutter_screenutil (e.g., ScreenUtil or ScreenUtil-based sizing like `w`, `h`), do not require scaled units for literal zero values. Specifically, in EdgeInsets (and similar sizing/padding/margin/gap APIs), bare numeric `0` should be allowed (e.g., `EdgeInsets.all(0)`, `EdgeInsets.symmetric(vertical: 0)`, `SizedBox(width: 0)`), because scaling `0` remains `0`. Only flag ScreenUtil violations when a non-zero literal needs to be expressed via `0.w`/`0.h` equivalents; do not flag bare `0`.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/hooks/use_chat_input.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-01-05T22:40:27.888Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_image_picker.dart:127-143
Timestamp: 2026-01-05T22:40:27.888Z
Learning: In marmot-protocol's Sloth project (lib/widgets/...): image hosting URLs returned by blossom servers are always lowercase (http:// or https://). Do not implement or rely on case-insensitive URL protocol detection in WN image picker code; assume the protocol is lowercase. If URL protocol checks are needed, compare against the exact literals 'http://' and 'https://'. Add tests that assert URLs use lowercase protocols. This guideline applies to all Dart files under lib/widgets that handle image URLs.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-04T10:35:43.112Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 170
File: lib/widgets/wn_list_item.dart:232-234
Timestamp: 2026-02-04T10:35:43.112Z
Learning: In this repository ( marmot-protocol/sloth ), when writing widget build methods in Dart files under lib/widgets, only extract a local typography variable if context.typographyScaled or context.typography is used multiple times within the same build method. If typography is used only once, keep the inline access to avoid an unnecessary extra line. This guideline helps maintain readability and avoids premature refactoring.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-14T05:09:20.007Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 260
File: lib/widgets/wn_message_quote.dart:32-88
Timestamp: 2026-02-14T05:09:20.007Z
Learning: In Dart widget files under lib/widgets (matching lib/widgets/**/*.dart), spacing literals such as padding, gap values, and border radii should not be extracted into constants based on josefinalliende's preference. Apply this rule consistently across widget files to improve readability and avoid over-abstracting simple literals.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/chat_message_bubble.dart
  • lib/widgets/message_content_text.dart
  • lib/widgets/wn_message_bubble.dart
📚 Learning: 2026-02-19T02:12:52.425Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 316
File: test/hooks/use_chat_scroll_test.dart:96-114
Timestamp: 2026-02-19T02:12:52.425Z
Learning: In hook tests under test/hooks, using tester.pumpWidget directly is acceptable for simple hook logic tests that do not rely on localizations, providers, or screen utilities. If the test requires those features, use the mountWidget helper from test/test_helpers.dart (which wires in localizations, providers, and screen utilities). Apply this guideline to all files matching *_test.dart in test/hooks.

Applied to files:

  • test/hooks/use_chat_input_test.dart
📚 Learning: 2026-01-22T20:15:04.277Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 78
File: lib/hooks/use_accounts.dart:21-38
Timestamp: 2026-01-22T20:15:04.277Z
Learning: In the Sloth Flutter app, for hook files under lib/hooks (e.g., use_accounts.dart), prefer logging errors using the project's logger (e.g., _logger.severe) rather than displaying SnackBars. SnackBars should not be used in hooks or for error handling; log errors centrally and surface user notifications only at UI layers. Ensure consistent use of logging severity and avoid UI side effects within hooks.

Applied to files:

  • lib/hooks/use_chat_input.dart
📚 Learning: 2026-02-05T20:27:05.455Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/hooks/use_android_signer.dart:38-41
Timestamp: 2026-02-05T20:27:05.455Z
Learning: In Dart files under lib/hooks that use useMemoized (flutter_hooks), if a platform check like platformIsAndroid is used only to construct a service and the platform will not change at runtime, you can omit that platform check from the useMemoized dependency list. Do not include invariant platform values in the dependencies, which prevents unnecessary recomputations. Apply this pattern to all relevant files in lib/hooks where the platform is constant over the app lifecycle.

Applied to files:

  • lib/hooks/use_chat_input.dart
📚 Learning: 2026-02-11T17:51:41.426Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/hooks/use_delete_all_data.dart:34-49
Timestamp: 2026-02-11T17:51:41.426Z
Learning: In the whitenoise Flutter app, within custom hooks under lib/hooks, do not wrap returned functions with useCallback. Define functions inline inside the hook body to maintain consistency across all hooks. This aligns with the project-wide convention for consistency. If a function must be stable across rebuilds, consider using appropriate Dart/Flutter patterns (e.g., local closures or memoization strategies) per the framework's guidance.

Applied to files:

  • lib/hooks/use_chat_input.dart
📚 Learning: 2026-02-11T23:40:32.726Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/hooks/use_delete_all_data.dart:7-24
Timestamp: 2026-02-11T23:40:32.726Z
Learning: In the whitenoise Flutter app, internal hook state classes (e.g., DeleteAllDataState in use_delete_all_data.dart) should implement manual copyWith methods rather than using Freezed. The Freezed package is appropriate for UI states, but not for hook-internal state classes. During reviews, flag hook state implementations that rely on Freezed and prefer explicit, hand-written copyWith for reliability and lighter dependencies. Apply this pattern broadly to all Dart files under lib/hooks/*.dart and any similar hook directories.

Applied to files:

  • lib/hooks/use_chat_input.dart
📚 Learning: 2026-04-02T18:58:10.024Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 494
File: lib/hooks/use_scroll_to_message.dart:17-18
Timestamp: 2026-04-02T18:58:10.024Z
Learning: When reviewing Flutter/Dart hooks that use `flutter_hooks` (e.g., `useRef` / “latest-value ref” pattern), do not treat `useRef.current`/`ref.value` reads inside async loops as a stale-closure bug. If a parameter like `hasMoreMessages` is passed into the hook and immediately synced at the top of the build (`ref.value = hasMoreMessages`), then async code should read the latest `ref.value` on each iteration, reflecting the most recent rebuild triggered by state changes. The stale-closure concern should apply to captured local variables that are not updated via a `useRef`, not to `useRef`-backed fields.

Applied to files:

  • lib/hooks/use_chat_input.dart
📚 Learning: 2026-02-07T04:45:18.077Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/hooks/use_nsec.dart:76-79
Timestamp: 2026-02-07T04:45:18.077Z
Learning: In Flutter, localization strings should be resolved and presented by widgets/screens, not inside hooks. Hooks (like those under lib/hooks) should return error codes (e.g., nsec_load_failed). Widgets should provide helper functions to map these codes to localized strings via AppLocalizations (e.g., _noticeMessageL10n, _signerErrorL10n). Apply this pattern across all hook files: hub on returning error codes, while UI components handle localization lookups using AppLocalizations.

Applied to files:

  • lib/hooks/use_chat_input.dart
📚 Learning: 2025-12-23T14:37:11.241Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 9
File: test/screens/error_screen_test.dart:60-71
Timestamp: 2025-12-23T14:37:11.241Z
Learning: In tests for screen widgets that are not wired through the app's Routes.build (e.g., ErrorScreen under lib/screens/error_screen.dart), avoid mounting the full app with mountTestApp. instead, configure a minimal test harness with a custom router or navigator setup (e.g., a MaterialApp with a mock router) to render the screen in isolation. This ensures the test focuses on the screen's behavior without depending on the app's global routing configuration.

Applied to files:

  • test/screens/chat_screen_test.dart
📚 Learning: 2026-01-15T15:00:17.356Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 39
File: lib/screens/user_search_screen.dart:50-75
Timestamp: 2026-01-15T15:00:17.356Z
Learning: In lib/screens/user_search_screen.dart and similar screen widgets in the sloth repository, prefer using inline ternary expressions for conditional rendering (e.g., isLoading → empty state → list) over extracting into separate widget classes when the conditional UI is simple. This improves readability by keeping the render logic in a single place. Reserve extracting to a separate widget only when the conditional UI becomes sufficiently complex or is reused across multiple screens.

Applied to files:

  • lib/screens/chat_screen.dart
📚 Learning: 2026-02-10T04:57:31.475Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 208
File: lib/screens/signup_screen.dart:66-70
Timestamp: 2026-02-10T04:57:31.475Z
Learning: In Dart/Flutter using flutter_hooks, a ValueNotifier (from useState) is disposed when the widget unmounts. Async callbacks (e.g., .then() on animations) may still run after disposal and throw 'A ValueNotifier was used after being disposed'. Reviewers should ensure that state updates in async callbacks are guarded by a mounted check (e.g., if (mounted) { notifier.value = ... } or cancel/avoid updating after disposal). This guideline applies to screen widgets under lib/screens and similar Dart files using hooks that manage widget lifecycle. Update code patterns to prevent post-disposal state updates, and document the rationale in comments where necessary.

Applied to files:

  • lib/screens/chat_screen.dart
📚 Learning: 2026-02-19T10:33:16.889Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 315
File: lib/screens/group_info_screen.dart:146-149
Timestamp: 2026-02-19T10:33:16.889Z
Learning: Pubkey validation (length/format) is performed by the Rust crate before data reaches Dart. Do not add defensive substring guards in Dart for pubkeys in lib/screens/**/*.dart or other Flutter code consuming group member pubkeys, as this would duplicate validation and be unnecessary. If any validation is needed in Dart, keep it to lightweight sanity checks only after data enters Dart and ensure it aligns with the Rust validation rules.

Applied to files:

  • lib/screens/chat_screen.dart
📚 Learning: 2026-04-06T09:40:51.965Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 552
File: lib/screens/sign_out_screen.dart:86-86
Timestamp: 2026-04-06T09:40:51.965Z
Learning: When reviewing Flutter/Dart code that uses ScreenUtil (e.g., ScreenUtil `.h`/`.w`/`.sp`), do not require converting a literal `0` used for padding/margin/sizing to a ScreenUtil expression. In sizing contexts like `EdgeInsets` (or other `EdgeInsets`/layout parameters), raw `0` is mathematically invariant (`0 * scale = 0`) and should not be flagged as a ScreenUtil violation. Only non-zero literal size values should be checked for ScreenUtil usage.

Applied to files:

  • lib/screens/chat_screen.dart

Comment on lines +159 to 169
child: MessageContentText(
content: content,
contentTokens: contentTokens,
style: textStyle,
linkStyle: linkStyle,
highlightSpans: highlightSpans,
highlightColor: highlightColor,
nostrDisplayNamesByUri: nostrDisplayNamesByUri,
onNostrTap: onNostrTap,
maxLines: effectiveMaxLines,
overflow: TextOverflow.ellipsis,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep the timestamp layout in sync with the rendered rich text.

These branches now render MessageContentText, but the truncation and single-line checks above still lay out raw content. A nostr:npub... token can render as @Bob, and fenced code strips the fence text entirely, so the painter can overestimate width and choose the wrong overflow/timestamp layout for the new rich-content cases.

Also applies to: 182-193, 203-214, 228-238

🤖 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 `@lib/widgets/wn_message_bubble.dart` around lines 159 - 169, The
timestamp/truncation logic currently measures raw `content` and can misjudge
width when `MessageContentText` renders transformed tokens (e.g.,
`nostr:npub...` → `@Bob` or fenced code removal); update the layout checks to
measure the actual rich text used by `MessageContentText` instead of the raw
string: obtain the TextSpan/TextPainter that `MessageContentText` will render
(e.g., via a shared helper or `MessageContentText`'s span-construction method)
and use that TextPainter for the single-line/truncation and timestamp placement
logic (replace the raw-content painter usage near the `MessageContentText`
instantiation and apply the same change to the other `MessageContentText`
branches referenced). Ensure `effectiveMaxLines`/`maxLines` and `overflow`
behavior are preserved when measuring the rich TextSpan.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@lib/utils/mention_text_editing_controller.dart`:
- Around line 34-51: insertMention currently removes overlapping mentions but
fails to shift mentions that occur after the replaced range, causing misaligned
_TrackedMention ranges; update the logic that builds updatedMentions (which
currently filters with _rangesOverlap and adds the new _TrackedMention) to also
apply a delta shift to any mention whose start is >= end: compute delta = (start
+ displayText.length) - end (i.e. newLength - oldLength) and for each retained
mention with mention.start >= end produce a shifted _TrackedMention( start:
mention.start + delta, end: mention.end + delta, displayText:
mention.displayText, uri: mention.uri ); keep the existing removal of overlaps
via _rangesOverlap, add the new mention, sort by start, then replace _mentions
and set _isApplyingInternalChange as before.

In `@test/utils/mention_text_editing_controller_test.dart`:
- Around line 24-44: Add a regression test to verify that inserting a mention
before an existing mention preserves the existing mention's URI mapping: create
a test using MentionTextEditingController, call insertMention to add the first
mention (e.g., Bob), then set controller.value text/selection to place caret
before that mention and call insertMention again to insert a second mention
ahead, then assert controller.messageText contains the correct URIs for both
mentions (use MentionTextEditingController, insertMention, and messageText to
locate behavior to exercise and validate).
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: 43b3577c-6460-407a-bf0b-7e45f71dcd36

📥 Commits

Reviewing files that changed from the base of the PR and between d6afcc2 and 9a79c45.

⛔ Files ignored due to path filters (10)
  • lib/l10n/generated/app_localizations.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_de.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_en.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_es.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_fr.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_it.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_pt.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_ru.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_tr.dart is excluded by !**/generated/**, !lib/l10n/generated/**
  • lib/l10n/generated/app_localizations_zh.dart is excluded by !**/generated/**, !lib/l10n/generated/**
📒 Files selected for processing (18)
  • lib/hooks/use_group_members.dart
  • lib/l10n/app_de.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_it.arb
  • lib/l10n/app_pt.arb
  • lib/l10n/app_ru.arb
  • lib/l10n/app_tr.arb
  • lib/l10n/app_zh.arb
  • lib/l10n/app_zh_Hant.arb
  • lib/screens/chat_screen.dart
  • lib/utils/mention_text_editing_controller.dart
  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
  • test/hooks/use_group_members_test.dart
  • test/screens/chat_screen_test.dart
  • test/utils/mention_text_editing_controller_test.dart
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (4/4)
  • GitHub Check: Test (1/4)
  • GitHub Check: Test (2/4)
  • GitHub Check: Test (3/4)
  • GitHub Check: Check
🧰 Additional context used
📓 Path-based instructions (12)
**/*.arb

⚙️ CodeRabbit configuration file

**/*.arb: These are localization files. Check for:

  • Consistent key naming across all locale files
  • Proper ICU message format for plurals/gender
  • No hardcoded strings that should be localized

Files:

  • lib/l10n/app_ru.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_it.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_tr.arb
  • lib/l10n/app_zh.arb
  • lib/l10n/app_pt.arb
  • lib/l10n/app_zh_Hant.arb
  • lib/l10n/app_de.arb
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use single quotes for strings in Dart/Flutter code
Enable and respect prefer_const_constructors lint rule in Dart/Flutter code
Enable and respect prefer_final_locals lint rule in Dart/Flutter code
Set line width to 100 characters in Dart/Flutter code
Preserve trailing commas in Dart/Flutter code
Maintain minimum test coverage of 99%; never submit a PR that reduces test coverage
Avoid using // coverage:ignore, // coverage:ignore-line, // coverage:ignore-start, or // coverage:ignore-end to bypass coverage requirements; write tests instead (only exception: truly unreachable code)

Files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
lib/hooks/use_*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/hooks/use_*.dart: Hook files should be prefixed with use_ (e.g., use_chat_list.dart)
Hook functions should start with use (e.g., useChatList())

Files:

  • lib/hooks/use_group_members.dart
**/*.{dart,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Do not add comments except for code that is really complex or hard to understand

Files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/**/*.dart: Use flutter_screenutil for all size values (padding, margins, gaps, icon sizes, font sizes, border radius, container dimensions) to ensure responsive layouts. Use .w for width, .h for height, .sp for font size/letter spacing, and .r for radius
Avoid StatefulWidget; prefer providers (for shared app-wide state) or hooks (for widget-local state) instead

Files:

  • lib/hooks/use_group_members.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
lib/hooks/**

⚙️ CodeRabbit configuration file

lib/hooks/**: This is a flutter_hooks hook (ephemeral widget-local state).
Rules:

  • Files must be prefixed with use_ (e.g. use_chat_list.dart)
  • Hook functions must start with use (e.g. useChatList())
  • Hooks receive data as parameters, not widget refs
  • Ensure proper cleanup/dispose of subscriptions and resources

Files:

  • lib/hooks/use_group_members.dart
test/**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

test/**/*_test.dart: Test files should mirror source structure with _test.dart suffix
Use helpers from test/test_helpers.dart in tests: setUpTestView(), mountTestApp(), mountHook(), mountWidget(), mountStackedWidget()
Mock Rust API by extending MockWnApi from test/mocks/mock_wn_api.dart instead of implementing RustLibApi directly
Prefer find.byKey() over find.byIcon() in tests; add keys to icons in widgets and use find.byKey(const Key('icon_name')) in tests
Use valid 64-character hex strings for pubkeys in tests, not dummy values like 'abc' or 'test-pubkey'

Files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
test/**

⚙️ CodeRabbit configuration file

test/**: IMPORTANT: CI enforces coverage regression (coverage must never decrease). It does not enforce a fixed 95% minimum threshold.
Rules:

  • Test files mirror source structure with _test.dart suffix
  • Use helpers from test/test_helpers.dart (setUpTestView, mountTestApp, etc.)
  • Mock Rust API using RustLib.initMock(api: mockApi)
  • Always extend MockWnApi from test/mocks/mock_wn_api.dart
  • Prefer find.byKey() over find.byIcon() for widget testing
  • Use valid 64-char hex strings for pubkeys, not dummy values like 'abc'
  • Tests must be deterministic — no external service dependencies

Files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
lib/widgets/!(wn_)*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Complex reusable widgets should not have wn_/Wn prefix and may contain translations, hooks with Rust API calls, or other complex logic

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
lib/widgets/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

Screen-scoped widgets extracted from a single screen should be prefixed with the screen name and only used in that one screen

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
lib/widgets/**

⚙️ CodeRabbit configuration file

lib/widgets/**: This is a reusable widget.
There are two kinds of reusable widgets:

  1. Design system widgets — simple, presentational widgets that match
    the Figma design system in name and structure. They have Widgetbook
    stories, contain only presentational logic, and do NOT have
    translations or Rust API calls.

    • File MUST be prefixed with wn_ (e.g. wn_filled_button.dart)
    • Class MUST be prefixed with Wn (e.g. WnFilledButton)
  2. Complex reusable widgets — used across multiple screens but contain
    translations, hooks with Rust API calls, or other complex logic
    that makes them harder to display in Widgetbook.

    • These do NOT use the wn_/Wn prefix
    • Example: OnboardingCarousel (used in multiple screens, has
      translations and a page controller inside)

General rules for all widgets in this directory:

  • Use const constructors where possible
  • Use flutter_screenutil (.w, .h, .sp, .r) for all dimensions
  • Avoid StatefulWidget — prefer hooks for local state
  • No comments except for truly complex logic

Files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
lib/screens/**

⚙️ CodeRabbit configuration file

lib/screens/**: This is a Flutter screen (full-page widget).
Architecture rules:

  • Screens should WATCH Riverpod providers for shared state
  • Use flutter_hooks for ephemeral/local state (NOT StatefulWidget)
  • Pass data to hooks, not refs
  • Use flutter_screenutil (.w, .h, .sp, .r) for all size values
  • Widgets should use const constructors where possible
  • No comments except for truly complex logic
  • When a widget is extracted from a screen and only used in that one
    screen, it should be prefixed with the screen name (e.g.
    ChatListTile for a widget only used in the chat list screen).
    These are screen-scoped widgets and do NOT use the Wn prefix.

Files:

  • lib/screens/chat_screen.dart
🧠 Learnings (38)
📚 Learning: 2026-03-04T16:40:26.588Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 408
File: lib/l10n/app_ru.arb:107-107
Timestamp: 2026-03-04T16:40:26.588Z
Learning: In the whitenoise Flutter project lib/l10n/app_ru.arb, the invitedYouToChatSuffix uses masculine past-tense (пригласил) as the default form. Since Nostr identities have no gender metadata, ICU gender-select is not feasible. Do not flag masculine default as a missing gender-agreement issue for Russian or other locales in this project.

Applied to files:

  • lib/l10n/app_ru.arb
📚 Learning: 2026-01-05T20:05:32.918Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/screens/settings_screen.dart:76-88
Timestamp: 2026-01-05T20:05:32.918Z
Learning: In the Sloth project, tooltips should not be used for UI elements. During code reviews, verify that UI components do not include Tooltip widgets and replace them with accessible alternatives (e.g., long-press hints, labels, or interactive guidance). Apply this guidance across Dart UI files under lib (not just settings_screen.dart) to maintain a consistent design approach.

Applied to files:

  • lib/hooks/use_group_members.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-09T13:25:18.531Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: lib/services/message_service.dart:14-26
Timestamp: 2026-01-09T13:25:18.531Z
Learning: When errors are presented to users in the Sloth codebase (UI layers such as snackbars, dialogs, toasts), show friendly, user-facing messages instead of raw Rust error messages. Implement a mapping from internal error types or codes to clear, non-technical messages, and surface actionable guidance for end users where appropriate. Avoid exposing internal stack traces or language runtime details; centralize common user-facing error wording in a dedicated utility or service and translate backend errors into concise, helpful UI messages.

Applied to files:

  • lib/hooks/use_group_members.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-04-06T09:40:41.044Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 552
File: lib/screens/profile_keys_screen.dart:71-71
Timestamp: 2026-04-06T09:40:41.044Z
Learning: In this Flutter/Dart codebase, when using flutter_screenutil (e.g., ScreenUtil or ScreenUtil-based sizing like `w`, `h`), do not require scaled units for literal zero values. Specifically, in EdgeInsets (and similar sizing/padding/margin/gap APIs), bare numeric `0` should be allowed (e.g., `EdgeInsets.all(0)`, `EdgeInsets.symmetric(vertical: 0)`, `SizedBox(width: 0)`), because scaling `0` remains `0`. Only flag ScreenUtil violations when a non-zero literal needs to be expressed via `0.w`/`0.h` equivalents; do not flag bare `0`.

Applied to files:

  • lib/hooks/use_group_members.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-06T01:08:41.552Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_warning_box.dart:28-79
Timestamp: 2026-01-06T01:08:41.552Z
Learning: In reviews for the marmot-protocol/sloth repository, avoid suggesting accessibility, design, or general improvements unless they are strictly relevant to the PR description and its stated goals. Focus feedback on the specific PR objectives and requirements; ignore unrelated stylistic or broad improvement suggestions.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-15T14:42:54.111Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 39
File: lib/hooks/use_user_search.dart:47-53
Timestamp: 2026-01-15T14:42:54.111Z
Learning: When using npubFromHex(String hexPubkey) from lib/utils/formatting.dart, it already handles errors internally and returns null on failure. Do not wrap calls to this function in try/catch blocks. Instead, check for a null return and handle accordingly (e.g., treat as invalid hex). This guideline applies to all Dart files in the repository that may call this function.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-16T17:35:32.431Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 43
File: lib/hooks/use_network_relays.dart:155-184
Timestamp: 2026-01-16T17:35:32.431Z
Learning: In Dart/Flutter code, prefer void as the return type for fire-and-forget async functions that are intended to run in the background without blocking the caller. Using Future or Future<void> would allow callers to accidentally await, which could block for long periods. This pattern makes the call fire-and-forget by design. Apply to functions that intentionally should not be awaited; document the intent where appropriate. (Example context: pollRelayStatus in lib/hooks/use_network_relays.dart uses void to prevent callers from awaiting a potentially long-running background task.)

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-19T15:50:56.684Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 52
File: lib/screens/app_settings_screen.dart:30-40
Timestamp: 2026-01-19T15:50:56.684Z
Learning: Target the Flutter SDK version >= 3.27 across the repo. Since features like Column.spacing and Row.spacing were added in Flutter 3.27, you can safely use them in Dart files (e.g., lib/screens/app_settings_screen.dart) as long as the pubspec.yaml environment specifies Flutter >= 3.27. If needed, enforce this by validating the environment constraint (e.g., flutter, sdk: flutter) in pubspec.yaml.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-29T03:02:34.290Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 100
File: lib/widgets/wn_dropdown_selector.dart:126-139
Timestamp: 2026-01-29T03:02:34.290Z
Learning: Global font-family usage guideline: Since Manrope is configured in lib/theme.dart for both light and dark themes, individual TextStyle declarations should not specify fontFamily: 'Manrope'. During reviews, verify that no TextStyle instances override fontFamily unnecessarily; rely on theme inheritance instead. If a TextStyle must specify a font for a specific case, ensure it is clearly justified and localized, and document why the override is needed. This guideline applies across Dart files in the project (e.g., lib/widgets/wn_dropdown_selector.dart and related components).

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-29T16:02:52.588Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 129
File: test/widgets/wn_overlay_test.dart:84-91
Timestamp: 2026-01-29T16:02:52.588Z
Learning: In Dart/Flutter code, when constructing widgets inside an already-const context (for example, within a const Scaffold with a child that is a collection of widgets), avoid adding explicit const keywords to the children. The Dart analyzer treats these as implicitly const, and adding explicit const can trigger the unnecessary_const lint. Review test files and general Dart files to ensure you do not redundantly prefix children with const in const contexts; rely on the implicit const behavior instead.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-07T03:58:22.587Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/services/android_signer_service.dart:13-13
Timestamp: 2026-02-07T03:58:22.587Z
Learning: In Dart files across the repository, retain NIP (Nostr Implementation Possibilities) and MIP (Marmot Implementation Possibilities) protocol reference comments that link code to their specifications. While general guidance discourages extraneous comments, these references improve traceability and maintainability by documenting the protocol context for related implementation details. Keep them in Dart sources (e.g., lib/services/android_signer_service.dart) and ensure they are kept up-to-date.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-11T17:29:43.985Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/screens/app_settings_screen.dart:64-69
Timestamp: 2026-02-11T17:29:43.985Z
Learning: In the whitenoise Flutter app, after deleting all data and resetting auth, use Routes.goToHome(context) instead of Routes.goToLogin(context) because the home screen is the app entry point and will manage authentication routing. Apply this change to navigation calls that should land on the home screen after a data reset or auth reset. Update lib/screens/app_settings_screen.dart and any similar navigation points accordingly.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-18T18:36:13.394Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 315
File: lib/screens/edit_group_screen.dart:45-47
Timestamp: 2026-02-18T18:36:13.394Z
Learning: Whitenoise uses automated code formatting through the precommit workflow (just precommit). Do not tweak line wrapping or formatting manually; follow the formatter's output. Run the precommit formatter locally and ensure the code matches its styling decisions, and only deviate if the formatter cannot express the intended style (in which case adjust the code to satisfy the formatter).

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-04-06T09:36:06.726Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 553
File: lib/widgets/wn_copyable_field.dart:38-40
Timestamp: 2026-04-06T09:36:06.726Z
Learning: In Dart, `String.operator*` is a valid built-in operator for repeating strings (e.g., `'⬤' * 16`). In code reviews of Dart (`.dart`) files, do not treat expressions like `'text' * n` as invalid syntax and do not recommend replacing them with alternatives such as `List.filled(...).join()`; the operator is supported by `dart:core`.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-05-13T13:46:32.612Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/hooks/use_block_actions.dart:3-3
Timestamp: 2026-05-13T13:46:32.612Z
Learning: In marmot-protocol/whitenoise code reviews, do not flag imports that reference FRB-generated bindings under `package:rust_lib_whitenoise/src/rust/...` as private-internals violations. In `rust_lib_whitenoise`, Dart bindings are intentionally generated into `lib/src/rust/`, and `analysis_options.yaml` sets `implementation_imports: false` to suppress the related Dart lint—so `package:rust_lib_whitenoise/src/rust/...` imports are the correct/only way to consume that FRB-generated API.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-05-13T13:46:39.250Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/services/foreground_service.dart:13-16
Timestamp: 2026-05-13T13:46:39.250Z
Learning: In this repo (marmot-protocol/whitenoise), `rust_lib_whitenoise` is an FRB-generated Flutter package where the generated Dart bindings are intentionally exposed under `lib/src/rust/` as the public API surface. Do not treat imports like `package:rust_lib_whitenoise/src/rust/...` as breaking package API boundaries in code reviews (the project also sets `implementation_imports: false` in `analysis_options.yaml` to opt into this). Never raise concerns about `src/` imports from `rust_lib_whitenoise`.

Applied to files:

  • lib/hooks/use_group_members.dart
  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
  • lib/widgets/chat_list_tile.dart
  • lib/screens/chat_screen.dart
  • lib/widgets/message_content_text.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-22T20:15:04.277Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 78
File: lib/hooks/use_accounts.dart:21-38
Timestamp: 2026-01-22T20:15:04.277Z
Learning: In the Sloth Flutter app, for hook files under lib/hooks (e.g., use_accounts.dart), prefer logging errors using the project's logger (e.g., _logger.severe) rather than displaying SnackBars. SnackBars should not be used in hooks or for error handling; log errors centrally and surface user notifications only at UI layers. Ensure consistent use of logging severity and avoid UI side effects within hooks.

Applied to files:

  • lib/hooks/use_group_members.dart
📚 Learning: 2026-02-05T20:27:05.455Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/hooks/use_android_signer.dart:38-41
Timestamp: 2026-02-05T20:27:05.455Z
Learning: In Dart files under lib/hooks that use useMemoized (flutter_hooks), if a platform check like platformIsAndroid is used only to construct a service and the platform will not change at runtime, you can omit that platform check from the useMemoized dependency list. Do not include invariant platform values in the dependencies, which prevents unnecessary recomputations. Apply this pattern to all relevant files in lib/hooks where the platform is constant over the app lifecycle.

Applied to files:

  • lib/hooks/use_group_members.dart
📚 Learning: 2026-02-11T17:51:41.426Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/hooks/use_delete_all_data.dart:34-49
Timestamp: 2026-02-11T17:51:41.426Z
Learning: In the whitenoise Flutter app, within custom hooks under lib/hooks, do not wrap returned functions with useCallback. Define functions inline inside the hook body to maintain consistency across all hooks. This aligns with the project-wide convention for consistency. If a function must be stable across rebuilds, consider using appropriate Dart/Flutter patterns (e.g., local closures or memoization strategies) per the framework's guidance.

Applied to files:

  • lib/hooks/use_group_members.dart
📚 Learning: 2026-02-11T23:40:32.726Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/hooks/use_delete_all_data.dart:7-24
Timestamp: 2026-02-11T23:40:32.726Z
Learning: In the whitenoise Flutter app, internal hook state classes (e.g., DeleteAllDataState in use_delete_all_data.dart) should implement manual copyWith methods rather than using Freezed. The Freezed package is appropriate for UI states, but not for hook-internal state classes. During reviews, flag hook state implementations that rely on Freezed and prefer explicit, hand-written copyWith for reliability and lighter dependencies. Apply this pattern broadly to all Dart files under lib/hooks/*.dart and any similar hook directories.

Applied to files:

  • lib/hooks/use_group_members.dart
📚 Learning: 2026-04-02T18:58:10.024Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 494
File: lib/hooks/use_scroll_to_message.dart:17-18
Timestamp: 2026-04-02T18:58:10.024Z
Learning: When reviewing Flutter/Dart hooks that use `flutter_hooks` (e.g., `useRef` / “latest-value ref” pattern), do not treat `useRef.current`/`ref.value` reads inside async loops as a stale-closure bug. If a parameter like `hasMoreMessages` is passed into the hook and immediately synced at the top of the build (`ref.value = hasMoreMessages`), then async code should read the latest `ref.value` on each iteration, reflecting the most recent rebuild triggered by state changes. The stale-closure concern should apply to captured local variables that are not updated via a `useRef`, not to `useRef`-backed fields.

Applied to files:

  • lib/hooks/use_group_members.dart
📚 Learning: 2026-02-07T04:45:18.077Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/hooks/use_nsec.dart:76-79
Timestamp: 2026-02-07T04:45:18.077Z
Learning: In Flutter, localization strings should be resolved and presented by widgets/screens, not inside hooks. Hooks (like those under lib/hooks) should return error codes (e.g., nsec_load_failed). Widgets should provide helper functions to map these codes to localized strings via AppLocalizations (e.g., _noticeMessageL10n, _signerErrorL10n). Apply this pattern across all hook files: hub on returning error codes, while UI components handle localization lookups using AppLocalizations.

Applied to files:

  • lib/hooks/use_group_members.dart
📚 Learning: 2026-02-02T10:05:10.264Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 128
File: lib/l10n/app_it.arb:53-53
Timestamp: 2026-02-02T10:05:10.264Z
Learning: For the Sloth app localization (lib/l10n/app_it.arb), ensure the Italian translations for the keys 'cancel' and 'discard' are both set to 'Annulla'. This should be consistently used across UI labels, dialogs, and tooltips. If there are multiple occurrences of these keys, verify all use the same translation to avoid inconsistencies.

Applied to files:

  • lib/l10n/app_it.arb
📚 Learning: 2026-01-05T21:28:05.652Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: test/screens/profile_keys_screen_test.dart:14-23
Timestamp: 2026-01-05T21:28:05.652Z
Learning: In test mocks for the marmot-protocol/sloth project, allow skipping bounds checking for substring operations when test inputs are controlled and have proven sufficient length (e.g., 'test_pubkey' is always 12 characters). Favor simpler, deterministic mock implementations in such controlled test contexts, but avoid relaxing bounds checks in production code or tests with variable inputs. Apply this guideline primarily to test files under the test/ directory.

Applied to files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
📚 Learning: 2026-01-07T16:49:18.694Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: test/screens/chat_screen_test.dart:245-282
Timestamp: 2026-01-07T16:49:18.694Z
Learning: In the marmot-protocol/sloth repository, it is acceptable to use force-unwrapping (!) in Dart test files as an implicit assertion: if a value is unexpectedly null, the test will fail. This guideline applies only to test code under the test/ directory and should not be used in production code.

Applied to files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
📚 Learning: 2026-01-11T22:43:09.610Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 36
File: test/screens/chat_list_screen_test.dart:74-76
Timestamp: 2026-01-11T22:43:09.610Z
Learning: In Dart tests (e.g., test/screens/chat_list_screen_test.dart and related files), use setUp() to call _api.reset() to clean up StreamController resources. Do not suggest or rely on tearDownAll for this cleanup.

Applied to files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
📚 Learning: 2026-04-14T03:52:37.716Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 565
File: test/providers/offline_provider_test.dart:63-177
Timestamp: 2026-04-14T03:52:37.716Z
Learning: In this repository, it’s acceptable to use fixed-time waits for test timing using `Future.delayed` (e.g., `await Future.delayed(const Duration(milliseconds: 10))`) inside test files. During code reviews, don’t recommend replacing these fixed sleeps with event-based or poll-based synchronization when the change is localized to Dart test files under the `test/` directory.

Applied to files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
📚 Learning: 2026-02-05T11:59:09.400Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 189
File: test/widgets/wn_filter_chip_test.dart:1-3
Timestamp: 2026-02-05T11:59:09.400Z
Learning: In Dart/Flutter test files, Offset is exported by package:flutter_test/flutter_test.dart. If you already import flutter_test, you do not need to import dart:ui for Offset. Ensure there are no conflicting Offset symbols when both imports occur.

Applied to files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
📚 Learning: 2026-02-19T02:12:52.425Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 316
File: test/hooks/use_chat_scroll_test.dart:96-114
Timestamp: 2026-02-19T02:12:52.425Z
Learning: In hook tests under test/hooks, using tester.pumpWidget directly is acceptable for simple hook logic tests that do not rely on localizations, providers, or screen utilities. If the test requires those features, use the mountWidget helper from test/test_helpers.dart (which wires in localizations, providers, and screen utilities). Apply this guideline to all files matching *_test.dart in test/hooks.

Applied to files:

  • test/hooks/use_group_members_test.dart
📚 Learning: 2026-04-28T17:39:39.941Z
Learnt from: pepina-dev
Repo: marmot-protocol/whitenoise PR: 600
File: test/hooks/use_leave_group_test.dart:67-78
Timestamp: 2026-04-28T17:39:39.941Z
Learning: In this repo’s Dart test files under `test/` (e.g., `test/hooks/use_leave_group_test.dart`), it’s acceptable to avoid calling `mockApi.reset()` at the start of `setUp()` when the test suite already manually resets all relevant individual mock fields in `setUp()`. Reviewers should not flag the absence of a `reset()` call as long as every field that could leak state between tests is explicitly reinitialized and the tests are passing.

Applied to files:

  • test/hooks/use_group_members_test.dart
  • test/utils/mention_text_editing_controller_test.dart
  • test/screens/chat_screen_test.dart
📚 Learning: 2025-12-23T14:37:11.241Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 9
File: test/screens/error_screen_test.dart:60-71
Timestamp: 2025-12-23T14:37:11.241Z
Learning: In tests for screen widgets that are not wired through the app's Routes.build (e.g., ErrorScreen under lib/screens/error_screen.dart), avoid mounting the full app with mountTestApp. instead, configure a minimal test harness with a custom router or navigator setup (e.g., a MaterialApp with a mock router) to render the screen in isolation. This ensures the test focuses on the screen's behavior without depending on the app's global routing configuration.

Applied to files:

  • test/screens/chat_screen_test.dart
📚 Learning: 2026-01-05T22:40:27.888Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_image_picker.dart:127-143
Timestamp: 2026-01-05T22:40:27.888Z
Learning: In marmot-protocol's Sloth project (lib/widgets/...): image hosting URLs returned by blossom servers are always lowercase (http:// or https://). Do not implement or rely on case-insensitive URL protocol detection in WN image picker code; assume the protocol is lowercase. If URL protocol checks are needed, compare against the exact literals 'http://' and 'https://'. Add tests that assert URLs use lowercase protocols. This guideline applies to all Dart files under lib/widgets that handle image URLs.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
📚 Learning: 2026-02-04T10:35:43.112Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 170
File: lib/widgets/wn_list_item.dart:232-234
Timestamp: 2026-02-04T10:35:43.112Z
Learning: In this repository ( marmot-protocol/sloth ), when writing widget build methods in Dart files under lib/widgets, only extract a local typography variable if context.typographyScaled or context.typography is used multiple times within the same build method. If typography is used only once, keep the inline access to avoid an unnecessary extra line. This guideline helps maintain readability and avoids premature refactoring.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
📚 Learning: 2026-02-14T05:09:20.007Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 260
File: lib/widgets/wn_message_quote.dart:32-88
Timestamp: 2026-02-14T05:09:20.007Z
Learning: In Dart widget files under lib/widgets (matching lib/widgets/**/*.dart), spacing literals such as padding, gap values, and border radii should not be extracted into constants based on josefinalliende's preference. Apply this rule consistently across widget files to improve readability and avoid over-abstracting simple literals.

Applied to files:

  • lib/widgets/chat_list_tile.dart
  • lib/widgets/message_content_text.dart
📚 Learning: 2026-01-15T15:00:17.356Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 39
File: lib/screens/user_search_screen.dart:50-75
Timestamp: 2026-01-15T15:00:17.356Z
Learning: In lib/screens/user_search_screen.dart and similar screen widgets in the sloth repository, prefer using inline ternary expressions for conditional rendering (e.g., isLoading → empty state → list) over extracting into separate widget classes when the conditional UI is simple. This improves readability by keeping the render logic in a single place. Reserve extracting to a separate widget only when the conditional UI becomes sufficiently complex or is reused across multiple screens.

Applied to files:

  • lib/screens/chat_screen.dart
📚 Learning: 2026-02-10T04:57:31.475Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 208
File: lib/screens/signup_screen.dart:66-70
Timestamp: 2026-02-10T04:57:31.475Z
Learning: In Dart/Flutter using flutter_hooks, a ValueNotifier (from useState) is disposed when the widget unmounts. Async callbacks (e.g., .then() on animations) may still run after disposal and throw 'A ValueNotifier was used after being disposed'. Reviewers should ensure that state updates in async callbacks are guarded by a mounted check (e.g., if (mounted) { notifier.value = ... } or cancel/avoid updating after disposal). This guideline applies to screen widgets under lib/screens and similar Dart files using hooks that manage widget lifecycle. Update code patterns to prevent post-disposal state updates, and document the rationale in comments where necessary.

Applied to files:

  • lib/screens/chat_screen.dart
📚 Learning: 2026-02-19T10:33:16.889Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 315
File: lib/screens/group_info_screen.dart:146-149
Timestamp: 2026-02-19T10:33:16.889Z
Learning: Pubkey validation (length/format) is performed by the Rust crate before data reaches Dart. Do not add defensive substring guards in Dart for pubkeys in lib/screens/**/*.dart or other Flutter code consuming group member pubkeys, as this would duplicate validation and be unnecessary. If any validation is needed in Dart, keep it to lightweight sanity checks only after data enters Dart and ensure it aligns with the Rust validation rules.

Applied to files:

  • lib/screens/chat_screen.dart
📚 Learning: 2026-04-06T09:40:51.965Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 552
File: lib/screens/sign_out_screen.dart:86-86
Timestamp: 2026-04-06T09:40:51.965Z
Learning: When reviewing Flutter/Dart code that uses ScreenUtil (e.g., ScreenUtil `.h`/`.w`/`.sp`), do not require converting a literal `0` used for padding/margin/sizing to a ScreenUtil expression. In sizing contexts like `EdgeInsets` (or other `EdgeInsets`/layout parameters), raw `0` is mathematically invariant (`0 * scale = 0`) and should not be flagged as a ScreenUtil violation. Only non-zero literal size values should be checked for ScreenUtil usage.

Applied to files:

  • lib/screens/chat_screen.dart
🔇 Additional comments (31)
lib/l10n/app_pt.arb (1)

197-198: LGTM!

lib/l10n/app_zh_Hant.arb (1)

197-198: LGTM!

lib/l10n/app_it.arb (1)

197-198: LGTM!

lib/l10n/app_fr.arb (1)

197-198: LGTM!

lib/l10n/app_es.arb (1)

197-198: LGTM!

lib/l10n/app_ru.arb (1)

197-198: LGTM!

lib/l10n/app_tr.arb (1)

197-198: LGTM!

lib/l10n/app_zh.arb (1)

197-198: LGTM!

lib/widgets/message_content_text.dart (11)

1-45: LGTM!


46-66: LGTM!


68-139: LGTM!


141-212: LGTM!


214-337: LGTM!


339-499: LGTM!


501-649: LGTM!


651-732: LGTM!


733-761: LGTM!


763-801: LGTM!


803-966: LGTM!

test/screens/chat_screen_test.dart (6)

69-94: LGTM!


114-115: LGTM!

Also applies to: 151-152


382-397: LGTM!


647-674: LGTM!


882-929: LGTM!


1781-1804: LGTM!

lib/l10n/app_de.arb (1)

197-198: LGTM!

lib/l10n/app_en.arb (1)

848-855: LGTM!

lib/hooks/use_group_members.dart (1)

24-25: LGTM!

Also applies to: 34-42, 65-65

test/hooks/use_group_members_test.dart (1)

139-139: LGTM!

Also applies to: 146-146, 205-222

lib/widgets/chat_list_tile.dart (1)

19-23: LGTM!

Also applies to: 31-31, 67-103, 327-341, 426-426, 448-448

lib/screens/chat_screen.dart (1)

17-17: LGTM!

Also applies to: 34-35, 40-42, 59-59, 66-170, 209-235, 535-535, 801-801, 857-1079

Comment thread lib/utils/mention_text_editing_controller.dart Outdated
Comment thread test/utils/mention_text_editing_controller_test.dart
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@lib/utils/mention_text_editing_controller.dart`:
- Line 249: The local variable shifted in _TrackedMention logic is never
reassigned so declare it as final to follow immutability guidelines; update the
declaration of shifted (the list of _TrackedMention created near the shifting
logic in mention_text_editing_controller.dart) to use final instead of
var/implicit mutable declaration so only its contents are mutated via add().
- Around line 286-287: The function isMentionBoundary is intended for internal
use only but is public; rename it to _isMentionBoundary and update all internal
call sites (the place that currently calls isMentionBoundary) to call
_isMentionBoundary so the helper matches the file's private naming convention
and prevents external export.
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: d2950de2-8d46-467e-b2b4-c171a135073e

📥 Commits

Reviewing files that changed from the base of the PR and between 9a79c45 and d5a8cf5.

📒 Files selected for processing (2)
  • lib/utils/mention_text_editing_controller.dart
  • test/utils/mention_text_editing_controller_test.dart
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (4/4)
  • GitHub Check: Check
  • GitHub Check: Test (1/4)
  • GitHub Check: Test (3/4)
  • GitHub Check: Test (2/4)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*.dart: Use single quotes for strings in Dart/Flutter code
Enable and respect prefer_const_constructors lint rule in Dart/Flutter code
Enable and respect prefer_final_locals lint rule in Dart/Flutter code
Set line width to 100 characters in Dart/Flutter code
Preserve trailing commas in Dart/Flutter code
Maintain minimum test coverage of 99%; never submit a PR that reduces test coverage
Avoid using // coverage:ignore, // coverage:ignore-line, // coverage:ignore-start, or // coverage:ignore-end to bypass coverage requirements; write tests instead (only exception: truly unreachable code)

Files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
**/*.{dart,rs}

📄 CodeRabbit inference engine (AGENTS.md)

Do not add comments except for code that is really complex or hard to understand

Files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
test/**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

test/**/*_test.dart: Test files should mirror source structure with _test.dart suffix
Use helpers from test/test_helpers.dart in tests: setUpTestView(), mountTestApp(), mountHook(), mountWidget(), mountStackedWidget()
Mock Rust API by extending MockWnApi from test/mocks/mock_wn_api.dart instead of implementing RustLibApi directly
Prefer find.byKey() over find.byIcon() in tests; add keys to icons in widgets and use find.byKey(const Key('icon_name')) in tests
Use valid 64-character hex strings for pubkeys in tests, not dummy values like 'abc' or 'test-pubkey'

Files:

  • test/utils/mention_text_editing_controller_test.dart
test/**

⚙️ CodeRabbit configuration file

test/**: IMPORTANT: CI enforces coverage regression (coverage must never decrease). It does not enforce a fixed 95% minimum threshold.
Rules:

  • Test files mirror source structure with _test.dart suffix
  • Use helpers from test/test_helpers.dart (setUpTestView, mountTestApp, etc.)
  • Mock Rust API using RustLib.initMock(api: mockApi)
  • Always extend MockWnApi from test/mocks/mock_wn_api.dart
  • Prefer find.byKey() over find.byIcon() for widget testing
  • Use valid 64-char hex strings for pubkeys, not dummy values like 'abc'
  • Tests must be deterministic — no external service dependencies

Files:

  • test/utils/mention_text_editing_controller_test.dart
lib/**/*.dart

📄 CodeRabbit inference engine (AGENTS.md)

lib/**/*.dart: Use flutter_screenutil for all size values (padding, margins, gaps, icon sizes, font sizes, border radius, container dimensions) to ensure responsive layouts. Use .w for width, .h for height, .sp for font size/letter spacing, and .r for radius
Avoid StatefulWidget; prefer providers (for shared app-wide state) or hooks (for widget-local state) instead

Files:

  • lib/utils/mention_text_editing_controller.dart
🧠 Learnings (21)
📚 Learning: 2026-01-05T21:28:05.652Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: test/screens/profile_keys_screen_test.dart:14-23
Timestamp: 2026-01-05T21:28:05.652Z
Learning: In test mocks for the marmot-protocol/sloth project, allow skipping bounds checking for substring operations when test inputs are controlled and have proven sufficient length (e.g., 'test_pubkey' is always 12 characters). Favor simpler, deterministic mock implementations in such controlled test contexts, but avoid relaxing bounds checks in production code or tests with variable inputs. Apply this guideline primarily to test files under the test/ directory.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
📚 Learning: 2026-01-07T16:49:18.694Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: test/screens/chat_screen_test.dart:245-282
Timestamp: 2026-01-07T16:49:18.694Z
Learning: In the marmot-protocol/sloth repository, it is acceptable to use force-unwrapping (!) in Dart test files as an implicit assertion: if a value is unexpectedly null, the test will fail. This guideline applies only to test code under the test/ directory and should not be used in production code.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
📚 Learning: 2026-01-11T22:43:09.610Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 36
File: test/screens/chat_list_screen_test.dart:74-76
Timestamp: 2026-01-11T22:43:09.610Z
Learning: In Dart tests (e.g., test/screens/chat_list_screen_test.dart and related files), use setUp() to call _api.reset() to clean up StreamController resources. Do not suggest or rely on tearDownAll for this cleanup.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
📚 Learning: 2026-04-14T03:52:37.716Z
Learnt from: josefinalliende
Repo: marmot-protocol/whitenoise PR: 565
File: test/providers/offline_provider_test.dart:63-177
Timestamp: 2026-04-14T03:52:37.716Z
Learning: In this repository, it’s acceptable to use fixed-time waits for test timing using `Future.delayed` (e.g., `await Future.delayed(const Duration(milliseconds: 10))`) inside test files. During code reviews, don’t recommend replacing these fixed sleeps with event-based or poll-based synchronization when the change is localized to Dart test files under the `test/` directory.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
📚 Learning: 2026-01-06T01:08:41.552Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/widgets/wn_warning_box.dart:28-79
Timestamp: 2026-01-06T01:08:41.552Z
Learning: In reviews for the marmot-protocol/sloth repository, avoid suggesting accessibility, design, or general improvements unless they are strictly relevant to the PR description and its stated goals. Focus feedback on the specific PR objectives and requirements; ignore unrelated stylistic or broad improvement suggestions.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-15T14:42:54.111Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 39
File: lib/hooks/use_user_search.dart:47-53
Timestamp: 2026-01-15T14:42:54.111Z
Learning: When using npubFromHex(String hexPubkey) from lib/utils/formatting.dart, it already handles errors internally and returns null on failure. Do not wrap calls to this function in try/catch blocks. Instead, check for a null return and handle accordingly (e.g., treat as invalid hex). This guideline applies to all Dart files in the repository that may call this function.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-16T17:35:32.431Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 43
File: lib/hooks/use_network_relays.dart:155-184
Timestamp: 2026-01-16T17:35:32.431Z
Learning: In Dart/Flutter code, prefer void as the return type for fire-and-forget async functions that are intended to run in the background without blocking the caller. Using Future or Future<void> would allow callers to accidentally await, which could block for long periods. This pattern makes the call fire-and-forget by design. Apply to functions that intentionally should not be awaited; document the intent where appropriate. (Example context: pollRelayStatus in lib/hooks/use_network_relays.dart uses void to prevent callers from awaiting a potentially long-running background task.)

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-19T15:50:56.684Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 52
File: lib/screens/app_settings_screen.dart:30-40
Timestamp: 2026-01-19T15:50:56.684Z
Learning: Target the Flutter SDK version >= 3.27 across the repo. Since features like Column.spacing and Row.spacing were added in Flutter 3.27, you can safely use them in Dart files (e.g., lib/screens/app_settings_screen.dart) as long as the pubspec.yaml environment specifies Flutter >= 3.27. If needed, enforce this by validating the environment constraint (e.g., flutter, sdk: flutter) in pubspec.yaml.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-29T03:02:34.290Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 100
File: lib/widgets/wn_dropdown_selector.dart:126-139
Timestamp: 2026-01-29T03:02:34.290Z
Learning: Global font-family usage guideline: Since Manrope is configured in lib/theme.dart for both light and dark themes, individual TextStyle declarations should not specify fontFamily: 'Manrope'. During reviews, verify that no TextStyle instances override fontFamily unnecessarily; rely on theme inheritance instead. If a TextStyle must specify a font for a specific case, ensure it is clearly justified and localized, and document why the override is needed. This guideline applies across Dart files in the project (e.g., lib/widgets/wn_dropdown_selector.dart and related components).

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-29T16:02:52.588Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 129
File: test/widgets/wn_overlay_test.dart:84-91
Timestamp: 2026-01-29T16:02:52.588Z
Learning: In Dart/Flutter code, when constructing widgets inside an already-const context (for example, within a const Scaffold with a child that is a collection of widgets), avoid adding explicit const keywords to the children. The Dart analyzer treats these as implicitly const, and adding explicit const can trigger the unnecessary_const lint. Review test files and general Dart files to ensure you do not redundantly prefix children with const in const contexts; rely on the implicit const behavior instead.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-07T03:58:22.587Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 193
File: lib/services/android_signer_service.dart:13-13
Timestamp: 2026-02-07T03:58:22.587Z
Learning: In Dart files across the repository, retain NIP (Nostr Implementation Possibilities) and MIP (Marmot Implementation Possibilities) protocol reference comments that link code to their specifications. While general guidance discourages extraneous comments, these references improve traceability and maintainability by documenting the protocol context for related implementation details. Keep them in Dart sources (e.g., lib/services/android_signer_service.dart) and ensure they are kept up-to-date.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-11T17:29:43.985Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 225
File: lib/screens/app_settings_screen.dart:64-69
Timestamp: 2026-02-11T17:29:43.985Z
Learning: In the whitenoise Flutter app, after deleting all data and resetting auth, use Routes.goToHome(context) instead of Routes.goToLogin(context) because the home screen is the app entry point and will manage authentication routing. Apply this change to navigation calls that should land on the home screen after a data reset or auth reset. Update lib/screens/app_settings_screen.dart and any similar navigation points accordingly.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-18T18:36:13.394Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 315
File: lib/screens/edit_group_screen.dart:45-47
Timestamp: 2026-02-18T18:36:13.394Z
Learning: Whitenoise uses automated code formatting through the precommit workflow (just precommit). Do not tweak line wrapping or formatting manually; follow the formatter's output. Run the precommit formatter locally and ensure the code matches its styling decisions, and only deviate if the formatter cannot express the intended style (in which case adjust the code to satisfy the formatter).

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-04-06T09:36:06.726Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 553
File: lib/widgets/wn_copyable_field.dart:38-40
Timestamp: 2026-04-06T09:36:06.726Z
Learning: In Dart, `String.operator*` is a valid built-in operator for repeating strings (e.g., `'⬤' * 16`). In code reviews of Dart (`.dart`) files, do not treat expressions like `'text' * n` as invalid syntax and do not recommend replacing them with alternatives such as `List.filled(...).join()`; the operator is supported by `dart:core`.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-05-13T13:46:32.612Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/hooks/use_block_actions.dart:3-3
Timestamp: 2026-05-13T13:46:32.612Z
Learning: In marmot-protocol/whitenoise code reviews, do not flag imports that reference FRB-generated bindings under `package:rust_lib_whitenoise/src/rust/...` as private-internals violations. In `rust_lib_whitenoise`, Dart bindings are intentionally generated into `lib/src/rust/`, and `analysis_options.yaml` sets `implementation_imports: false` to suppress the related Dart lint—so `package:rust_lib_whitenoise/src/rust/...` imports are the correct/only way to consume that FRB-generated API.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-05-13T13:46:39.250Z
Learnt from: dannym-arx
Repo: marmot-protocol/whitenoise PR: 656
File: lib/services/foreground_service.dart:13-16
Timestamp: 2026-05-13T13:46:39.250Z
Learning: In this repo (marmot-protocol/whitenoise), `rust_lib_whitenoise` is an FRB-generated Flutter package where the generated Dart bindings are intentionally exposed under `lib/src/rust/` as the public API surface. Do not treat imports like `package:rust_lib_whitenoise/src/rust/...` as breaking package API boundaries in code reviews (the project also sets `implementation_imports: false` in `analysis_options.yaml` to opt into this). Never raise concerns about `src/` imports from `rust_lib_whitenoise`.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-02-05T11:59:09.400Z
Learnt from: erskingardner
Repo: marmot-protocol/sloth PR: 189
File: test/widgets/wn_filter_chip_test.dart:1-3
Timestamp: 2026-02-05T11:59:09.400Z
Learning: In Dart/Flutter test files, Offset is exported by package:flutter_test/flutter_test.dart. If you already import flutter_test, you do not need to import dart:ui for Offset. Ensure there are no conflicting Offset symbols when both imports occur.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
📚 Learning: 2026-04-28T17:39:39.941Z
Learnt from: pepina-dev
Repo: marmot-protocol/whitenoise PR: 600
File: test/hooks/use_leave_group_test.dart:67-78
Timestamp: 2026-04-28T17:39:39.941Z
Learning: In this repo’s Dart test files under `test/` (e.g., `test/hooks/use_leave_group_test.dart`), it’s acceptable to avoid calling `mockApi.reset()` at the start of `setUp()` when the test suite already manually resets all relevant individual mock fields in `setUp()`. Reviewers should not flag the absence of a `reset()` call as long as every field that could leak state between tests is explicitly reinitialized and the tests are passing.

Applied to files:

  • test/utils/mention_text_editing_controller_test.dart
📚 Learning: 2026-01-05T20:05:32.918Z
Learnt from: untreu2
Repo: marmot-protocol/sloth PR: 22
File: lib/screens/settings_screen.dart:76-88
Timestamp: 2026-01-05T20:05:32.918Z
Learning: In the Sloth project, tooltips should not be used for UI elements. During code reviews, verify that UI components do not include Tooltip widgets and replace them with accessible alternatives (e.g., long-press hints, labels, or interactive guidance). Apply this guidance across Dart UI files under lib (not just settings_screen.dart) to maintain a consistent design approach.

Applied to files:

  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-01-09T13:25:18.531Z
Learnt from: josefinalliende
Repo: marmot-protocol/sloth PR: 33
File: lib/services/message_service.dart:14-26
Timestamp: 2026-01-09T13:25:18.531Z
Learning: When errors are presented to users in the Sloth codebase (UI layers such as snackbars, dialogs, toasts), show friendly, user-facing messages instead of raw Rust error messages. Implement a mapping from internal error types or codes to clear, non-technical messages, and surface actionable guidance for end users where appropriate. Avoid exposing internal stack traces or language runtime details; centralize common user-facing error wording in a dedicated utility or service and translate backend errors into concise, helpful UI messages.

Applied to files:

  • lib/utils/mention_text_editing_controller.dart
📚 Learning: 2026-04-06T09:40:41.044Z
Learnt from: untreu2
Repo: marmot-protocol/whitenoise PR: 552
File: lib/screens/profile_keys_screen.dart:71-71
Timestamp: 2026-04-06T09:40:41.044Z
Learning: In this Flutter/Dart codebase, when using flutter_screenutil (e.g., ScreenUtil or ScreenUtil-based sizing like `w`, `h`), do not require scaled units for literal zero values. Specifically, in EdgeInsets (and similar sizing/padding/margin/gap APIs), bare numeric `0` should be allowed (e.g., `EdgeInsets.all(0)`, `EdgeInsets.symmetric(vertical: 0)`, `SizedBox(width: 0)`), because scaling `0` remains `0`. Only flag ScreenUtil violations when a non-zero literal needs to be expressed via `0.w`/`0.h` equivalents; do not flag bare `0`.

Applied to files:

  • lib/utils/mention_text_editing_controller.dart
🔇 Additional comments (3)
lib/utils/mention_text_editing_controller.dart (1)

3-11: LGTM!

Also applies to: 13-56, 58-81, 83-114, 116-178, 181-235, 237-284, 289-305

test/utils/mention_text_editing_controller_test.dart (2)

9-22: LGTM!

Also applies to: 24-44, 46-68, 70-86, 88-98, 100-128


92-94: No compilation issue exists with const usage.

testNpubB and testNpubC are declared as const in test_helpers.dart, making string interpolation within const constructors valid. The code at lines 92–94 and 122–124 compiles correctly.

			> Likely an incorrect or invalid review comment.

Comment thread lib/utils/mention_text_editing_controller.dart
Comment thread lib/utils/mention_text_editing_controller.dart Outdated
Copy link
Copy Markdown
Collaborator

@pepina-dev pepina-dev left a comment

Choose a reason for hiding this comment

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

This PR is too big and complex to review to feel confident enough that I'm not missing something 😰

🤔 My main doubt/concern with this PR is why some things are parsed in rust crate (with messages tokens) and others are parsed here in flutter side. I think having all the parsing in message token in rust side would be more consistent, but maybe I am missing something.

✅ Links work great across different devices

My agent raised an issue related to messages bubbles rebuild that I think it's worth checking 🤖 also, here are some other issues I found:

  • In chat screen, tags only look like tags in my messages, but I can't see tags from messages sent from other devices.
Image
  • When I try to tag someone in a message with image, I can't select the user cause it is above the image, I think that maybe it should appear below the images preview
Image
  • UX: In whatsapp, when I tap a user tag ina group, I see the same info that I would have seen when tapping that user from the members list. I mean not showing this start chat at the bottom on tap but the member info screen
What now shows on tapping a tag What I think it should show instead
Image Image
  • In chat list code blocks look a bit weird:
Image

color: Colors.white,
fontWeight: FontWeight.w700,
) ??
const TextStyle(color: Colors.white, fontWeight: FontWeight.w700);
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.

prefer app typography styles for sizing and weights and design system colors instead of using white directly.

contentMaxLines: contentMaxLines,
bubbleWidthFactor: bubbleWidthFactor,
forceTightHeight: forceTightHeight,
onNostrTap: onNostrTap ?? (uri) => _openNostrProfile(context, uri),
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.

🤖 My agent flagged this:

Gesture recognizer churn on every rebuild. This creates a new closure every build. It flows through to MessageContentText.useMemoized's dependency list — so _buildSpanState runs on every rebuild, creating and disposing

TapGestureRecognizer objects for every URL/mention in every visible message. With a real chat history of 50+ link-containing messages, this causes scroll jank.

);
}

if (_startsWithAt(content, markerStart, '```')) {
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.

I am a little bit confused on why some things are handled as tokens parsed in the rust side but others, like code blocks are parsed here. To me both seem like both are for display concerns and I don't understand why some leave in one side and one on the other.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the reason for this is that the tokens in rust come from rust-nostr and it doesn't do full MD parsing (nor do we want full md parsing yet). I think for a first pass this is completely fine and doesn't require a full parsing engine in rust for something small. in the future if/when we want to expand it, we'll move things.

Comment thread lib/widgets/chat_list_tile.dart

bool _hasPreviewNostrUri(String text) => text.contains('nostr:npub1');

String _replacePreviewNostrMentions(String text, Map<String, String> displayNamesByUri) {
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.

The rust crate doesn't have this info in the already processed message tokens? like to know the tokens of the last message in the rust side and if they are mention or not?

Comment thread lib/widgets/message_content_text.dart
@dannym-arx
Copy link
Copy Markdown
Contributor

dannym-arx commented May 15, 2026

@erskingardner I was gonna add markdown parsing (#406) (including nostr: links and bare npubs) first, implemented in rust, which then flutter would just take the output of and render. I think it would be better to do that first, then we do the @ mention dialog afterwards, once that has landed.

@pepina-dev
Copy link
Copy Markdown
Collaborator

@erskingardner I was gonna add markdown parsing (#406) (including nostr: links and bare npubs) first, implemented in rust, which then flutter would just take the output of and render. I think it would be better to do that first, then we do the @ mention dialog afterwards, once that has landed.

Nice, that would make the logic in flutter side simpler. Thanks @dannym-arx

@dannym-arx
Copy link
Copy Markdown
Contributor

dannym-arx commented May 15, 2026

@erskingardner @pepina-dev Please take a look at #665

dannym-arx added a commit that referenced this pull request May 18, 2026
- Typing `@` in a group chat opens a member picker driven by useGroupMembers;
  selecting an entry inserts a styled `@DisplayName` token in the input.
- MentionTextEditingController tracks mention spans, renders them in the
  primary color via buildTextSpan, and exposes `messageText` so the wire
  representation is the bare `@npub1...` form regardless of how it's
  displayed.
- Bare `@npub1...` strings typed or pasted into the input are auto-tracked:
  if a name resolver is wired, they show as `@DisplayName`; otherwise they
  truncate to `@npub1abcdefgh…wxyz` matching the receiver bubble.
- chat_screen wires the resolver from getAuthorMetadata so mentions of users
  outside the current group still display by name when we know them, while
  the picker stays group-only.
- useChatInput now uses MentionTextEditingController and saves
  controller.messageText to drafts.

Adapted from PR #659 (rich message rendering / mentions); the receiver-side
markdown path on this branch already handles NostrMention inlines so no
bubble changes were needed.

Co-authored-by: erskingardner <202880+erskingardner@users.noreply.github.com>
@erskingardner erskingardner deleted the codex/parse-message-links-and-mentions branch May 18, 2026 13:35
dannym-arx added a commit that referenced this pull request May 18, 2026
- Typing `@` in a group chat opens a member picker driven by useGroupMembers;
  selecting an entry inserts a styled `@DisplayName` token in the input.
- MentionTextEditingController tracks mention spans, renders them in the
  primary color via buildTextSpan, and exposes `messageText` so the wire
  representation is the bare `@npub1...` form regardless of how it's
  displayed.
- Bare `@npub1...` strings typed or pasted into the input are auto-tracked:
  if a name resolver is wired, they show as `@DisplayName`; otherwise they
  truncate to `@npub1abcdefgh…wxyz` matching the receiver bubble.
- chat_screen wires the resolver from getAuthorMetadata so mentions of users
  outside the current group still display by name when we know them, while
  the picker stays group-only.
- useChatInput now uses MentionTextEditingController and saves
  controller.messageText to drafts.

Adapted from PR #659 (rich message rendering / mentions); the receiver-side
markdown path on this branch already handles NostrMention inlines so no
bubble changes were needed.

Co-authored-by: erskingardner <202880+erskingardner@users.noreply.github.com>
dannym-arx added a commit that referenced this pull request May 19, 2026
* Bridge whitenoise-markdown AST through flutter-rust-bridge

Bumps the whitenoise dep to 53fbbdd, which replaces the
`Vec<SerializableToken>` content representation with a
`whitenoise_markdown::Document` (CommonMark + GFM + nostr-extension
AST). Field names `tokens` / `content_tokens` are retained for FFI
continuity as the upstream author signaled.

- rust/src/api/markdown.rs: new module mirroring every AST node
  (Document, Block, Inline, ListItem, ListKind, Alignment,
  CodeBlockKind, AutolinkKind, NostrEntity, NostrHrp, TableCell) as
  FRB-friendly types, with From impls and 24 unit tests.
- rust/src/api/messages.rs: SerializableToken removed; tokens and
  content_tokens retyped to MarkdownDocument.
- rust/src/api/groups.rs: add disappearing_message_secs: None to
  NostrGroupConfigData / NostrGroupDataUpdate (incidental upstream
  change pulled in by the rev bump).
- Regenerated FRB bindings — recursive AST bridged cleanly as Dart
  sealed classes.
- lib/screens/chat_raw_debug_screen.dart: rewrote the token-dump
  section as a recursive AST pretty-printer.
- 12 test fixtures: tokens/contentTokens fixtures retyped.

No new rendering yet — content is still rendered as plain text by
WnMessageBubble; the parsed AST sits unused on ChatMessage until the
renderer lands in the next commit.

* Render markdown in chat message bubbles

Wires the parsed whitenoise_markdown::Document AST through to a new
WnMarkdownText widget that renders inside ChatMessageBubble.

Renderer (lib/widgets/wn_markdown_text.dart, 480 lines):
- Faithful AST walk: every Block (Paragraph, Heading, ThematicBreak,
  CodeBlock, BlockQuote, List, Table, MathBlock) and every Inline
  (Text, SoftBreak, HardBreak, Code, Emph, Strong, Strikethrough,
  Link, Image, Autolink, Math, NostrMention, NostrUri) renders.
- Task-list checkboxes for ListItem.checked = Some(...).
- Table with per-column alignment, scrolls horizontally on overflow.
- Inline images render as tappable '[image: alt]' text — no
  in-bubble network image loads.
- Math: source rendered as italic monospace (no LaTeX engine).
- URL scheme allow-list: http, https, mailto, nostr, tel. Anything
  else (javascript:, data:, file:, ...) gets no tap recognizer.
- Highlight-query support: per-inline substring matching merged with
  AST rendering, so search results show both markdown formatting and
  highlight backgrounds in the same view.
- HookWidget manages TapGestureRecognizer lifetimes via useRef +
  useEffect cleanup (no StatefulWidget).

Integration (lib/widgets/wn_message_bubble.dart, chat_message_bubble.dart):
- New `document` parameter on WnMessageBubble; `onLinkTap` /
  `onNostrTap` callbacks.
- Plain-text fast path: when the document is empty, or is a single
  Paragraph whose inlines are only Text/SoftBreak/HardBreak, the
  existing Text widget is used — plain messages render byte-identically
  to before, preserving the inline-timestamp layout in _TextWithTimestamp.
- Formatted messages route through WnMarkdownText; timestamp moves to
  its own row below the content.
- Deleted messages ignore the document.
- ChatMessageBubble provides default tap handlers using url_launcher
  (LaunchMode.externalApplication), guarded by isSafeMarkdownUrl.

Tests:
- test/widgets/wn_markdown_text_test.dart (51 widget tests) covers
  every Block and Inline variant, URL safety, highlight merging,
  maxLines truncation, empty / edge cases.
- test/widgets/wn_message_bubble_test.dart: 8 new tests for the
  plain-text / markdown routing and callback wiring.
- test/widgets/chat_message_bubble_test.dart: 3 new tests verifying
  url_launcher integration (safe URL launches externally, javascript:
  is silently dropped, nostr: URIs launch).
- All 4487 tests pass. Coverage: 99.04%.

Known limitations (worth surfacing in QA):
- Chat-list previews, reply quotes, and notifications still render
  the raw message string — markdown syntax characters will appear
  as literal text in those surfaces. This matches what most chat
  apps do and Whitenoise gives us plain content there anyway.
- Multi-block markdown messages move the timestamp to its own row;
  plain-text and single-formatted-paragraph messages keep the inline
  timestamp behavior.

* Render SoftBreak as newline so user-typed line breaks survive

The CommonMark default for SoftBreak is a space, which is fine on the
web where authors hard-wrap source for readability and expect rendering
to reflow. In a chat composer every Enter press produces an in-paragraph
newline, which the parser emits as SoftBreak — so the default would
collapse multi-line messages into one long visual line.

This matches what every chat app does (Slack, Telegram, Discord,
iMessage all map SoftBreak → newline). HardBreak already mapped to
newline; the two now share an arm.

The image-alt flattening helper (_flattenInlines) still emits a space
for SoftBreak — alt text shouldn't carry hard line breaks.

* Render @npub mentions as underlined display name

Per Vlad's design call: Nostr npub mentions should appear as the
mentioned user's display name with an underline, not as a chip and not
in link-blue. Other Nostr HRPs (note/nevent/nprofile/naddr/nrelay)
still render with the link-style fallback since they point to events,
not people.

- WnMarkdownText: new `mentionDisplayName: String? Function(String hexPubkey)?`
  callback. For each npub mention/URI the renderer decodes the bech32
  to a hex pubkey via hexFromNpub() and asks the caller for a display
  name. If resolved → '@DisplayName'. If unresolved or callback absent
  → '@npub1abcdefgh…wxyz' truncated fallback. Style is the surrounding
  text's color with TextDecoration.underline — mentions inherit the
  bubble text color rather than the link color.
- Plumbed through WnMessageBubble and ChatMessageBubble as a single
  optional param at each layer; ChatScreen wires it to its existing
  `presentName(getAuthorMetadata(hex))` cache. Mentions of message
  authors resolve immediately; mentions of users not yet in the
  metadata cache fall back to truncation until metadata loads (a
  later follow-up can subscribe to mentioned pubkeys).
- 3 new widget tests: resolved name, empty-name fallback, no-callback
  fallback. 55 markdown tests pass. Coverage 99.03%.

* Plumb mentionDisplayName into the long-press action menu

When a user long-presses a message, the action menu re-renders the
bubble inside MessageActionsModal — a separate widget tree from the
chat list. The mentionDisplayName callback was not being forwarded to
that re-render, so @mentions fell back to truncated npubs in the
preview while showing resolved names in the main chat. Wire it through
MessageActionsScreen \u2192 MessageActionsModal \u2192 ChatMessageBubble.

* Bump whitenoise-rs to 7577289f (bare-npub parsing)

Picks up two commits on the markdown PR since 53fbbdd:

  c4ef3d99  address review feedback
              \u2014 cosmetic-only: drops `#[cfg(feature = "serde")]`
              gating (serde derives now unconditional), removes doc
              references to deleted PLAN.md, internal scanner refactor.
              Database path switches from re-deriving the AST on read
              to reading from a persisted `content_tokens` column \u2014
              same observable behavior at the FFI boundary.

  7577289f  bare npubs should also parse
              \u2014 the parser now recognizes bare `npub1\u2026` strings as
              `Inline::NostrMention` (previously only `@npub1\u2026` and
              `nostr:npub1\u2026` worked). Restricted to the `npub` HRP
              to avoid false positives on prose starting with
              `note1\u2026` / `nevent1\u2026`. No AST shape change.

No bridge or renderer changes needed: our existing NostrMention/Npub
path with the mentionDisplayName callback handles the new shape
identically.

* Bump whitenoise-rs to f421a8ad (fix nested-blockquote misparse)

Single fast-forward commit on the markdown PR:

  f421a8ad  fix bug with inner quotes inside quotes
              \u2014 trailing paragraphs after a blank \`> >\` line inside
              a nested blockquote were escaping to the document root.
              Verified by re-running the local AST dump on the user's
              repro input: 'Trailing paragraph in inner quote.' now
              sits as the third child of the inner BlockQuote, not as
              document.blocks[0].

No FFI shape changes; bridge regen is format-noise only (handled by
dart format).

* Tapping an @npub mention opens the user's profile in a shade

Per Vlad's design call. Previously, tapping a mention launched a
nostr:<bech32> URI externally; that bounced users out to the OS and
felt out of place inside a chat.

New flow:
- chat_message_bubble: when the renderer reports an Npub tap, decode
  bech32 to hex and open UserProfileShade in-app. Non-npub Nostr URIs
  (note/nevent/nprofile/naddr/nrelay) keep the external launchUrl
  behavior \u2014 those point to events, not people.
- lib/screens/user_profile_shade.dart: new HookConsumerWidget pushed
  via PageRouteBuilder with an opaque:false barrier. Wraps
  WnUserProfileCard inside a WnSlate. Header navigates back with
  Navigator.pop. For non-self users, a 'Start chat' button pops the
  shade and routes to StartChatScreen. Copy actions surface as
  SnackBars.
- The shade drops input focus before opening (mirrors what chat_screen
  does before MessageActionsScreen.show). Without this the chat list
  snaps to the bottom on dismiss because the keyboard reappears and
  resizes the viewport.
- 7 new shade tests cover: card rendered, button shown/hidden by
  isSelf, copy and copy-error snackbars, header dismiss, and the
  start-chat button popping then routing via a stub GoRouter.
- Updated chat_message_bubble tests: the npub tap test now asserts
  launchUrl is NOT called; a new test exercises the non-npub
  external-launch fallback.

4498 tests pass. Coverage 99.03%.

* Bump whitenoise-rs to 935683af (bare-URL autolinks + longer bech32)

Two fast-forward commits from f421a8ad:

  dbd595b3  nostr bech32 are not limited to 90ch
              \u2014 lifts the bech32 length cap from BIP-173's 90 to 1024
              per NIP-19, which explicitly waives 90 for TLV-encoded
              nevent / naddr / nprofile entities with multiple relay hints.
              Pure parser change.

  935683af  add better bare-url parsing for whitenoise, http, https, tel, mailto
              \u2014 bare URLs now parse as Inline::Autolink for the schemes
              http://, https://, mailto:, tel:, whitenoise://, and
              whitenoise-staging://. Trailing punctuation excluded.
              The opaque form 'whitenoise:foo' (no //) stays literal.

No AST shape change. Bare URLs flow through our existing autolink path.
Bridge regen is format-noise only.

Note: my URL allow-list in wn_markdown_text.dart includes http, https,
mailto, nostr, tel but not whitenoise(-staging). Bare whitenoise://
links will render styled but tap is a no-op until that's extended.

* Restyle @npub mentions: user color, bold, no @ prefix

Per the latest design call (Vlad). Mentions now render with three
properties:

- No '@' prefix.
- Bold (FontWeight.w700).
- Color seeded from the pubkey via AvatarColor.fromPubkey() \u2014 same
  palette the bubble already uses for sender names, so a mention of
  someone reads in the same hue as their own messages in a group.

Underline is dropped \u2014 color + bold is the new mention affordance.

When hexFromNpub fails to decode (malformed bech32), the mention falls
back to its surrounding text color so it stays bold but doesn't pretend
to identify someone.

Tests updated: drop '@' from expected strings, replace underline
assertions with FontWeight.w700 + per-pubkey color.

* Bump whitenoise-rs to bb717d1c (content_tokens backfill migration)

Two fast-forward commits from 935683af:

  6fae0178  fix stale comment
  bb717d1c  backfill migration to reparse content_tokens with markdown parser

The migration adds m0044_reparse_content_tokens.rs in the whitenoise
crate. It runs once at app init and reparses any persisted
content_tokens rows whose stored AST is older than the current parser
(necessary now that the AST is persisted instead of re-derived on each
read). All internal to whitenoise; no FFI surface change.

The only types.rs touch is a doc-comment update on
ChatMessage::content_tokens, removing the now-stale 'never persisted'
sentence.

* bump + just regenerate

* Address review: rename MarkdownText, honor list 'tight' flag, stable test keys

Three findings from PR review:

1. Drop the 'Wn' prefix on MarkdownText.
   Per CLAUDE.md, the 'Wn'/'wn_' prefix is for design-system widgets
   that are presentational only and make no Rust API calls. This
   widget calls hexFromNpub (Rust) for mention decoding, so it doesn't
   belong in the design-system namespace.
   - Renamed class WnMarkdownText -> MarkdownText.
   - Renamed file lib/widgets/wn_markdown_text.dart -> markdown_text.dart.
   - Renamed test file accordingly.
   - Updated imports in chat_message_bubble, wn_message_bubble, and
     both test files (66 references in total).

2. Honor MarkdownBlock_List.tight.
   The renderer previously destructured only kind and items and used a
   fixed 4.h gap between list items, ignoring the tight flag the
   parser emits. Now forwards tight to _buildList and picks a smaller
   inter-item gap (2.h) for tight lists vs a larger one (8.h) for
   loose lists, matching CommonMark semantics.

3. Stable keys on task-list checkbox icons.
   The two task-list rendering tests were keying off Icons.check_box
   and Icons.check_box_outline_blank via find.byIcon, which is fragile
   if the underlying Icon ever changes. Added Key('check_box') and
   Key('check_box_outline_blank') to the renderer's checkbox Icon and
   switched the tests to find.byKey.

All 4536 Flutter + 75 Rust tests pass. Coverage 99.05%.

* Tap-to-open whitenoise:// links; unify mention/profile UI under Start Chat shade

- Allow whitenoise:// and whitenoise-staging:// in markdown_text safe schemes
  so taps on the Rust-emitted autolinks dispatch to the bubble handler.
- whitenoise://chat/<id> routes through GoRouter; whitenoise://user/<npub>
  and bare @npub mentions now open the Start New Chat menu as a shade.
- Show a localized error dialog when a whitenoise:// URL fails to parse.
- Make StartChatScreen shade-capable (transparent scaffold, scrollable
  content) and remove the now-redundant UserProfileShade.

Co-authored-by: nvk <797193+nvk@users.noreply.github.com>

* Add @mention picker with rich input and npub display resolution

- Typing `@` in a group chat opens a member picker driven by useGroupMembers;
  selecting an entry inserts a styled `@DisplayName` token in the input.
- MentionTextEditingController tracks mention spans, renders them in the
  primary color via buildTextSpan, and exposes `messageText` so the wire
  representation is the bare `@npub1...` form regardless of how it's
  displayed.
- Bare `@npub1...` strings typed or pasted into the input are auto-tracked:
  if a name resolver is wired, they show as `@DisplayName`; otherwise they
  truncate to `@npub1abcdefgh…wxyz` matching the receiver bubble.
- chat_screen wires the resolver from getAuthorMetadata so mentions of users
  outside the current group still display by name when we know them, while
  the picker stays group-only.
- useChatInput now uses MentionTextEditingController and saves
  controller.messageText to drafts.

Adapted from PR #659 (rich message rendering / mentions); the receiver-side
markdown path on this branch already handles NostrMention inlines so no
bubble changes were needed.

Co-authored-by: erskingardner <202880+erskingardner@users.noreply.github.com>

* fix jumpy cursor bug

* coderabbit

* strikethrough

* render in reply content block thing

* fmt

* carrots for our rabbit, including THE GOLDEN CARROT

* tests: cover mention-controller branches and bubble truncation path

Restores coverage above the 99% gate after the markdown/mention/timestamp
fixes. New cases:

- insertMention shifts a later tracked mention via _TrackedMention.shift
- buildTextSpan emits a leading text segment before a mention span
- setMentionTargets snaps the cursor when it sat mid-URI
- setMentionTargets handles a known URI that sits past existing text
- _shiftMentions shifts a mention past a prefix insertion
- _replaceBareNpubs preserves and remaps a later mention via
  _shiftPastReplacements when a bare npub is inserted before it
- IME composing-at-mention-boundary preservation + the non-composing
  drop control case
- A tight finite-height truncation path for _TextWithTimestamp

* fix: synchronous recognizer disposal + permissive mention boundaries

Markdown link/nostr recognizers were being torn down via
addPostFrameCallback. If the widget was removed before the next frame
(Navigator.pop during scroll, hot reload, fast list rebuild), the
callback still ran but no longer had a clean owner. Switch to disposing
the previous frame's recognizers synchronously at the start of build,
before assigning the new list. Unmount cleanup via useEffect stays.

Add a testWidgets that mounts a MarkdownText with a link, tears the
subtree down, pumps a follow-up frame, and asserts no exception is
thrown. Use Flutter's experimentalLeakTesting + leak_tracker so any
leaked TapGestureRecognizer surfaces a test failure.

Also:
- _isMentionBoundary now accepts sentence punctuation (. , ; : ! ? ) ])
  so '@npub1...!' / '@npub1...,' auto-truncate the way the Rust parser
  already permits.
- markdown_text's display-name resolver trims the returned name before
  the empty-check, matching the controller's behavior and dropping
  whitespace-only resolutions back to the truncated fallback.

* fix: use theme color for markdown highlight fallback; move mention picker below images

Addresses two review notes:

1. r3261917942 ("Use colors set in theme") — markdown_text.dart:145.
   The default highlightColor was Colors.yellow, a raw Material color
   inconsistent with the rest of the codebase. Fall back to
   context.colors.intentionInfoContent, which matches how
   wn_message_bubble.dart already builds its highlight color when the
   caller doesn't specify one.

2. Mention picker UX — chat_screen.dart. When media attachments are
   present the @-picker was being rendered above the entire input box,
   pushing the image preview down. It now rides along inside the
   attachment area, sitting below the image previews but still above
   the text field. Without media it still appears above the input as
   before. Two new chat-screen tests pin both branches in place,
   asserting the picker's position relative to the media preview and
   the input via getTopLeft / getBottomLeft.

---------

Co-authored-by: nvk <797193+nvk@users.noreply.github.com>
Co-authored-by: erskingardner <202880+erskingardner@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants