Skip to content

fix(ui): printer detail metadata + template preview + paused-bool gap#82

Merged
strausmann merged 9 commits into
mainfrom
fix/ui-printer-template-paused-metadata
May 17, 2026
Merged

fix(ui): printer detail metadata + template preview + paused-bool gap#82
strausmann merged 9 commits into
mainfrom
fix/ui-printer-template-paused-metadata

Conversation

@strausmann
Copy link
Copy Markdown
Owner

Summary

Drei UI-Bugs aus Production-Smoke nach Phase 7b.1 + ein zugehöriger Refactor:

  1. Paused-Badge falsch — Drucker mit paused=false zeigte trotzdem "Paused" auf der Homepage. Root cause: Pydantic-Schema paused: bool = False (mit default) → OpenAPI optional-with-default → oapi-codegen Paused *bool, omitempty → Go-Template {{if pointer}} truthy für jeden non-nil-pointer auch &false. Fix: Schema paused: bool (required), Route setzt Wert explizit.
  2. Printer-Detail ohne MetadatenGET /api/printers/{id} 404te (Endpoint fehlte). Frontend printer.go fetchte nur status/tape/queue. Fix: neuer Backend-Detail-Endpoint + Frontend-Handler-4. errgroup + printer.html Metadata-Block.
  3. Template-Preview "Preview unavailable"/api/render/preview fehlte. Fix: neuer POST-Endpoint + LabelRenderer-Integration. Returns image/png.
  4. Refactor: Preview-Sample lebt im Template — Commit 3 hatte hardcoded per-app sample-data in der Route. Falsche Verantwortungs-Lokalität — jedes Template muss seine Preview-Daten selbst deklarieren. Neuer optionaler preview_sample Block in der Template-Definition, Route ohne hardcoded-mockup-fallback, 12 Seed-Templates erweitert.

Commits

SHA Was
414be23 fix(api): PrinterRead.paused non-optional
fe86563 feat(api): GET /api/printers/{id} + UI-Metadata-Block
0f93bb3 feat(api): POST /api/render/preview
e570896 refactor(api): preview_sample im Template statt Route-Mockup

Test plan

  • Backend 685 passed (was 678), 3 skipped, 0 failed
  • Frontend 4 packages OK, vet clean
  • ruff + ruff format + mypy clean
  • oapi-codegen client regeneriert für beide neuen Endpoints
  • Production-Smoke nach Merge: Homepage Drucker zeigt "Online" (nicht "Paused"); Printer-Detail zeigt Model+Host+Port+Enabled+Paused+Timestamps; Template-Detail-Preview zeigt gerenderte PNG (kein placeholder.svg mehr)

Out-of-scope / Follow-ups

  • Phase 7e Phase 7e: Template Layout System v2 — semantic qr-first + N-text-lines deklarativ #81 Template Layout System v2 — semantic qr-first + N-text-lines deklarativ, Renderer berechnet Positionen
  • Mini-PR Doku-SVG-Samples — pure-vector SVG pro Template als git-diff-bare Grundlage für Phase-7e-Brainstorming (Mini-PR folgt direkt)
  • Latenz-Anzeige im Printer-Detaillast_probe_age_s ist schon drin; eine echte TCP-connect-Latenz-Probe wäre eigenes Feature

Refs #22

…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
Copilot AI review requested due to automatic review settings May 17, 2026 21:14
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, 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 paused-Status-Anzeige, der Bereitstellung fehlender Metadaten auf der Drucker-Detailseite und der Einführung einer robusten, template-basierten Vorschau-Funktionalität für Etiketten.

Highlights

  • Paused-Badge Korrektur: Das Pydantic-Schema für den paused-Status wurde von optional (mit Default) auf required umgestellt, um zu verhindern, dass false als truthy Pointer in Go-Templates interpretiert wird.
  • Printer-Detail-Endpoint: Ein neuer Backend-Endpoint GET /api/printers/{id} wurde hinzugefügt, um vollständige Metadaten im Frontend-Detail-Panel anzuzeigen.
  • Template-Preview-Funktionalität: Ein neuer POST /api/render/preview-Endpoint wurde implementiert, der Vorlagen-Vorschauen als PNG rendert, basierend auf neuen preview_sample-Blöcken in den Template-Definitionen.
  • Refactoring der Preview-Daten: Preview-Beispieldaten wurden aus der Route in die jeweiligen Template-Definitionen (YAML) verschoben, um die Verantwortlichkeit zu dezentralisieren.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Comment thread backend/app/api/routes/templates.py Fixed
Comment thread backend/app/api/routes/templates.py Fixed
@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

❌ Patch coverage is 51.56250% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.14%. Comparing base (4e74a03) to head (d04d7e8).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
backend/app/api/routes/templates.py 45.45% 30 Missing ⚠️
backend/app/api/routes/printers.py 80.00% 1 Missing ⚠️
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              
Components Coverage Δ
Printer Backends (transport) 87.50% <ø> (ø)
Printer Models (drivers) 91.42% <ø> (ø)
Services 92.09% <ø> (ø)
REST API 87.04% <48.33%> (-4.26%) ⬇️
Pydantic Schemas 100.00% <100.00%> (ø)
Integration Plugins 100.00% <ø> (ø)
Files with missing lines Coverage Δ
backend/app/main.py 86.27% <100.00%> (+0.16%) ⬆️
backend/app/schemas/printer.py 100.00% <100.00%> (ø)
backend/app/schemas/template.py 100.00% <ø> (ø)
backend/app/api/routes/printers.py 83.60% <80.00%> (-0.16%) ⬇️
backend/app/api/routes/templates.py 55.88% <45.45%> (-44.12%) ⬇️
Flag Coverage Δ
backend 91.14% <51.56%> (-0.84%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e74a03...d04d7e8. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.paused required and adds GET /api/printers/{id}.
  • Adds frontend printer detail metadata fetching/rendering.
  • Adds backend template preview rendering from per-template preview_sample seed 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

Comment thread frontend/web/templates/printer.html
Comment thread backend/app/api/routes/templates.py
Comment thread backend/app/schemas/template.py Outdated
Comment thread frontend/internal/api/openapi.snapshot.json
Comment thread backend/app/api/routes/templates.py Outdated
Comment thread frontend/internal/handlers/dashboard_test.go
Comment thread frontend/internal/handlers/printer_test.go Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread backend/app/api/routes/templates.py Outdated
Comment thread backend/app/api/routes/templates.py Outdated
Comment thread backend/app/api/routes/templates.py Outdated
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
@strausmann strausmann merged commit 52bab83 into main May 17, 2026
18 of 19 checks passed
@strausmann strausmann deleted the fix/ui-printer-template-paused-metadata branch May 17, 2026 22:57
github-actions Bot pushed a commit that referenced this pull request May 18, 2026
## 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]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants