Skip to content

refactor: display all makers without filters - #8

Merged
hulxv merged 1 commit into
citadel-foss:mainfrom
hulxv:refactor/display-all-makers
Jun 3, 2026
Merged

hulxv merged 1 commit into
citadel-foss:mainfrom
hulxv:refactor/display-all-makers

Conversation

@hulxv

@hulxv hulxv commented Jun 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Switched API to track makers instead of offers, with /api/makers returning comprehensive maker information including connection state and protocol details.
    • Updated health endpoint to report maker metrics (maker_count, with_offer) instead of offer counts.
    • Enhanced web dashboard to display all known makers with their state, protocol type, and optional offer details.
  • Tests

    • Updated integration and unit tests to validate maker-focused API behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@hulxv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 45 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc4db1af-196e-4207-af01-51de06c0e86d

📥 Commits

Reviewing files that changed from the base of the PR and between 32529de and 47a20c2.

📒 Files selected for processing (7)
  • .github/workflows/docker-build.yml
  • src/server.rs
  • src/state.rs
  • src/sync.rs
  • tests/e2e.rs
  • tests/http.rs
  • web/src/App.jsx
📝 Walkthrough

Walkthrough

This PR refactors the market data API from offers-centric to makers-centric. The system now tracks all known makers (good, unresponsive, bad) with their connection state, protocol, and optional embedded offers, replacing the previous filtered view of only good makers with active offers.

Changes

Makers-Centric API Refactor

Layer / File(s) Summary
API types and store structure
src/state.rs
Introduce ApiMaker struct capturing address, state (Good/Unresponsive { retries }/Bad), protocol, timestamps, and optional offer; simplify ApiOffer by removing maker identity fields; add MakerStore holding Vec<ApiMaker> and last_sync, replacing OfferStore.
API endpoints and health metrics
src/server.rs
Register /api/makers endpoint with optional state query filtering; replace /api/offers route; update /api/health to report maker_count and with_offer instead of offer counts.
Sync logic and maker population
src/sync.rs
Refactor sync loop to build makers vector from all tracked makers (not filtered by state), compute and log maker/with-offer counts, persist complete maker list to shared store.
Backend test validation
tests/http.rs, tests/e2e.rs, .github/workflows/docker-build.yml
Update HTTP and e2e tests to verify /api/makers structure (state nesting, protocol, optional offer), validate health endpoint maker metrics, add seeded scenarios with good/unresponsive/bad makers, update Docker smoke tests.
Frontend makers display
web/src/App.jsx
Refactor to fetch /api/makers with state/protocol helpers, add State and Protocol table columns, render embedded offers within maker rows, update summary to show maker count and count of makers with offers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • citadel-tech/marketd#6: Introduced initial HTTP server with /api/offers and OfferStore that this PR refactors to makers-based API.

Suggested reviewers

  • keraliss

Poem

🐰 From offers we hop to makers true,
Tracking all their states—the tried and new!
Good, unresponsive, bad—we see them all,
No filters hiding, just makers' hall. 💰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main architectural change: shifting from displaying filtered offers to displaying all makers regardless of state.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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 and usage tips.

@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: 3

🤖 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 `@tests/http.rs`:
- Around line 101-141: Add focused tests in tests/http.rs that call the makers
endpoint with the state filter (use
guard.agent.get(&guard.url("/api/makers?state=good")), "/api/makers?state=bad",
and "/api/makers?state=unresponsive") and assert the returned array length and
contents match the expected single-kind contract: for "good" assert the returned
maker has state.kind=="good" and contains the offer/protocol fields (e.g.,
base_fee and fidelity_bond present), for "unresponsive" assert
state.kind=="unresponsive", state.retries==3 and that offer and protocol are
null, and for "bad" assert state.kind=="bad" and offer is null; reuse the
existing pattern (resp.into_json(), as_array(), indexing into arr) so tests
mirror the earlier checks around the GET /api/makers block.

In `@web/src/App.jsx`:
- Around line 51-54: The footer's lastUpdated is never cleared when makers
become empty; update the logic around data handling in App.jsx so that if
data.length === 0 you call setLastUpdated(null) (or undefined) to clear the
stale timestamp, otherwise keep the existing branch that computes latest with
Math.max and calls setLastUpdated(new Date(latest)); modify the block around the
existing setLastUpdated usage (referencing setLastUpdated and the data
map/timestamp calculation) to add the else branch that clears the state.
- Around line 148-150: The row key currently switches between bond-based
(`${bond.outpoint.txid}:${bond.outpoint.vout}`) and `m.address`, causing
remounts when bond presence changes; make the key stable by always using the
maker identifier (use `m.address`) as the primary key (optionally append bond
details but do not replace the base key) so the row keeps the same key whether
`bond` is present or not.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: b431f701-3489-4871-a07c-3c39960b484b

📥 Commits

Reviewing files that changed from the base of the PR and between 544c866 and 32529de.

📒 Files selected for processing (7)
  • .github/workflows/docker-build.yml
  • src/server.rs
  • src/state.rs
  • src/sync.rs
  • tests/e2e.rs
  • tests/http.rs
  • web/src/App.jsx

Comment thread tests/http.rs
Comment on lines 101 to 141
let resp = guard
.agent
.get(&guard.url("/api/offers"))
.get(&guard.url("/api/makers"))
.call()
.expect("GET /api/offers");
.expect("GET /api/makers");
assert_eq!(resp.status(), 200);
let offers: Value = resp.into_json().expect("offers JSON");
let arr = offers.as_array().expect("array");
assert_eq!(arr.len(), 1);
let makers: Value = resp.into_json().expect("makers JSON");
let arr = makers.as_array().expect("array");
assert_eq!(arr.len(), 3, "bad and unresponsive makers must be returned");

// Good maker
let good = &arr[0];
assert_eq!(good["address"], "127.0.0.1:6102");
assert_eq!(good["state"]["kind"], "good");
assert_eq!(good["protocol"], "taproot");
assert_eq!(good["offer"]["base_fee"], 1000);
assert_eq!(good["offer"]["fidelity_bond"]["amount"], 5_000_000);
assert_eq!(good["offer"]["fidelity_bond"]["outpoint"]["txid"], "deadbeef");

// Unresponsive maker — kind=unresponsive with a retries count.
let unresp = &arr[1];
assert_eq!(unresp["state"]["kind"], "unresponsive");
assert_eq!(unresp["state"]["retries"], 3);
assert!(unresp["offer"].is_null(), "offer must be null for unresponsive maker");
assert!(unresp["protocol"].is_null());

let o = &arr[0];
assert_eq!(o["address"], "test.onion:6102");
assert_eq!(o["base_fee"], 1000);
assert_eq!(o["fidelity_bond"]["amount"], 5_000_000);
assert_eq!(o["fidelity_bond"]["outpoint"]["txid"], "deadbeef");
assert_eq!(o["fidelity_bond"]["outpoint"]["vout"], 0);
// Bad maker — same shape, kind=bad.
let bad = &arr[2];
assert_eq!(bad["state"]["kind"], "bad");
assert!(bad["offer"].is_null());

let resp = guard
.agent
.get(&guard.url("/api/health"))
.call()
.expect("GET /api/health");
let body: Value = resp.into_json().expect("health JSON");
assert_eq!(body["offer_count"], 1);
assert_eq!(body["maker_count"], 3);
assert_eq!(body["with_offer"], 1);
assert_eq!(body["last_sync"], 1_700_000_000);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add direct coverage for /api/makers?state=... filter behavior.

Current smoke tests validate shape/counts but not the new state-filter contract; add focused assertions for good, bad, and unresponsive queries.

Suggested test addition
+    // Filtered query: good only
+    let resp = guard
+        .agent
+        .get(&guard.url("/api/makers?state=good"))
+        .call()
+        .expect("GET /api/makers?state=good");
+    let only_good: Value = resp.into_json().expect("good makers JSON");
+    assert_eq!(only_good.as_array().expect("array").len(), 1);
+    assert_eq!(only_good[0]["state"]["kind"], "good");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let resp = guard
.agent
.get(&guard.url("/api/offers"))
.get(&guard.url("/api/makers"))
.call()
.expect("GET /api/offers");
.expect("GET /api/makers");
assert_eq!(resp.status(), 200);
let offers: Value = resp.into_json().expect("offers JSON");
let arr = offers.as_array().expect("array");
assert_eq!(arr.len(), 1);
let makers: Value = resp.into_json().expect("makers JSON");
let arr = makers.as_array().expect("array");
assert_eq!(arr.len(), 3, "bad and unresponsive makers must be returned");
// Good maker
let good = &arr[0];
assert_eq!(good["address"], "127.0.0.1:6102");
assert_eq!(good["state"]["kind"], "good");
assert_eq!(good["protocol"], "taproot");
assert_eq!(good["offer"]["base_fee"], 1000);
assert_eq!(good["offer"]["fidelity_bond"]["amount"], 5_000_000);
assert_eq!(good["offer"]["fidelity_bond"]["outpoint"]["txid"], "deadbeef");
// Unresponsive maker — kind=unresponsive with a retries count.
let unresp = &arr[1];
assert_eq!(unresp["state"]["kind"], "unresponsive");
assert_eq!(unresp["state"]["retries"], 3);
assert!(unresp["offer"].is_null(), "offer must be null for unresponsive maker");
assert!(unresp["protocol"].is_null());
let o = &arr[0];
assert_eq!(o["address"], "test.onion:6102");
assert_eq!(o["base_fee"], 1000);
assert_eq!(o["fidelity_bond"]["amount"], 5_000_000);
assert_eq!(o["fidelity_bond"]["outpoint"]["txid"], "deadbeef");
assert_eq!(o["fidelity_bond"]["outpoint"]["vout"], 0);
// Bad maker — same shape, kind=bad.
let bad = &arr[2];
assert_eq!(bad["state"]["kind"], "bad");
assert!(bad["offer"].is_null());
let resp = guard
.agent
.get(&guard.url("/api/health"))
.call()
.expect("GET /api/health");
let body: Value = resp.into_json().expect("health JSON");
assert_eq!(body["offer_count"], 1);
assert_eq!(body["maker_count"], 3);
assert_eq!(body["with_offer"], 1);
assert_eq!(body["last_sync"], 1_700_000_000);
}
let resp = guard
.agent
.get(&guard.url("/api/makers"))
.call()
.expect("GET /api/makers");
assert_eq!(resp.status(), 200);
let makers: Value = resp.into_json().expect("makers JSON");
let arr = makers.as_array().expect("array");
assert_eq!(arr.len(), 3, "bad and unresponsive makers must be returned");
// Good maker
let good = &arr[0];
assert_eq!(good["address"], "127.0.0.1:6102");
assert_eq!(good["state"]["kind"], "good");
assert_eq!(good["protocol"], "taproot");
assert_eq!(good["offer"]["base_fee"], 1000);
assert_eq!(good["offer"]["fidelity_bond"]["amount"], 5_000_000);
assert_eq!(good["offer"]["fidelity_bond"]["outpoint"]["txid"], "deadbeef");
// Unresponsive maker — kind=unresponsive with a retries count.
let unresp = &arr[1];
assert_eq!(unresp["state"]["kind"], "unresponsive");
assert_eq!(unresp["state"]["retries"], 3);
assert!(unresp["offer"].is_null(), "offer must be null for unresponsive maker");
assert!(unresp["protocol"].is_null());
// Bad maker — same shape, kind=bad.
let bad = &arr[2];
assert_eq!(bad["state"]["kind"], "bad");
assert!(bad["offer"].is_null());
// Filtered query: good only
let resp = guard
.agent
.get(&guard.url("/api/makers?state=good"))
.call()
.expect("GET /api/makers?state=good");
let only_good: Value = resp.into_json().expect("good makers JSON");
assert_eq!(only_good.as_array().expect("array").len(), 1);
assert_eq!(only_good[0]["state"]["kind"], "good");
let resp = guard
.agent
.get(&guard.url("/api/health"))
.call()
.expect("GET /api/health");
let body: Value = resp.into_json().expect("health JSON");
assert_eq!(body["maker_count"], 3);
assert_eq!(body["with_offer"], 1);
assert_eq!(body["last_sync"], 1_700_000_000);
}
🤖 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 `@tests/http.rs` around lines 101 - 141, Add focused tests in tests/http.rs
that call the makers endpoint with the state filter (use
guard.agent.get(&guard.url("/api/makers?state=good")), "/api/makers?state=bad",
and "/api/makers?state=unresponsive") and assert the returned array length and
contents match the expected single-kind contract: for "good" assert the returned
maker has state.kind=="good" and contains the offer/protocol fields (e.g.,
base_fee and fidelity_bond present), for "unresponsive" assert
state.kind=="unresponsive", state.retries==3 and that offer and protocol are
null, and for "bad" assert state.kind=="bad" and offer is null; reuse the
existing pattern (resp.into_json(), as_array(), indexing into arr) so tests
mirror the earlier checks around the GET /api/makers block.

