Skip to content

feat(presets): Phase 1k.3 — User-Presets Store + CRUD + Preview (Refs #104 #101)#136

Open
strausmann wants to merge 14 commits into
mainfrom
feat/phase-1k3-user-presets
Open

feat(presets): Phase 1k.3 — User-Presets Store + CRUD + Preview (Refs #104 #101)#136
strausmann wants to merge 14 commits into
mainfrom
feat/phase-1k3-user-presets

Conversation

@strausmann

Copy link
Copy Markdown
Owner

Phase 1k.3 — User-Presets (Hub-Backend)

Baut die zuvor ungenutzte presets-Tabelle zum Layout-Preset-Store aus: ein "User-Template" = ein gespeichertes Preset aus content_type + tape_mm + Default-Feldwerten. Der Hangar-Editor (Companion, späteres Issue) nutzt diese Presets, um das Druck-Formular vorzubefüllen — der Druck-Pfad bleibt unangetastet (template-agnostisch).

Refs #104 #101

Wichtig: #104 hatte eine veraltete Prämisse

#104 wurde vor der Layout-Engine (Phase 1k.1a) geschrieben und nahm templates-Tabelle, TemplateLoader, YAML-Templates und einen templates:write-Scope an — die in 1k.1a alle entfernt wurden. Dieser PR ist entsprechend neu zugeschnitten (Spec dokumentiert die Abweichung). #104 sollte vom PM an diesen Stand angeglichen + Label superpowers:brainstorming gesetzt werden.

Architektur-Entscheidungen

  • Variante B: Hub = Store + Preview, Druck-Pfad bleibt template-agnostisch (keine Wieder-Kopplung, die 1k.1a entfernt hat)
  • B1: bestehende presets-Tabelle erweitert (bewusst akzeptierter Semantik-Blur)
  • Geometrie-Overrides: defern (YAGNI) — eigenes Folge-Issue
  • Auth: Writes require_print (damit Hangars print-scoped Key Presets verwalten kann; SSO-Browser-User nach ADR 0014 trusted), Reads/Preview require_read
  • content_type-Default: qr_three_lines

Änderungen

  • Model/Migration: presets +content_type+tape_mm (Alembic, server_default, leere Tabelle)
  • Repo: get_by_name (case-insensitiv)
  • Schemas: PresetCreatePayload/PresetUpdatePayload/PresetResponse
  • Service: PresetService (Validierung gegen ContentType/TAPE_GEOMETRY + CRUD), wiederverwendete Domain-Errors
  • API: /api/v1/presets CRUD + GET /{id}/preview.png (nutzt bestehende LayoutEngine, kein neuer Render-Pfad)

Qualität

  • TDD strict über 11 Commits, volle Suite 1069 passed / 0 failed, ruff clean, mypy nur 2 pre-existing (auth/dependencies.py)
  • Per-Task-Reviews (spec + quality) + Whole-Branch-Final-Review (Opus)
  • Final-Review fand & fixte: Critical qr_with_listing-Preset → 500 bei Preview (jetzt 422 abgelehnt), CWE-209 sichere Fehlermeldungen (wie print.py), Render via asyncio.to_thread

Out of Scope / Follow-ups

  • Editor-UI (Hangar-Companion)
  • Geometrie-Overrides; volle qr_with_listing/items-Unterstützung
  • Defensiver UnsupportedContentTypeError-Catch im Preview-Handler (aktuell unerreichbar)
  • Doku-Pipeline (MkDocs + Protokoll)

Spec & Plan

  • docs/superpowers/specs/2026-06-23-user-presets-design.md
  • docs/superpowers/plans/2026-06-23-phase-1k3-user-presets.md

🤖 Generated with Claude Code

strausmann and others added 14 commits June 23, 2026 14:03
…tiveNav-Werte

Die Nav-Bar hatte keinen Link auf /admin/printers, obwohl Phase 7 die Admin-UI
für Drucker-Verwaltung bereitgestellt hat. User mussten die Route raten oder
via Browser-Adresszeile direkt aufrufen.

Außerdem teilten die beiden Admin-Bereiche (Drucker + API-Keys) denselben
ActiveNav-Wert "admin" — der aktive Tab war nicht eindeutig.

Änderungen:
- layout.html: "Drucker" Link zwischen Templates und API Keys ergänzt
- admin_printers.go: ActiveNav = "admin-printers" (7 Handler)
- admin_api_keys.go: ActiveNav = "admin-api-keys" (5 Handler)
- base.go: TemplateData Docstring aktualisiert

Templates-Link bleibt drin — laut Issue #104 (Phase 1k.3 User-Templates) ist
das Konzept weiterhin geplant. Aktuell gibt der Link 503 weil der Backend-
Endpoint noch nicht da ist. Das wird mit #104 reaktiviert.

Refs Issue #104 (Templates Phase 1k.3)
Spec für Phase 1k.3. Prämisse von #104 war veraltet (templates-Table/
TemplateLoader/templates:write-Scope wurden in Phase 1k.1a entfernt). Neu
zugeschnitten auf:

- Variante B: Hub als Preset-Store + Preview, Druck-Pfad bleibt template-agnostisch
- B1: bestehende presets-Tabelle erweitern (content_type default qr_three_lines,
  tape_mm default 12)
- CRUD /api/v1/presets + GET /{id}/preview.png (nutzt LayoutEngine wieder)
- Writes require_print, Reads require_read
- Geometrie-Overrides defern (YAGNI)

Refs #104 #101

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9QnxBQkA1URkoVE14vxdc
… (Refs #104 #101)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01R9QnxBQkA1URkoVE14vxdc
- Neues Modul app/services/preset_service.py:
  - Domain-Errors PresetNotFoundError, DuplicatePresetNameError
  - _validate_layout(): prüft tape_mm ∈ TAPE_GEOMETRY und ContentType-Pflichtfelder
  - PresetService mit create/get/list_all/update/delete (CRUD vollständig)
  - Nutzt UnsupportedTapeError + ContentTypeDataMismatchError aus printer_backends.exceptions

- LayoutEngine.required_fields(@classmethod) als öffentlicher Accessor auf _REQUIRED_FIELDS

- Tests in test_preset_service.py ergänzt:
  - session-Fixture (in-memory SQLite, lokal in der Datei)
  - 8 neue Service-Tests (create happy/error×3, update happy/error, delete happy/error)
  - TDD: RED zuerst (ModuleNotFoundError), dann GREEN (10/10 passed)
  - ruff + mypy sauber

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9QnxBQkA1URkoVE14vxdc
…(Refs #104)

M1: Falsy-`or`-Guard durch explizites `is not None` ersetzt (konsistent
    mit übrigen Guards in PresetService.update).
M2: Test für inkompatiblen content_type-Wechsel beim update (ContentTypeDataMismatchError).
M3: Test für Namens-Duplikat beim update (case-insensitiv, DuplicatePresetNameError).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9QnxBQkA1URkoVE14vxdc
- presets_api_router in main.py importiert und via include_router registriert
- Smoke-Test test_presets_router_registered_in_app ergänzt (TDD: RED → GREEN)
- response_class=Response für preview.png-Endpunkt ergänzt (kein Default-JSON-Schema)
- test_openapi_completeness: Endpunkt-Bereich auf 28-55 erweitert (35 Endpunkte erwartet)
- test_openapi_completeness: Segment-Regex erlaubt jetzt Punkte für .png-Endpunkte

Refs #104
….to_thread (Refs #104)

Fix 1 (Critical): qr_with_listing-Presets werden mit UnsupportedContentTypeError
abgelehnt (→ 422), statt 500 beim Preview-Render. _validate_layout prüft vor
Tape-Validierung; create + update decken beide Pfade ab.

Fix 2 (Important): Alle str(exc)-Interpolationen in presets_api.py durch feste
deutschsprachige Meldungen ersetzt (CWE-209: keine internen Details an Clients).

Fix 3 (Important): render_preview_png lagert den CPU-gebundenen Render-Block
(engine.render + image.save) via asyncio.to_thread aus — analog print.py.

Tests: +test_create_rejects_qr_with_listing (Service),
       +test_create_qr_with_listing_returns_422 (API).
@gemini-code-assist

Copy link
Copy Markdown

Warning

Gemini encountered an error creating the summary. You can try again by commenting /gemini summary.

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.

1 participant