Fix/general issues#36
Conversation
…yncs
- upsert_invoice now returns {:ok, invoice, :inserted | :updated} so the
sync counter only counts genuinely new invoices instead of all processed
(including re-synced) ones.
- trigger_manual_sync checks for available/scheduled/executing job states,
preventing duplicate jobs when tapping "Sync Now" rapidly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion buttons Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…en refresh errors - All KSeF API calls now have 30s receive_timeout and Req transient retry (2 retries with 1s delay) to handle intermittent timeouts. - TokenManager.do_refresh distinguishes network errors (retryable) from auth rejections (reauth_required) so transient failures don't permanently cancel sync jobs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add mb-2 to Invite button to align with input fields in the form. - Rename "invoice_reviewer" role to "reviewer" across schemas, validation, UI, and tests. - Add data migration to update existing rows in memberships and invitations tables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eldset structure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge the separate "Pending invitations" and "Members" tables into one table with columns: Email, Name, Role, Status, Expires, Action. Pending invitations show em-dash for name, "pending" badge in status, and expiry date. Members show em-dash for expires. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove buyer column, reorder to Number/Date/Type/Seller/Gross/Status, prevent date wrapping. Capitalize team table headers to match .table component. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fetch and adapt official FA(3) stylesheet from crd.gov.pl with local imports and XSD lookups patched for --nonet usage - Fix Req multipart tuple format for Gotenberg (was 3-tuple, needs 2-tuple) - Pass invoice metadata (ksef_number) through PDF pipeline via xsltproc --stringparam and fallback template - Extract sales_date (P_6) in parser for fallback template - Update scripts/update-ksef-stylesheet.sh to handle real gov.pl URLs, auto-download shared templates and XSD files, inject ksef_number param Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use 1/3 + 2/3 grid layout instead of 50/50. Replace .list component with dense table, shorter labels, taller preview iframe (600px). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Oban's job.errors is append-only and never cleared, so completed jobs that failed on a previous attempt still showed the old error message. Now only show job.errors for non-completed states. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughRename role Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(200,200,255,0.5)
participant Controller
end
rect rgba(200,255,200,0.5)
participant PdfMod as PDF Module
end
rect rgba(255,200,200,0.5)
participant Xsltproc
end
rect rgba(255,255,200,0.5)
participant Gotenberg
end
Controller->>PdfMod: generate_html(xml_content, metadata)
PdfMod->>Xsltproc: transform(xml_content, metadata)
Xsltproc-->>PdfMod: {:ok, html}
PdfMod->>Gotenberg: generate_pdf(html)
Gotenberg-->>PdfMod: {:ok, pdf_binary}
PdfMod-->>Controller: {:ok, html} / {:ok, pdf_binary}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
lib/ksef_hub/sync/invoice_fetcher.ex (1)
141-159:⚠️ Potential issue | 🟡 MinorUpdate
download_and_upsert/3spec to match new tuple shape.It now returns the
:inserted | :updatedtag fromInvoices.upsert_invoice/1, so the spec should reflect that.📌 Suggested update
- `@spec` download_and_upsert(map(), String.t() | nil, map()) :: - {:ok, Invoices.Invoice.t()} | {:error, term()} + `@spec` download_and_upsert(map(), String.t() | nil, map()) :: + {:ok, Invoices.Invoice.t(), :inserted | :updated} | {:error, term()}test/ksef_hub_web/live/team_live_test.exs (1)
93-103:⚠️ Potential issue | 🟡 MinorAvoid asserting role text; prefer structural selectors.
The updated assertion checks the badge text
"reviewer", which makes the test brittle. Consider a data-testid/data-role attribute on the badge and assert on that instead.🧩 Example adjustment (requires a data-role attribute in the template)
- assert has_element?(view, "[data-testid='pending-invitations'] .badge", "reviewer") + assert has_element?(view, "[data-testid='pending-invitations'] .badge[data-role='reviewer']")Based on learnings, Instead of relying on testing text content which can change, favor testing for the presence of key elements in LiveView tests.
lib/ksef_hub/ksef_client/live.ex (1)
34-60:⚠️ Potential issue | 🟠 MajorDisable retries for non-idempotent KSeF auth and token endpoints.
KSeF API v2 does not define idempotency for
/auth/xades-signature,/auth/token/redeem, and/auth/token/refresh. Currently,retry: :transientinreq_options()(lines 20–28) applies to all endpoints including these non-idempotent POSTs, risking duplicate side effects:
/auth/xades-signature: Retrying on transient errors (500, 502, etc.) creates multiple authentication operations with different reference numbers./auth/token/redeem: Tokens are redeemable only once; a transient error followed by automatic retry will fail with 400/21302 if the initial request succeeded./auth/token/refresh: Each retry generates a new access token; automatic retries can create unintended duplicates.Scope
retry: :transientto idempotent endpoints (GETs like/auth/{referenceNumber},/invoices/query/metadata,/invoices/ksef/{ksef_number}) or disable retries for auth/token operations and handle retries explicitly at the call site. Alternatively, useretry: falseon non-idempotent operations and rely on Req's transient defaults only for safe methods.Also applies to: lines 87 (poll_auth_status—idempotent GET), 143 (refresh_access_token), 208 (download_invoice—idempotent GET), 229 (terminate_session—has side effects).
lib/ksef_hub/pdf.ex (1)
14-28: 🛠️ Refactor suggestion | 🟠 MajorAdd
@doc/@SPEC for the public API.
generate_html/2is public and now part of the updated interface—please document it and add type specs (same forgenerate_pdf/1while you’re here).As per coding guidelines, "Every public function must have `@doc` documentation" and "Every function (public and private) must have `@spec` type specification".✅ Proposed update
+ `@doc` "Generates HTML from FA(3) XML using xsltproc with fallback." + `@spec` generate_html(String.t(), map()) :: {:ok, String.t()} | {:error, term()} def generate_html(xml_content, metadata \\ %{}) do case Xsltproc.transform(xml_content, metadata) do {:ok, html} -> {:ok, html} @@ Logger.debug("Xsltproc failed, using fallback template") FallbackTemplate.render(xml_content, metadata) end end + `@doc` "Converts HTML to PDF via Gotenberg." + `@spec` generate_pdf(String.t()) :: {:ok, binary()} | {:error, term()} def generate_pdf(html) do Gotenberg.convert(html) end
🤖 Fix all issues with AI agents
In `@lib/ksef_hub_web/live/invoice_live/show.ex`:
- Around line 198-200: The file has formatting issues causing CI to fail; run
the Elixir formatter (mix format) on the InvoiceLive.Show module
(lib/ksef_hub_web/live/invoice_live/show.ex) to correct whitespace/indentation
and trailing HTML fragments (e.g., the render/0 or render/1 template that
contains the closing divs), then commit the formatted file so CI passes; ensure
the module name InvoiceLive.Show and its render function are formatted
consistently with project style.
In `@lib/ksef_hub/invoices.ex`:
- Around line 69-110: The pre-check using Repo.exists? in upsert_invoice creates
a TOCTOU race; remove the existed? check and instead determine inserted vs
updated from the single DB-returned row after Repo.insert (which already uses
conflict_target and returning: true) by comparing timestamp fields on the
returned Invoice (e.g., compare invoice.inserted_at and invoice.updated_at or
check whether updated_at changed) to decide :inserted or :updated; alternatively
wrap the operation in a Repo.transaction and use the returned invoice within the
same transaction to ensure atomicity—update the branch that matches on
Repo.insert/2 to compute the atom from the returned invoice timestamps rather
than from existed?.
In `@lib/ksef_hub/ksef_client/token_manager.ex`:
- Around line 194-200: The warning logs raw API response bodies in the {:error,
reason} branch; change it to avoid leaking sensitive response bodies by
pattern-matching known KSeF error tuples and logging only the status (e.g.,
match {:ksef_error, status, _} and call Logger.warning("Token refresh failed,
re-auth required: status=#{status}")), and for any other reason log a generic
message without interpolating the full reason (e.g., Logger.warning("Token
refresh failed, re-auth required")). Ensure the clause still returns {:error,
:reauth_required} as shown and keep the existing {:error, {:request_failed, _}}
branch behavior.
In `@priv/xsl/WspolneSzablonyWizualizacji_v12-0E.xsl`:
- Around line 699-722: There are two identical templates matching
"*[local-name()='Naglowek']/*[local-name()='KodUrzedu'] |
*[local-name()='Naglowek']/*[local-name()='PoprzedniNaczelnikUS']" causing a
duplicate; either remove the unintended duplicate or consolidate them into one
template that chooses between $schema-urzedow and $schema-urzedowexwus (or
between 'TKodUS' and 'TKodUS1') before calling the named template
ZnajdzWEnumeracji, or give each template a distinct mode and update callers to
use that mode; modify the template(s) to pass the correct schema/typ based on a
clear condition or mode so only one matching template applies at runtime.
- Around line 7-8: The schema-walut parameter ('schema-walut') points to a
remote XSD URL which will break the document() call inside the ZnajdzWEnumeracji
template (referenced at lines where ZnajdzWEnumeracji is called and document()
is used); fix by ensuring KodyWalut_v1-0E.xsd is available locally and change
the schema-walut parameter to reference the local filename (or run the
update-ksef-stylesheet.sh end-to-end so it downloads and patches the path); keep
the parameter name 'schema-walut' and ensure the local path matches what
ZnajdzWEnumeracji/document() expects.
🧹 Nitpick comments (3)
lib/ksef_hub/invoices.ex (1)
75-111: Consider extracting helpers to keepupsert_invoice/1compact.The function now mixes query, changeset construction, conflict fields, and result tagging; a couple of small helpers would improve readability and align with the size guideline.
As per coding guidelines, Keep functions small and focused (< 15 lines ideally) in Elixir.
scripts/update-ksef-stylesheet.sh (1)
28-40: Inconsistent curl options in XSD download loop.The XSD download loop at line 31 uses hardcoded timeout values instead of the
CURL_OPTSvariable defined at line 12. Consider using${CURL_OPTS}for consistency.Suggested fix
for xsd_url in $(grep -oP "select=\"'\\Khttps?://[^']*\\.xsd" "${XSL_DIR}/${SHARED_FILENAME}" || true); do xsd_file=$(basename "${xsd_url}") echo " Downloading ${xsd_file}..." - if curl --connect-timeout 10 --max-time 30 -fsSL "${xsd_url}" -o "${XSL_DIR}/${xsd_file}" 2>/dev/null; then + if curl ${CURL_OPTS} "${xsd_url}" -o "${XSL_DIR}/${xsd_file}" 2>/dev/null; then echo " OK"lib/ksef_hub_web/live/team_live.ex (1)
214-214: Consider updating data-testid to reflect merged table content.The
data-testid="pending-invitations"on the table is misleading since it now contains both members and pending invitations. Consider renaming to something like"team-table"or"members-and-invitations".Suggested fix
- <table class="table table-sm" data-testid="pending-invitations"> + <table class="table table-sm" data-testid="team-table">
…non-idempotent KSeF endpoints - Correct download_and_upsert/3 @SPEC to match 3-element ok tuple - Add data-role attribute to invitation badge, assert on it in test - Introduce req_options_no_retry/0 for non-idempotent KSeF API calls (get_challenge, authenticate_xades, redeem_tokens, refresh_access_token, terminate_session) to prevent duplicate side effects on transient errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and remote URLs - Remove invoice_exists? check before upsert; determine insert vs update by comparing inserted_at/updated_at timestamps on the returned row - Sanitize token_manager logging to avoid leaking sensitive API response bodies in the catch-all error branch - Consolidate duplicate KodUrzedu/PoprzedniNaczelnikUS XSL templates into one with primary/fallback lookup - Replace remote XSD URLs in XSL params with local filenames and add placeholder XSD files for KodyWalut and KodyNaczelnikow - Fix formatting in show.ex (CI blocker) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…URL_OPTS - Add @doc and @SPEC to generate_html/2 and generate_pdf/1 in Pdf module to satisfy project documentation conventions - Rename data-testid from "pending-invitations" to "team-table" since the table contains all members, not just pending invitations - Update team_live_test selectors to match renamed testid - Use $CURL_OPTS variable in update-ksef-stylesheet.sh instead of hardcoded curl flags for consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/ksef_hub/ksef_client/live.ex (1)
213-231:⚠️ Potential issue | 🔴 CriticalImplement proactive rate limiting per coding guidelines.
The code relies only on reactive 429 handling, but the coding guidelines require proactive rate limiting: 8 req/s for invoice downloads (
Process.sleep(125)between requests) and 2 req/s for KSeF queries (Process.sleep(500)between queries). Bothdownload_invoiceandquery_invoice_metadataare called in loops without rate limiting delays, causing unnecessary API errors. The orphanedRateLimitermodule exists but is unused and not integrated.
🤖 Fix all issues with AI agents
In `@priv/xsl/KodyWalut_v1-0E.xsd`:
- Around line 1-2: The empty XSD priv/xsl/KodyWalut_v1-0E.xsd is referenced by
the XSLT template ZnajdzWEnumeracji to enumerate values for the type TKodWaluty
but defines no types, causing runtime failures; fix by either adding an
explanatory comment matching the pattern used in KodyUrzedowSkarbowych_v8-0E.xsd
(stating this is a placeholder because the authoritative schema couldn’t be
downloaded) or by implementing the missing TKodWaluty enumeration/type
definitions (or adding an xs:import to the real schema) so that
ZnajdzWEnumeracji can successfully load and enumerate currency codes.
In `@scripts/update-ksef-stylesheet.sh`:
- Around line 56-66: Make the ksef_number injections idempotent by checking for
existing fragments before inserting: test for an existing <xsl:param
name="ksef_number"> (grep -q '<xsl:param name="ksef_number"' fa3-styl.xsl) and
only run the sed that adds the param if that check fails, and likewise test for
the injected block (grep -q 'Numer KSeF' or the exact '<xsl:if
test="$ksef_number' ... 'Numer KSeF'>' snippet) and only run the sed that
appends the NaglowekTytulowyKSeF + Numer KSeF block if not present; keep the
same inserted content and preserve the .bak/remove steps but guard both
insertions with these existence checks so reruns do not duplicate the param or
the display block.
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"/> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and verify the file exists and show its actual contents
find . -name "KodyWalut_v1-0E.xsd" -type fRepository: appunite/appunite-ksef-ex
Length of output: 99
🏁 Script executed:
# Check the actual file size and contents
if [ -f "priv/xsl/KodyWalut_v1-0E.xsd" ]; then
echo "=== File exists ===" && \
wc -l priv/xsl/KodyWalut_v1-0E.xsd && \
cat -n priv/xsl/KodyWalut_v1-0E.xsd
fiRepository: appunite/appunite-ksef-ex
Length of output: 229
🏁 Script executed:
# Search for references to this schema file in the codebase
rg "KodyWalut_v1-0E" --type-listRepository: appunite/appunite-ksef-ex
Length of output: 458
🏁 Script executed:
# Search for XSD schema imports, references, or validations
rg -i "KodyWalut|xsd.*schema|schema.*xsd" -t xml -t xsl -t py -t jsRepository: appunite/appunite-ksef-ex
Length of output: 96
🏁 Script executed:
# Check for related XSD files in the same directory
ls -la priv/xsl/*.xsd 2>/dev/null || echo "No XSD files found"Repository: appunite/appunite-ksef-ex
Length of output: 517
🏁 Script executed:
# Check git history for this file (commit message and changes)
git log --oneline -n 20 -- "priv/xsl/KodyWalut_v1-0E.xsd" 2>/dev/null || echo "No git history available"Repository: appunite/appunite-ksef-ex
Length of output: 119
🏁 Script executed:
# Check content of other small XSD files to see if they're also empty
echo "=== KodyNaczelnikowUrzedowSkarbowych_v4-0E.xsd ===" && \
cat priv/xsl/KodyNaczelnikowUrzedowSkarbowych_v4-0E.xsd && \
echo -e "\n=== KodyUrzedowSkarbowychExWUS_v8-0E.xsd ===" && \
cat priv/xsl/KodyUrzedowSkarbowychExWUS_v8-0E.xsd && \
echo -e "\n=== KodyUrzedowSkarbowych_v8-0E.xsd ===" && \
cat priv/xsl/KodyUrzedowSkarbowych_v8-0E.xsdRepository: appunite/appunite-ksef-ex
Length of output: 660
🏁 Script executed:
# Search for references to KodyWalut or schema imports in XML files
rg "KodyWalut" -t xml -t htmlRepository: appunite/appunite-ksef-ex
Length of output: 181
🏁 Script executed:
# Search for schema validation or XSD references in the codebase
rg "schema" -t xml -t html -A 2 -B 2Repository: appunite/appunite-ksef-ex
Length of output: 6073
🏁 Script executed:
# Look for any validation logic in code files
rg -i "validate|xsd|schema" -t py --max-count 20Repository: appunite/appunite-ksef-ex
Length of output: 217
🏁 Script executed:
# Check if there are any XSLT or processing instructions referencing this schema
rg "schemaLocation|import|include" -t xmlRepository: appunite/appunite-ksef-ex
Length of output: 209
Add explanatory comment to empty currency codes schema, or implement the missing type definitions.
This XSD is actively referenced in WspolneSzablonyWizualizacji_v12-0E.xsl where the XSLT template ZnajdzWEnumeracji attempts to load it via document() and enumerate values for type TKodWaluty. Since the schema defines no types, currency code lookups will fail at runtime.
Compare with similar placeholder schemas in the same directory (e.g., KodyUrzedowSkarbowych_v8-0E.xsd), which include comments explaining they are placeholders that "could not download from gov.pl." Either add a similar explanatory comment to document this limitation, or include the actual currency code type definitions or an xs:import reference.
🤖 Prompt for AI Agents
In `@priv/xsl/KodyWalut_v1-0E.xsd` around lines 1 - 2, The empty XSD
priv/xsl/KodyWalut_v1-0E.xsd is referenced by the XSLT template
ZnajdzWEnumeracji to enumerate values for the type TKodWaluty but defines no
types, causing runtime failures; fix by either adding an explanatory comment
matching the pattern used in KodyUrzedowSkarbowych_v8-0E.xsd (stating this is a
placeholder because the authoritative schema couldn’t be downloaded) or by
implementing the missing TKodWaluty enumeration/type definitions (or adding an
xs:import to the real schema) so that ZnajdzWEnumeracji can successfully load
and enumerate currency codes.
| # Add ksef_number param (not in gov stylesheet, injected by our system) | ||
| echo "Adding ksef_number parameter..." | ||
| sed -i.bak '/<xsl:output/a\ | ||
| <xsl:param name="ksef_number" select="'\'''\''"/> | ||
| ' "${XSL_DIR}/fa3-styl.xsl" | ||
| rm -f "${XSL_DIR}/fa3-styl.xsl.bak" | ||
|
|
||
| # Add KSeF number display after NaglowekTytulowyKSeF call | ||
| sed -i.bak 's|<xsl:call-template name="NaglowekTytulowyKSeF"/>|<xsl:call-template name="NaglowekTytulowyKSeF"/>\ | ||
| <xsl:if test="$ksef_number != '\'''\''"><div style="text-align:right; margin-bottom:0.5em; font-size:0.9em;">Numer KSeF: <b><xsl:value-of select="$ksef_number"/></b></div></xsl:if>|' "${XSL_DIR}/fa3-styl.xsl" | ||
| rm -f "${XSL_DIR}/fa3-styl.xsl.bak" |
There was a problem hiding this comment.
Make the ksef_number injection idempotent.
Re-running the script will append duplicate <xsl:param name="ksef_number"> entries and repeated “Numer KSeF” blocks, which can break validation or render duplicates. Guard the inserts so reruns are safe.
🛠️ Suggested fix (idempotent guards)
# Add ksef_number param (not in gov stylesheet, injected by our system)
echo "Adding ksef_number parameter..."
-sed -i.bak '/<xsl:output/a\
- <xsl:param name="ksef_number" select="'"''"'"/>
-' "${XSL_DIR}/fa3-styl.xsl"
-rm -f "${XSL_DIR}/fa3-styl.xsl.bak"
+if ! grep -q 'name="ksef_number"' "${XSL_DIR}/fa3-styl.xsl"; then
+ sed -i.bak '/<xsl:output/a\
+ <xsl:param name="ksef_number" select="'"''"'"/>
+' "${XSL_DIR}/fa3-styl.xsl"
+ rm -f "${XSL_DIR}/fa3-styl.xsl.bak"
+fi
# Add KSeF number display after NaglowekTytulowyKSeF call
-sed -i.bak 's|<xsl:call-template name="NaglowekTytulowyKSeF"/>|<xsl:call-template name="NaglowekTytulowyKSeF"/>\
- <xsl:if test="$ksef_number != '"'"''"'"'"><div style="text-align:right; margin-bottom:0.5em; font-size:0.9em;">Numer KSeF: <b><xsl:value-of select="$ksef_number"/></b></div></xsl:if>|' "${XSL_DIR}/fa3-styl.xsl"
-rm -f "${XSL_DIR}/fa3-styl.xsl.bak"
+if ! grep -q 'Numer KSeF:' "${XSL_DIR}/fa3-styl.xsl"; then
+ sed -i.bak 's|<xsl:call-template name="NaglowekTytulowyKSeF"/>|<xsl:call-template name="NaglowekTytulowyKSeF"/>\
+ <xsl:if test="$ksef_number != '"'"''"'"'"><div style="text-align:right; margin-bottom:0.5em; font-size:0.9em;">Numer KSeF: <b><xsl:value-of select="$ksef_number"/></b></div></xsl:if>|' "${XSL_DIR}/fa3-styl.xsl"
+ rm -f "${XSL_DIR}/fa3-styl.xsl.bak"
+fi🤖 Prompt for AI Agents
In `@scripts/update-ksef-stylesheet.sh` around lines 56 - 66, Make the ksef_number
injections idempotent by checking for existing fragments before inserting: test
for an existing <xsl:param name="ksef_number"> (grep -q '<xsl:param
name="ksef_number"' fa3-styl.xsl) and only run the sed that adds the param if
that check fails, and likewise test for the injected block (grep -q 'Numer KSeF'
or the exact '<xsl:if test="$ksef_number' ... 'Numer KSeF'>' snippet) and only
run the sed that appends the NaglowekTytulowyKSeF + Numer KSeF block if not
present; keep the same inserted content and preserve the .bak/remove steps but
guard both insertions with these existence checks so reruns do not duplicate the
param or the display block.
Add Process.sleep delays before query_invoice_metadata (500ms, 2 req/s) and download_invoice (125ms, 8 req/s) in the Live client per KSeF rate limit guidelines. Use RateLimiter.handle_rate_limit/1 for reactive 429 handling in InvoiceFetcher with jitter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GenServer was never started in the supervision tree and never called. Proactive rate limiting uses simple Process.sleep delays in the Live client; reactive 429 handling uses inline sleep with jitter in InvoiceFetcher. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Bug Fixes
UI/UX Improvements
Chores