From 0ee4f5a6bafab4cdc6d3eea996ce005bb4e7061f Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 26 Jun 2026 20:01:51 -0400 Subject: [PATCH] USB label batch: brand colors + print-color-adjust; document migration strategy - USBLabelBatch.vue: recolor controls/selection/cell borders to the app CSS variables (was hardcoded #667eea/#28a745/#dc3545) and add `print-color-adjust: exact` so barcodes print even with "Background graphics" off. (Barcodes render as vector SVG, so no canvas->img change needed.) - ADR-004: document the resolved migration strategy - single core Alembic chain (`flask db upgrade`), bundled plugin schema folded into core (7c04), resolving the Phase 7B per-plugin-chain conflict footgun. External plugins may still ship their own chains. Co-Authored-By: Claude Opus 4.8 --- docs/adr/ADR-004-deployment-topology.md | 13 +++++++ frontend/src/views/print/USBLabelBatch.vue | 42 +++++++++++++--------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/docs/adr/ADR-004-deployment-topology.md b/docs/adr/ADR-004-deployment-topology.md index ac38049..0bb2340 100644 --- a/docs/adr/ADR-004-deployment-topology.md +++ b/docs/adr/ADR-004-deployment-topology.md @@ -56,6 +56,19 @@ The framework provides: 1. **Multi-tenant single instance.** Lower operational overhead at scale, easier cross-site reporting, but adds significant code complexity and risk: every query needs a tenant filter, auth gets complex, schema migrations affect every site at once, and a bug at one site can leak data across sites. Rejected for v1; revisit if and only if more than five sites adopt and operational overhead becomes painful. 2. **Hybrid: per-site DB but central app server.** Adds the operational complexity of multi-tenancy without isolating the failure domain (one app crash = all sites down). Rejected. +## Migration strategy (resolved) + +Deploys run a single core Alembic chain: `flask db upgrade`. Bundled plugins do +NOT carry their own migration chains - their tables are folded into the core +chain (migration `7c04_fold_plugin_schema`). This was a deliberate resolution of +the Phase 7B footgun where bundled-plugin baselines and the core baseline both +created the same tables, so `flask plugin upgrade-all` would conflict. A fresh +`flask db upgrade` reproduces the live schema exactly (verified on a scratch DB). + +External (out-of-tree) plugins per ADR-003 may still ship their own migrations; +the framework supports per-plugin chains for them. Only the in-tree bundled +plugins are consolidated into core. + ## Open questions - Should the framework provide an optional **read-only fleet roll-up** mode where a "central" instance can pull aggregate metrics from each site's API? Defer. Out of scope for v1. diff --git a/frontend/src/views/print/USBLabelBatch.vue b/frontend/src/views/print/USBLabelBatch.vue index 31468e4..aab6109 100644 --- a/frontend/src/views/print/USBLabelBatch.vue +++ b/frontend/src/views/print/USBLabelBatch.vue @@ -207,26 +207,27 @@ function print() { @page { size: letter; margin: 0; } .no-print { margin-bottom: 20px; padding: 20px; } -.controls { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; } +.controls { background: var(--bg-card); color: var(--text); padding: 20px; border-radius: 8px; margin-bottom: 20px; border: 1px solid var(--border); } .controls h3 { margin-top: 0; } .print-btn { padding: 10px 30px; font-size: 16px; cursor: pointer; - background: #667eea; + background: var(--primary); color: white; border: none; border-radius: 5px; margin-right: 10px; } -.print-btn:disabled { background: #ccc; cursor: not-allowed; } +.print-btn:hover:not(:disabled) { background: var(--primary-dark); } +.print-btn:disabled { background: var(--text-light); cursor: not-allowed; } .clear-btn { padding: 10px 20px; font-size: 14px; cursor: pointer; - background: #dc3545; + background: var(--danger); color: white; border: none; border-radius: 5px; @@ -237,7 +238,7 @@ function print() { padding: 10px 20px; font-size: 14px; cursor: pointer; - background: #28a745; + background: var(--success); color: white; border: none; border-radius: 5px; @@ -249,31 +250,32 @@ function print() { gap: 10px; max-height: 300px; overflow-y: auto; - border: 1px solid #ddd; + border: 1px solid var(--border); padding: 10px; - background: #fafafa; + background: var(--bg); } .usb-item { display: flex; align-items: center; padding: 8px; - background: white; - border: 1px solid #ddd; + background: var(--bg-card); + color: var(--text); + border: 1px solid var(--border); border-radius: 4px; cursor: pointer; } -.usb-item:hover { background: #f0f0f0; } -.usb-item.selected { background: #e7f1ff; border-color: #667eea; } +.usb-item:hover { border-color: var(--primary); } +.usb-item.selected { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); } .usb-item input { margin-right: 10px; } .usb-item label { cursor: pointer; flex: 1; } -.usb-item .alias { font-size: 11px; color: #666; } +.usb-item .alias { font-size: 11px; color: var(--text-light); } -.selected-count { font-weight: bold; margin: 10px 0; } -.selected-count .count { color: #667eea; } -.selected-count .pages { color: #28a745; } +.selected-count { font-weight: bold; margin: 10px 0; color: var(--text); } +.selected-count .count { color: var(--primary); } +.selected-count .pages { color: var(--success); } -.loading-msg { text-align: center; padding: 2rem; color: #666; } +.loading-msg { text-align: center; padding: 2rem; color: var(--text-light); } .sheets-container { display: flex; flex-direction: column; gap: 20px; } @@ -298,7 +300,7 @@ function print() { border: 1px dashed #ccc; overflow: hidden; } -.label-cell.has-content { border: 1px solid #667eea; } +.label-cell.has-content { border: 1px solid var(--primary); } .label-cell.empty { background: #fafafa; } .cell-1 { top: 0.875in; left: 1.1875in; } @@ -353,6 +355,12 @@ function print() { } @media print { + /* Force the barcodes/borders to print even when "Background graphics" is + off. */ + body, .print-sheet, .label-cell, .mini-label, .barcode-container { + -webkit-print-color-adjust: exact !important; + print-color-adjust: exact !important; + } body { padding: 0; margin: 0; background: white; } .no-print { display: none !important; } .sheets-container { gap: 0; }