Conversation
In map_error_response, the BOM-not-found branch was gated on the JSON error text containing "bom". Since RequestTarget.bom_id is only set for GET /api/boms/:bomId — a request that is neither board- nor order-scoped — a 404 with an empty, non-JSON, or differently worded body fell through to the order branch and claimed the order id was not found, even though `pcb order bom` had already fetched that order successfully. Report BOM-not-found unconditionally whenever the failed request targeted a BOM id. Flagged by Cursor Bugbot on #926.
Introduce `pcb order list|show|bom`, backed by existing authenticated API endpoints (reusing the pcb-diode-api client + bearer-token auth). No new backend routes. - Board identity resolves from `workspace.repository` (code.diode.computer/demo/b/DM0002 -> workspace demo, board DM0002), overridable with --workspace/--board; clear error when run outside a workspace with no flags. - `order list` -> GET /api/boards/:workspace/:name/orders - `order show` -> GET /api/boards/:workspace/:name/orders/:orderId - `order bom` -> fetch order, then GET /api/boms/:bomId joined client-side with GET .../orders/:orderId/selections. Emits one row per BOM line with design entry, candidate offers, and the effective selection (order_override > default > none) plus selectedMpn/selectedManufacturer. --mismatches-only filters lines whose selected MPN differs from the design MPN under the backend's normalizeBomLookupMpn normalization. - All subcommands support -f table|json (table default); table stays compact, JSON carries full offer detail. - Uniform error handling: 401/403 -> prompt `pcb auth login`, 404 -> distinguish unknown board vs unknown order, network failures -> single-line error with non-zero exit. - Tests for board-identity resolution, selection-join precedence, --mismatches-only normalization edge cases, and JSON snapshots against mocked API responses. Docs + CLI help updated. Order-mutating subcommands (create, select) intentionally out of scope.
In map_error_response, the BOM-not-found branch was gated on the JSON error text containing "bom". Since RequestTarget.bom_id is only set for GET /api/boms/:bomId — a request that is neither board- nor order-scoped — a 404 with an empty, non-JSON, or differently worded body fell through to the order branch and claimed the order id was not found, even though `pcb order bom` had already fetched that order successfully. Report BOM-not-found unconditionally whenever the failed request targeted a BOM id. Flagged by Cursor Bugbot on #926.
Amp-Thread-ID: https://ampcode.com/threads/T-019fd3e7-f9df-721b-b321-d288ee240a9e Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fd3e7-f9df-721b-b321-d288ee240a9e Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019fd3e7-f9df-721b-b321-d288ee240a9e Co-authored-by: Amp <amp@ampcode.com>
| // Resolve the selected offer (if any) to convenience MPN/manufacturer. | ||
| let selected_offer = selected_offer_id | ||
| .as_deref() | ||
| .and_then(|id| line.offers.iter().find(|o| o.id == id)); |
There was a problem hiding this comment.
🟡 Order lines whose chosen part cannot be looked up vanish from the mismatch report
A line whose chosen part cannot be found in its own list of quoted parts is treated as having no chosen part name (line.offers.iter().find(...) at crates/pcb-diode-api/src/order.rs:385-387), so pcb order bom --mismatches-only silently drops it instead of showing that the order picked something unresolvable.
Impact: Users auditing an order for wrong parts can miss lines where the order points at a part that is no longer offered.
Why the unresolved selection is silently swallowed
build_order_bom_rows resolves selected_offer_id (either the order override from the selections map or the line default) against line.offers. When the referenced offer id is absent from offers (stale selection, offers list trimmed/paginated by the backend, or a default id pointing at a removed offer), selected_mpn/selected_manufacturer stay None even though selection_source is OrderOverride/Default.
OrderBomRow::is_mpn_mismatch (crates/pcb-diode-api/src/order.rs:336-343) returns false whenever selected_mpn is None, so rows.retain(OrderBomRow::is_mpn_mismatch) in crates/pcb-diode-api/src/order.rs:1022 removes such rows entirely. In table mode the row would also show — under "Selected MPN" while claiming order_override, which is contradictory.
A safer behavior is to treat an unresolvable selection as a reportable condition (e.g. keep it under --mismatches-only, or mark it explicitly) rather than as "no selection".
Prompt for agents
In crates/pcb-diode-api/src/order.rs, build_order_bom_rows resolves the effective selected offer id against line.offers; when the id is not present in that list (stale or trimmed offers), selected_mpn/selected_manufacturer end up None while selection_source still says order_override/default. OrderBomRow::is_mpn_mismatch then returns false for such rows, so `pcb order bom --mismatches-only` silently hides lines whose selection cannot be resolved, and table output shows an em dash for the selected MPN next to an 'order_override' label. Consider distinguishing 'no selection' from 'selection could not be resolved' — e.g. an extra flag on OrderBomRow or an additional SelectionSource variant — and make the mismatch filter/table surface unresolved selections instead of dropping them.
Was this helpful? React with 👍 or 👎 to provide feedback.
| fn fmt_price(currency: Option<&str>, price: Option<f64>) -> String { | ||
| match price { | ||
| Some(p) => match currency { | ||
| Some(c) if !c.is_empty() => format!("{p:.2} {c}"), | ||
| _ => format!("${p:.2}"), | ||
| }, | ||
| None => NONE.into(), | ||
| } | ||
| } |
There was a problem hiding this comment.
🟡 Prices with an unknown currency are displayed as US dollars
A quoted amount whose currency the server did not report is printed with a dollar sign (format!("${p:.2}") at crates/pcb-diode-api/src/order.rs:651), so a non-USD quote can be shown to the user as if it were in US dollars.
Impact: Someone reviewing an order quote can misread the price as USD when it is in another currency.
Currency fallback in fmt_price
fmt_price (crates/pcb-diode-api/src/order.rs:647-655) formats 12.34 USD when a currency string is present, but falls back to $12.34 when currency is None/empty. Since QuoteSummary.currency is optional (crates/pcb-diode-api/src/order.rs:174-175), a quote in EUR/CNY with a missing currency field renders with a $ prefix. A neutral fallback (bare 12.34) avoids asserting a currency the API never returned.
| fn fmt_price(currency: Option<&str>, price: Option<f64>) -> String { | |
| match price { | |
| Some(p) => match currency { | |
| Some(c) if !c.is_empty() => format!("{p:.2} {c}"), | |
| _ => format!("${p:.2}"), | |
| }, | |
| None => NONE.into(), | |
| } | |
| } | |
| fn fmt_price(currency: Option<&str>, price: Option<f64>) -> String { | |
| match price { | |
| Some(p) => match currency { | |
| Some(c) if !c.is_empty() => format!("{p:.2} {c}"), | |
| _ => format!("{p:.2}"), | |
| }, | |
| None => NONE.into(), | |
| } | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Adds a read-only
pcb ordercommand group with three subcommands, all backed by existing authenticated API endpoints (reusing thepcb-diode-apiclient andpcb authbearer-token infra used bysearch/scan/preview). No new backend routes.pcb order list→GET /api/boards/:workspace/:name/orders— renders id, name, status, quantity, release version, provider, created date.pcb order show <order-id>→GET /api/boards/:workspace/:name/orders/:orderId— renders the full order (release id/version, bom id, quote summary if present, timeline, shipping location id).pcb order bom <order-id>→ fetches the order, thenGET /api/boms/:bomId(fails clearly with "order has no BOM" ifbomIdis null) andGET /api/boards/:workspace/:name/orders/:orderId/selections, joined client-side.Board identity
Resolves from
workspace.repository(e.g.code.diode.computer/demo/b/DM0002→ workspacedemo, boardDM0002, as surfaced bypcb info), overridable with--workspace <slug>/--board <name>. Errors clearly when run outside a workspace with no flags.order bomjoinOne row per BOM line: bomLineId, design entry (mpn, manufacturer, package, value, designator/path, declared alternatives), match status, candidate offers (id, mpn, manufacturer, distributor, stock, price), and the effective selection:
{bomLineId → offerId}) if present, elseselectedOfferId, elsesurfaced as
selectionSource(order_override|default|none) plus convenienceselectedMpn/selectedManufacturer.--mismatches-onlyfilters to lines where the selected MPN differs from the design MPN under the backend'snormalizeBomLookupMpnnormalization (trim, uppercase, strip everything exceptA–Z,0–9,.). Lines with no selection or no design MPN are excluded. Table mode stays compact (designator, design MPN, selected MPN, match, selection source); full offer detail is reserved for-f json.Output & errors
-f table|json(table default), followingpcb bomconventions.pcb auth login; 404 → distinguishes unknown board from unknown order id; network failures → single-line error, non-zero exit.Tests
--mismatches-onlynormalization edge cases (case, punctuation, missing MPNs).Out of scope
Order-mutating subcommands (
create,select) intentionally land in a separate PR.Note
Low Risk
Read-only GETs against existing APIs; no mutations or auth changes beyond reusing
pcb authtokens.Overview
Adds a read-only
pcb orderCLI group (list,show,bom) wired throughpcb-diode-apiand the existing authenticated Diode API client—no new backend routes.Board targeting resolves from
pcb.toml(workspace.name,workspace.repositorywith/b/parsing) or--workspace/--board. Subcommands support-f table|json; table output uses comfy-table.pcb order bomloads the order BOM and order selections, joins them client-side with precedence order override → line default → none, and exposesselectionSourceplus selected MPN fields.--mismatches-onlyfilters lines where design vs selected MPN differ after backend-aligned MPN normalization.Docs and changelog are updated; tests cover identity resolution, join logic, mismatch rules, and JSON snapshots.
Reviewed by Cursor Bugbot for commit 03c31d9. Bugbot is set up for automated code reviews on this repo. Configure here.