Skip to content

feat(label-generator): print button, maker layout, and print rotation#1536

Open
Wrr2216 wants to merge 3 commits into
sysadminsmedia:mainfrom
Wrr2216:feat/label-generator-printing
Open

feat(label-generator): print button, maker layout, and print rotation#1536
Wrr2216 wants to merge 3 commits into
sysadminsmedia:mainfrom
Wrr2216:feat/label-generator-printing

Conversation

@Wrr2216

@Wrr2216 Wrr2216 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

  • bug
  • feature

What this PR does / why we need it:

Cleans up the label generator, mostly around the label maker (continuous tape) mode so it actually prints right on a real label printer. I have a Brother QL-820NWB with DK-2205 tape and was fighting the output, so a few of these are things I needed to make it usable.

  • frontend/pages/reports/label-generator.vue
    • Added a Print button next to Generate Page. It regenerates the labels and opens the print dialog. The form is already hidden in print so only the labels come out.
    • Maker labels now flow across the page and wrap instead of stacking one per row, and each label is forced onto its own printed page so it lines up with a single tape segment.
    • Added a Print rotation dropdown (None/90/180/270) for printers that rotate the page onto the tape.
    • Keyed the property inputs by ref instead of the array index. With the index key, switching modes recycled a number input into the Base URL text field and threw a "value cannot be parsed" error, which scrambled the inputs and broke generation.
  • frontend/lib/reports/label-generator.ts
    • Defaulted the maker preset to the DK-2205 roll (2.4" wide, 1" tall).
    • buildPageCss takes an optional rotation and swaps the page to portrait for 90/270.
    • Added buildRotateCss for the print-only transform that rotates and re-centers the label.
  • frontend/locales/en.json
    • New strings for the Print button and Print rotation, and a note on the maker instruction about using the system print dialog to set orientation.

Which issue(s) this PR fixes:

(none)

Special notes for your reviewer:

Worth knowing: Chrome does not reliably honor @page { size } for label printers (chromium bug 238303), so the real orientation control lives in the OS "Print using system dialog". The rotation dropdown is there for printers that rotate the page, but on my QL the actual fix was setting the printer orientation in the system dialog, which is why I added the hint in the maker instructions.

The typecheck on main is already throwing a pile of pre-existing errors (the EntitySummary/location stuff). This PR does not add to that.

Testing

  • Added unit tests for buildPageCss (rotation and page size) and buildRotateCss. pnpm vitest run lib/reports/label-generator.test.ts passes (27 tests).
  • eslint clean on the changed files.
  • Printed against a Brother QL-820NWB with DK-2205 tape to confirm the maker output and one-label-per-segment behavior.

Wrr2216 added 2 commits June 6, 2026 09:29
Move pure label-generation logic into frontend/lib/reports/label-generator.ts (types and functions: fmtAssetID, calculateGridData, calculateMakerGrid, makerPageSize, buildPageCss, presetFor, normalizeMeasure) and add comprehensive unit tests. Update the label-generator Vue page to import and use the new module, add an output-mode selector (sheet / label maker / custom), maker-specific inputs (labelsPerRow, labelGap), seed presets for modes, compute maker page size and inject @page CSS, and adapt page/grid calculation flow to handle maker vs sheet. Also add new localization strings for the label maker UI.
A handful of fixes to the label generator, mostly to get the label maker
(continuous tape) output printing the way it should.

- Add a Print button next to Generate Page that regenerates the labels and
  opens the print dialog. The form is already hidden in print, so only the
  labels come out.
- Lay the maker labels out across the page and wrap them instead of stacking
  one per row, and force one label per printed page so each lands on its own
  tape segment.
- Default the maker preset to a Brother QL DK-2205 roll (2.4" wide, 1" tall),
  which is what these are actually sized for.
- Add a Print rotation option (None/90/180/270) for printers that rotate the
  page onto the tape, plus a note pointing people at the system print dialog
  where the orientation actually lives.
- Key the property inputs by ref instead of index. Switching modes was
  recycling a number input into the base URL field, which threw a "value
  cannot be parsed" error and scrambled the inputs.