Comment thread web/src/App.jsx
Comment on lines 51 to 54
if (data.length > 0) {
const latest = Math.max(...data.map((o) => o.timestamp * 1000));
const latest = Math.max(...data.map((m) => m.timestamp * 1000));
setLastUpdated(new Date(latest));
}

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 | 🟡 Minor | ⚡ Quick win

Clear stale lastUpdated when no makers are returned.

lastUpdated is only set for non-empty data. If the list later becomes empty, the footer can still show an old update time.

Suggested fix
         if (data.length > 0) {
           const latest = Math.max(...data.map((m) => m.timestamp * 1000));
           setLastUpdated(new Date(latest));
+        } else {
+          setLastUpdated(null);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (data.length > 0) {
const latest = Math.max(...data.map((o) => o.timestamp * 1000));
const latest = Math.max(...data.map((m) => m.timestamp * 1000));
setLastUpdated(new Date(latest));
}
if (data.length > 0) {
const latest = Math.max(...data.map((m) => m.timestamp * 1000));
setLastUpdated(new Date(latest));
} else {
setLastUpdated(null);
}
🤖 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 `@web/src/App.jsx` around lines 51 - 54, The footer's lastUpdated is never
cleared when makers become empty; update the logic around data handling in
App.jsx so that if data.length === 0 you call setLastUpdated(null) (or
undefined) to clear the stale timestamp, otherwise keep the existing branch that
computes latest with Math.max and calls setLastUpdated(new Date(latest)); modify
the block around the existing setLastUpdated usage (referencing setLastUpdated
and the data map/timestamp calculation) to add the else branch that clears the
state.

Comment thread web/src/App.jsx
Comment on lines +148 to +150
const key = bond
? `${bond.outpoint.txid}:${bond.outpoint.vout}`
: m.address;

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 | 🟡 Minor | ⚡ Quick win

Use a stable key per maker row.

Lines 149-150 switch key source based on optional bond data. If a maker toggles between having an offer and not having one, the key changes and the row remounts unnecessarily.

Suggested fix
-                    const key = bond
-                      ? `${bond.outpoint.txid}:${bond.outpoint.vout}`
-                      : m.address;
+                    const key = m.address;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const key = bond
? `${bond.outpoint.txid}:${bond.outpoint.vout}`
: m.address;
const key = m.address;
🤖 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 `@web/src/App.jsx` around lines 148 - 150, The row key currently switches
between bond-based (`${bond.outpoint.txid}:${bond.outpoint.vout}`) and
`m.address`, causing remounts when bond presence changes; make the key stable by
always using the maker identifier (use `m.address`) as the primary key
(optionally append bond details but do not replace the base key) so the row
keeps the same key whether `bond` is present or not.

@hulxv
hulxv force-pushed the refactor/display-all-makers branch from 32529de to 47a20c2 Compare June 3, 2026 11:33
@hulxv
hulxv merged commit 32dd295 into citadel-foss:main Jun 3, 2026
15 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 7, 2026
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