Skip to content

docs(phase-7d): foundation design — print API + QR tab + hangar plugin#79

Merged
strausmann merged 1 commit into
mainfrom
docs/phase-7d-foundation-spec
May 17, 2026
Merged

docs(phase-7d): foundation design — print API + QR tab + hangar plugin#79
strausmann merged 1 commit into
mainfrom
docs/phase-7d-foundation-spec

Conversation

@strausmann
Copy link
Copy Markdown
Owner

@strausmann strausmann commented May 17, 2026

Summary

Phase 7d foundation design spec — covers:

  1. Generic Print API (POST /api/preview + POST /api/print) with uniform γ-hybrid item schema (name/subtitle/qr_url/image_url + per-item copies + extras dict)
  2. Plugin protocol extensionsearch(query), get_item(item_id), optional get_children(item_id) for hierarchical sources
  3. QR Print Tab UI in label-printer-hub (HTMX, platform-toggle across 4 plugins, per-item copies input, tape-match preview)
  4. Hangar plugin module (new) with search + get_item + get_children
  5. Extension of 3 existing plugins (Grocy, Snipe-IT, Spoolman) with search + get_item

Cross-app coordination

The Hangar side of this integration is tracked in strausmann/hangar#63 (GitLab) — describes the new Hangar print-page UI + 3 new Hangar API endpoints the label-printer-hub plugin will consume.

Dependencies

Review

Please review the spec at docs/superpowers/specs/2026-05-17-phase-7d-foundation-design.md — 13 sections + self-review notes. Key decisions documented:

  • Item-payload model: γ-hybrid (Section 2)
  • Hierarchy logic placement: V1 Hangar decides for push, V2 Label-Hub uses plugin.get_children() for QR-Tab — both implemented
  • Tape-mismatch UX: modal-warn with force-override + audit flag
  • Multi-item × copies: total_jobs = sum(items × copies), each copy is one Job row
  • Plugin scope: 4 plugins in MVP (Grocy + Snipe-IT + Spoolman + new Hangar)

Bot-review fixes applied (commit 61602d0)

Objective findings from the 20 unresolved threads addressed in follow-up commit 61602d0 (pushed to main after squash-merge):

Fix Threads Details
Privacy sanitise Copilot #12, #13, #14 / Gemini #7 strausmann.cloudexample.com; self-review note corrected
PRINTER_HUB_ env prefix Copilot #15, #16, #17 HANGAR_BASE_URLPRINTER_HUB_HANGAR_BASE_URL throughout
Protocol self params Gemini #1–4 / Copilot #8, #9 Added self to lookup, search, get_item, get_children
Import path Copilot #25 app.integrations.registryapp.integrations.base
Module path consistency Copilot #24 Section 3 table label_hub_hangar/ aligned to backend/app/integrations/hangar/
Preview render count Copilot #19 items_rendered_count: Always min(N,5)Always 1 (items[0] only)
Migration column count Copilot #18 "two new optional columns" → "five"
HTMX auth note Copilot #20 Section 5 note now consistent with Section 9 auth matrix
PluginItem.extras Any Gemini #5 / Copilot #28 Added docstring note: Any intentional — plugin payloads are untrusted; PrintItem.extras is the tighter public boundary
Snipe-IT spelling Copilot #35 All occurrences corrected

Spec polish — deferred suggestions

The following bot suggestions are acknowledged but explicitly deferred to the writing-plans / implementation phase:

Deferred to implementation:

Refs #22

Generic /api/preview + /api/print accepting a uniform PrintItem schema
(name, subtitle, qr_url, image_url, copies, extras). Plugin protocol
extended with search() + get_item() + optional get_children() — Grocy,
SnipeIt, Spoolman gain search; new Hangar plugin module added. QR Print
Tab in the HTMX UI with platform toggle, per-item copies, tape-match
preview indicator. Aligns with strausmann/hangar#63 for the Hangar side.

Refs #22
Copilot AI review requested due to automatic review settings May 17, 2026 20:16
@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!

This pull request establishes the foundation for Phase 7d, enabling cross-application label printing within the label-printer-hub. It introduces a standardized API for external applications to request prints, adds a new user-facing QR Print Tab for streamlined selection and printing, and extends the plugin architecture to support advanced search and hierarchical data retrieval. These changes facilitate a cohesive integration with external systems like Hangar while maintaining strict tape-match validation and auditability.

Highlights

  • Generic Print API: Introduced new POST /api/preview and /api/print endpoints using a uniform γ-hybrid item schema to support cross-application label printing.
  • QR Print Tab UI: Added a new HTMX-based UI for searching items across plugins (Grocy, SnipeIt, Spoolman, Hangar), selecting templates, and managing print jobs with live tape-match feedback.
  • Plugin Protocol Extension: Extended the existing plugin interface with search(query), get_item(item_id), and optional get_children(item_id) methods to support hierarchical data sources.
  • Hangar Integration: Implemented a new Hangar plugin module to enable deep integration with the Hangar application, including support for hierarchical location lookups.
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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.93%. Comparing base (784decc) to head (a075665).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #79   +/-   ##
=======================================
  Coverage   91.93%   91.93%           
=======================================
  Files          70       70           
  Lines        3038     3038           
  Branches      259      259           
=======================================
  Hits         2793     2793           
  Misses        181      181           
  Partials       64       64           
Components Coverage Δ
Printer Backends (transport) 87.50% <ø> (ø)
Printer Models (drivers) 91.42% <ø> (ø)
Services 92.09% <ø> (ø)
REST API 91.30% <ø> (ø)
Pydantic Schemas 100.00% <ø> (ø)
Integration Plugins 100.00% <ø> (ø)
Flag Coverage Δ
backend 91.93% <ø> (ø)

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 784decc...a075665. 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

@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 adds the design specification for Phase 7d, detailing a generic print API, an HTMX-based QR print tab, and a new Hangar integration plugin. The review feedback identifies several issues in the documentation's code examples, including missing 'self' parameters in Python protocol methods and the use of 'Any' which violates type safety guidelines. Additionally, the reviewer noted a naming discrepancy for item identifiers between the backend logic and the plugin implementation, as well as a privacy violation concerning the use of real domain names.

name: str # e.g. "grocy"
display_name: str # e.g. "Grocy"

async def lookup(identifier: str) -> PluginItem | None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The lookup method signature in the IntegrationPlugin protocol is missing the self parameter. Please include it to align with Python's protocol requirements and maintain consistency with the implementation in Section 8.

Suggested change
async def lookup(identifier: str) -> PluginItem | None:
async def lookup(self, identifier: str) -> PluginItem | None:
References
  1. Ensure classes claiming to implement a Protocol fulfill all required methods to prevent runtime failures.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — added self parameter to lookup method signature in IntegrationPlugin Protocol. Refs #22

"""Existing — barcode/identifier lookup."""
...

async def search(query: str, limit: int = 20) -> list[PluginItemSummary]:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The search method signature in the IntegrationPlugin protocol is missing the self parameter.

Suggested change
async def search(query: str, limit: int = 20) -> list[PluginItemSummary]:
async def search(self, query: str, limit: int = 20) -> list[PluginItemSummary]:
References
  1. Ensure classes claiming to implement a Protocol fulfill all required methods to prevent runtime failures.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — added self parameter to search method signature. Refs #22

"""NEW Phase 7d — free-text search returning summary rows."""
...

async def get_item(item_id: str) -> PluginItem | None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The get_item method signature in the IntegrationPlugin protocol is missing the self parameter.

Suggested change
async def get_item(item_id: str) -> PluginItem | None:
async def get_item(self, item_id: str) -> PluginItem | None:
References
  1. Ensure classes claiming to implement a Protocol fulfill all required methods to prevent runtime failures.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — added self parameter to get_item method signature. Refs #22

class HasChildren(Protocol):
"""Optional capability — plugins with hierarchy implement this."""

async def get_children(item_id: str) -> list[PluginItemSummary]:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The get_children method signature in the HasChildren protocol is missing the self parameter.

Suggested change
async def get_children(item_id: str) -> list[PluginItemSummary]:
async def get_children(self, item_id: str) -> list[PluginItemSummary]:
References
  1. Ensure classes claiming to implement a Protocol fulfill all required methods to prevent runtime failures.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — added self parameter to get_children method signature in HasChildren Protocol. Refs #22

subtitle: str | None = None
qr_url: str | None = None # Plugin computes the deep-link
image_url: str | None = None
extras: dict[str, Any] = {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The use of Any in the PluginItem model's extras field violates the project's type safety guidelines. Please use the same union type defined for PrintItem in Section 2 to maintain consistency and strict typing.

Suggested change
extras: dict[str, Any] = {}
extras: dict[str, str | int | float | bool] = {}
References
  1. Type safety. mypy --strict on app/. Flag new Any introductions. (link)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; keeping Any on PluginItem.extras by design — plugin payloads are untrusted and carry arbitrary types before mapping into PrintItem. A docstring note was added to the spec to make this intentional. The tighter dict[str, str | int | float | bool] is the public boundary on PrintItem.extras. Documented in PR description under 'deferred suggestions'. Refs #22

state="queued",
tape_match_override=request.force_tape_mismatch and not tape_match,
plugin=request.plugin,
plugin_item_id=item.extras.get("plugin_item_id"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There is a discrepancy between the job creation logic and the plugin implementation. The logic here expects a plugin_item_id key in extras, but the HangarPlugin implementation in Section 8 uses slug. Consider standardizing the key name or adding plugin_item_id to the PrintItem model directly to ensure the audit trail is correctly populated.

References
  1. Attributes intended to satisfy a Protocol must use the exact names defined in that Protocol (e.g., use 'id' instead of 'printer_id' if the Protocol requires 'id').

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; deferred to writing-plans — the implementation will standardise on a reserved _plugin_item_id extras key. Documented in PR description under 'deferred suggestions'. Refs #22

id=loc["slug"],
name=loc["name"],
subtitle=" > ".join(loc["path"][:-1]) if len(loc["path"]) > 1 else None,
qr_url=f"https://hangar.strausmann.cloud/locations/{loc['slug']}",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The use of the real domain strausmann.cloud violates the project's privacy rules. Please use example.com as a placeholder for all documentation and example code, as per the Repository Style Guide and General Rules. This also applies to lines 356 and 439.

Suggested change
qr_url=f"https://hangar.strausmann.cloud/locations/{loc['slug']}",
qr_url=f"https://hangar.example.com/locations/{loc['slug']}",
References
  1. Privacy violations. Flag any hardcoded LAN IPs, real hostnames, real domains, real tokens, or PII. The maintainer's network must not be deducible from this repository. (link)
  2. Use RFC 5737 documentation IPs and 'example.com' placeholders instead of real LAN IPs, hostnames, or domains in documentation and code to maintain privacy.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Sanitised in 61602d0 — replaced all personal-domain references in code examples with example.com placeholder. Refs #22

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

This PR adds a Phase 7d foundation design spec for extending Label Printer Hub with a generic print API, QR Print Tab, and Hangar integration planning.

Changes:

  • Defines proposed print request/item schemas and /api/preview + /api/print behavior.
  • Extends the integration plugin contract with search/item/children capabilities.
  • Documents QR Print Tab UX, Hangar plugin behavior, auth dependencies, testing strategy, and DoD.
Comments suppressed due to low confidence (5)

docs/superpowers/specs/2026-05-17-phase-7d-foundation-design.md:103

  • The integration name is written as SnipeIt, but the repository consistently uses the product/trademark spelling Snipe-IT (for example docs/policies/trademarks.md:31 and existing UI docs). Use the established spelling in the plugin table and UI labels.
| SnipeIt | ✅ new | ✅ new | — | Same |

docs/superpowers/specs/2026-05-17-phase-7d-foundation-design.md:180

  • The UI mockup uses SnipeIt, but existing docs and trademark references use Snipe-IT. Keeping the visible label consistent avoids shipping a misspelled integration name in the QR tab.
|   (o) Grocy   ( ) SnipeIt   ( ) Spoolman   ( ) Hangar         |

docs/superpowers/specs/2026-05-17-phase-7d-foundation-design.md:16

  • This visible integration label should be Snipe-IT, not SnipeIt, to match the spelling used elsewhere in the project.
2. **QR Print Tab** (`/qr-print`) inside the label-printer-hub HTMX UI. Users can search across multiple external sources (Grocy, SnipeIt, Spoolman, Hangar) through a single search field with a platform toggle, select an item, choose a template, see a live preview with tape-match indicator, and print.

docs/superpowers/specs/2026-05-17-phase-7d-foundation-design.md:416

  • Use the established spelling Snipe-IT here instead of SnipeIt, consistent with the existing integration documentation.
- **Per-plugin search-result filters** (e.g. SnipeIt: filter by location/status; Grocy: filter by stock-level)

docs/superpowers/specs/2026-05-17-phase-7d-foundation-design.md:427

  • Use the established spelling Snipe-IT here instead of SnipeIt so the DoD matches the plugin's documented name.
- [ ] 3 existing plugins (Grocy, SnipeIt, Spoolman) implement `search()` + `get_item()` with at least one passing integration test against a mock HTTP server

Comment on lines +60 to +68
async def lookup(identifier: str) -> PluginItem | None:
"""Existing — barcode/identifier lookup."""
...

async def search(query: str, limit: int = 20) -> list[PluginItemSummary]:
"""NEW Phase 7d — free-text search returning summary rows."""
...

async def get_item(item_id: str) -> PluginItem | None:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — added self parameter to get_item (and all Protocol methods: lookup, search, get_item, get_children). Refs #22

class HasChildren(Protocol):
"""Optional capability — plugins with hierarchy implement this."""

async def get_children(item_id: str) -> list[PluginItemSummary]:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — added self parameter to get_children in HasChildren Protocol. Refs #22

Comment on lines +26 to +38
name: str # Required main line
subtitle: str | None = None # Optional secondary line
qr_url: str | None = None # QR-code payload (deep-link to source app)
image_url: str | None = None # Optional thumbnail
copies: int = Field(default=1, ge=1, le=99) # NEW (Δ from initial design): per-item copies
extras: dict[str, str | int | float | bool] = {} # Template-specific fields (Jinja-accessible)


class PrintRequest(BaseModel):
template_id: UUID
printer_id: UUID
items: list[PrintItem] # 1..N items
force_tape_mismatch: bool = False # Optional override
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; deferred — spec uses simplified syntax for readability. Implementation will use Field(default_factory=dict) as per project convention. Documented in PR description under 'deferred suggestions'. Refs #22

subtitle: str | None = None
qr_url: str | None = None # Plugin computes the deep-link
image_url: str | None = None
extras: dict[str, Any] = {}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; deferred — same as above. Will use Field(default_factory=dict) in implementation. Documented in PR description under 'deferred suggestions'. Refs #22

Comment on lines +326 to +330
return PluginItem(
id=loc["slug"],
name=loc["name"],
subtitle=" > ".join(loc["path"][:-1]) if len(loc["path"]) > 1 else None,
qr_url=f"https://hangar.strausmann.cloud/locations/{loc['slug']}",
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Sanitised in 61602d0 — replaced with https://hangar.example.com/locations/{slug}. Refs #22


```
Body: PrintRequest
Header: Idempotency-Key (optional — prevents double-submit on retry)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; deferred to Phase 7c which owns the idempotency layer design. Phase 7d spec declares the header as optional; full semantics (storage, scope, expiry, replay response) are Phase 7c scope. Documented in PR description under 'deferred suggestions'. Refs #22

Comment on lines +282 to +284
Mid-batch failure handling:
- If one job fails to enqueue (e.g. DB constraint), the response reports `accepted_count: 4, refused_count: 1, refused_reason: "Job for item B copy 2 conflicted"`.
- Already-enqueued jobs are NOT rolled back. The UI shows which ones succeeded.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; deferred — the pseudocode illustrates intent. Actual per-job commit strategy (savepoints vs individual flushes) will be specified in writing-plans. Documented in PR description under 'deferred suggestions'. Refs #22

| `GET /qr-print/*` (HTMX) | Pangolin-SSO required (browser) | — |
| `POST /qr-print/{preview,print}` | Pangolin-SSO OR API-Key | `read` / `print` |

When Phase 7c lands, the Hangar plugin needs an API-Key issued by Label-Hub admin UI. The key is stored in Hangar's env as `LABEL_HUB_API_KEY`.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; the section describes two different keys: settings.PRINTER_HUB_HANGAR_API_KEY (Label-Hub → Hangar) and LABEL_HUB_API_KEY (Hangar → Label-Hub). The phrasing was confusing — will be reworded in writing-plans to make the bidirectional auth explicit. Documented in PR description under 'deferred suggestions'. Refs #22

Comment on lines +14 to +16
1. **Generic Print API** (`POST /api/preview` + `POST /api/print`) that any external app can call with a uniform item payload. Hangar will be the first consumer (see strausmann/hangar#63), but the API is plugin-agnostic — Grocy/SnipeIt/Spoolman could also push from their own UIs if desired.

2. **QR Print Tab** (`/qr-print`) inside the label-printer-hub HTMX UI. Users can search across multiple external sources (Grocy, SnipeIt, Spoolman, Hangar) through a single search field with a platform toggle, select an item, choose a template, see a live preview with tape-match indicator, and print.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 61602d0 — all occurrences of SnipeIt replaced with Snipe-IT throughout the spec. Refs #22

plugin=request.plugin,
plugin_item_id=item.extras.get("plugin_item_id"),
api_key_id=current_api_key.id,
source_ip=request.client.host,
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged in 61602d0; deferred — pseudocode elides the None guard. Implementation will use source_ip=request.client.host if request.client else None. Documented in PR description under 'deferred suggestions'. Refs #22

@strausmann strausmann merged commit cdaedeb into main May 17, 2026
16 of 17 checks passed
@strausmann strausmann deleted the docs/phase-7d-foundation-spec branch May 17, 2026 22:32
strausmann added a commit that referenced this pull request May 17, 2026
…lf + consistency fixes

- Replace all strausmann.cloud / personal-domain references with example.com
- Replace HANGAR_BASE_URL with PRINTER_HUB_HANGAR_BASE_URL (settings prefix)
- Add self parameter to all Protocol method signatures (lookup, search, get_item, get_children)
- Fix import path: app.integrations.registry → app.integrations.base
- Fix module path: label_hub_hangar/ → backend/app/integrations/hangar/ (Section 3 vs 8 consistency)
- Clarify preview renders items[0] only (items_rendered_count always 1, not min(N,5))
- Fix Job-DB migration column count: "two" → "five"
- Align HTMX auth note (Section 5) with Section 9 auth matrix
- Add PluginItem.extras Any-intentional docstring note (reject tightening — keeps Any by design)
- Fix SnipeIt → Snipe-IT throughout (trademark spelling)
- Update Section 14 self-review privacy note

Refs #22
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]
strausmann added a commit that referenced this pull request May 18, 2026
Replace the pre-PR-#79-fix version of the Phase 7d spec with the
already-merged, privacy-sanitised version from main to eliminate
the merge conflict caused by main advancing past the PR base.

Refs #22
strausmann added a commit that referenced this pull request May 18, 2026
…min/api-keys UI (#85)

* docs(api): Phase 7c API-Auth design — 3-scope keys, bcrypt, rate-limit, /admin/api-keys UI

App-side API-Key authentication replacing the current
single-secret Pangolin-Bypass for all writes. 3 scopes
(read/print/admin), bcrypt hashed keys with prefix UI
display, in-memory token-bucket rate limit, per-key
printer ACL, audit-trail in Jobs table, transition path
via feature flag for the Pangolin-Bypass scope-downgrade.

Refs #22
Refs #78

* docs(api): address PR #85 bot-review — privacy sanitise + objective fixes

- 7c spec: fix key_prefix length example (11→12 chars consistent)
- 7c spec: datetime fields use DateTime(timezone=True) sa_column
- 7c spec: bootstrap seed key prints plaintext to Alembic stdout only (not app logger)
- 7c spec: require_scope() reconciles SSO=admin for /admin/* routes, 401 for missing creds / 403 for insufficient scope
- 7c spec: Pangolin-bypass reads feature flag settings.pangolin_bypass_scope_downgrade in pseudocode
- 7c spec: cache key is api_key_id (after prefix DB lookup), not raw hash; bcrypt uses asyncio.to_thread
- 7c spec: source_ip propagation via X-Real-IP injected by frontend proxy
- 7c spec: rate_limit_per_minute applies to all requests (not print-only); rename clarified
- 7c spec: RateLimiter uses asyncio.Lock + capacity-drift detection to handle concurrent requests + config changes
- 7d spec: replace strausmann.cloud with example.com (privacy scan fix)

Refs #22

* docs(api): sync 7d spec with main (privacy-clean version from PR #79)

Replace the pre-PR-#79-fix version of the Phase 7d spec with the
already-merged, privacy-sanitised version from main to eliminate
the merge conflict caused by main advancing past the PR base.

Refs #22
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.

2 participants