- Add unit tests for the page and rotation CSS helpers.
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6bd76a9a-f30d-43ae-b8e9-6bae13d99d8e

📥 Commits

Reviewing files that changed from the base of the PR and between da5c2fa and 63019b7.

📒 Files selected for processing (3)
  • frontend/lib/reports/label-generator.test.ts
  • frontend/lib/reports/label-generator.ts
  • frontend/pages/reports/label-generator.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/lib/reports/label-generator.test.ts
  • frontend/lib/reports/label-generator.ts

Summary by CodeRabbit

  • New Features

    • Added label printing with sheet, maker, and custom modes; maker-specific sizing, gaps, and single-row tape support.
    • Added print rotation options and page-size/CSS handling for accurate printed output.
    • “Generate page” and “Print page” flow now recalculates and opens the browser print dialog.
  • Tests

    • Added comprehensive tests covering formatting, grid/layout calculations, CSS generation, rotation, and presets.
  • Documentation

    • Expanded UI labels and configuration text for label printing options.

Walkthrough

Extracts label geometry, grid calculations, and print CSS into a reusable TypeScript library and integrates it into the label-generator Vue page to add sheet/maker/custom modes, maker-specific inputs, print rotation, tests, and updated English locale keys.

Changes

Label Generator Library and Mode Support

Layer / File(s) Summary
Library types, presets, normalization, and ID formatting
frontend/lib/reports/label-generator.ts
Adds Measure/LabelMode/LabelPreset/Grid types, exports DEFAULT_MEASURE/SHEET_PRESET/MAKER_PRESET, implements normalizeMeasure, presetFor, and fmtAssetID formatting.
Sheet grid calculation
frontend/lib/reports/label-generator.ts
Implements calculateGridData to compute columns/rows/gaps/paddings and return GridResult or page_too_small_card error.
Maker tape sizing and single-row grid
frontend/lib/reports/label-generator.ts
Implements makerPageSize and calculateMakerGrid to derive maker page metrics and single-row GridData; exports PrintRotation.
Print @page and rotation CSS builders
frontend/lib/reports/label-generator.ts
Adds buildPageCss (maker-only @page rule, 90/270 swap) and buildRotateCss (rotation transforms with recentering offsets).
Unit tests for formatting, grids, sizing, and CSS
frontend/lib/reports/label-generator.test.ts
New Vitest suite covering fmtAssetID, calculateGridData edge cases, makerPageSize/calculateMakerGrid, buildPageCss/buildRotateCss outputs, and presetFor.
Vue integration: imports, state, calc, print, and template
frontend/pages/reports/label-generator.vue
Imports helpers/types, adds mode and printRotation, extends displayProperties with labelsPerRow/labelGap, filters property inputs by mode, branches calcPages() by mode, adds printLabels()/preset watcher, injects CSS via useHead, and updates template with mode selector and maker UI.
English locale keys
frontend/locales/en.json
Adds/expands reports.label_generator keys for printing, rotation options, label gap/labelsPerRow, maker instructions, and output mode labels.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Component as label-generator.vue
  participant GridLib as label-generator lib
  participant CSS as buildPageCss/buildRotateCss
  participant Print as Browser Print
  User->>Component: Select mode / adjust inputs
  Component->>GridLib: presetFor(mode)
  GridLib-->>Component: LabelPreset
  Component->>Component: Apply preset & update displayProperties
  User->>Component: Generate page
  Component->>GridLib: calculateGridData() or calculateMakerGrid()
  GridLib-->>Component: GridData or error
  Component->>CSS: buildPageCss(mode, rotation)
  CSS-->>Component: `@page` rule (maker only)
  Component->>CSS: buildRotateCss(mode, rotation)
  CSS-->>Component: rotation CSS
  Component->>Print: printLabels() -> window.print()
  Print-->>User: Browser print dialog
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • sysadminsmedia/homebox#1306: Overlapping changes to frontend/pages/reports/label-generator.vue page calculation and print-label generation logic.

Suggested labels

⬆️ enhancement

Suggested reviewers

  • tankerkiller125
  • tonyaellie

🔒 Security Recommendations

  • CSS Injection: buildPageCss and buildRotateCss generate CSS injected via useHead. Confirm these functions never interpolate unvalidated user strings; keep inputs strictly typed (e.g., PrintRotation union) and sanitize/validate numeric inputs.
  • Input bounds: Add validation for user-provided sizes, counts, and gaps (non-negative, sensible upper bounds) to avoid extreme layout computations or rendering blowups.
  • Error handling: Ensure calculateGridData errors are surfaced safely to the UI (already toasts on failure) and that calling code prevents printing when GridResult is invalid.

"Gridlines hum, the presets sing,
Paper turns beneath the spring,
Rotate, recenter, margins fall—
Labels march in ordered thrall." 🏷️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding a print button, maker layout improvements, and print rotation support for the label generator.
Description check ✅ Passed The description covers all required sections: PR type (bug/feature), detailed explanation of changes with file-by-file breakdown, testing evidence, and special notes for reviewers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the ⬆️ enhancement New feature or request label Jun 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
frontend/lib/reports/label-generator.test.ts (1)

176-181: ⚡ Quick win

Add a 270° rotation assertion to lock symmetry behavior.

buildRotateCss tests currently skip 270°, leaving one branch unguarded. Add a dedicated 270 case so rotation recentering regressions are caught.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/lib/reports/label-generator.test.ts` around lines 176 - 181, Add a
new test that asserts buildRotateCss("maker", size, 270) produces the symmetric
recentering CSS (mirror of the 90° case) so the 270° rotation branch is covered;
locate the test block in label-generator.test.ts near the existing 90° test and
add a case named like "270 rotation sizes and re-centers the label onto the
swapped page" that calls buildRotateCss with rotation 270 and expects the
appropriate `@media` print CSS string for 270deg with the corresponding translate
offsets and transform-origin.
frontend/pages/reports/label-generator.vue (1)

403-409: ⚡ Quick win

Harden dynamic print CSS inputs before injecting style innerHTML.

At Line 407–408, generated CSS is injected via innerHTML. Please clamp/normalize numeric dimensions (labelWidth, labelHeight, labelsPerRow, labelGap) to finite bounds before CSS generation to reduce malformed CSS injection surface from tampered client state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/pages/reports/label-generator.vue` around lines 403 - 409, The
generated CSS is injected via useHead innerHTML using buildPageCss and
buildRotateCss with values from mode.value, makerSize.value and
printRotation.value; before calling those functions, validate and clamp any
numeric inputs (e.g., labelWidth, labelHeight, labelsPerRow, labelGap, rotation)
taken from makerSize or printRotation to finite, reasonable bounds (use
Number.isFinite checks and min/max limits) and normalize types (parseFloat/Int)
so buildPageCss/buildRotateCss always receive safe numbers; update the code path
that constructs the innerHTML (the useHead call) to pass the sanitized/clamped
values instead of raw state to prevent malformed CSS injection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/lib/reports/label-generator.ts`:
- Around line 228-230: The translation used to recenter rotated labels
incorrectly reuses the 90° vector for 270°; update the transform calculation in
the function that builds the CSS string (where shift and rotation are used and
the class .maker-label is returned) to invert the shift for 270°. Compute
translateX and translateY based on rotation (e.g., for rotation===90 use
translateX = -shift, translateY = shift; for rotation===270 use translateX =
shift, translateY = -shift; for 0/180 use 0 as appropriate) and then use those
variables in the translate(...) portion of the returned transform instead of
always using (-shift, shift).
- Line 141: The vertical gap calculation for rows uses page.height but should
use the printable area height; update the gapY computation in label-generator.ts
(where gapY is defined using rows, cardHeight) to use availablePageHeight
instead of page.height so top/bottom padding is honored and spacing is computed
as (availablePageHeight - rows * cardHeight) / (rows - 1) when rows > 1 (or 0
otherwise).

In `@frontend/pages/reports/label-generator.vue`:
- Around line 318-321: The calcPages() failure path currently returns early but
leaves stale pages visible/printable; update calcPages() so when result.ok is
false it clears the pages state (e.g., set pages = [] or pages.value = []) and
also set a flag or return a falsy value indicating calculation failed; then
update printLabels() to check that pages is non-empty (or the calc success flag)
before calling window.print() and show the toast instead of printing when no
valid pages exist. Ensure you reference and modify the existing calcPages(),
printLabels(), pages variable and the result handling so a failed geometry
recalculation both clears pages and prevents window.print() from executing.

---

Nitpick comments:
In `@frontend/lib/reports/label-generator.test.ts`:
- Around line 176-181: Add a new test that asserts buildRotateCss("maker", size,
270) produces the symmetric recentering CSS (mirror of the 90° case) so the 270°
rotation branch is covered; locate the test block in label-generator.test.ts
near the existing 90° test and add a case named like "270 rotation sizes and
re-centers the label onto the swapped page" that calls buildRotateCss with
rotation 270 and expects the appropriate `@media` print CSS string for 270deg with
the corresponding translate offsets and transform-origin.

In `@frontend/pages/reports/label-generator.vue`:
- Around line 403-409: The generated CSS is injected via useHead innerHTML using
buildPageCss and buildRotateCss with values from mode.value, makerSize.value and
printRotation.value; before calling those functions, validate and clamp any
numeric inputs (e.g., labelWidth, labelHeight, labelsPerRow, labelGap, rotation)
taken from makerSize or printRotation to finite, reasonable bounds (use
Number.isFinite checks and min/max limits) and normalize types (parseFloat/Int)
so buildPageCss/buildRotateCss always receive safe numbers; update the code path
that constructs the innerHTML (the useHead call) to pass the sanitized/clamped
values instead of raw state to prevent malformed CSS injection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9ce2fed9-65fe-4de3-b09e-5f112d3cf539

📥 Commits

Reviewing files that changed from the base of the PR and between 4ea8383 and da5c2fa.

📒 Files selected for processing (4)
  • frontend/lib/reports/label-generator.test.ts
  • frontend/lib/reports/label-generator.ts
  • frontend/locales/en.json
  • frontend/pages/reports/label-generator.vue

Comment thread frontend/lib/reports/label-generator.ts Outdated
Comment on lines +228 to +230
const shift = (size.width - size.height) / 2;
return `@media print { .maker-label { width: ${size.width}${m}; height: ${size.height}${m}; transform: translate(${-shift}${m}, ${shift}${m}) rotate(${rotation}deg); transform-origin: center center; } }`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

270° recentering translation is mirrored incorrectly.

Line 229 applies the 90° translate vector for both 90 and 270. For 270, the shift must be inverted; otherwise labels render offset on the swapped page.

Proposed fix
   const m = size.measure;
   const shift = (size.width - size.height) / 2;
-  return `@media print { .maker-label { width: ${size.width}${m}; height: ${size.height}${m}; transform: translate(${-shift}${m}, ${shift}${m}) rotate(${rotation}deg); transform-origin: center center; } }`;
+  const tx = rotation === 270 ? shift : -shift;
+  const ty = rotation === 270 ? -shift : shift;
+  return `@media print { .maker-label { width: ${size.width}${m}; height: ${size.height}${m}; transform: translate(${tx}${m}, ${ty}${m}) rotate(${rotation}deg); transform-origin: center center; } }`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/lib/reports/label-generator.ts` around lines 228 - 230, The
translation used to recenter rotated labels incorrectly reuses the 90° vector
for 270°; update the transform calculation in the function that builds the CSS
string (where shift and rotation are used and the class .maker-label is
returned) to invert the shift for 270°. Compute translateX and translateY based
on rotation (e.g., for rotation===90 use translateX = -shift, translateY =
shift; for rotation===270 use translateX = shift, translateY = -shift; for 0/180
use 0 as appropriate) and then use those variables in the translate(...) portion
of the returned transform instead of always using (-shift, shift).

Comment thread frontend/pages/reports/label-generator.vue
Used the code rabbit requested changes. I feel these are mostly trivial.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⬆️ enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant