Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
fdf04fc
fix(deps): pin patched System.Security.Cryptography.Pkcs 10.0.13
claude Oct 23, 2026
8ee3037
Merge pull request #3 from P4Software/claude/project-thread-jz5754
glenntosco Oct 23, 2026
b6de82f
fix(integration): start PostgreSQL through pg_ctl so it runs on the C…
claude Oct 23, 2026
fdb419b
ci: keep the Rotula.Core coverage gate on the Rotula.Tests report
claude Oct 23, 2026
cbedadc
Merge pull request #4 from P4Software/claude/project-thread-jz5754
glenntosco Oct 24, 2026
d4b3043
feat(printing): read the printer status with ~HS and check it around …
claude Oct 24, 2026
cdd509a
feat(app): show the printer status check in the Print dialog and prin…
claude Oct 24, 2026
f4730d2
feat(mcp): add a get_printer_status tool
claude Oct 24, 2026
ad45cfd
docs: describe the printer status check and note ~HS use in the comma…
claude Oct 24, 2026
9342a2b
fix(printing): a status query that times out while connecting is unre…
claude Oct 24, 2026
0a911e0
test(printing): give the poll-counting verification tests a long limit
claude Oct 24, 2026
593b46e
feat(files): print station configuration, exit PIN and scanner sequen…
claude Oct 24, 2026
7f064ff
feat(app): print station window and PIN tool
claude Oct 24, 2026
0edf5d9
docs: print station
claude Oct 24, 2026
e2676d7
feat(policy): read Group Policy / Intune settings from the registry
claude Oct 24, 2026
e96eb98
feat(app): managed settings in the UI and an opt-in update check
claude Oct 24, 2026
84c9875
feat(packaging): machine-wide MSI built and signed with the release
claude Oct 24, 2026
8e3ba87
feat(winget): schema 1.10 manifests with the MSI and an es-MX locale
claude Oct 24, 2026
10f47a5
docs: deployment guide, update-check rule and winget submission steps
claude Oct 24, 2026
e7eb29d
fix(integration): start PostgreSQL through pg_ctl so it runs on the C…
claude Oct 23, 2026
2c3ca07
ci: keep the Rotula.Core coverage gate on the Rotula.Tests report
claude Oct 23, 2026
7227cc6
feat(l10n): add Brazilian Portuguese UI strings
claude Oct 24, 2026
9d195ba
feat(l10n): add Canadian French UI strings
claude Oct 24, 2026
2afd59c
feat(l10n): offer pt-BR and fr-CA in the language menu
claude Oct 24, 2026
c5ec5c5
docs(l10n): document the four languages and add them to the installer
claude Oct 24, 2026
09c20e7
feat(l10n): translate printer status, print station and update strings
claude Oct 24, 2026
9eb323d
feat(policy): accept pt-BR and fr-CA as the managed language
claude Oct 24, 2026
8b6ecb2
fix(l10n): distinct Alt key for the Spanish Help menu; quote-style-ne…
claude Oct 24, 2026
622559d
feat(core): sheet layout model, generic templates and imposition
claude Oct 24, 2026
a566740
feat(rendering): multi-page label sheet PDF from resolved label values
claude Oct 24, 2026
47dc2cd
feat(files): remember the sheet layout in the .rotula file
claude Oct 24, 2026
b7ff962
feat(app): Label sheets dialog with PDF export
claude Oct 24, 2026
e19a0a2
feat(preview): show print orientation (^PO) and mirror (^PM) as printed
claude Oct 24, 2026
0d596b6
test: sheet layout math, imposition, PDF pages, file round trip and d…
claude Oct 24, 2026
759552b
feat(l10n): translate the print orientation preview strings
claude Oct 24, 2026
d6a7100
docs: label sheets for office printers
claude Oct 24, 2026
da5335f
feat(l10n): translate the label sheets strings
claude Oct 24, 2026
d2a5ed4
test(sheets): set the Spanish language setting before opening the window
claude Oct 24, 2026
b7ae8ab
Merge pull request #5 from P4Software/claude/project-thread-5e0lug
glenntosco Oct 24, 2026
deeed47
Merge main into the print station branch
claude Oct 24, 2026
0db8297
Merge pull request #6 from P4Software/claude/project-thread-5e0lug-pr…
glenntosco Oct 24, 2026
2ef3cef
Merge branch 'main' into claude/project-thread-5e0lug-deployment
claude Oct 24, 2026
2d3a507
Merge pull request #7 from P4Software/claude/project-thread-5e0lug-de…
glenntosco Oct 24, 2026
f2fa5ba
Merge branch 'main' into claude/project-thread-5e0lug-localization
claude Oct 24, 2026
6923b1f
Merge pull request #8 from P4Software/claude/project-thread-5e0lug-lo…
glenntosco Oct 24, 2026
8a6abae
Merge branch 'main' into claude/project-thread-5e0lug-print-orientation
claude Oct 24, 2026
d66e3a5
Merge pull request #9 from P4Software/claude/project-thread-5e0lug-pr…
glenntosco Oct 24, 2026
208aa88
Merge branch 'main' into claude/project-thread-5e0lug-sheet-layouts
claude Oct 24, 2026
7353c9a
Merge pull request #10 from P4Software/claude/project-thread-5e0lug-s…
glenntosco Oct 24, 2026
5be26d0
fix(l10n): drop the printer vendor name from the port and start-page …
claude Oct 24, 2026
7662a01
Add the headless print engine: rotula CLI and REST API
claude Oct 24, 2026
a70c3f3
Keep request values from forging server log lines
claude Oct 24, 2026
1a2eba2
Start the MCP server from its own build output in the protocol tests
claude Oct 24, 2026
853f87d
Merge pull request #12 from P4Software/claude/project-thread-4ng9io
glenntosco Oct 24, 2026
3ae7423
Merge remote-tracking branch 'origin/main' into claude/project-thread…
claude Oct 24, 2026
dda13a1
Merge pull request #13 from P4Software/claude/project-thread-4ng9io-p…
glenntosco Oct 24, 2026
e6e5efb
Ship rotula.exe in the installer, the MSI and the portable zip
claude Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- [ ] `dotnet build Rotula.sln -c Release` and `dotnet test Rotula.sln -c Release` pass
- [ ] New behavior has tests; `Rotula.Core` stays at or above 80% line coverage
- [ ] Public members have XML docs and non-obvious logic has a "why" comment (in English)
- [ ] New user-visible text exists in both `Strings.en.resx` and `Strings.es.resx`
- [ ] New user-visible text exists in all four languages: `Strings.en.resx`, `Strings.es.resx`, `Strings.pt-BR.resx` and `Strings.fr-CA.resx`
- [ ] No network calls or telemetry added; light theme only
- [ ] New dependencies are permissively licensed and listed in `THIRD-PARTY-NOTICES.md`
- [ ] `CHANGELOG.md` updated under "Unreleased" when users would notice the change
Expand Down
17 changes: 15 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,24 @@ jobs:
-p:Version=${{ steps.ver.outputs.version }}
-o publish/win-x64-mcp

# The print engine (rotula.exe: command line and REST API), published the same way.
- name: Publish print engine single-file win-x64
run: >
dotnet publish src/Rotula.Cli/Rotula.Cli.csproj -c Release -r win-x64 --self-contained true
-p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
-p:EnableCompressionInSingleFile=true -p:DebugType=none
-p:Version=${{ steps.ver.outputs.version }}
-o publish/win-x64-engine

# The zip is an UNSIGNED CI artifact for inspection only; the signed portable zip comes from the local build.
- name: Stage files and create the portable zip
shell: pwsh
run: |
$v = '${{ steps.ver.outputs.version }}'
New-Item -ItemType Directory -Force artifacts, stage | Out-Null
Copy-Item publish/win-x64/Rotula.App.exe, publish/win-x64-mcp/Rotula.Mcp.exe stage/
Copy-Item publish/win-x64/Rotula.App.exe, publish/win-x64-mcp/Rotula.Mcp.exe, publish/win-x64-engine/rotula.exe stage/
New-Item -ItemType Directory -Force stage/docs | Out-Null
Copy-Item docs/mcp.md stage/docs/
Copy-Item docs/mcp.md, docs/print-engine.md stage/docs/
Copy-Item LICENSE, THIRD-PARTY-NOTICES.md, README.md stage/
Compress-Archive -Path stage/* -DestinationPath "artifacts/Rotula-$v-win-x64-portable-unsigned-ci.zip" -Force

Expand All @@ -100,6 +109,10 @@ jobs:
$alive = -not $p.HasExited
if ($alive) { $p.Kill() }
if (-not $alive) { throw "Rotula.App.exe exited early with code $($p.ExitCode)." }
# The print engine must start too: "version" prints the version and exits 0.
$out = & publish/win-x64-engine/rotula.exe version
if ($LASTEXITCODE -ne 0) { throw "rotula.exe version failed with exit code $LASTEXITCODE." }
Write-Host "rotula.exe version: $out"

- name: Upload build artifacts
uses: actions/upload-artifact@v4
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]

### Added
- **Print engine for scripts and warehouse systems** (`rotula.exe`): print `.rotula` labels with no window. `rotula print` takes a label, field values (`--set`) or records from a CSV, Excel or JSON file, and a Windows printer or network address, and prints through the designer's own print path: resolution conversion, darkness and speed, variables, questions (`--answer`, otherwise the designer default; never a sample value), copies, and serial numbers used once. Before every job it checks the printer status (`~HS`) and holds the job back on a fault; after the last job it waits until the printer reports it finished. `rotula status` and `rotula printers` report printers, `--json` gives a machine-readable result, and exit codes tell a script what happened. `rotula serve` offers the same as a REST API (`/api/v1/jobs`, printer status and lists, OpenAPI at `/openapi/v1.json`) that can run as a Windows service: off until started, on `127.0.0.1` by default, an API key on every call (only its hash is stored), and only the label folders and printers its settings file lists. One job at a time per printer. Messages in English, Spanish, Portuguese (Brazil) and French (Canada). A new `PrintEngine` policy lets an administrator switch it off. The setup program, the MSI and the portable zip include `rotula.exe` beside the app. See [docs/print-engine.md](docs/print-engine.md).
- **Label sheets for office printers** (File > Export > Label sheets (PDF), or Label sheets in the Print dialog): put the label on sheets for a laser or inkjet printer and save them as a PDF at the sheet's real size, each label at its own physical size. Pick a generic template (Letter 30-up, 20-up, 14-up, 10-up, 6-up, 80-up and half sheets; A4 3 x 8, 21-up, 65-up, 16-up, 14-up, 8-up and 2-up) or type your own layout, start at any position of a partly used sheet, and add crop marks or label outlines. With data loaded the ticked records go on the sheets with their copies, and serial numbers are used once, exactly as when printing. The layout is saved with the label; older versions of Rotula open such a file and ignore it. Sending the sheets straight to a Windows printer is not offered: print the PDF at actual size.
- **Machine-wide MSI for IT departments**: every release now also has `Rotula-X.Y.Z-win-x64.msi`, signed like the setup program. It installs to Program Files for every user with a Start menu entry and the `.rotula` association, upgrades older versions in place, and is made for silent deployment with Intune, Group Policy or Configuration Manager. The setup program can now also install for all users silently with `/ALLUSERS`. Silent install and uninstall commands, exit codes, Intune detection rules and Group Policy steps are in the new [docs/deployment.md](docs/deployment.md).
- **Settings your organization can manage**: administrators can fix the language and switch the update check on or off for everyone, suggest a default printer, keep add-ons in the user's own folder from loading, and switch printing or database access of the MCP server off, through Group Policy or Intune. Administrative templates (English and Spanish) are in `packaging/policies`. A setting fixed this way is greyed out in Rotula with "Managed by your organization".
- **Optional update check** (Help > Check for updates once a day): off unless you switch it on. Then Rotula asks GitHub once a day, with one anonymous request that carries nothing about you or your PC, whether a newer version exists, and shows a notice with a link to the release page. It never downloads or installs anything.
- **winget**: the manifests use schema 1.10, offer the MSI for `--scope machine` next to the per-user installer, and have a Spanish (es-MX) description. The submission steps are in docs/releasing.md.
- **Print station** for the warehouse floor: a locked, print-only screen started with `Rotula.exe --station <folder, station.json or label>` (without a path it uses `%ProgramData%\Rotula\station.json`) or from **File > Open print station**. Labels show as large tiles with thumbnails and a search box; scanning a label's code opens it. Its questions are large fields that take keyboard-wedge scanner input: Enter or Tab moves to the next one, the last scan can print at once, and the label's own rules (required, length, pattern) are checked before anything is sent. Printing goes through the same print path as the Print dialog with the printer status check, and the result is shown in large plain words (ready, held back with the reason, printed and verified, not confirmed); the questions then reset for the next item, keeping the answers the station is told to keep. A `station.json` sets the labels and their scan codes, the printer of each label or a default, copy limits, whether the label's data may be printed, and an exit PIN stored only as a salted PBKDF2 hash (made with **File > Create print station PIN** or `Rotula.exe --station-pin`); five wrong PINs lock the PIN box for 30 seconds. Every setting is checked and a mistake is explained with the setting and line. See [docs/print-station.md](docs/print-station.md).
- **Printer status before and after printing** (ZPL `~HS`). Before each job Rotula asks the printer for its status and does not send while it reports it is out of labels, its printhead is open, the ribbon is out, it is paused, too hot, in diagnostics mode or its buffer is full; the Print dialog says which and what to do. After the job it waits (up to 30 seconds) until the printer reports it finished, then says "printed and verified", names a fault that appeared after the job (the labels still count as possibly printed, so no serial number is reused), or says it could not confirm. A printer that cannot answer never blocks printing. On by default for network printers and off for Windows printers, switchable in the Print dialog; "Check printer status" shows the decoded answer at any time. The MCP server has a matching `get_printer_status` tool.
- **A gallery of eight showcase labels** in the README (GS1-128 pallet, parcel, food, wine, lab specimen, asset tag, shelf tags with data merge, print conditions). Their `.rotula` files and the CSV data are in `docs/showcase`, and the pictures are rendered with Rotula's own PNG export by a test that only runs when `ROTULA_RENDER_SHOWCASE=1` is set.
- **Text fit for wrapped text** (Properties panel, Text fit): choose what a text block does when its text is longer than the box. "Shrink to fit" uses the largest font size, between a smallest and a largest size you set, at which all the text fits; "Grow height" adds lines, up to a limit you set, so all the text shows. With data merge every record gets the size or the lines its own text needs, the canvas shows the same size that prints, and "Do not warn" hides the does-not-fit warning when cutting the text off is what you want. Labels that do not use it are saved exactly as before; a label that does cannot be opened by older versions of Rotula.
- **Print an object only when you want it**: in the new Printing section of the properties, mark an object "Do not print this object" (a note or guide that never reaches the printer or an export), set "Print only when" a field equals, does not equal, contains, is empty or is not empty a value (so a warning can appear on some labels only), and "Lock position" so nothing moves it by accident. The Objects list and the canvas show small No print, Hidden and Locked badges.
- **The Preview window shows a label the way it prints when it is set to print upside down (`^POI`) or mirrored (`^PMY`).** It opens on "As printed", says in one sentence why the picture is turned, and has an "As designed" switch to see it upright. The canvas stays upright for editing and the status bar says "Prints upside down" (or "Prints mirrored"). Imported ZPL with `^POI` / `^PON` and `^PMY` sets the label's setting, so the preview follows it. PNG, JPEG and PDF exports stay as designed.
- **Portuguese (Brazil) and French (Canada)**: the whole interface is now also available in Brazilian Portuguese and Canadian French (View > Language, where each language is listed in its own name), and the installer offers both. On first run Rotula follows Windows: a Portuguese desktop gets Portuguese (Brazil), a French one French (Canada), Spanish stays Spanish and anything else English. Numbers and dates follow the chosen language. A language you picked before stays as it was. The "Language of Rotula" policy (Group Policy / Intune) also accepts `pt-BR` and `fr-CA`.

### Fixed
- **Windows fonts that are named after a style now draw in that style.** "Arial Black", "Segoe UI Semibold", "Segoe UI Black", "Bahnschrift Condensed" and similar names used to be replaced by the default font in the preview, PNG/PDF export and print picture; they now resolve to their parent family at the weight and width the name says.
Expand Down
15 changes: 11 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ in [SECURITY.md](SECURITY.md), not a public issue.

These are product rules; changes that break them will not be merged.

- **Offline-first**: no network calls, no telemetry, no update checks, no analytics. Printing to the printer address the user
entered is the only network traffic.
- **Offline-first**: no telemetry, no analytics, and no network traffic unless the user asks for it: printing to the printer
address the user entered and the databases the user connects. The one exception is the **opt-in update check**: it sends
nothing unless the user (Help menu) or an administrator (policy) explicitly switches it on, and then it is at most one
anonymous `GET` of the public GitHub "latest release" API a day, with nothing about the user or the computer in it. It never
downloads or installs anything. Do not add other network calls, and do not add anything to that request. The print engine's
REST server (`rotula serve`) is not a call out: it listens only when someone starts it, only on the addresses in its settings
file (`127.0.0.1` by default), and talks only to the printers that file allows.
- **Light theme only.**
- **Spanish and English** through `.resx` resources (`src/Rotula.App/Resources/Strings.en.resx` and `Strings.es.resx`). Every new
user-visible string needs both. Core returns message keys, not text.
- **English, Spanish, Portuguese (Brazil) and French (Canada)** through `.resx` resources in `src/Rotula.App/Resources`
(`Strings.en.resx`, `Strings.es.resx`, `Strings.pt-BR.resx`, `Strings.fr-CA.resx`). Every new user-visible string needs all
four: en, es, pt-BR, fr-CA, with the same `{0}` placeholders and `_` access keys. French follows Canadian (OQLF) usage, with
a non-breaking space before `:` `;` `!` `?` and inside « ». Core returns message keys, not text.
- **Permissive dependencies only** (MIT, Apache-2.0, BSD and similar). Add any new package to
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) in the same pull request. Discuss new dependencies in an issue first.
- **Plain-language messages**: say what happened and what the user can do next.
Expand Down
Loading
Loading