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 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
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.
|
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
|
## 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.
|
- 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.
|
||||||
|
|||||||
@@ -207,26 +207,27 @@ function print() {
|
|||||||
@page { size: letter; margin: 0; }
|
@page { size: letter; margin: 0; }
|
||||||
|
|
||||||
.no-print { margin-bottom: 20px; padding: 20px; }
|
.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; }
|
.controls h3 { margin-top: 0; }
|
||||||
|
|
||||||
.print-btn {
|
.print-btn {
|
||||||
padding: 10px 30px;
|
padding: 10px 30px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #667eea;
|
background: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-right: 10px;
|
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 {
|
.clear-btn {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #dc3545;
|
background: var(--danger);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -237,7 +238,7 @@ function print() {
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #28a745;
|
background: var(--success);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
@@ -249,31 +250,32 @@ function print() {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid var(--border);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #fafafa;
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.usb-item {
|
.usb-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background: white;
|
background: var(--bg-card);
|
||||||
border: 1px solid #ddd;
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.usb-item:hover { background: #f0f0f0; }
|
.usb-item:hover { border-color: var(--primary); }
|
||||||
.usb-item.selected { background: #e7f1ff; border-color: #667eea; }
|
.usb-item.selected { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
|
||||||
.usb-item input { margin-right: 10px; }
|
.usb-item input { margin-right: 10px; }
|
||||||
.usb-item label { cursor: pointer; flex: 1; }
|
.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 { font-weight: bold; margin: 10px 0; color: var(--text); }
|
||||||
.selected-count .count { color: #667eea; }
|
.selected-count .count { color: var(--primary); }
|
||||||
.selected-count .pages { color: #28a745; }
|
.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; }
|
.sheets-container { display: flex; flex-direction: column; gap: 20px; }
|
||||||
|
|
||||||
@@ -298,7 +300,7 @@ function print() {
|
|||||||
border: 1px dashed #ccc;
|
border: 1px dashed #ccc;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.label-cell.has-content { border: 1px solid #667eea; }
|
.label-cell.has-content { border: 1px solid var(--primary); }
|
||||||
.label-cell.empty { background: #fafafa; }
|
.label-cell.empty { background: #fafafa; }
|
||||||
|
|
||||||
.cell-1 { top: 0.875in; left: 1.1875in; }
|
.cell-1 { top: 0.875in; left: 1.1875in; }
|
||||||
@@ -353,6 +355,12 @@ function print() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media 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; }
|
body { padding: 0; margin: 0; background: white; }
|
||||||
.no-print { display: none !important; }
|
.no-print { display: none !important; }
|
||||||
.sheets-container { gap: 0; }
|
.sheets-container { gap: 0; }
|
||||||
|
|||||||
Reference in New Issue
Block a user