fix(ui): printer detail metadata + template preview + paused-bool gap#82
Conversation
…not *bool
Pydantic schema 'paused: bool = False' produced OpenAPI 'optional with
default' which oapi-codegen translates to 'Paused *bool, omitempty'.
Go templates evaluate '{{if pointer}}' as truthy for any non-nil pointer
including &false — causing the dashboard to show 'Paused' badge for
every printer regardless of actual paused state.
Schema is now 'paused: bool' (required). list_printers populates the
field explicitly from PrinterState (False when state row absent).
Generated client has 'Paused bool'. Regression test added to
internal/api/client_test.go (compile-time RED before fix).
Refs #22
The detail page previously called only /status, /tape, /queue and had no model/host/enabled/paused information. Production smoke flagged 'keine Metadaten vom Drucker'. Adds the missing detail endpoint plus a Metadata block in printer.html with model, backend, host:port, enabled, paused, created/updated timestamps. Handler now fetches the printer detail in a 4th errgroup goroutine alongside status/tape/queue. Returns 404 when the printer is not registered. Refs #22
The template detail page fell back to /static/preview-placeholder.svg because the backend had no preview endpoint — the frontend client already called POST /api/render/preview?key= and treated any error as 'show placeholder'. Adds the missing route that renders the template with app-appropriate sample data (snipeit/grocy/spoolman/generic) using the existing Phase 4 LabelRenderer. Returns image/png. OpenAPI response declares schema=binary so the completeness gate passes. Frontend template.go already calls RenderPreview — now succeeds end-to-end. Refs #22
…inition Commit 0f93bb3 added the preview endpoint with hardcoded per-app sample data in the route — wrong responsibility lokality. The template itself must declare how it wants to be previewed. TemplateSchema gains an optional preview_sample field. Every seed template (grocy/snipeit/spoolman x 12/18/24 mm + qr-only x 3) gets a preview_sample block with semantically appropriate placeholder values. URLs use RFC 2606 example.com domains. The render_preview route now reads preview_sample directly from template.definition; templates without a preview_sample block return HTTP 422 with a clear error message instead of relying on backend-side mockup data. Refs #22
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! Dieser Pull Request behebt drei UI-Bugs aus der Produktion und führt ein Refactoring der Template-Vorschau-Logik durch. Der Fokus liegt auf der Korrektur der Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #82 +/- ##
==========================================
- Coverage 91.98% 91.14% -0.84%
==========================================
Files 70 70
Lines 3055 3116 +61
Branches 261 263 +2
==========================================
+ Hits 2810 2840 +30
- Misses 181 212 +31
Partials 64 64
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds missing backend/frontend support for printer metadata and template preview rendering, while correcting the paused OpenAPI shape so Go templates treat false correctly.
Changes:
- Makes
PrinterRead.pausedrequired and addsGET /api/printers/{id}. - Adds frontend printer detail metadata fetching/rendering.
- Adds backend template preview rendering from per-template
preview_sampleseed data.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/web/templates/printer.html |
Adds printer metadata block. |
frontend/internal/handlers/printer.go |
Fetches printer metadata alongside status/tape/queue. |
frontend/internal/handlers/printer_test.go |
Adds printer metadata regression coverage. |
frontend/internal/handlers/dashboard_test.go |
Adds paused badge regression test. |
frontend/internal/api/openapi.snapshot.json |
Updates printer schema and adds printer detail route snapshot. |
frontend/internal/api/client.go |
Adds printer detail API wrapper. |
frontend/internal/api/client.gen.go |
Regenerates printer detail client and Paused bool. |
frontend/internal/api/client_test.go |
Verifies paused=false decodes as plain bool false. |
backend/tests/unit/api/test_templates_routes.py |
Adds preview endpoint and seed preview tests. |
backend/tests/unit/api/test_printers_routes.py |
Adds printer detail endpoint tests. |
backend/app/seed/templates/*.yaml |
Adds preview samples to seed templates. |
backend/app/schemas/template.py |
Adds optional preview_sample schema field. |
backend/app/schemas/printer.py |
Makes paused required. |
backend/app/main.py |
Registers render router. |
backend/app/api/routes/templates.py |
Adds /api/render/preview. |
backend/app/api/routes/printers.py |
Adds /api/printers/{id} detail endpoint. |
Files not reviewed (1)
- frontend/internal/api/client.gen.go: Language not supported
There was a problem hiding this comment.
Code Review
This pull request implements a new printer detail endpoint and a template preview rendering service, alongside a fix for a bug where the "Paused" badge was incorrectly displayed on the dashboard. The printer detail page now includes a metadata block. Review feedback identifies opportunities to simplify template schema reconstruction and optimize LabelRenderer instantiation. Crucially, it is noted that synchronous rendering operations should be wrapped in asyncio.to_thread to avoid blocking the FastAPI event loop, adhering to the project's performance guidelines.
Lines 140 and 149 of render_preview passed the raw user-supplied query parameter `key` to _log.warning. Replace with `template_row.key`, which is the sanitised value read back from the database after the 404 check, so no user-controlled data reaches the log calls. Fixes CodeQL alert CWE-117 (log injection) in both branches. Refs #22
Printer.connection is dict[str, object] — USB printers store
{interface: "usb"} with no host/port. The metadata block now wraps
the host:port <dd> in a conditional that checks both keys exist;
USB printers fall through to an "Interface" row showing the interface
value. The stub printer template in base.go is updated to render real
metadata fields so handler tests can assert on model, host:port, and
the USB badge. A new TestPrinterDetailUSBConnection test covers the
USB code path and guards against regression.
Refs #22
…ient The render_preview endpoint existed in the backend since commit 0f93bb3 but was absent from the OpenAPI snapshot, so the generated client had no typed method for it. Add the path to openapi.snapshot.json and re-run `make gen-client` (oapi-codegen v2.7.0). The generated client.gen.go now exposes RenderPreviewApiRenderPreviewPost on both the Client and ClientWithResponses types. The hand-written HubClient.RenderPreview in client.go is kept because the endpoint returns binary image/png — the generated method returns a raw *http.Response which the wrapper converts to []byte with proper error handling. Refs #22
Two MEDIUM perf findings in render_preview: 1. LabelRenderer was instantiated on every POST /api/render/preview request, paying repeated font-loading cost. The lifespan now creates a single shared instance stored in app.state.label_renderer (also passed to PrintService so there is only one renderer in the process). The route reads it from app.state and falls back to a fresh instance for tests that don't wire the full lifespan. 2. image.save(buf, "PNG") is a synchronous CPU-bound call that blocked the event loop. The render + encode is now wrapped in a private _render_and_encode() helper executed via asyncio.to_thread so the loop stays free during image processing. Refs #22
…nger regression tests Address remaining MEDIUM Bot-Review findings on PR #82: - Add _validate_preview_sample_fields helper that rejects (422) templates whose preview_sample is missing fields referenced by their elements, instead of silently rendering empty output (Copilot finding). - Drop the redundant 'strip preview_sample from schema_dict' since TemplateSchema now includes preview_sample as an optional field (Copilot + Gemini finding). Comment removed. - TemplateSchema.preview_sample sequence type changed from list[str] to tuple[str, ...] so that the frozen=True schema is deeply immutable — pydantic frozen prevents attribute reassignment but does NOT freeze nested mutable containers (Copilot finding). - dashboard_test now asserts printer-grid wrapper presence + <nil> absence — the prior assertions would have passed even if Paused were still rendered as a *bool pointer (Copilot finding on test quality). - printer_test now asserts the model, host:port, and data-enabled attribute round-trip through the rendered body — confirming the new metadata block actually surfaces the printer data, not just the container div (Copilot finding on test quality). Refs #22
## 0.6.0 (2026-05-18) * docs(api): address PR #79 bot-review — privacy sanitise + protocol self + consistency fixes ([61602d0](61602d0)), closes [#79](#79) [#22](#22) * docs(api): pure-vector SVG samples for all 12 seed templates (#83) ([a066dde](a066dde)), closes [#83](#83) [#81](#81) [#22](#22) * docs(phase-7b): foundation design spec — init-robustness + health-split + pangolin-bypass (#74) ([c5a7964](c5a7964)), closes [#74](#74) [fosrl/pangolin#3099](fosrl/pangolin#3099) [#22](#22) [#22](#22) * docs(phase-7d): foundation design — print API + QR tab + hangar plugin (#79) ([cdaedeb](cdaedeb)), closes [#79](#79) [strausmann/hangar#63](https://github.com/strausmann/hangar/issues/63) [#22](#22) * fix: 3 production bugs from local smoke-test + dev/ folder ([c0fc903](c0fc903)), closes [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) * fix: Phase 6b code-cleanup — 6 audit findings + plugin pattern wired ([f77aa44](f77aa44)), closes [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) [#67](#67) * fix(api): Phase 7b.1 — upsert name-collision + /readiness proxy gap (#77) ([4e74a03](4e74a03)), closes [#77](#77) [#76](#76) [#22](#22) [#76](#76) [#22](#22) [#77](#77) [#1](#1) [#76](#76) [#22](#22) * fix(ui): printer detail metadata + template preview + paused-bool gap (#82) ([52bab83](52bab83)), closes [#82](#82) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) * feat(api): Phase 7b foundation — init, datetime-TZ, /readiness, status cache, proxy widening (#75) ([784decc](784decc)), closes [#75](#75) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [HI#priority](https://github.com/HI/issues/priority) [#75](#75) [#22](#22) * feat(ui): proxy legacy /print to backend (restores First-Print smoke curl) (#84) ([8ef36ed](8ef36ed)), closes [#84](#84) [#22](#22) [#22](#22) [#84](#84) [#22](#22) [skip ci]
Summary
Drei UI-Bugs aus Production-Smoke nach Phase 7b.1 + ein zugehöriger Refactor:
paused=falsezeigte trotzdem "Paused" auf der Homepage. Root cause: Pydantic-Schemapaused: bool = False(mit default) → OpenAPIoptional-with-default→ oapi-codegenPaused *bool, omitempty→ Go-Template{{if pointer}}truthy für jeden non-nil-pointer auch&false. Fix: Schemapaused: bool(required), Route setzt Wert explizit.GET /api/printers/{id}404te (Endpoint fehlte). Frontendprinter.gofetchte nur status/tape/queue. Fix: neuer Backend-Detail-Endpoint + Frontend-Handler-4. errgroup +printer.htmlMetadata-Block./api/render/previewfehlte. Fix: neuer POST-Endpoint + LabelRenderer-Integration. Returns image/png.preview_sampleBlock in der Template-Definition, Route ohne hardcoded-mockup-fallback, 12 Seed-Templates erweitert.Commits
fix(api): PrinterRead.paused non-optionalfeat(api): GET /api/printers/{id} + UI-Metadata-Blockfeat(api): POST /api/render/previewrefactor(api): preview_sample im Template statt Route-MockupTest plan
Out-of-scope / Follow-ups
last_probe_age_sist schon drin; eine echte TCP-connect-Latenz-Probe wäre eigenes FeatureRefs #22