Back to feed

sledtools/pika branch #77

pika-git-inbox-store-1

Extract branch inbox persistence into inbox_store

Target branch: master

Merge Commit: 8e0ee9b37c5b5f6a24e86b4186b27d696f854470

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #99 success

2 passed

head b587f2ce72ef29e7130960326431c3b4bb6e4f92 · queued 2026-03-26 00:10:15 · 2 lane(s)

queued 1m 38s · ran 23s

check-notifications · success check-agent-contracts · success

Summary

This branch extracts all branch-inbox persistence logic from the monolithic storage.rs file into a dedicated inbox_store.rs module within the pika-news crate. The refactor moves 12 public methods on Store, 2 private helper functions, and the BranchInboxStateRow struct without changing any behavior or SQL queries. The only functional change is widening the visibility of artifact_user_state_rank from private to pub(crate) so the new module can reuse the shared ranking function, and converting merge_branch_inbox_state_alias from a file-private function to a pub(crate) function imported back into storage.rs.

Tutorial Steps

Create the new `inbox_store.rs` module

Intent: Provide a dedicated file to house all branch-inbox persistence logic, reducing the size and cognitive load of `storage.rs`.

Affected files: crates/pika-news/src/inbox_store.rs

Evidence
@@ -0,0 +1,634 @@
+use std::cmp::{max, min};
+
+use anyhow::Context;
+use rusqlite::{params, Connection, OptionalExtension};
+
+use crate::storage::{artifact_user_state_rank, InboxItem, InboxReviewContext, Store};

A new file crates/pika-news/src/inbox_store.rs is created with 634 lines. It imports the shared types InboxItem, InboxReviewContext, and Store from crate::storage, as well as the ranking helper artifact_user_state_rank. All branch-inbox logic will live here as impl Store blocks and module-private helpers.

The module is structured as:

  1. An impl Store block containing all public branch-inbox methods.
  2. A BranchInboxStateRow struct (moved from storage.rs).
  3. A pub(crate) function merge_branch_inbox_state_alias (moved from storage.rs).
  4. Two private helper functions: merge_branch_inbox_state_rows and insert_branch_inbox_rows_for_artifact.

Move `populate_branch_inbox` and `backfill_branch_inbox_for_npub`

Intent: Relocate the two write-path methods that insert or refresh branch inbox rows for recipients.

Affected files: crates/pika-news/src/inbox_store.rs, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,634 @@
+    pub fn populate_branch_inbox(
+        &self,
+        artifact_id: i64,
+        npubs: &[String],
+    ) -> anyhow::Result<usize> {
@@ -0,0 +1,634 @@
+    pub fn backfill_branch_inbox_for_npub(&self, npub: &str) -> anyhow::Result<usize> {
@@ -1128,350 +1130,6 @@
-    pub fn populate_branch_inbox(
-    pub fn backfill_branch_inbox_for_npub(

populate_branch_inbox inserts inbox rows for a list of npubs when a new branch artifact becomes ready. backfill_branch_inbox_for_npub scans all current ready artifacts and creates inbox entries for a single newly-added npub. Both methods are moved verbatim, including their transactional wrappers and delegation to the private insert_branch_inbox_rows_for_artifact helper.

Move branch inbox query methods

Intent: Relocate the read-path methods that list, count, and provide review context for branch inbox items.

Affected files: crates/pika-news/src/inbox_store.rs, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,634 @@
+    pub fn list_branch_inbox(
+    pub fn branch_inbox_count(
+    pub fn branch_inbox_total(
+    pub fn branch_inbox_review_context(
@@ -1128,350 +1130,6 @@
-    pub fn list_branch_inbox(
-    pub fn branch_inbox_count(
-    pub fn branch_inbox_total(
-    pub fn branch_inbox_review_context(

Four query methods are moved:

  • list_branch_inbox — paginated listing ordered by needs_review DESC, updated_at DESC, joining branch_records, repos, and latest branch_artifact_versions.
  • branch_inbox_count — returns the count of items that still need review (artifact mismatch filter).
  • branch_inbox_total — returns the total inbox item count regardless of review state.
  • branch_inbox_review_context — computes prev/next neighbors and position/total for keyboard-driven navigation.

All SQL and mapping logic is preserved exactly.

Move inbox state mutation methods

Intent: Relocate the methods that mark items reviewed, dismiss specific items, and dismiss all items.

Affected files: crates/pika-news/src/inbox_store.rs, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,634 @@
+    pub fn mark_branch_inbox_reviewed(
+    pub fn dismiss_branch_inbox_items(
+    pub fn dismiss_all_branch_inbox(
@@ -1128,350 +1130,6 @@
-    pub fn mark_branch_inbox_reviewed(
-    pub fn dismiss_branch_inbox_items(
-    pub fn dismiss_all_branch_inbox(

Three mutation methods are moved:

  • mark_branch_inbox_reviewed — sets last_reviewed_artifact_id = artifact_id and timestamps the review.
  • dismiss_branch_inbox_items — dismisses a batch of branch IDs using dynamic SQL placeholders with Box<dyn ToSql> for safe parameterization.
  • dismiss_all_branch_inbox — bulk-dismisses every inbox item for a given npub.

The dismiss-items method's dynamic placeholder construction (?2, ?3, ...) is carried over unchanged.

Move `list_branch_inbox_allowlist_npubs`

Intent: Relocate the allowlist query that determines which npubs should receive branch inbox entries.

Affected files: crates/pika-news/src/inbox_store.rs, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,634 @@
+    pub fn list_branch_inbox_allowlist_npubs(&self) -> anyhow::Result<Vec<String>> {
@@ -1585,29 +1243,6 @@
-    pub fn list_branch_inbox_allowlist_npubs(&self) -> anyhow::Result<Vec<String>> {

This method queries the chat_allowlist table for npubs where active = 1 OR can_forge_write = 1. It was previously located among the chat-allowlist methods in storage.rs but logically belongs with the branch inbox population logic, since its sole purpose is to determine inbox recipients.

Move `BranchInboxStateRow` struct and alias-merge functions

Intent: Relocate the internal data structure and merge logic used during npub alias consolidation.

Affected files: crates/pika-news/src/inbox_store.rs, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,634 @@
+#[derive(Debug, Clone)]
+struct BranchInboxStateRow {
+pub(crate) fn merge_branch_inbox_state_alias(
+fn merge_branch_inbox_state_rows(
@@ -2085,19 +1720,6 @@
-#[derive(Debug, Clone)]
-struct BranchInboxStateRow {
@@ -2232,183 +1854,6 @@
-fn merge_branch_inbox_state_alias(
-fn merge_branch_inbox_state_rows(

The BranchInboxStateRow struct and two functions are moved:

  • BranchInboxStateRow — a module-private struct with 9 fields tracking per-npub branch inbox state.
  • merge_branch_inbox_state_alias — upgraded from fn to pub(crate) fn so storage.rs can import and call it. Handles re-keying or merging inbox rows when two npubs are discovered to be the same user.
  • merge_branch_inbox_state_rows — the pure merge logic comparing two rows by artifact ID and state rank, using artifact_user_state_rank from the parent module.

The merge strategy: when artifact IDs differ, the row with the most recent updated_at wins; when they match, the higher-ranked state wins (dismissed > inbox > other), with ties broken by updated_at.

Move `insert_branch_inbox_rows_for_artifact` helper

Intent: Relocate the core insertion helper that both `populate_branch_inbox` and `backfill_branch_inbox_for_npub` delegate to.

Affected files: crates/pika-news/src/inbox_store.rs, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,634 @@
+fn insert_branch_inbox_rows_for_artifact(
+    conn: &Connection,
+    artifact_id: i64,
+    npubs: &[String],
+) -> anyhow::Result<usize> {
@@ -2493,75 +1938,6 @@
-fn insert_branch_inbox_rows_for_artifact(

This private function implements the three-way logic for inserting branch inbox rows:

  1. Same artifact already exists — no-op (skip).
  2. Different artifact exists — update to new artifact, reset state to 'inbox' with reason 'new_commits', clear dismissed_at.
  3. No existing row — insert a fresh inbox entry.

It first validates that the given artifact_id corresponds to a ready, current branch_artifact_versions row and resolves the branch_id before iterating over recipients.

Widen visibility of `artifact_user_state_rank`

Intent: Allow the new `inbox_store` module to call the shared state-ranking function without duplicating it.

Affected files: crates/pika-news/src/storage.rs

Evidence
@@ -2223,7 +1845,7 @@
-fn artifact_user_state_rank(state: &str) -> u8 {
+pub(crate) fn artifact_user_state_rank(state: &str) -> u8 {

The only non-move change in storage.rs: artifact_user_state_rank is promoted from fn (file-private) to pub(crate) fn. This function maps state strings to numeric ranks (dismissed = 3, inbox = 2, default = 1) and is used by both the PR inbox merge logic (still in storage.rs) and the branch inbox merge logic (now in inbox_store.rs).

Register the module and wire the import

Intent: Make the new module visible to the compiler and ensure `storage.rs` can call the extracted alias-merge function.

Affected files: crates/pika-news/src/main.rs, crates/pika-news/src/storage.rs

Evidence
@@ -9,6 +9,7 @@
+mod inbox_store;
@@ -11,6 +11,8 @@
+use crate::inbox_store::merge_branch_inbox_state_alias;

Two wiring changes complete the extraction:

  1. main.rs — adds mod inbox_store; to the module declarations, alphabetically between github and live.
  2. storage.rs — adds use crate::inbox_store::merge_branch_inbox_state_alias; so the existing alias-consolidation transaction in storage.rs can continue to call this function after it was moved out.

No other callers needed updating because all public Store methods remain on the same Store type — Rust resolves impl blocks across modules transparently.

Diff