Skip to content

feat: add Sonar-compatible sticker support#875

Open
vincenzopalazzo wants to merge 9 commits into
marmot-protocol:masterfrom
vincenzopalazzo:codex/sonar-stickers
Open

feat: add Sonar-compatible sticker support#875
vincenzopalazzo wants to merge 9 commits into
marmot-protocol:masterfrom
vincenzopalazzo:codex/sonar-stickers

Conversation

@vincenzopalazzo

@vincenzopalazzo vincenzopalazzo commented Jul 15, 2026

Copy link
Copy Markdown

Summary

  • integrate the pinned sonar-stickers SDK for NIP-30031 packs, kind-10031 installed lists, and exact kind-9 sticker references
  • persist validated pack/install/outbox projections in the per-account encrypted SQLite database
  • add bounded public relay and Blossom flows, Signal pack import, typed app/runtime projections, and UniFFI bindings

Security and privacy

  • exact hash, MIME, dimension, animation-frame, redirect, DNS/IP, and byte-size validation
  • trusted Sonar/Signal URL origins only; relay hints are ignored
  • Signal pack keys are zeroized and never persisted
  • external-signer Signal import is explicitly gated to avoid mass signer prompts

Verification

  • cargo test -p marmot-app -p marmot-uniffi -p storage-sqlite
  • cargo clippy -p marmot-app -p marmot-uniffi -p storage-sqlite --all-targets -- -D warnings

Open in Stage

Summary by CodeRabbit

  • New Features

    • Added Sonar-compatible sticker packs: discovery, search, sync, install/uninstall, and sticker-asset retrieval.
    • Added Signal sticker-pack import with strict validation.
    • Added sticker sending in chats and surfaced sticker references across messages, notifications, previews, timelines, and UniFFI APIs.
    • Added public relay fetch/publish helpers and sticker-related persistence (new storage migrations).
  • Bug Fixes

    • Improved sticker intent handling, authorization, notification triggers, and read-marker error reporting.
    • Hardened Blossom blob fetching with configurable byte limits.
  • Tests

    • Added sticker intent/tag coverage and expanded sticker-related verification.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@vincenzopalazzo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 76b204f8-cc06-490b-b0a3-e4689dab5193

📥 Commits

Reviewing files that changed from the base of the PR and between cb64f17 and e0e12be.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • Cargo.toml
  • crates/marmot-app/Cargo.toml
  • crates/marmot-app/src/client/audit.rs
  • crates/marmot-app/src/client/mod.rs
  • crates/marmot-app/src/client/projection.rs
  • crates/marmot-app/src/client/push.rs
  • crates/marmot-app/src/error.rs
  • crates/marmot-app/src/lib.rs
  • crates/marmot-app/src/media/blossom.rs
  • crates/marmot-app/src/media/mod.rs
  • crates/marmot-app/src/messages/intents.rs
  • crates/marmot-app/src/notifications.rs
  • crates/marmot-app/src/relay_plane/mod.rs
  • crates/marmot-app/src/runtime/mod.rs
  • crates/marmot-app/src/stickers.rs
  • crates/marmot-app/src/tests.rs
  • crates/marmot-uniffi/src/commands/message.rs
  • crates/marmot-uniffi/src/commands/mod.rs
  • crates/marmot-uniffi/src/commands/sticker.rs
  • crates/marmot-uniffi/src/conversions/chat_list.rs
  • crates/marmot-uniffi/src/conversions/message.rs
  • crates/marmot-uniffi/src/conversions/mod.rs
  • crates/marmot-uniffi/src/conversions/notification.rs
  • crates/marmot-uniffi/src/conversions/sticker.rs
  • crates/marmot-uniffi/src/conversions/timeline.rs
  • crates/marmot-uniffi/src/errors.rs
  • crates/marmot-uniffi/src/lib.rs
  • crates/storage-sqlite/src/chat_list.rs
  • crates/storage-sqlite/src/lib.rs
  • crates/storage-sqlite/src/migrations.rs
  • crates/storage-sqlite/src/migrations/0027_sonar_stickers.rs
  • crates/storage-sqlite/src/migrations/0028_sticker_asset_history.rs
  • crates/storage-sqlite/src/stickers.rs
  • crates/storage-sqlite/src/timeline.rs

Walkthrough

Adds Sonar sticker-pack storage, validation, relay synchronization, Signal import, sticker messaging, chat projections, and UniFFI APIs. The workspace version advances to 0.10.0, with new typed sticker data and error mappings across application layers.

Changes

Sonar sticker integration

Layer / File(s) Summary
Sticker contracts and persistence
Cargo.toml, crates/storage-sqlite/..., crates/marmot-app/src/stickers.rs
Adds sticker domain types, SQLite migrations and storage, installed-pack rebasing, outbox persistence, and tag-aware timeline/chat-list projections.
Application sticker workflows
crates/marmot-app/src/stickers.rs, crates/marmot-app/src/relay_plane/..., crates/marmot-app/src/media/...
Implements pack listing, fetching, installation, synchronization, Signal import, public relay operations, Blossom asset validation, image inspection, signing, and outbox handling.
Sticker messaging and projections
crates/marmot-app/src/messages/..., crates/marmot-app/src/client/..., crates/marmot-app/src/runtime/..., crates/marmot-app/src/notifications.rs
Adds sticker intents, authorization, event tags, audit and notification handling, typed errors, runtime sending, and chat-list refreshes.
UniFFI API and records
crates/marmot-uniffi/...
Exposes sticker commands, records, conversions, message metadata, notification metadata, typed errors, and sticker sending through UniFFI.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding Sonar-compatible sticker support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@stage-review

stage-review Bot commented Jul 15, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai 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.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/storage-sqlite/src/timeline.rs (1)

797-805: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle NULL values when parsing tags_json.

If tags_json in the message_timeline table can be NULL (e.g., for rows created before the schema migration), using row.get::<_, String> will return a rusqlite::Error::InvalidColumnType and fail the query. Consider reading it as an Option<String>.

🐛 Proposed fix
                 Ok((
                     row.get::<_, String>(0)?,
                     row.get::<_, String>(1)?,
                     row.get::<_, String>(2)?,
                     row.get::<_, i64>(3)?,
-                    row.get::<_, String>(4)?,
+                    row.get::<_, Option<String>>(4)?,
                     row.get::<_, i64>(5)?,
                     row.get::<_, i64>(6)?,
                 ))
🤖 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 `@crates/storage-sqlite/src/timeline.rs` around lines 797 - 805, Update the
row-mapping tuple in the timeline query to read the nullable tags_json column as
Option<String> instead of String, preserving successful parsing for existing
non-NULL values and allowing legacy NULL rows without InvalidColumnType errors.
🤖 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 `@crates/marmot-app/src/relay_plane/mod.rs`:
- Around line 381-393: Update the relay connection loop in fetch_public_events
so failures from add_relay, connection timeout, or connect_relay are handled per
relay without propagating via ?. Log or otherwise record each individual
failure, then continue iterating through relay_urls so successfully connected
relays can still be used for fetching.
- Around line 381-393: Update the relay connection loop in fetch_public_events
so failures from add_relay, connection timeout, or connect_relay do not
propagate through ?. Handle each relay independently, optionally logging the
failure, and continue processing the remaining relay_urls so fetching proceeds
with all successfully connected relays.

In `@crates/marmot-app/src/stickers.rs`:
- Around line 1030-1048: Update the PNG parsing loop around the acTL handling to
count each fcTL chunk as an actual APNG frame, enforce the existing frame limit
against that count, and require exactly one valid acTL declaration whose frame
count matches the observed fcTL total before accepting the image. Do not rely
solely on the declared frames value.
- Around line 459-528: Update the sticker import flow surrounding the upload
loop and pack publication to persist a staged import intent before the first
external side effect. Advance that intent after each successful upload, signing,
publication, and install-operation step, and add compensation or resumable
recovery for failures after any step has completed so no orphaned assets or
published packs remain without durable state.
- Around line 239-247: Update the sticker listing flow around
desired_installed_sticker_packs and sticker_packs so installed_only is evaluated
against the desired installed coordinate set rather than the remote base
projection. Pass that desired set into storage filtering, while preserving the
existing search behavior and applying limit semantics to the resulting
desired-state-filtered packs.
- Around line 324-325: In the sync flow around sync_sticker_packs, call
refresh_installed_base(self, &context) before flush_sticker_outbox(self,
&context). Ensure the outbox replay and resulting publication are regenerated
from the refreshed remote winner so pending kind-10031 operations are not
cleared against a stale local base.
- Around line 454-456: In the imported sticker loop, add a
MAX_STICKER_ASSET_BYTES validation for imported_sticker.bytes before
inspect_image and upload processing. Reject oversized sticker data using the
existing error-handling pattern, while preserving the current inspection and
upload flow for buffers within the limit.

In `@crates/storage-sqlite/src/chat_list.rs`:
- Around line 902-904: Handle nullable tags_json consistently across
crates/storage-sqlite/src/chat_list.rs:902-904,
crates/storage-sqlite/src/timeline.rs:797-805, and
crates/storage-sqlite/src/timeline.rs:2418-2420: read column 4 as
Option<String>, preserve the row tuple mapping at timeline.rs:797-805, and make
the chat-list and timeline tag parsing fall back to an empty vector when the
value is None or invalid JSON.
- Around line 902-904: Update the tags parsing in the row-conversion function
around the tags field to read tags_json as an Option<String>, then fall back to
the same default used by chat_list_row_from_row when it is NULL before
deserializing. Preserve the existing FromSqlConversionFailure mapping for
invalid JSON.

In `@crates/storage-sqlite/src/timeline.rs`:
- Around line 2418-2420: Update the tags parsing in the row-mapping code around
the tags field to read the column as Option<String> instead of String, then fall
back to the existing default tags value when it is NULL before deserializing.
Preserve the current serde_json error-to-rusqlite conversion for present values.

---

Outside diff comments:
In `@crates/storage-sqlite/src/timeline.rs`:
- Around line 797-805: Update the row-mapping tuple in the timeline query to
read the nullable tags_json column as Option<String> instead of String,
preserving successful parsing for existing non-NULL values and allowing legacy
NULL rows without InvalidColumnType errors.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89219b8a-5074-46b1-bd4f-1b8faebd7fc2

📥 Commits

Reviewing files that changed from the base of the PR and between 363b1fe and 41e88b4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • Cargo.toml
  • crates/marmot-app/Cargo.toml
  • crates/marmot-app/src/client/audit.rs
  • crates/marmot-app/src/client/mod.rs
  • crates/marmot-app/src/client/projection.rs
  • crates/marmot-app/src/client/push.rs
  • crates/marmot-app/src/error.rs
  • crates/marmot-app/src/lib.rs
  • crates/marmot-app/src/media/blossom.rs
  • crates/marmot-app/src/media/mod.rs
  • crates/marmot-app/src/messages/intents.rs
  • crates/marmot-app/src/notifications.rs
  • crates/marmot-app/src/relay_plane/mod.rs
  • crates/marmot-app/src/runtime/mod.rs
  • crates/marmot-app/src/stickers.rs
  • crates/marmot-app/src/tests.rs
  • crates/marmot-uniffi/src/commands/message.rs
  • crates/marmot-uniffi/src/commands/mod.rs
  • crates/marmot-uniffi/src/commands/sticker.rs
  • crates/marmot-uniffi/src/conversions/chat_list.rs
  • crates/marmot-uniffi/src/conversions/message.rs
  • crates/marmot-uniffi/src/conversions/mod.rs
  • crates/marmot-uniffi/src/conversions/notification.rs
  • crates/marmot-uniffi/src/conversions/sticker.rs
  • crates/marmot-uniffi/src/conversions/timeline.rs
  • crates/marmot-uniffi/src/errors.rs
  • crates/marmot-uniffi/src/lib.rs
  • crates/marmot-uniffi/tests/smoke.rs
  • crates/storage-sqlite/src/chat_list.rs
  • crates/storage-sqlite/src/lib.rs
  • crates/storage-sqlite/src/migrations.rs
  • crates/storage-sqlite/src/migrations/0026_sonar_stickers.rs
  • crates/storage-sqlite/src/stickers.rs
  • crates/storage-sqlite/src/timeline.rs

Comment thread crates/marmot-app/src/relay_plane/mod.rs Outdated
Comment thread crates/marmot-app/src/stickers.rs
Comment thread crates/marmot-app/src/stickers.rs Outdated
Comment thread crates/marmot-app/src/stickers.rs
Comment thread crates/marmot-app/src/stickers.rs
Comment thread crates/marmot-app/src/stickers.rs
Comment thread crates/storage-sqlite/src/chat_list.rs
Comment thread crates/storage-sqlite/src/timeline.rs

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/storage-sqlite/src/stickers.rs (1)

180-192: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape SQL LIKE metacharacters to preserve literal search semantics.

The installed path uses literal contains, while this path treats % and _ as wildcards. The same query therefore returns different matches depending on installed_only.

Proposed fix
-                let pattern = format!("%{}%", search.to_ascii_lowercase());
+                let escaped = search
+                    .replace('!', "!!")
+                    .replace('%', "!%")
+                    .replace('_', "!_");
+                let pattern = format!("%{escaped}%");
...
-                         WHERE lower(title) LIKE ?1
-                            OR lower(COALESCE(description, '')) LIKE ?1
+                         WHERE lower(title) LIKE ?1 ESCAPE '!'
+                            OR lower(COALESCE(description, '')) LIKE ?1 ESCAPE '!'
🤖 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 `@crates/storage-sqlite/src/stickers.rs` around lines 180 - 192, Update the
searched branch of the sticker-pack lookup around the search pattern and SQL in
the coordinates query to escape LIKE metacharacters (`%`, `_`, and the escape
character) before binding the value, then add the matching ESCAPE clause.
Preserve case-insensitive contains semantics so installed and non-installed
searches return equivalent literal matches.
🤖 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.

Outside diff comments:
In `@crates/storage-sqlite/src/stickers.rs`:
- Around line 180-192: Update the searched branch of the sticker-pack lookup
around the search pattern and SQL in the coordinates query to escape LIKE
metacharacters (`%`, `_`, and the escape character) before binding the value,
then add the matching ESCAPE clause. Preserve case-insensitive contains
semantics so installed and non-installed searches return equivalent literal
matches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02f27817-87f0-483f-b20b-9ade3aa669c6

📥 Commits

Reviewing files that changed from the base of the PR and between 41e88b4 and e3c8068.

📒 Files selected for processing (3)
  • crates/marmot-app/src/relay_plane/mod.rs
  • crates/marmot-app/src/stickers.rs
  • crates/storage-sqlite/src/stickers.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/marmot-app/src/relay_plane/mod.rs
  • crates/marmot-app/src/stickers.rs

@vincenzopalazzo

Copy link
Copy Markdown
Author

Addressed the remaining non-inline CodeRabbit feedback in 9b8f3cb: sticker-pack searches now escape SQLite LIKE metacharacters (%, _, and !) and include a regression test proving literal semantics. The later Grok closure review also found and drove the cached/offline historical-reference fix in 61feace; its final verdict was No findings. Full storage-sqlite and marmot-app suites, clippy with warnings denied, and formatting are green.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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.

1 participant