Skip to content

feat(reputation): import reputation from lnp2pBot - #676

Merged
grunch merged 8 commits into
mainfrom
feat/reputation-import-ui
Oct 14, 2026
Merged

grunch merged 8 commits into
mainfrom
feat/reputation-import-ui

Conversation

@grunch

@grunch grunch commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Linked issue

Part of #674, phase 5b.2. The core dependency #675 is merged; this branch is now rebased directly on main.

Type

feat

What changes and why

Add the Import reputation screen for reputation earned on lnp2pBot or another Mostro. Users see their complete identity npub before confirming the binding in the bot, paste or prefill an attestation, check its signature/destination/issuer locally, inspect the figures, and explicitly import it into the active node.

The screen waits for privacy and identity checks before enabling actions. The Telegram handoff rechecks privacy, identity and node support; leaving the route cancels it, and a changed identity must be reviewed before retrying. Editing withdraws a checked attestation, and the input stays fixed while an import is in flight. Launch failures, missing identities and read failures have localized feedback. Counts, ratings and plurals follow the locale; screen readers receive result announcements.

Affected flow

  • /import_reputation, within the persistent home shell. The Settings entry remains part of the subsequent UI PR; this PR adds the route and screen.
  • Dart: reputation screen, API adapter, Telegram URL/extraction helper, error mapping and buttons.
  • Rust bridge: check_reputation_import shares the import's local checks; reputation_identity_npub encodes the displayed public key in Rust.
  • The existing reputation transfer contract and reputation.md now document the screen's inputs, gates, side effects and failures.

Blast radius

The existing export/import transport, identity publication guards and identity-scoped pending slot from #675 are preserved. No storage schema, wire format or dependency change is introduced. The pending attestation is read for prefill; import still repeats local checks and the daemon has the final decision.

The route follows main's navigation structure. All existing ARB values are preserved, with 24 added strings in every locale. FRB bindings and localizations were regenerated from source. The six reputation.import.* automation IDs include the displayed identity readout; the automation contract is updated. Existing trade, wallet and notification flows are unchanged by the feature.

Platforms tested

Linux host: Rust tests, clippy, native library build, Flutter analyzer and widget/unit suites. Rust also compile-checked for wasm32-unknown-unknown. No Android/iOS/device run or web browser smoke test was performed in this review.

Manual testing

Setup

No live app-to-Telegram-to-daemon import was run. The checks below are a pending manual procedure, not reported results. A run requires a compatible Mostro node trusting the source issuer and an eligible test lnp2pBot account. The screen currently has a route but no Settings entry in this PR.

Steps

  1. Open the import route with a reputation-mode identity and a compatible node. Compare the entire displayed npub with the bot's confirmation before binding the test account.
    Expected: the keys match; the bot provides an attestation after confirmation.
  2. Paste the attestation and press Check; edit the text before pressing Import.
    Expected: the checked figures and Import action disappear until the new text is checked. The older PR revision incorrectly kept the previous attestation; this screen does not exist on main.
  3. Check the original attestation and import it.
    Expected: the daemon confirms the import; an error or timeout is never shown as success.
  4. Regression: return with privacy mode enabled, then with a node that does not advertise imports.
    Expected: the screen explains the gate and cannot open Telegram or import. Restore supported settings and Retry to reload the gates.
  5. For an attestation exported from another Mostro, restart the app before opening this route.
    Expected: its pending JSON prefills and is locally checked; a successful matching import clears the pending slot.

Not covered

Live account binding/import, upgrade/restart on a device, actual Telegram handler behavior, Android/iOS, and web runtime behavior. No external account was bound during the review. Maintainer exemption applies; the earlier review's request for live integration evidence remains a validation limitation.

Screenshots

Visible feature. No device screenshots or recording were captured, and no manual UI results are claimed. Ten automated fixture-render tests passed; before/after renders were inspected locally in English/Spanish and both themes, plus German at 2x. They use a fake API and do not establish live integration behavior. Automated layout coverage exercises the screen and full-length npub in both themes at 320 dp, 2x text, German, with bundled fonts. Relevant rules: DS-CMP-3/4, DS-TYP-2/3, DS-L10N-1/2/3, DS-A11Y-2/4. No new color token.

