Skip to content

feat(panel): printer pairing QR export — per-printer QR PNG + tenant CSV - #109

Merged
thevladbog merged 10 commits into
mainfrom
feat/printer-pairing-qr-export
Jul 23, 2026
Merged

feat(panel): printer pairing QR export — per-printer QR PNG + tenant CSV#109
thevladbog merged 10 commits into
mainfrom
feat/printer-pairing-qr-export

Conversation

@thevladbog

@thevladbog thevladbog commented Jul 23, 2026

Copy link
Copy Markdown
Owner

What & why

Lets operators export the pairing data of registered network (ethernet) printers from the panel's Equipment hub, so labels with connection QR codes can be mass-produced in an external label tool (BarTender / NiceLabel / ZebraDesigner). Scanning such a QR in the mobile app connects the phone to that printer — that scan-to-connect half already exists (PrinterQRData + camera), so this PR only adds the export half.

Design spec: docs/superpowers/specs/2026-07-23-printer-pairing-qr-export-design.md
Plan: docs/superpowers/plans/2026-07-23-printer-pairing-qr-export.md

What's in it

Backend

  • buildPrinterQRPayload — single source of truth mapping an equipment_devices row → the exact PrinterQRData JSON the mobile app parses.
  • ListEquipmentPrintersForTenant store method — all kind=network printers across the tenant's machines + hostname.
  • GET /api/equipment/devices/:device_id/pairing-qr.png — PNG QR for one network printer (422 for non-network, 404 for missing/foreign).
  • GET /api/equipment/printers/pairing-export.csv — tenant-wide CSV, UTF-8 BOM, columns name,machine,printer_type,ip,port,dpi,qr_payload,device_id. The qr_payload column is the ready JSON to bind the QR in a label tool.
  • OpenAPI paths added + panel client (schema.d.ts) regenerated.

Panel

  • pairingExport.ts — authed downloads via the shared api client (parseAs: "blob") → save.
  • Equipment hub: per-network-printer "Download pairing QR" action + a "Export printers (CSV)" button; i18n en/ru.

Load-bearing invariant

The CSV qr_payload and the PNG encode byte-identical PrinterQRData for the same device (both via buildPrinterQRPayload) — verified by a round-trip test — so a scanned label reliably connects the mobile app.

Scope

Network/ethernet printers only (they have a reachable ip:port). Bluetooth (not in the registry) and system/CUPS (unreachable from mobile) are out of scope by design.

Testing

  • Backend: helper units, handler 200/422/404, tenant-scoping tests, pgxmock SQL-pin, CSV round-trip. go test ./... green, build clean.
  • Panel: pairingExport unit tests + Equipment UI wiring tests; npm run typecheck clean; equipment + i18n suites green (173 tests).

Review notes

Built via spec → plan → per-task implement+review. A whole-branch review flagged one non-blocking issue — the CSV export button was gated on the current machine while the export is tenant-wide — fixed in 0e6504a (button always enabled; export handles the empty-tenant case), along with a PNG OpenAPI 403 and two tenant-scoping tests.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added printer pairing QR downloads for eligible network printers.
    • Added tenant-wide CSV export of printer pairing data.
    • Added equipment page actions for downloading individual QR codes and exporting all printers.
    • Added English and Russian labels for the new actions.
  • Documentation

    • Documented the new pairing QR and CSV export endpoints.
  • Tests

    • Added coverage for downloads, exports, validation, filenames, and tenant scoping.

CI Bot and others added 9 commits July 23, 2026 13:47
…nt registry)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds downloadPrinterPairingQr/downloadPrinterPairingCsv, fetching the
Task 3-4 pairing-qr.png / pairing-export.csv endpoints via the shared
authed api client and saving the returned Blob via a temporary anchor
click, mirroring attendees/exportCsv.ts's downloadCsv mechanics.
Copilot AI review requested due to automatic review settings July 23, 2026 18:03

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added documentation Improvements or additions to documentation backend panel labels Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@thevladbog, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 72263784-52a0-4c97-8a16-151686fdc0f3

📥 Commits

Reviewing files that changed from the base of the PR and between 0e6504a and 65fddba.

📒 Files selected for processing (4)
  • backend/internal/handler/openapi_contract_printer_pairing_test.go
  • backend/internal/handler/printer_pairing_export.go
  • backend/internal/handler/printer_pairing_export_test.go
  • docs/superpowers/specs/2026-07-23-printer-pairing-qr-export-design.md
📝 Walkthrough

Walkthrough

Adds tenant-scoped network printer pairing exports: individual 512px QR PNG downloads and bulk UTF-8 BOM CSV exports. The backend validates payloads and sanitizes CSV fields, while the equipment panel exposes corresponding download actions with generated API contracts, localized labels, and tests.

Changes

Printer pairing export

Layer / File(s) Summary
Payload contract and printer lookup
backend/internal/models/equipment.go, backend/internal/store/..., backend/internal/handler/printer_pairing_export.go
Adds the printer export model, tenant-scoped network-printer query with hostnames, shared QR payload construction, validation, and supporting tests.
QR and CSV backend endpoints
backend/internal/handler/..., backend/openapi.yaml
Registers and implements PNG and CSV endpoints with tenant isolation, response headers, CSV sanitization, malformed-row skipping, and endpoint tests.
Panel download data layer
panel/src/features/equipment/pairingExport.*, panel/src/shared/api/schema.d.ts
Adds authenticated blob downloads, filename sanitization and fallback behavior, generated endpoint contracts, and download tests.
Equipment hub actions
panel/src/features/equipment/DeviceCard.tsx, panel/src/features/equipment/EquipmentPage.*, panel/src/shared/i18n/*
Adds per-network-printer QR actions, tenant-wide CSV export, localized labels, and UI coverage for both actions.
Feature design and implementation documentation
docs/superpowers/plans/*, docs/superpowers/specs/*
Documents the payload contract, endpoint behavior, CSV format, panel wiring, test coverage, and implementation checklist.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant EquipmentPage
  participant pairingExport
  participant PrinterPairingAPI
  participant PGStore

  Operator->>EquipmentPage: Select QR or CSV export action
  EquipmentPage->>pairingExport: Start authenticated download
  pairingExport->>PrinterPairingAPI: Request PNG or tenant CSV blob
  PrinterPairingAPI->>PGStore: Load tenant-scoped printer data
  PGStore-->>PrinterPairingAPI: Network printer records
  PrinterPairingAPI-->>pairingExport: Pairing file blob
  pairingExport-->>Operator: Save downloaded file
Loading

Possibly related PRs

  • thevladbog/idento#62: Rewrites OpenAPI/client plumbing used to generate the API schema updated here.
  • thevladbog/idento#83: Introduced the equipment hub and device components extended by these pairing actions.

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% 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 summarizes the main feature: printer pairing QR export with per-printer PNGs and a tenant-wide CSV.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/printer-pairing-qr-export

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
backend/internal/store/pg_store_equipment_test.go (1)

717-749: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the required table-driven parallel test pattern.

Convert this unit test to a table case and call t.Parallel(); the repository requires both for *_test.go tests.

As per coding guidelines, “Write unit tests using table-driven patterns and parallel execution.”

🤖 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 `@backend/internal/store/pg_store_equipment_test.go` around lines 717 - 749,
Convert TestListEquipmentPrintersForTenant_ReturnsNetworkPrintersWithHostname
into a table-driven test with the existing scenario represented as a test case,
call t.Parallel() at the test or subtest level as appropriate, and run the case
through t.Run while preserving all current assertions and mock expectations.

Source: Coding guidelines

🤖 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 `@backend/internal/handler/printer_pairing_export.go`:
- Around line 43-57: Normalize shape.IP once by trimming whitespace, validate
the normalized value for emptiness and valid IP syntax, and use that normalized
value when populating the QR payload’s IP field in the printer pairing export
flow. Add coverage for whitespace-padded valid input and invalid IP input.
- Around line 91-126: Instrument
backend/internal/handler/printer_pairing_export.go lines 91-126 in
GetPrinterPairingQR and lines 134-202 in the sibling export handler with
request-context spans. Add tenant/device or export attributes as appropriate,
propagate the span context through downstream calls, and record/fail the span
for storage, encoding, CSV-writing, and QR-generation errors before returning
the existing responses.

In `@docs/superpowers/plans/2026-07-23-printer-pairing-qr-export.md`:
- Around line 1056-1061: Remove stale network-printer gating from the export
plan and design. In
docs/superpowers/plans/2026-07-23-printer-pairing-qr-export.md:1056-1061,
replace the disabled-button test with enabled export behavior producing an
empty/header-only CSV; at 1112-1131 remove hasNetworkPrinter and the disabled
prop; at 1187-1189 update the no-printers edge-case summary. In
docs/superpowers/specs/2026-07-23-printer-pairing-qr-export-design.md:134-140
and 189-190, state that tenant-wide CSV export remains available with headers
only and remove disabled/hidden behavior.

In `@docs/superpowers/specs/2026-07-23-printer-pairing-qr-export-design.md`:
- Around line 74-102: Update the design contract for buildPrinterQRPayload to
show only the models.EquipmentDevice parameter and remove the hostname argument,
since hostname is CSV-only display data. Revise the filename description to
state that slugForFilename removes non-ASCII characters, with device_id as the
fallback, rather than claiming Cyrillic transliteration.

In `@panel/src/features/equipment/DeviceCard.tsx`:
- Around line 282-286: Move the pairing-QR download item using
onDownloadPairingQr and the network device.kind check outside the !agentDown
action subtree in DeviceCard, while preserving its existing label and handler.
Keep it available during agent outages, independently of live-agent actions.

In `@panel/src/features/equipment/EquipmentPage.tsx`:
- Around line 465-473: Update the download handlers in EquipmentPage.tsx at
lines 465-473 and 554-556 to catch rejections from downloadPrinterPairingCsv and
the QR download helper instead of discarding them with void. In both sites,
display the same localized error feedback using the existing translation and
notification mechanisms.

---

Nitpick comments:
In `@backend/internal/store/pg_store_equipment_test.go`:
- Around line 717-749: Convert
TestListEquipmentPrintersForTenant_ReturnsNetworkPrintersWithHostname into a
table-driven test with the existing scenario represented as a test case, call
t.Parallel() at the test or subtest level as appropriate, and run the case
through t.Run while preserving all current assertions and mock expectations.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d7dc0d5b-524f-4492-9b8b-384f509f2ec7

📥 Commits

Reviewing files that changed from the base of the PR and between a6f369e and 0e6504a.

📒 Files selected for processing (19)
  • backend/internal/handler/handler.go
  • backend/internal/handler/printer_pairing_export.go
  • backend/internal/handler/printer_pairing_export_test.go
  • backend/internal/handler/testsupport_test.go
  • backend/internal/models/equipment.go
  • backend/internal/store/interface.go
  • backend/internal/store/pg_store_equipment.go
  • backend/internal/store/pg_store_equipment_test.go
  • backend/openapi.yaml
  • docs/superpowers/plans/2026-07-23-printer-pairing-qr-export.md
  • docs/superpowers/specs/2026-07-23-printer-pairing-qr-export-design.md
  • panel/src/features/equipment/DeviceCard.tsx
  • panel/src/features/equipment/EquipmentPage.test.tsx
  • panel/src/features/equipment/EquipmentPage.tsx
  • panel/src/features/equipment/pairingExport.test.ts
  • panel/src/features/equipment/pairingExport.ts
  • panel/src/shared/api/schema.d.ts
  • panel/src/shared/i18n/en.json
  • panel/src/shared/i18n/ru.json

Comment thread backend/internal/handler/printer_pairing_export.go
Comment on lines +91 to +126
func (h *Handler) GetPrinterPairingQR(c echo.Context) error {
deviceID, err := uuid.Parse(c.Param("device_id"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid device ID"})
}
tenantID, err := tenantIDFromContext(c)
if err != nil {
return writeErr(c, err)
}

device, err := h.Store.GetEquipmentDeviceForTenant(c.Request().Context(), tenantID, deviceID)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Internal error"})
}
if device == nil {
return c.JSON(http.StatusNotFound, map[string]string{"error": "Device not found"})
}

payload, err := buildPrinterQRPayload(*device)
if err != nil {
return c.JSON(http.StatusUnprocessableEntity, map[string]string{"error": err.Error()})
}

jsonData, err := json.Marshal(payload)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to encode printer data"})
}
png, err := qrcode.Encode(string(jsonData), qrcode.Medium, 512)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to generate QR code"})
}

c.Response().Header().Set("Content-Disposition",
fmt.Sprintf(`attachment; filename="%s-pairing-qr.png"`, slugForFilename(device.DisplayName, device.ID)))
return c.Blob(http.StatusOK, "image/png", png)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --glob '*.go' \
  'otel\.Tracer|trace\.SpanFromContext|span\.RecordError|span\.SetStatus' \
  backend/internal

Repository: thevladbog/idento

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file outline =="
ast-grep outline backend/internal/handler/printer_pairing_export.go || true

echo
echo "== handler imports and relevant handler implementation =="
sed -n '1,240p' backend/internal/handler/printer_pairing_export.go

echo
echo "== tracing references across backend =="
rg -n --glob '*.go' '(\bobot?|otel|trace|Span|RecordError|SetStatus|NewTracer|NewTracerProvider)' backend || true

echo
echo "== imports context/json/qrcode/buildPrinterQRPayload/tenantIDFromContext usages =="
rg -n --glob '*.go' 'trace\.Tracer|SpanFromContext|otel|NewTracer|RecordError|SetStatus|buildPrinterQRPayload|tenantIDFromContext|Request\(\)\.Context\(\)' backend/internal || true

Repository: thevladbog/idento

Length of output: 37147


Instrument the printer pairing export endpoints with tracing.

Both handlers now create new observability-critical request flows but still do not start a span or record errors. Add spans propagated on request context, with tenant/device or export attributes, and fail/span errors for storage, encoding, CSV writing, and QR-generation failures.

📍 Affects 1 file
  • backend/internal/handler/printer_pairing_export.go#L91-L126 (this comment)
  • backend/internal/handler/printer_pairing_export.go#L134-L202
🤖 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 `@backend/internal/handler/printer_pairing_export.go` around lines 91 - 126,
Instrument backend/internal/handler/printer_pairing_export.go lines 91-126 in
GetPrinterPairingQR and lines 134-202 in the sibling export handler with
request-context spans. Add tenant/device or export attributes as appropriate,
propagate the span context through downstream calls, and record/fail the span
for storage, encoding, CSV-writing, and QR-generation errors before returning
the existing responses.

Source: Coding guidelines

Comment thread docs/superpowers/plans/2026-07-23-printer-pairing-qr-export.md
Comment thread docs/superpowers/specs/2026-07-23-printer-pairing-qr-export-design.md Outdated
Comment on lines +282 to +286
{onDownloadPairingQr && device.kind === "network" ? (
<DropdownMenuItem onSelect={() => onDownloadPairingQr(device)}>
{t("equipmentDownloadPairingQr")}
</DropdownMenuItem>
) : null}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep pairing-QR download available when the agent is offline.

This item is inside the !agentDown action subtree, so it disappears during an agent outage. The backend generates the QR from the tenant-scoped stored printer config; expose this download independently of live-agent actions.

🤖 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 `@panel/src/features/equipment/DeviceCard.tsx` around lines 282 - 286, Move the
pairing-QR download item using onDownloadPairingQr and the network device.kind
check outside the !agentDown action subtree in DeviceCard, while preserving its
existing label and handler. Keep it available during agent outages,
independently of live-agent actions.

Comment on lines +465 to +473
<Button
type="button"
variant="ghost"
onClick={() => {
void downloadPrinterPairingCsv();
}}
>
{t("equipmentExportPrinters")}
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle download failures instead of discarding them.

Both helpers can reject on API failures or empty responses, but void leaves those rejections unhandled and the operator gets no feedback.

  • panel/src/features/equipment/EquipmentPage.tsx#L465-L473: catch CSV download failures and show localized error feedback.
  • panel/src/features/equipment/EquipmentPage.tsx#L554-L556: catch QR download failures and show the same localized error feedback.
📍 Affects 1 file
  • panel/src/features/equipment/EquipmentPage.tsx#L465-L473 (this comment)
  • panel/src/features/equipment/EquipmentPage.tsx#L554-L556
🤖 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 `@panel/src/features/equipment/EquipmentPage.tsx` around lines 465 - 473,
Update the download handlers in EquipmentPage.tsx at lines 465-473 and 554-556
to catch rejections from downloadPrinterPairingCsv and the QR download helper
instead of discarding them with void. In both sites, display the same localized
error feedback using the existing translation and notification mechanisms.

…in QR payload

- Add validateResponse-based contract tests for pairing-qr.png and
  pairing-export.csv — satisfies the OPENAPI_COVERAGE=1 gate that failed
  'validate / Test Go' (every documented operation needs a contract test).
- Trim config.ip before it reaches the QR payload so a stored padded value
  (' 10.0.0.5 ') can't produce an unusable mobile endpoint.
- Align design spec with shipped behavior: buildPrinterQRPayload takes only
  the device (hostname is a CSV column), the CSV export button is always
  enabled (tenant-wide), and the filename slug drops non-ASCII (no translit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thevladbog
thevladbog merged commit fc487dd into main Jul 23, 2026
34 checks passed
@thevladbog
thevladbog deleted the feat/printer-pairing-qr-export branch July 23, 2026 18:30
thevladbog added a commit that referenced this pull request Jul 23, 2026
…rs (PR #109 follow-ups) (#110)

* docs: design for equipment pairing-QR UX follow-ups (CodeRabbit PR #109)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(panel): keep pairing-QR reachable offline + surface download errors (PR #109 follow-ups)

Two deferred CodeRabbit findings from PR #109 (printer pairing QR export),
panel-only UX polish in the Equipment hub; backend unchanged.

Finding 1 — DeviceCard.tsx: the per-device 'Download pairing QR' row action
lived inside the card's {!agentDown} degraded branch, so an agent outage hid
it. The QR is generated server-side from the tenant-scoped stored config and
needs no live agent. Render the row menu whenever it would hold >=1 item and
gate each item: only 'Download pairing QR' (network-only) is agent-independent;
Rename/Edit address/Set-default/Delete stay hidden while the agent is down, so
the intentional 'unreachable' degraded state is preserved. A system printer
(no QR) still shows no menu offline.

Finding 2 — EquipmentPage.tsx: both download call sites used void, swallowing
the helpers' ApiError/empty-response rejections. Surface them via a dismissible
destructive banner (role=alert), reusing the existing mirror-warning banner
convention rather than adding a toast primitive. Each fresh attempt clears the
stale error; Close dismisses it. New i18n key equipmentDownloadError (EN + RU).

Tests: agent-down network printer keeps QR reachable while agent-dependent
items stay hidden; a system printer shows no offline menu; a rejected QR/CSV
download surfaces the banner and Close dismisses it. Mock stubs now resolve so
the new .catch() chains work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: CI Bot <ci@example.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
thevladbog added a commit that referenced this pull request Jul 23, 2026
…n flake) (#111)

The three 'PUT is pending' tests in BadgeEditorPage.test.tsx gated the save
PUT with a fixed 60ms MSW delay, then synchronously asserted the guard
buttons were disabled. On a loaded CI runner the 60ms could elapse before
those assertions ran, re-enabling the buttons ('Received element is not
disabled') — a flake that reddened main after the (unrelated) #109/#110
squash-merges. Replace the timing window with a manually-released gate
(armPutGate) so the PUT stays pending until the test explicitly releases it.

Co-authored-by: CI Bot <ci@example.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend documentation Improvements or additions to documentation panel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants