Repository navigation
feat(cli): frame the invoice QR code for scanning on any theme - #109
Conversation
89e87ed to
bc5cc91
Compare
bc5cc91 to
afbb0f7
Compare
afbb0f7 to
d674fb9
Compare
tee8z
left a comment
There was a problem hiding this comment.
Requesting changes on d674fb9 (base #108). Framing the QR on a light background is right for dark themes. Color-on polarity is correct (dark modules are drawn as the foreground, black on white), encode is unchanged with no truncation, the QR and caption go only to stderr, and --json, --output-file and piped stdout are unchanged.
P1: PaymentData.expiration: Option<String> (src/payment.rs:244) breaks every payment wait whose payment carries an expiration. In api/openapi.json, data.expiration is oneOf[null, Expiration], and Expiration is {"type":"integer","format":"u-int32"}: the invoice's lifetime in seconds, the same unit as --expiration. When the API sends "expiration": 3600, deserializing into Option<String> fails for the whole PaymentView. from_body then falls back to unwrap_or_default(), so status, invoice, direction and id all become None. progress() stays Pending, so payments receive --qr and payments get --wait … time out on a payment that is already ready or completed. check_invoice_payment and reconcile_payment lose the view too. The tests pass only because they use RFC 3339 strings. The field is a duration, so expires_at would also read it wrongly. Keep the display-only field out of the view that drives the wait, for example as Option<Value>, and take the expiry from the invoice's own timestamp and expiry, or from created_at plus the seconds. A unit test such as PaymentView::from_body(&json!({"status":"completed","data":{"expiration":3600}})).progress(WaitTarget::Completed), which should be Reached(Completed), would pin it.
P2: the best-effort wallet read has no short limit. It uses the full --timeout (60 seconds by default) and is not bounded by the wait's deadline, so a slow wallets endpoint holds back the QR, and the deadline can pass during the read. #108 now gives its extra reads a 5-second READ_LIMIT, and the same treatment fits here.
P2: the quiet zone and the color-off claim. The frame gives a 2-module quiet zone, but the QR spec asks for 4, and on a dark theme the terminal background starts right outside it. Going to 4 costs 4 columns. With NO_COLOR, --color never or TERM=dumb on a terminal, the framed form still prints with its colors stripped, so on a dark theme the code is inverted. Yet payments.md now says that the code "scans on a dark theme too". Branch on colors_stderr() or caveat the docs, and add a test.
Nonblocking: ASCII glyph mode still draws Unicode half-blocks. In the ready path, a QR encode error now comes after the record was written to stdout. The comment at the test's "No wallet read answers here" is inaccurate, because no read is made at all. The paths where the wallet read fails or has no network are untested.
Gate and CRAP on this head are running, and I will add the results.
|
Gate results for |
d674fb9 to
7033511
Compare
|
Review-body items, in 7033511:
|
On a terminal, --qr draws the invoice black on bright white with a
two-module quiet zone, so a phone scans it on a dark theme as well as a
light one, under a caption led by the network ("● mutinynet", the dot in
the website's network color), then a bold "Scan to pay" and the amount,
memo, and expiry when the payment has them. The CLI reads the payment's
wallet for its network, since mutinynet and signet invoices share a prefix,
and falls back to the prefix. A wait that ends at ready shows the code
after the result, so it stays on screen; with --wait completed it comes
first and the status line counts down to the expiry. Redirected stderr
keeps the compact code it always had.
|
Quiet zone: per blu, it now follows the spec at four modules (8280881): two blank rows above and below, and four blank columns each side, in bright white. The module-by-module unit test checks four light modules on every side. |
7033511 to
8280881
Compare
tee8z
left a comment
There was a problem hiding this comment.
Approved 8280881 against feat/cli-look-09-balance (#108 73d4b3f). My findings are fixed:
- P1,
expiration: the display-only fields (amount,memo,expiration, andcreated_at) areOption<Value>, so no shape fails the view that drives the wait.expires_atiscreated_atplusexpirationseconds, andcreated_atis a required date-time onReceivePaymentin the contract. The new unit test is the case from my review,{"status":"completed","data":{"expiration":3600}}givingReached(Completed), and it also checks that odd shapes leave the view intact. The PTY test now sends an integer expiration. - Wallet read: bounded by
READ_LIMITor the time left before the wait's deadline, whichever is shorter. - Quiet zone: four modules, as the spec asks: two text rows above and below, which is four modules, and four columns each side. The unit test checks every side.
- Color off: the compact form, with no styles to strip, and
payments.mdsays it may read inverted on a dark theme. - Nonblocking notes:
qr::checknow fails before the record is written. The test comment is corrected.a_failed_or_networkless_wallet_read_falls_back_to_the_invoice_prefixcovers a 404 and a wallet without a network. Keeping half-blocks in ASCII mode is reasonable.
Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (185 unit, 140 integration) and python3 scripts/check-coverage.py passed. CI is green. I did not run the CRAP report for this re-review.
Stack position: 10 of 13 in the look-and-feel stack. Base:
feat/cli-look-09-balance(#108).Problem
--qrdrew the invoice in the terminal's own colors with no quiet zone, and with--qralone the full payment record printed after the code and pushed it off screen. On a dark theme, the dark modules render light, so the code is inverted, and some wallets will not scan an inverted code or one without a quiet zone. The caption,Scan to pay:, did not say what you were paying or until when.Behavior
On a terminal:
● mutinynet, with the dot in the website's network-icon color: mainnet#FF7333, mutinynet#AD44FF, signet#A31AF7, testnet#13CE66. It falls back to 256 colors, then basic colors (yellow, magenta, green), and to*in ASCII. The QR code itself stays plain black on white; colored frames, finder patterns, and logos were tried and dropped.--qron a terminal, because mutinynet and signet invoices share thelntbsprefix. The prefix is the fallback; an unknown network (such as regtest) gets no badge.--qralone (a wait that ends atready), the payment record prints first and the caption and code come last, so the code stays on screen. With--wait completed, the code comes first, because you need it while the CLI waits.--wait completed, the status line counts down to the invoice's expiry, using the countdown from feat(cli): link the login URL, bold the code, and count down to expiry #96.Scan to pay:text it always had.Dropped from the plan: redrawing the code in place to
✓ Paid. The timeline prints status lines below the code, and erasing upward would wipe them, or break when the code has scrolled off screen. The closing✓ Payment … completedline from #91 already marks the payment.Design notes
qr::showholds the rendering that used to be inpresent_invoice.render(code, framed)is the only difference between the terminal and redirected forms; it pads the unframed code with a quiet zone ofQUIET_MODULES(4, the spec's), as blank half-block rows and columns.qr::network_stylemaps a network to its dot color at the terminal's depth.Call::wallet_networkdoes the best-effort wallet read, andLine::with_stylepaints scrubbed text in a style chosen at run time.wait_for_payment, the early presentation applies only to--wait completed. On reaching the target, the result is written and then the invoice is presented.Role::QrCodeis black on bright white. It is the one role that sets a background, because the code must not follow the theme.PaymentDatagainsamount,memo, andexpiration, andPaymentView::expires_atparses the latter.Timeline::expires_atandTimeline::pausehold the countdown, sowait_for_paymentcallstimeline.pause(...)instead ofterminal.waiting(...)with no new branch, and its CRAP score dropped. The expiry instant useschecked_add, so an absurd expiry cannot overflow.Try it on staging
Setup: Try a pull request on staging (added by #97). This creates a receive invoice (
--execute): nothing is paid, and it simply expires. Use a test-network wallet; find one withvoltage wallets list --account me.Expect:
● mutinynetwith a purple dot, then the boldScan to pay · 1,000 satscaption (plusexpires …when the API sends an expiration). Below it, a plain black-on-white code with a white border. Scan it with a phone on both a dark and a light terminal theme; the wallet app should read a signet or mutinynet invoice.Expect: the same code, then a spinner line that adds
· expires in m:sswhen the invoice has an expiration. Ctrl-C to stop waiting; the invoice stays valid until it expires.Expect: the old compact code under
Scan to pay:, with no colors.Validation
cargo fmt --check,cargo clippy --locked --all-targets -- -D warnings,cargo machete --with-metadata,cargo test --locked(166 unit and 129 integration tests), andpython3 scripts/check-coverage.pyall pass.receive --qr --wait completedon a terminal shows the caption with amount, memo, and expiry, and every code line is black on bright white with a blank first row. The wait shows· expires in. The existing redirected test still seesScan to pay:. A new PTY test, with all streams on the terminal, checks that for--qralone the payment record comes before the caption and nothing follows the code, and that the wallet read namesmutinynetin the badge. The framed test checks the signet fallback badge, its purple dot, and that the code has no frame.src/cli.rsendpoint, untouched).Docs
docs/guides/payments.mdandCHANGELOG.md.