Automated tests

  • Read all issue comments, review bodies, inline comments and threads, including the outdated launch thread; verified earlier fixes against the current code.
  • Separate signed regression-test commits precede the new fixes. Nine cases failed on the rebased PR before the fixes; the large-count locale test also failed before its fix. The feature is absent on main, so these regressions target the pre-fix PR revision.
  • Focused reputation Flutter suite: 30 passed after the final accessibility fix. Covers valid/refused import, stale and delayed checks, pending prefill, capability/privacy gates, unavailable/missing identity, npub display, changed identity, cancelled handoff, launch false/exception, in-flight input locking, localized numbers/plurals and German 2x layout.
  • Rust: 1,280 passed, 30 ignored; cargo clippy --locked -- -D warnings, cargo check --locked --target wasm32-unknown-unknown, and cargo build --locked passed. Includes npub round-trip/invalid-input coverage and local import checks.
  • flutter analyze: no issues. Design check: no breaks. Changed Dart and Rust files pass formatting; git diff --check is clean.
  • Full Flutter suite: 2,820 passed, 4 skipped. The subsequent accessibility and button-layout changes were checked with the full focused reputation suite (30 passed), including three new accessibility regressions and assertions that the German action label stays on one line in both themes.
  • Whole-repository cargo fmt --check reports existing formatting differences in 53 files, none changed by this PR. Unrelated files were left intact.
  • CodeRabbit CLI could not review: HTTP 403, organization membership. No successful CodeRabbit CLI review is claimed.

Checklist

  • Whole-tree cargo fmt --check passes (pre-existing differences noted above); Rust tests/clippy/build pass
  • Changed Dart files are formatted; flutter analyze passes
  • Full Flutter suite result recorded above
  • Changed Rust API/ARB: generated bindings and localizations regenerated and committed
  • New user-facing text translated in all six locales
  • Automation identifiers documented; existing identifiers preserved
  • Protocol/transport impact described; no wire change
  • Living reputation contract updated
  • Commits signed; rebased history has no merge commits
  • Manual integration procedure and device screenshots completed

AI assistance

The original implementation used Claude Code. Codex reviewed all PR feedback and the code, resolved rebase conflicts, added regression tests, fixed the remaining issues, and ran the checks reported above.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository YAML (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7264ac59-3b4e-4879-bbea-e5e6691bc873
📥 Commits

Reviewing files that changed from the base of the PR and between c37df54 and 8844a09.

📒 Files selected for processing: 29
  • docs/automation-contract.md
  • lib/core/app_routes.dart
  • lib/core/automation/automation_ids.dart
  • lib/features/reputation/lnp2pbot.dart
  • lib/features/reputation/reputation_api.dart
  • lib/features/reputation/reputation_errors.dart
  • lib/features/reputation/screens/import_reputation_screen.dart
  • lib/features/reputation/widgets/reputation_controls.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_localizations.dart
  • lib/l10n/app_localizations_de.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_es.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_it.dart
  • lib/l10n/app_localizations_nl.dart
  • lib/l10n/app_nl.arb
  • lib/src/rust/api/reputation_transfer.dart
  • lib/src/rust/frb_generated.dart
  • rust/src/api/reputation_transfer.rs
  • rust/src/frb_generated.rs
  • specs/004-mostro-p2p-client/contracts/reputation.md
  • specs/004-mostro-p2p-client/contracts/reputation_transfer.md
  • test/features/reputation/import_reputation_screen_test.dart
  • test/features/reputation/lnp2pbot_test.dart
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T16:04:36.675411Z 35c5de7 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35c5de794a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/features/reputation/screens/import_reputation_screen.dart
Comment thread lib/features/reputation/screens/import_reputation_screen.dart
Comment thread lib/features/reputation/screens/import_reputation_screen.dart Outdated

@Catrya Catrya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review evidence

Decision: Request changes

Tested commit: 2615478f (PR head). I reviewed the PR's own diff against #675's head (a55bd4b1..2615478f). I also merged #675 and #676 locally onto main at 588ff66c (not pushed).

Environment:

  • Harness: Neither. Plain cargo and flutter in a git worktree; no Ortsom or Mortsom run.
  • Platform: Linux. Web compile-checked only (wasm32-unknown-unknown).
  • Daemon source: Not applicable. No daemon here carries MostroP2P/mostro#1028–#1032.
  • App source: Pull request #676, with #675 below it, merged onto current main.

Commands and tests executed:

  • git merge --no-ff --no-commit origin/feat/reputation-import-ui onto origin/main. 18 files conflict:
    • the six ARBs and the generated app_localizations*.dart;
    • lib/src/rust/api/orders.dart, lib/src/rust/frb_generated.dart, rust/src/frb_generated.rs;
    • rust/Cargo.toml and rust/Cargo.lock.
  • I resolved them locally:
    • the ARBs, by keeping both sides' keys;
    • Cargo.toml and Cargo.lock, with main's side (mostro-core = "0.17.1");
    • the generated files, by regenerating with ./scripts/frb-generate.sh and flutter gen-l10n.
  • On the merged tree:
    • cargo test --locked
    • cargo clippy --locked -- -D warnings
    • cargo check --locked --target wasm32-unknown-unknown
    • flutter analyze
    • dart tool/design_check.dart --base origin/main
    • flutter test (full suite)
  • Throwaway widget probes on ImportReputationScreen:
    • Stale attestation: paste attestation A, tap Check, replace the text with something else, then tap Import.
    • npub on screen: count any text containing npub.
    • Rating format: Spanish locale, check the rendered figure.
    • Layout: 320 dp wide, 2× text, German. Scroll the whole ListView before and after Check, checking for
  • Rating format: Spanish locale, check the rendered figure.
  • Layout: 320 dp wide, 2× text, German. Scroll the whole ListView before and after Check, checking for overflow.
  • Read the PR's own diff in full: lnp2pbot.dart, reputation_api.dart, reputation_errors.dart, import_reputation_screen.dart, reputation_controls.dart, the ARBs and the tests.
  • Compared the hand-off with lnp2pBot/bot#933 (open): the start-parameter format and the bot's confirmation text.
  • Checked how main shows the identity key: account_screen.dart.

Results:

  • Passed:
    • cargo test --locked: 1129 passed, 0 failed, 30 ignored. That is with mostro-core 0.17.1, not the PR's pin.
    • clippy and wasm check: clean.
    • flutter analyze: no issues.
    • design_check: no breaks (5 files).
    • flutter test: 2139 passed, including the PR's tests.
    • Layout probe: no overflow anywhere on the screen.
    • The bot link matches bot#933, which expects ^/start(?:@\w+)? rep_([A-Za-z0-9_-]{43})$.
  • Failed:
    • Merge with main: 18 conflicting files (see above).
    • flutter test: 2 failures, both attachment_bubbles_golden_test (dark and light). They fail the same way on main in this environment, so they are not caused by this PR.
    • Stale-attestation probe: after the text was replaced, Import was still offered, and it sent attestation A (imported=[{"id":"a",…}]).
    • npub probe: 0 texts containing npub on the screen.
    • Spanish rating probe: renders 4.87, not 4,87.
  • Skipped: 4 in flutter test, also skipped on main. Web smoke test not run on the merge.
  • Flaky: None observed

Manual observations:

  • bot#933 relies on the app to show the npub. The bot asks the user to confirm: "Check that it is the one your Mostro app shows. Once confirmed, your account here is bound to it". But the app shows the identity nowhere. On main the Account screen exposes the public key only as a 1-px automation node (keysPublicKey), and this screen does not show it either.
  • No gate on what the node imports. The screen never reads support().importIssuers. "Get it from lnp2pBot" and Check are offered even when the active node does not import. Check also accepts an attestation from an issuer the node does not trust, or addressed to another identity. All of these are refused only at Import.
  • Privacy mode is not checked. The screen does not read it. _openBot sends the identity key to Telegram and lnp2pBot regardless, and Rust then refuses the import. #677 disables the button that opens this screen, but the screen itself does not guard.
  • _openBot fails silently. It ignores a false from launchUrl and only catches exceptions. It also does nothing visible when there is no identity or the key is not hex.
  • Codex's comments on this PR (2026-10-03) have no reply and no change. The last push was 2026-10-03 16:26.

Not tested or limitations:

  • No import from this app against a daemon, and no round trip with a real lnp2pBot (bot#933 is open). The PR's own body says "Not run on a device yet", and it has no screenshots.
  • No web build or smoke test on the merge.
  • In the layout probe, I did not get the figures card itself on screen at 2× text.
  • The stack was tested with mostro-core 0.17.1 from main, not with the PR's pin.

Unresolved findings:

Blocking:

  1. Import can send an attestation other than the one in the field.
    • After Check, editing or replacing the text does not clear _attestation, so Import stays enabled and sends the previously checked attestation.
    • Reproduced above. Import is irreversible and links the two accounts.
    • Fix: clear the verified state when the field changes, or re-validate the current text before importing. Codex P2, open.
  2. The user cannot check the npub that lnp2pBot asks them to confirm.
    • bot#933's confirmation binds the user's lnp2pBot account and tells them to check the npub against what the app shows. The app shows none.
    • Fix: show the identity npub, encoded in Rust (see the #677 review), next to "Get it from lnp2pBot".
  3. The node's import capability is learned only at the last step.
    • On a node that does not import, the user is sent to lnp2pBot, binds their account there, pastes and checks, and is refused only at Import.
    • An untrusted issuer, or an attestation addressed to another identity, also passes Check.
    • Fix: when importIssuers is null, say so up front and disable the bot button. At Check, run the local issuer and destination checks.
  4. No end-to-end run.
    • The Manual testing steps were written but never executed: "Not run on a device yet", no screenshots.
    • Maintainers are exempt from the contribution bar, but this flow ends in a binding on two external systems (the node and lnp2pBot).
  5. The stack cannot merge as it stands.
    • It depends on #675 (changes requested).
    • It inherits the mostro-core pin to the deleted feat/reputation-merge branch. Switch to 0.17.1, which works (verified above).

Non-blocking:

  • Silent bot button. _openBot ignores a false from launchUrl and gives no feedback when there is no identity or the key is not hex. Codex P2, open.
  • Rating not locale-formatted. attestation.rating is shown raw (4.87 in Spanish), which breaks DS-L10N-3 (MUST). order_list_item.dart already uses NumberFormat('0.##', locale).
  • No plural forms. reputationFigures has no ICU plurals: "1 ratings received", "trading for 1 days".
  • No living contract. specs/004-mostro-p2p-client/contracts/reputation.md does not describe the import flow. Codex P1, open; same finding as on #675.
  • Privacy mode. The screen does not check it, so _openBot sends the identity key to Telegram and lnp2pBot even though the import will be refused. Mitigated by #677, not by this screen.
  • Tests:
    • nothing covers the pending-attestation prefill;
    • nothing covers the stale state in blocking finding 1;
    • the DS-A11Y-4 test (320 dp, 2× text, German) that a new screen needs is missing. The behaviour holds in my probe, but no test pins it.
  • Noise: an unrelated formatting change to the lockEscrow route in app_routes.dart.

@grunch
grunch force-pushed the feat/reputation-transfer-core branch from 111a167 to ac91f6b Compare October 14, 2026 19:57
Base automatically changed from feat/reputation-transfer-core to main October 14, 2026 20:07
grunch and others added 8 commits October 14, 2026 17:12
Phase 5b, PR 5b.2 of the reputation portability plan (#674). A new
Import reputation screen (/import_reputation):

- "Get it from lnp2pBot" opens t.me/lnp2pbot?start=rep_<identity>, the
  identity as 43 base64url characters (Telegram caps the parameter).
- The user pastes what the bot sent, or finds an attestation exported
  from another Mostro already filled in; the Rust core verifies it and
  the screen shows its figures before importing them into the node.
- Refusals, local or from the node, are localized
  (reputation_errors.dart), in every ARB.

ReputationApi wraps the bridge calls so the screen is tested without
the Rust core.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrKanPgEfEBiEo3snHz4WZ
The Design guide check refused the theme-default Material buttons and
the paste field's v1 decoration (DS-CMP-17, DS-CMP-19). The buttons
now come from reputation_controls.dart, styled like the NWC screen's
(lime primary and outlined secondary, radius 16), and the field sets
its enabled and focused borders and filled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrKanPgEfEBiEo3snHz4WZ
check_reputation_import runs every check import_reputation makes before it
asks the node (the node imports, privacy mode is off, the attestation
verifies, names the identity and comes from a trusted issuer) without
sending anything, so the import screen can refuse on Check instead of at
the irreversible last step. Both share one helper; import_reputation's
behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jssAfzZj1LeWCnx7MHu2d
…e up front

- Editing the text after Check withdraws the checked attestation (and an
  answer to a text that changed meanwhile is dropped), so Import only ever
  sends the attestation in the field.
- Check runs check_reputation_import: an untrusted issuer or an attestation
  for another identity is refused before the user commits to the import.
- A node that does not import, or full privacy mode, is said up front, and
  the bot button, the field and Check are disabled: the identity never goes
  to Telegram for an import that would be refused.
- A link that could not be opened says so, with what to do by hand; the
  launcher sits behind ReputationApi so tests reach it.
- The rating is formatted for the locale (DS-L10N-3), and the figures use
  ICU plurals in all six languages. The new keys carry descriptions.
- Tests: pending prefill, stale state, local refusal, unsupported node,
  privacy mode, failed launch, locale, singular, and DS-A11Y-4 (German,
  320 dp, 2× text, both themes).
- The reputation automation ids get their own section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jssAfzZj1LeWCnx7MHu2d
@grunch
grunch force-pushed the feat/reputation-import-ui branch from 0a49c13 to 8844a09 Compare October 14, 2026 20:31
@grunch

grunch commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

@Catrya, following up on this review. The code findings are addressed at 8844a094, including fixes already present in commits after the reviewed 2615478f. The live end-to-end validation in blocking item 4 remains outstanding.

Disposition of the blocking findings:

  1. Stale attestation: editing the input clears the checked state; delayed Check results are discarded if the text changed. Input is also disabled while Import is in flight. Regression tests cover these cases.
  2. Missing npub: the screen now displays the full, selectable npub next to the bot handoff. Rust encodes it from the same identity used in the URL, with a round-trip test. If the identity changes during the handoff, the new npub is displayed and another press is required before opening Telegram.
  3. Capability and local validation: unsupported nodes disable the handoff and import flow up front. Check runs the shared Rust issuer and destination validation. Privacy and identity reads fail closed; the handoff rereads identity, privacy and node support before launching.
  4. End-to-end run: still pending. I have not performed a real lnp2pBot account binding and import against a Mostro daemon, or captured device screenshots of that flow. Widget fixture renders are not evidence of this integration. The PR description records this limitation.
  5. Conflicts and dependency: rebased onto main with feat(reputation): export and import reputation in the core #675 merged. Source conflicts were resolved and FRB/localization outputs regenerated. The branch retains current main's registry dependency, mostro-core 0.17.3, with no deleted-branch pin. GitHub reports no merge conflicts.

The non-blocking code findings are also addressed: launch failures and missing/invalid identity produce localized feedback; ratings, counts and days use locale formatting with ICU plurals; the complete import flow is documented in the living contracts; privacy is guarded within this screen; pending-prefill, stale-state and German 320 dp / 2× text tests are present; and the unrelated lockEscrow formatting change is removed. The three corresponding inline threads have been resolved.

Validation: 30 focused reputation tests passed on the final changes; Rust tests passed with 1,280 passed and 30 ignored. All five jobs in the CI run for this head passed: Rust, Flutter, design guide, web build/smoke test, and iOS build without codesigning.

Please re-review the code changes when convenient. Item 4 remains open; I am not claiming the live integration has been verified.

@grunch
grunch merged commit fdacae0 into main Oct 14, 2026
6 checks passed
@grunch
grunch deleted the feat/reputation-import-ui branch October 14, 2026 21:02
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.

2 participants