Skip to content

chore(deps-dev): bump conventional-changelog-conventionalcommits from 7.0.2 to 9.3.1#7

Merged
strausmann merged 1 commit into
mainfrom
dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.3.1
May 10, 2026
Merged

chore(deps-dev): bump conventional-changelog-conventionalcommits from 7.0.2 to 9.3.1#7
strausmann merged 1 commit into
mainfrom
dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.3.1

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github May 10, 2026

Copy link
Copy Markdown
Contributor

⚠️ Dependabot is rebasing this PR ⚠️

Rebasing might not happen immediately, so don't worry if this takes some time.

Note: if you make any changes to this PR yourself, they will take precedence over the rebase.


Bumps conventional-changelog-conventionalcommits from 7.0.2 to 9.3.1.

Release notes

Sourced from conventional-changelog-conventionalcommits's releases.

conventional-changelog-conventionalcommits: v9.3.1

Bug Fixes

  • skip mention linkification inside inline code (#1444) (c598bf1)

conventional-changelog-conventionalcommits: v9.3.0

Features

conventional-changelog-conventionalcommits: v9.2.0

Features

conventional-changelog-conventionalcommits: v9.1.0

Features

conventional-changelog-conventionalcommits: v9.0.0

⚠ BREAKING CHANGES

  • bang notes handling is removed, it already being handled in conventional-commits-parser

Features

  • bang notes handling is removed (#1355) (5150f47)
  • scope, scopeOnly and bumpStrict options were added (e2973e2)

conventional-changelog-conventionalcommits: v8.0.0

⚠ BREAKING CHANGES

  • Node >= 18 is required
  • conventional-recommended-bump: new Bumper exported class (#1218)
  • cleanup presets interface (#1215)
  • conventional-changelog-writer: rewrite to TypeScript (#1150)
  • Now all packages, except gulp-conventional-changelog, are ESM-only.

Features

Bug Fixes

... (truncated)

Changelog

Sourced from conventional-changelog-conventionalcommits's changelog.

9.3.1 (2026-03-29)

Bug Fixes

  • skip mention linkification inside inline code (#1444) (c598bf1)

9.3.0 (2026-03-04)

Features

9.2.0 (2026-03-01)

Features

9.1.0 (2025-07-10)

Features

9.0.0 (2025-05-19)

⚠ BREAKING CHANGES

  • bang notes handling is removed, it already being handled in conventional-commits-parser

Features

  • bang notes handling is removed (#1355) (5150f47)
  • scope, scopeOnly and bumpStrict options were added (e2973e2)

8.0.0 (2024-04-26)

⚠ BREAKING CHANGES

  • Node >= 18 is required
  • conventional-recommended-bump: new Bumper exported class (#1218)
  • cleanup presets interface (#1215)
  • conventional-changelog-writer: rewrite to TypeScript (#1150)
  • Now all packages, except gulp-conventional-changelog, are ESM-only.

Features

... (truncated)

Commits
  • 08f44dc chore(release): monorepo release (#1442)
  • c598bf1 fix(conventional-changelog-angular,conventional-changelog-conventionalcommits...
  • 7d4a4f6 chore(release): monorepo release (#1435)
  • 0d5a4a6 feat(conventional-changelog-angular,conventional-changelog-atom,conventional-...
  • 44bc44d chore(release): monorepo release (#1423)
  • 441af37 docs: fix libraries io links
  • b0721e1 feat(conventional-changelog,conventional-changelog-angular,conventional-chang...
  • b3bec6c chore(release): monorepo release (#1392)
  • 81da809 feat(conventional-changelog-conventionalcommits): scope option can be an arra...
  • 93c34e4 ci: eslint v9 (#1389)
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by dangreen, a new releaser for conventional-changelog-conventionalcommits since your current version.


@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript JavaScript/Node dependencies labels May 10, 2026
@dependabot
dependabot Bot requested a review from strausmann as a code owner May 10, 2026 16:48
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript JavaScript/Node dependencies labels May 10, 2026
strausmann added a commit that referenced this pull request May 10, 2026
When Dependabot opens a PR against main and main does not yet contain a
backend/pyproject.toml (e.g. before the first feature branch lands), the
existing actions/setup-python step would fail because cache: pip insists
on resolving requirements.txt or pyproject.toml at checkout time.

Fix by detecting the project layout up-front and:
- using setup-python WITH cache when a pyproject.toml exists (in either
  ./backend or ./), with explicit cache-dependency-path
- using setup-python WITHOUT cache otherwise, and skipping all the
  ruff/mypy/pytest steps with a single no-op message

Also moves all working directories explicitly so we can later add a
backend/ matrix entry without rewriting steps.

This unblocks PRs #2-#9 (currently red on the cache step) and prevents
the same class of failure for any future contributor branch that touches
infrastructure but doesn't include the backend yet.

Refs #2, #3, #4, #6, #7, #8, #9
@strausmann

Copy link
Copy Markdown
Owner

@dependabot rebase

@dependabot dependabot Bot changed the title chore(deps-dev): Bump conventional-changelog-conventionalcommits from 7.0.2 to 9.3.1 chore(deps-dev): bump conventional-changelog-conventionalcommits from 7.0.2 to 9.3.1 May 10, 2026
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.3.1 branch from bd428a3 to 8d67e1f Compare May 10, 2026 18:16
@strausmann
strausmann merged commit 9f5370f into main May 10, 2026
8 of 10 checks passed
@strausmann
strausmann deleted the dependabot/npm_and_yarn/conventional-changelog-conventionalcommits-9.3.1 branch May 10, 2026 18:17
github-actions Bot pushed a commit that referenced this pull request May 10, 2026
## 1.0.0 (2026-05-10)

* ci: make Python lint/test job branch-tolerant for dependabot PRs (#30) ([6ee8e16](6ee8e16)), closes [#30](#30) [2-#9](https://github.com/2-/issues/9) [#2](#2) [#3](#3) [#4](#4) [#6](#6) [#7](#7) [#8](#8) [#9](#9)
* ci(deps): bump the actions-all group across 1 directory with 14 updates (#9) ([a88a027](a88a027)), closes [#9](#9)
* chore: initial repository skeleton ([0319e7e](0319e7e))
* chore(deps-dev): bump @commitlint/cli from 19.8.1 to 21.0.0 (#3) ([2642b26](2642b26)), closes [#3](#3)
* chore(deps-dev): bump @commitlint/config-conventional (#4) ([44be5f9](44be5f9)), closes [#4](#4)
* chore(deps-dev): bump @semantic-release/exec from 6.0.3 to 7.1.0 (#2) ([a8097b0](a8097b0)), closes [#2](#2)
* chore(deps-dev): bump @semantic-release/github from 11.0.6 to 12.0.8 (#8) ([098e766](098e766)), closes [#8](#8)
* chore(deps-dev): bump conventional-changelog-conventionalcommits (#7) ([9f5370f](9f5370f)), closes [#7](#7)
* chore(deps-dev): bump semantic-release from 24.2.9 to 25.0.3 (#6) ([306010f](306010f)), closes [#6](#6)
* feat(status): add Brother 32-byte status block parser (#29) ([ced0ff8](ced0ff8)), closes [#29](#29) [#11](#11) [#19](#19) [#29](#29)
* docs: refactor — ADRs in docs/decisions/, policies in docs/policies/, slim CLAUDE.md ([51c2cf1](51c2cf1)), closes [#1](#1)
* docs(decisions): ADR 0006 — PT vs QL ESC i S behaviour from Phase-0 hardware test ([0d12c63](0d12c63)), closes [#12](#12) [#11](#11)
* docs(decisions): ADR 0012 — layout management; clarify integration push/pull capabilities ([effcbdf](effcbdf)), closes [#17](#17) [#19](#19)
* docs(decisions): ADR 0013 + cart UI spec + AI-review workflow ([c72dc85](c72dc85)), closes [#26](#26) [#27](#27) [#28](#28)
* docs(docker): document image tag scheme (latest, 1.0.0, 1.0, 1) ([1f72396](1f72396))
* docs(examples): sample compose files for standalone/Traefik/Pangolin/Caddy ([a2f6f3d](a2f6f3d))

[skip ci]
github-actions Bot pushed a commit that referenced this pull request May 10, 2026
## 0.1.0 (2026-05-10)

* docs: refactor — ADRs in docs/decisions/, policies in docs/policies/, slim CLAUDE.md ([51c2cf1](51c2cf1)), closes [#1](#1)
* docs(ci): document mypy as hard gate; clarify CI gate policy in CONTRIBUTING (#31) ([e1e6f18](e1e6f18)), closes [#31](#31) [#30](#30) [#30](#30) [#30](#30)
* docs(decisions): ADR 0006 — PT vs QL ESC i S behaviour from Phase-0 hardware test ([0d12c63](0d12c63)), closes [#12](#12) [#11](#11)
* docs(decisions): ADR 0012 — layout management; clarify integration push/pull capabilities ([effcbdf](effcbdf)), closes [#17](#17) [#19](#19)
* docs(decisions): ADR 0013 + cart UI spec + AI-review workflow ([c72dc85](c72dc85)), closes [#26](#26) [#27](#27) [#28](#28)
* docs(docker): document image tag scheme (latest, 1.0.0, 1.0, 1) ([1f72396](1f72396))
* docs(examples): sample compose files for standalone/Traefik/Pangolin/Caddy ([a2f6f3d](a2f6f3d))
* docs(learnings): add code-review-patterns + reference from CLAUDE/CONTRIBUTING/AI configs (#33) ([0fc61d2](0fc61d2)), closes [#33](#33) [#29](#29) [#30](#30) [#32](#32) [#6](#6)
* ci: make Python lint/test job branch-tolerant for dependabot PRs (#30) ([6ee8e16](6ee8e16)), closes [#30](#30) [2-#9](https://github.com/2-/issues/9) [#2](#2) [#3](#3) [#4](#4) [#6](#6) [#7](#7) [#8](#8) [#9](#9)
* ci(deps): bump the actions-all group across 1 directory with 14 updates (#9) ([a88a027](a88a027)), closes [#9](#9)
* ci(release): trigger releases via cron + workflow_dispatch only (#32) ([9941958](9941958)), closes [#32](#32) [#32](#32) [#32](#32)
* chore(deps-dev): bump @commitlint/cli from 19.8.1 to 21.0.0 (#3) ([2642b26](2642b26)), closes [#3](#3)
* chore(deps-dev): bump @commitlint/config-conventional (#4) ([44be5f9](44be5f9)), closes [#4](#4)
* chore(deps-dev): bump @semantic-release/exec from 6.0.3 to 7.1.0 (#2) ([a8097b0](a8097b0)), closes [#2](#2)
* chore(deps-dev): bump @semantic-release/github from 11.0.6 to 12.0.8 (#8) ([098e766](098e766)), closes [#8](#8)
* chore(deps-dev): bump conventional-changelog-conventionalcommits (#7) ([9f5370f](9f5370f)), closes [#7](#7)
* chore(deps-dev): bump semantic-release from 24.2.9 to 25.0.3 (#6) ([306010f](306010f)), closes [#6](#6)
* chore(release): 1.0.0 ([15abeb3](15abeb3))
* feat(status): add Brother 32-byte status block parser (#29) ([ced0ff8](ced0ff8)), closes [#29](#29) [#11](#11) [#19](#19) [#29](#29)

[skip ci]
strausmann added a commit that referenced this pull request May 15, 2026
Five Critical + several Important findings from a fresh-eyes review.
The plan was producing tests against StatusBlock / Job / TemplateLoader
APIs that don't exist in the real codebase. Add a Phase 1.5 — Domain
Model Extensions — before any backend code is written:

* Task 1.5.1 — StatusBlock @Property tape_empty / cover_open /
  loaded_tape_mm (derived from the real errors IntFlag and
  media_width_mm fields). Plus tests/_helpers/status.make_status_block
  so subsequent tests don't spell out all 18 dataclass fields.
* Task 1.5.2 — TemplateNotFoundError in template_loader (subclasses
  KeyError; the route maps it to HTTP 404 per Acceptance #6).
* Task 1.5.3 — LookupFailedError umbrella + UnknownAppError as a
  subclass; AppLookupService.lookup wraps any plugin runtime
  exception. Route maps to HTTP 502 per Acceptance #7.
* Task 1.5.4 — Job carries error_code / error_message / error_detail
  (Acceptance #4 needs structured detail like {expected_mm, loaded_mm}).
* Task 1.5.5 — PrintQueue worker catches PrinterError subclasses,
  populates the three new Job fields, then transitions to FAILED.
  TapeMismatchError gets a typed detail dict.
* Task 1.5.6 — @runtime_checkable on _PrinterLike Protocol so the
  Phase 8 isinstance() check works.

Additional fixes:

* Phase 6.1 parse_status_reply delegates to the existing
  StatusBlockParser.parse instead of inventing a partial parser. The
  legacy _MEDIA_TYPE_LOOKUP table is removed.
* Phase 8 commit body no longer claims "returns empty bytes" —
  build_print_job raises NotImplementedError, which the code already
  does. Stale doc text removed.
* PTouchBackend._ptouch_print is now model-aware via _PTOUCH_PRINTER_
  CLASSES[model_id] lookup. Previously hardcoded PTP750W defeated
  Extensibility Path 1 (PT-P900, PT-E550W).
* PrintService no longer forwards copies to queue.submit. Multi-copy
  delivery is a Phase-5 follow-up; clients can post N times today.
  Test assertion updated to "copies not in kwargs"; commit body of
  the PrintService task corrected.
* app/main.py snippet has `import logging` (was missing while
  _log = logging.getLogger(__name__) was already present).
* app/api/routes/print.py snippet now has one consolidated import
  block — the previous "Required imports for the route module"
  trailing block is folded in to prevent paste mistakes.
* Convention note added at the top of Phase 2 spelling out that
  every later test uses make_status_block(...) instead of
  StatusBlock(...). The implementer rewrites the six existing
  StatusBlock(...) call sites to make_status_block(...) accordingly.

Refs #22
strausmann added a commit that referenced this pull request May 15, 2026
TDD-strict implementation plan for the First-Print pipeline, tracking
docs/designs/2026-05-15-first-print.md.

17 phases, ~70 atomic tasks. Phase 1.5 (Domain Model Extensions)
extends StatusBlock with derived properties, adds
TemplateNotFoundError / LookupFailedError, structured error fields
on Job, and the @runtime_checkable decorator on _PrinterLike —
unblocking three Acceptance Criteria (#4, #6, #7).

Plan covers:
- Phase 0: commitlint scope (printer-backends)
- Phase 1: ptouch + Brother status block + SNMP OID references
- Phase 1.5: Domain model extensions
- Phase 2: PrinterError hierarchy (incl. SnmpDiscoveryError + SnmpQueryError)
- Phase 3: PrinterBackend Protocol
- Phase 4: MockPrinterBackend
- Phase 5: BackendRegistry + entry_points
- Phase 6: ESC i S status query, PTouchBackend, SNMP helper
- Phase 7: ModelRegistry.find_by_model_id + entry_points
- Phase 8: PTP750WDriver + make_queue_printer + _PTPQueuePrinter
- Phase 9: Pydantic schemas (PrintRequest, RawLabelData, etc.)
- Phase 10: PrintService orchestrator
- Phase 11: REST routes incl. live SNMP block while PRINTING
- Phase 12: Settings (printer_backend, printer_model, discover_via_snmp, snmp_community, queue_timeout_s)
- Phase 13: Lifespan with SNMP-first discovery + fallback
- Phase 14: Integration tests (happy + tape mismatch + offline + SNMP discovery)
- Phase 15: Hardware smoke script
- Phase 16: Final verification + handoff (implementer never pushes)

Went through Gemini + superpowers code-review iterations (6 + 5 + 12
findings, all addressed or explicitly deferred). CI checks all green
including privacy / secret scan.

Refs #22
github-actions Bot pushed a commit that referenced this pull request May 16, 2026
## 0.4.0 (2026-05-16)

* ci(deps): bump the actions-all group with 2 updates (#44) ([d4a0401](d4a0401)), closes [#44](#44)
* docs(designs): First-Print pipeline design (#57) ([b100574](b100574)), closes [#57](#57) [#58](#58) [#22](#22)
* docs(plans): First-Print implementation plan (#58) ([e7c93de](e7c93de)), closes [#58](#58) [#4](#4) [#6](#6) [#7](#7) [#22](#22)
* feat(integrations): plugin architecture for lookup clients (Phase 3.5) (#55) ([488256b](488256b)), closes [#55](#55) [#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) [#55](#55) [#22](#22)
* feat(templates): default templates + TemplateLoader (Phase 4) (#56) ([7e500aa](7e500aa)), closes [#56](#56) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22)

[skip ci]
strausmann added a commit that referenced this pull request May 16, 2026
…ed) (#59)

* build(ci): add printer-backends scope to commitlint

First-Print introduces app/printer_backends/, which is conceptually
separate from app/printer_models/ (driver vs. transport). Add the
matching commit scope so backend-layer changes can be tagged correctly
in the changelog.

Refs #22

* docs(printer-backends): record ptouch entry-points + status gap

Reference for implementers: pinned ptouch classes we depend on,
exception hierarchy to wrap, and the explicit gap that ptouch
exposes no public status-query API — so PTouchBackend must
implement ESC i S over a raw socket.

Refs #22

* docs(printer-backends): wire-level Brother status block (ESC i S)

Single-page reference for the 3-byte request + 32-byte reply
implemented in PTouchBackend.query_status(). Pulled from Brother
Raster Command Reference (PT-Series).

Refs #22

* docs(printer-backends): Brother SNMP OIDs reference

Pins the two SNMP queries First-Print depends on:

* 1.3.6.1.4.1.2435.2.3.9.1.1.7.0 — Brother private PJL string, used
  for lifespan model discovery via ModelRegistry.find_by_pjl
* 1.3.6.1.2.1.25.3.5.1.1.1 / .2.1 — standard Host-Resources Printer
  MIB hrPrinterStatus + hrPrinterDetectedErrorState, used for live
  status while a print is running.

Notes on the bitmap of hrPrinterDetectedErrorState and why SNMP is
not enough on its own (no direct tape_mm — uses ESC i S for that).

Refs #22

* feat(status): derived tape_empty / cover_open / loaded_tape_mm properties

StatusBlock already carries every Brother byte (media_width_mm,
media_type, errors IntFlag, ...). The First-Print backend layer
prefers a tape_empty / cover_open / loaded_tape_mm vocabulary in
its validation chain. Add the three as @Property on the existing
dataclass without changing the parser or any persisted field.

Plus tests/_helpers/status.py: make_status_block(loaded_tape_mm=...,
tape_empty=..., cover_open=...) so every later test stops needing
to spell out all 18 dataclass fields.

Refs #22

* feat(api): TemplateNotFoundError typed exception

TemplateLoader.get() raised bare KeyError. The /print route needs a
typed exception to map onto HTTP 404 (Acceptance #6).
TemplateNotFoundError subclasses KeyError so any existing catcher
keeps working; new code can pattern-match precisely.

Refs #22

* feat(api): LookupFailedError umbrella exception

Typed exception family for /print route HTTP 502 (Acceptance #7).
LookupFailedError is the umbrella; UnknownAppError (config mismatch)
is a subclass. AppLookupService.lookup wraps any runtime exception
from the plugin into LookupFailedError so the REST layer has one
type to catch.

AppLookupNotFoundError (entity-not-found) is explicitly re-raised
unchanged — it is not a transport failure and must not be swallowed.

Deviation from plan: IntegrationRegistry.get raises
IntegrationNotFoundError (not KeyError) — except clause adjusted
accordingly. Monkeypatch target corrected to
app.integrations.registry.IntegrationRegistry.get.

Refs #22

* feat(queue): Job carries error_code / error_message / error_detail

REST /jobs/{id} surfaces these to the client (Acceptance #4 — tape
mismatch must produce error_code='tape_mismatch' and
error_detail={'expected_mm':24,'loaded_mm':12}). The worker (next
task) populates them when a PrinterError subclass surfaces from
the printer.print_image call. The existing error_msg / error_flags
fields stay for backward compatibility.

Refs #22

* feat(queue): worker maps PrinterError subclasses to Job error fields

When printer.print_image raises a PrinterError, the worker now
writes a structured (error_code, error_message, error_detail)
record onto the Job before transitioning to FAILED. TapeMismatchError
gets a typed detail dict carrying expected_mm + loaded_mm so the
REST client gets actionable diagnostics.

Other PrinterError subclasses map to error_code only. The legacy
error_msg field is kept in sync for backwards-compatible callers.

Also lands the app/printer_backends/ package with its exception
hierarchy (PrinterError + 8 subclasses incl. Snmp*) — earlier than
Phase 2 nominally specifies because the worker hook needs it now.
The Phase 2 exception-hierarchy task will only add the dedicated
test file (test_exceptions.py); the production code already lives.

Refs #22

* refactor(queue): runtime_checkable _PrinterLike

Phase 8 tests do isinstance(qp, _PrinterLike). That requires the
@runtime_checkable decorator; otherwise Python raises TypeError at
the isinstance call. runtime_checkable only relaxes isinstance --
the Protocol's structural shape is unchanged.

Refs #22

* test(printer-backends): coverage for PrinterError hierarchy

Parametrised test verifying every PrinterError subclass (8 in total
incl. Snmp* added in the SNMP-hybrid design) is properly rooted in
PrinterError → Exception, plus dedicated tests for TapeMismatchError
field access.

The production module landed earlier (commit f92dc0c) because the
PrintQueue worker mapping depended on it. This commit closes Phase 2
by adding the dedicated test surface.

Refs #22

* feat(printer-backends): PrinterBackend Protocol

Two-method runtime_checkable Protocol: print_image (encode + send)
and query_status (ESC i S equivalent). Backends are bound to one
host at construction time. send_bytes raw-raster escape hatch is
deliberately omitted — see design doc for rationale.

Refs #22

* feat(printer-backends): add MockPrinterBackend

In-memory PrinterBackend used by unit/integration tests and by local
dev runs without real hardware (PRINTER_HUB_PRINTER_BACKEND=mock).

Failure modes are constructor flags: offline, tape_empty, cover_open,
loaded_tape_mm. The backend records every printed image so tests can
assert dimensions and order.

Refs #22

* feat(printer-backends): BackendRegistry + entry_points discovery

Class-level registry of backend factory classes, populated at app
start via setuptools entry_points (group label_hub.printer_backends).
ensure_discovered() is idempotent. find_by_backend_id raises
UnknownBackendError with the list of registered options when the
requested backend is missing.

Third-party backends ship as pip packages declaring an entry-point
in this group; no core changes required.

Refs #22

* feat(printer-backends): register mock backend in entry_points

Declares mock = app.printer_backends.mock_backend:MockPrinterBackend
under the label_hub.printer_backends group so BackendRegistry.
ensure_discovered() picks it up at app start.

ptouch backend will be registered alongside in a later commit once
PTouchBackend exists.

Refs #22

* feat(printer-backends): ESC i S status query over asyncio socket

Implements the wire-level Brother status query that ptouch does not
expose. Uses asyncio.open_connection so the call is non-blocking
inside an async def; flushes via drain(), closes cleanly via
close() + wait_closed() to avoid truncating mid-transfer.

Parser delegates to the existing StatusBlockParser after a fast
header sanity check; malformed replies raise StatusQueryFailedError.

Refs #22

* feat(printer-backends): PTouchBackend wrapping ptouch library

Implements PrinterBackend against the ptouch Python library:

* query_status — uses the raw-socket ESC i S helper, makes exactly
  3 attempts with back-off (0s, 1s, 2s) on PrinterOfflineError.
* print_image — pre-validates against query_status (tape_empty,
  cover_open, tape mismatch); dispatches the synchronous ptouch.print
  via asyncio.to_thread; wraps ptouch's exception family into our
  PrinterError subtypes.
* from_settings — reads pt750w_host / pt750w_port and looks up the
  ptouch printer class from printer_model; raises clearly on empty
  host or unknown model.
* _ptouch_print is module-level + model-aware via
  _PTOUCH_PRINTER_CLASSES[model_id] — Extensibility Path 1 (PT-P900,
  PT-E550W) works without a fork.

Note: PrinterWriteError/PrinterPermissionError are caught before the
base PrinterConnectionError as both are subclasses of it — order
matters for correct exception routing.

Refs #22

* feat(printer-backends): register ptouch backend in entry_points

Joins the label_hub.printer_backends entry-points group so
BackendRegistry.ensure_discovered() picks it up at app start.

Refs #22

* feat(printer-backends): SNMP helpers (discovery + live status)

Two async helpers built on pysnmp's asyncio API:

* query_model_pjl(host) — reads Brother private OID 1.3.6.1.4.1.2435.
  2.3.9.1.1.7.0 → full PJL identification string. Used by the lifespan
  to resolve the model via ModelRegistry.find_by_pjl (ADR 0004).
* query_live_status(host) — reads Host-Resources Printer MIB
  hrPrinterStatus + hrPrinterDetectedErrorState in one round trip,
  returns LiveStatus { hr_printer_status, error_flags } for the
  /jobs/{id} response while a print is running.

Failure modes are separate: SnmpDiscoveryError stops app start;
SnmpQueryError is non-fatal at request time (live block is omitted).
SNMPv2c with a configurable community (default 'public'); printer is
on the LAN, so v2c is fine.

pysnmp 7.x API deviation: UdpTransportTarget no longer accepts the
address in __init__; production code uses
  await UdpTransportTarget.create((host, port), timeout=..., retries=0)
Tests monkeypatch get_cmd directly so they are unaffected by the
transport construction; an integration test would catch real-API
mismatches at runtime.

Test import fix: pysnmp.smi.rfc1902 does not re-export OctetString /
Integer directly in 7.x; tests use from pysnmp.proto import rfc1902.

Refs #22

* feat(printer-models): find_by_model_id + entry_points discovery

ModelRegistry gains find_by_model_id(model_id) — used by the lifespan
to resolve settings.printer_model to a driver class — and
ensure_discovered() which walks the label_hub.printer_models
entry-points group once at app start. register() now accepts class
objects too (entry-points return classes); the existing instance form
keeps working.

Refs #22

* feat(printer-models): PTP750WDriver + queue-printer factory

PT-P750W driver implements PrinterModel (model_id, dpi, pins, PJL/SNMP
signatures, width_to_pixels, query_status, build_print_job) and exposes
make_queue_printer(tape_registry, default_media_type=LAMINATED) which
produces a private _PTPQueuePrinter satisfying PrintQueue._PrinterLike.

query_status raises ValueError on a non-matching host argument rather
than silently ignoring it (the driver is bound to one backend).
build_print_job raises NotImplementedError so any unintended caller
fails loudly; the First-Print happy path uses backend.print_image
directly; raw-byte encoding is deferred until a concrete caller appears.

Registered at module import via ModelRegistry.register (cast to
type[PrinterModel] to satisfy mypy strict — Protocol conformance is
verified by runtime isinstance check) and via the
label_hub.printer_models entry-points group.

Refs #22

* feat(api): PrintRequest + RawLabelData + PrintOptions schemas

Top-level POST /print body. PrintRequest enforces exactly-one of
`lookup` or `data` via a model_validator. PrintOptions is frozen with
copies bounds (1..10). RawLabelData mirrors LabelData but rejects
source_app at the wire — PrintService sets it to "manual" for the
raw-data path.

PrintOptions uses Field(default_factory=) on PrintRequest so each
request gets its own instance (Pydantic shared-mutable-default
anti-pattern avoided).

Refs #22

* feat(api): PrintJobResponse + PrintJobStatusResponse schemas

POST /print returns a 202 with PrintJobResponse — status is the
literal 'queued'. GET /jobs/{job_id} returns PrintJobStatusResponse
typed as the real JobState enum (queued/paused/printing/completed/
failed/cancelled) so clients receive the same vocabulary the
PrintQueue uses internally.

`live: LiveStatus | None` is populated by the route handler when the
job is currently PRINTING (SNMP live-status fetched on demand).

Refs #22

* style(schemas): fix import sort order in Phase 9 test files

ruff I001 auto-fix: reorder imports so third-party (pydantic) follows
stdlib/local per isort convention enforced by ruff.

Refs #22

* feat(api): PrintService orchestrator

Three-step pipeline behind POST /print: template_loader.get → either
lookup_service.lookup (integration path) or LabelData(..., source_app=
'manual') from RawLabelData (raw-data path) → renderer.render →
print_queue.submit(printer_id, image, tape_mm=, **options).

source_app for the raw-data path is fixed to "manual" here so the
wire schema doesn't have to police it. Options propagate to submit
as keyword args (auto_cut, high_resolution). `copies` is intentionally
NOT forwarded for First-Print — multi-copy delivery is a Phase-5
follow-up (clients can post N times today).

Refs #22

* feat(api): POST /print + GET /jobs/{job_id}

POST returns 202 with PrintJobResponse on success. Synchronous errors
(TemplateNotFoundError, LookupFailedError) map to 404 / 502 with an
error_code in the JSON body; hardware/print errors travel to the
worker and surface via GET /jobs/{job_id}.

GET returns PrintJobStatusResponse with JobState (queued/paused/
printing/completed/failed/cancelled) and any error_* fields the
worker recorded. While the job is PRINTING and a printer host is in
app.state, the response includes a `live` sub-object fetched via SNMP
(1s timeout, non-fatal on failure). Unknown job_id is 404.

Refs #22

* feat(api): new First-Print settings (backend, model, snmp, timeout)

Five new fields on Settings:
* printer_backend — resolves against BackendRegistry at app start
  (built-ins: ptouch, mock)
* printer_model — fallback when SNMP discovery is off or fails
  (resolves against ModelRegistry)
* printer_discover_via_snmp — try SNMP discovery first, fall back
  to printer_model (default True)
* printer_snmp_community — SNMPv2c community (default 'public')
* printer_queue_timeout_s — graceful queue.stop() timeout

Existing pt750w_host / pt750w_port / ql820_host / ql820_port fields
are kept untouched. PTouchBackend.from_settings reads pt750w_host
and looks up the right ptouch class via printer_model.

Refs #22

* feat(api): lifespan wires plugin discovery + queue + service

App startup:
1. Discover printer-model + backend plugins via entry_points
   (idempotent ensure_discovered).
2. Resolve model id: SNMP-first via query_model_pjl when host is
   configured and discovery is enabled; falls back to
   settings.printer_model on SnmpDiscoveryError (warned in log).
3. Build the configured backend via BackendRegistry +
   from_settings(settings).
4. Resolve the driver via ModelRegistry.find_by_model_id and bind it
   to the backend.
5. Build the _PrinterLike via driver.make_queue_printer(tape_registry).
6. Start the PrintQueue with [printer].
7. Wire PrintService into app.state (template loader + renderer +
   queue + lookup_service + printer_id).

App shutdown: queue.stop(timeout_s=settings.printer_queue_timeout_s).

_LifespanManager wrapper intercepts ASGI lifespan scopes and runs
startup lazily on the first HTTP request when no real ASGI server
sends the lifespan scope (httpx ASGITransport test pattern). A real
server (uvicorn, TestClient) sends the lifespan scope which the
wrapper intercepts and relays without double-running the inner app.

ModelRegistry.ensure_discovered() handles module-type entry_points
(pt-series = app.printer_models.pt) by scanning module members for
PrinterModel classes, enabling correct re-registration after test
fixtures clear the registry.

Integration tests get an autouse fixture in
tests/integration/conftest.py that sets the mock backend so the
lifespan succeeds without hardware.

Unknown backend, unknown model, or SnmpDiscoveryError without a
printer_model fallback all raise at app start with clear errors.

Refs #22

* test(api): POST /print → GET /jobs/{id} happy path + 404

Drives the full lifespan: plugin discovery, mock backend, PrintQueue
worker, status polling. Asserts the job transitions to completed.

Template-not-found returns 404 synchronously with error_code set; no
job record is created.

Also fixes a latent bug exposed by the integration test: LabelRenderer.render
had parameters (data, template) but _RendererProto + PrintService called it as
(template, data). Parameter order is now aligned to the Protocol contract.
Removed the now-unnecessary type: ignore[arg-type] in main.py. All call
sites in unit tests updated accordingly.

Refs #22

* test(api): hardware smoke for PT-P750W First-Print

Manual smoke script (scripts/smoke_first_print.py) renders the
qr-only-24mm seed template with primary_id=SMOKE-001 and prints it
on a real PT-P750W identified by PRINTER_HUB_PT750W_HOST.

Adds the gated test tests/hardware/test_pt_p750w_smoke.py — skipped
by default, runs only with `pytest --hardware` and a configured host.
The conftest hardware-marker is already in place from earlier work.

Refs #22

* feat(api): trim whitespace on length axis to save tape material

Hardware smoke against a real PT-P750W revealed that the QR-only-12mm
template was producing 85mm-long labels for a ~12mm-wide QR. Background:
LabelRenderer assumes QL-series 300 DPI canvas geometry, but ptouch
prints 1:1 at PT-series 180 DPI — the canvas-pixel-to-mm ratio is wrong
on PT hardware.

Until the DPI-aware renderer refactor lands (follow-up), trim the
returned image to its inked bounding box on the length axis with a 6 px
(~1mm) margin. Height stays fixed — that's the pin-locked tape axis.

Real-world impact in the smoke test: QR-only-12mm went from 600×106 to
86×106 px → 12mm tape consumed instead of 85mm. 72.5 mm saved per print.

Also update test_seed_templates to reflect that width may now be trimmed
(asserts height pin-locked + 1 <= width <= DEFAULT_LABEL_WIDTH_PX).

Refs #22

* feat(printer-backends): SNMP preflight helpers (loaded tape + combined query)

Hardware smoke revealed ESC i S over TCP/9100 times out on PT-P750W
in idle state — the planned pre-print check doesn't work. SNMP runs
in parallel and is reliable.

Two new helpers:
* parse_loaded_tape_mm + query_loaded_tape_mm — read OID
  1.3.6.1.2.1.43.8.2.1.12.1.1 (prtInputMediaType), parse strings like
  '12mm(0.47")' into int mm. Returns None for empty / unparsable
  replies (no tape inserted).
* query_preflight — single round-trip combining hrPrinterStatus +
  hrPrinterDetectedErrorState + prtInputMediaType into a PreflightStatus
  dataclass. Used by PTouchBackend.preflight_check (next task).

Refs #22

* fix(printer-backends): use generic ptouch Tape*mm classes (not laminated)

Hardware smoke against a real PT-P750W revealed that
ptouch.PTP750W.get_tape_config() whitelists ONLY the generic Tape*mm
classes and raises ValueError on any LaminatedTape*mm subclass:

  ValueError: LaminatedTape24mm is not supported by PTP750W. Supported
  tapes: Tape3_5mm, ... Tape24mm, Tape36mm, ...

This is a hard regression-block guarded by a unit test that scans the
whole _PTOUCH_TAPE_CLASSES dict for 'Laminated' in the class name. The
test exists because the same trap is easy to fall into when adding new
tape widths.

The user's tape is still laminated — the physical Brother TZe-Tape works
fine; ptouch just doesn't model the laminated/non-laminated distinction
at this API surface. Media-type-specific handling lives in our own
TapeRegistry.lookup_pt(width_mm, media_type), which still differentiates
laminated vs heat-shrink etc. for our own validation.

Refs #22

* feat(printer-backends): PTouchBackend.preflight_check (SNMP-based)

Hardware smoke revealed ESC i S over TCP/9100 (the old query_status
path) times out on PT-P750W in idle state — the planned pre-print
check doesn't work. SNMP runs in parallel on UDP/161 and is reliable.

Adds preflight_check() that wraps query_preflight() from snmp_helper:
* Returns PreflightStatus(hr_printer_status, loaded_tape_mm, error_flags)
  on a healthy printer.
* Raises TapeEmptyError on noPaper error bit.
* Raises PrinterCoverOpenError on doorOpen error bit.
* Wraps SnmpQueryError as PrinterOfflineError.

Deliberately does NOT raise TapeMismatchError — the caller
(PrintService, next commit) compares preflight.loaded_tape_mm against
the request's tape_mm. Backend stays agnostic of what's being printed.

The old ESC-i-S query_status() stays in place for use during active
print sessions and as a fallback for other backends; this new method
is for the synchronous pre-print path only.

Refs #22

* feat(api): synchronous SNMP preflight + on_tape_mismatch + resume endpoint

User feedback after hardware smoke: "before submitting a print, always
check status + loaded tape via SNMP. Only print if request matches.
Otherwise: fail synchronously OR optionally queue the job, let user
change the tape and resume."

Three coupled changes:

1. PrintRequest gains on_tape_mismatch: Literal["fail", "queue"]
   (default "fail"). Documents the user's intent on mismatch.
2. PrintService.submit_print_job calls
   backend.preflight_check() BEFORE queue.submit. Three outcomes:
   * match → normal 202 + job_id, job will print.
   * mismatch + fail → raise TapeMismatchError → route maps to 409
     with error_detail={expected_mm, loaded_mm}. No job record.
   * mismatch + queue → job is created, immediately transitioned to
     PAUSED with error_code="tape_mismatch" + error_detail set.
     User changes physical tape, POSTs /jobs/{id}/resume to release.
   Other preflight failures (offline / tape empty / cover open) always
   raise synchronously → 503 / 409.
3. POST /jobs/{job_id}/resume — transitions PAUSED → QUEUED via
   PrintQueue.resume_job(), clears the tape-mismatch metadata.
   Returns 200 + updated status. Returns 404 if unknown, 409 if not PAUSED.

REST sync error map extended with TapeMismatchError, TapeEmptyError,
PrinterCoverOpenError, PrinterOfflineError. TapeMismatchError
responses include error_detail={expected_mm, loaded_mm}.

PrintService now takes `backend` as a constructor kwarg; lifespan
passes it in.

MockPrinterBackend gains preflight_check() so integration tests work
end-to-end. Integration tests updated: tape mismatch and offline now
assert synchronous 409/503 (pre-submit preflight) instead of the
previous async FAILED path.

Refs #22

* feat(api): worker preflight + queue pause on recoverable errors

User requirement: "Tape kann leer werden während des Drucks, muss
erkannt werden und zur Unterbrechung der Warteschlange führen."

Three coupled changes:

1. PTouchBackend.print_image now uses preflight_check() (SNMP-based,
   reliable on PT-P750W) instead of the broken ESC i S query_status()
   path. Pre-print validation is the same — tape mismatch / empty /
   cover open / offline raise the matching PrinterError subtype.
   query_status() stays in place for future use by custom backends.

2. PrintQueue worker classifies caught PrinterError into recoverable
   vs fatal:
   * Recoverable (TapeMismatchError, TapeEmptyError, PrinterCoverOpenError,
     PrinterOfflineError) — job goes to FAILED with error_code, AND the
     printer worker is paused via pause_printer(). The whole queue
     halts until POST /printer/resume.
   * Fatal (PrintFailedError, StatusQueryFailedError, …) — existing
     behaviour: job FAILED, queue continues with the next job.

3. New REST endpoint POST /printer/resume — wakes the paused worker
   so subsequent jobs in the queue resume. User changes the physical
   tape, closes the cover, fixes connectivity, then calls this.

This makes the queue self-protective: a tape running out mid-stream
no longer cascades into N failed jobs — it stops at the first
recoverable error and waits for the user to intervene.

Refs #22

* feat(api): smoke script auto-discovers loaded tape via SNMP

Updated the hardware smoke script to use the new APIs:

* Replaces backend.query_status() (legacy ESC i S, broken on PT-P750W
  in idle) with backend.preflight_check() (SNMP, reliable).
* Reads loaded_tape_mm from preflight and picks the matching
  qr-only-<n>mm seed template automatically (12 / 18 / 24 supported).
* Explicit error reporting for each PrinterError subtype with a
  non-zero exit code.

Verified end-to-end against a real PT-P750W: SNMP detects 12mm tape
idle, qr-only-12mm template is picked, render+trim yields an 86x106
image (~12mm tape used), printer accepts the job, label exits.

Refs #22

* ci(security): wire CODECOV_TOKEN + add CI/CodeQL/codecov README badges

The Codecov upload step ran tokenless before — that works for public
repos but is rate-limited and produces inconsistent uploads. CODECOV_TOKEN
is now configured at the repo level; wire it through the
codecov/codecov-action@v6 step and add a `flags: backend` so the
report appears under the right component in the Codecov dashboard.

README header gains three quality badges (CI, CodeQL, codecov)
alongside the existing License / Conventional Commits /
semantic-release / GitHub Issues — contributors see at a glance
whether main is green and where coverage stands.

Refs #22

* ci(security): also upload pytest JUnit XML to Codecov Test Analytics

Codecov has two adjacent features:
* Coverage (covered/uncovered lines) — already wired in a prior commit.
* Test Analytics (slow tests, flaky tests, pass/fail trends) — this
  commit. Requires JUnit XML output from pytest plus a separate
  uploader action.

pytest now emits both coverage.xml (cobertura) and junit.xml (JUnit
legacy format — the format codecov-test-results-action accepts).
codecov/test-results-action@v1 uploads junit.xml with the same
CODECOV_TOKEN that's already configured for the coverage upload.

Both XML artefacts are added to .gitignore so local pytest runs
don't leave stray files behind.

Refs #22

* docs(readme): drop dead project-board link

The /projects page on the repo is empty — no Projects (classic or v2)
have been created. Replace the broken link with a pointer to the
master tracking issue (#22) plus the open-issues list, which is where
phase progress actually lives.

Refs #22

* docs: deploy-ready env, backend-only compose, REST API + ENV docs

Three coupled additions so the feat/first-print branch can actually
be deployed and tested on an end-user docker host (hhdocker03 etc.):

* backend/.env.example — adds the five First-Print Phase-17 settings
  (PRINTER_HUB_PRINTER_{BACKEND,MODEL,DISCOVER_VIA_SNMP,SNMP_COMMUNITY,
  QUEUE_TIMEOUT_S}) with inline rationale for each. The existing
  per-model PT750W_HOST/PORT and QL820_HOST/PORT stay untouched.

* examples/compose.backend-only.yml — minimal compose recipe for
  early-development REST API testing. Builds the image from source
  (no GHCR pull required, no pre-built tag needed), maps host port
  8090 → container 8000, reads `.env` at the repo root. Designed for
  the phase where the frontend container isn't ready yet.

* README — adds a "REST API surface" table with all five endpoints
  (POST /print, GET /jobs/{id}, POST /jobs/{id}/resume, POST
  /printer/resume, GET /healthz), a `POST /print` body schema with
  on_tape_mismatch documented, and the synchronous-error-code map.
  Also adds an "Environment variables" reference table covering
  every PRINTER_HUB_* setting from the Settings model.

The Quick Start snippet now points at compose.backend-only.yml
(buildable today) instead of compose.standalone.yml (references a
not-yet-published frontend image).

Refs #22

* ci(coverage): tighten gates with --cov-fail-under, codecov.yml components, hardware-only exclusions

Make coverage drift fail CI loudly rather than silently:

- pytest invocation gains --cov-fail-under=80; the threshold mirrors
  [tool.coverage.report].fail_under so the gate surfaces in the pytest
  step's own TOTAL line.

- codecov.yml introduces project + patch status checks (project: drift
  ≤1pp + 80% floor; patch: 80% on changed lines) and per-component
  views aligned with the on-disk module layout (printer_backends,
  printer_models, services, api, schemas, integrations). Ignored paths
  cover tests, scripts, docs, examples, and frontend.

- _ptouch_print is marked `# pragma: no cover`: it runs only against
  real hardware and is monkeypatched in every unit test. Lifts the
  printer_backends folder from 84% to ~95% without losing real signal.

- coverage.run.omit drops app/__init__.py (package-metadata shim only;
  no measurable logic). main.py stays in scope — its lifespan wiring is
  exercised by integration tests.

- .gitignore picks up package-lock.json and backend/uv.lock so stray
  artefacts from local tooling don't end up in PRs.

Project coverage moves from 91.18% to 91.84% on the test suite as it
stands today.

Refs #22

* ci(docker): publish images for feature branches (slug) and main (dev) in addition to releases

Extend the existing release publisher to cover the full branch lifecycle:

  push to feature branch → ghcr.io/.../label-printer-hub-{backend,frontend}:<branch-slug>
  push to main           → ghcr.io/.../label-printer-hub-{backend,frontend}:dev
  release v1.2.3         → ghcr.io/.../label-printer-hub-{backend,frontend}:{1.2.3,1.2,1,latest}
                           (plus Docker Hub mirror if DOCKERHUB_USERNAME is set)

Implementation:

- on.push triggers added with path filters scoped to backend/, frontend/,
  and the workflow itself — docs-only pushes do not rebuild images.

- A concurrency group per ref cancels superseded branch builds but never
  release builds (a half-published release would leave the registry
  inconsistent).

- "Determine flavor and tag" classifies the run as release / dev /
  branch and emits both outputs. Replaces the previous tag-only step
  in both phases. Workflow_dispatch with no tag input now falls
  through to dev/branch behaviour (lets maintainers force a dev build
  from a feature branch via the Actions UI).

- docker/metadata-action tag rules are gated per flavor so each event
  produces exactly the tags it should. type=ref,event=branch handles
  branch-slug sanitisation for feature branches.

- Docker Hub is excluded from the images list for non-release flavors —
  branch/dev builds stay on GHCR only and don't pollute the public
  Docker Hub namespace.

The release path is unchanged in behaviour (same semver chain, same
multi-arch composition, same per-platform native runners).

Refs #22

* docs(governance): add Contributor License Agreement and link from PR template

Adapt the standard Dockhand-style CLA to Label-Printer-Hub: a perpetual,
worldwide, no-charge copyright + patent grant to the Maintainer with
explicit outbound-relicense permission. The wording mirrors the Finsys
CLA (which has its own field-tested legal precedent) with three local
adaptations:

  - Project name → Label-Printer-Hub
  - Maintainer   → Björn Strausmann (the GitHub repo owner)
  - Outbound license example → the MIT License under which the Project
    is currently distributed (instead of BSL/Apache 2.0)

The PR template's "Community Contribution License Agreement" section is
replaced with a short pointer to CLA.md plus a single acknowledgement
checkbox. The full legal text lives in CLA.md at the repo root so
GitHub renders it next to LICENSE — anyone evaluating the project can
read both side by side before contributing.

Why now: external contributors are starting to land via Copilot review
loops, and an unambiguous IP grant protects both sides.

Refs #22

* docs(readme): add CLA assistant badge

cla-assistant.io is now linked to this repo and reads the CLA text from
the public gist. The badge shows real-time signing status for any open
PR and links straight to the CLA page where contributors can sign once
for all future contributions.

Refs #22

* fix(ci): replace RFC1918 IPs and internal hostname references in tests/examples

Privacy scan in ci.yml flags any RFC1918 address (10.0.0.0/8, 172.16/12,
192.168/16, 100.64/10) and a list of internal hostnames so private
network artefacts never leak into a public release. Two regressions
were caught on the last push:

- backend/tests/ used the literal 10.0.0.5 as a fixture printer host in
  14 places. Replaced with 192.0.2.10 (RFC 5737, reserved for
  documentation — explicitly NOT a private network).

- examples/compose.backend-only.yml suggested running the container on
  "hhdocker03 (or any docker host …)" in a usage comment. Removed the
  internal-hostname example and kept just the generic guidance.

No functional change: tests use the host string only as an opaque
identifier (SNMP/TCP calls are monkeypatched). 355/355 tests still pass.

Refs #22

* docs(designs): spec for Templates Discovery API + minimal-payload validation

Closes the documentation gap identified during First-Print smoke testing:
the API requires `title` + `primary_id` + `qr_payload` on every POST /print
even when the chosen template (e.g. `qr-only-12mm`) renders only the QR.
Three coupled improvements lay out the cleanup path:

  1. GET /templates + GET /templates/{id} — discovery endpoints so clients
     learn per-template which payload fields the renderer will read, what
     role each field plays (qr-content / headline / subtitle / extra-line),
     tape width, and app-binding.
  2. RawLabelData.title/primary_id/qr_payload default to "" and
     PrintService validates against TemplateSchema.required_fields() at
     submit-time. Missing required fields raise a 422 with
     error_code=missing_template_fields and a detail body listing what's
     missing.
  3. Field(..., description=...) on RawLabelData and the new template-
     response schemas so FastAPI's Swagger UI at /docs becomes the
     canonical field reference (replacing the missing user-facing docs).

The spec is committed on feat/first-print because the validation hook
lives in PrintService which is part of PR #59. Implementation is scheduled
for AFTER PR #59 merges to main, on a fresh feat/templates-api-and-minimal-
payload branch off main — per user decision during brainstorming.

Output of superpowers:brainstorming. writing-plans runs next.

Refs #22
github-actions Bot pushed a commit that referenced this pull request May 17, 2026
## 0.5.0 (2026-05-17)

* feat(api): First-Print pipeline — POST /print to PT-P750W (E2E verified) (#59) ([d89d057](d89d057)), closes [#59](#59) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#22](#22) [#6](#6) [#22](#22) [#7](#7) [#22](#22) [#4](#4) [#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) [#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) [#59](#59) [#22](#22)
* feat(api): Phase 5 — SQLModel persistence layer (#63) ([806076f](806076f)), closes [#63](#63)
* feat(api): Phase 6a — REST API (21 endpoints, DB-backed) ([41da51d](41da51d)), closes [#18](#18) [#22](#22) [#14](#14)
* feat(api): Phase 6b — SSE EventBus for live status pages ([b589a30](b589a30)), closes [#14](#14) [#22](#22) [#22](#22)
* feat(ui): Phase 7a — Frontend-Basis (Tailwind v4 + 8 pages + SSE proxy) ([0b49d6a](0b49d6a)), closes [#15](#15) [#22](#22)
* fix(integration,printer-backends): perf — pooling + caching + SnmpEngine singleton + registry.clear  ([0e1d60f](0e1d60f)), closes [#67](#67) [#67](#67) [#67](#67) [#67](#67)
* fix(api): Phase 6b review-findings — 5 KRITISCH + 3 WICHTIG + 7 bot-review followups ([76f57e2](76f57e2)), closes [#14](#14) [#22](#22) [#65](#65) [63/#64](#64)
* fix(api): print-queue review-followups — race, 409 contracts, stop() cleanup ([650f823](650f823)), closes [#67](#67) [#67](#67) [#67](#67) [#68](#68) [#67](#67) [#68](#68) [#67](#67) [#68](#68) [#67](#67)
* ci(ci): raise commitlint header-max-length to 120 + add deps scopes (#60) ([d6be187](d6be187)), closes [#60](#60) [#43](#43)

[skip ci]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript JavaScript/Node dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant