Skip to content

Ship rotula.exe in the installer, the MSI and the portable zip - #14

Open
glenntosco wants to merge 57 commits into
mainfrom
claude/project-thread-4ng9io
Open

glenntosco wants to merge 57 commits into
mainfrom
claude/project-thread-4ng9io

Conversation

@glenntosco

Copy link
Copy Markdown
Collaborator

Requested by Glenn · project thread

What changed and why

This follows #13, which added the print engine (rotula.exe) but did not ship it.

Local release build (packaging/installer/build-installer.ps1):

  • Publishes src/Rotula.Cli the same way as the app and the MCP server (self-contained single file, no symbols).
  • Stages rotula.exe and docs/print-engine.md.
  • Signs rotula.exe and checks its signature together with the other files.
  • Includes Rotula.Cli's restore output in the licence-text lookup.

Installers:

  • Rotula.iss and Package.wxs install rotula.exe beside Rotula.App.exe, with docs\print-engine.md.
  • No shortcut and no PATH change.
  • The portable zip carries rotula.exe because it is built from the same stage folder.

CI release workflow (release.yml): publishes the engine, puts it in the unsigned CI zip, and smoke-tests that rotula.exe version exits 0.

Docs: README, print-engine.md (where it is installed, and a note on running it as a service), deployment.md, releasing.md and CHANGELOG.

rotula.exe is about 58 MB self-contained, because it includes ASP.NET Core and SkiaSharp.

How it was tested

  • On Linux, dotnet publish src/Rotula.Cli -r win-x64 --self-contained -p:PublishSingleFile=true ... produces a single rotula.exe.
  • scripts/check-hygiene.sh passes.
  • Not run: the Inno Setup and WiX builds, signing, and the release workflow. They need Windows and the signing session, so they have to be checked on the next local release build. Things to confirm then:
    • rotula.exe is signed;
    • it is present in the setup program, the MSI and the zip;
    • it is removed on uninstall.

Checklist

  • No new user-visible strings
  • Offline-first unchanged
  • Docs and CHANGELOG updated

Generated by Claude Code

claude and others added 30 commits October 23, 2026 20:50
Microsoft.Data.SqlClient 7.1.1 brings Pkcs 9.0.18 and Oracle.ManagedDataAccess.Core
brings 8.0.1. Both now have published advisories (GHSA-846f-f764-76mx high,
GHSA-2fmv-rv2c-59x7 moderate), which NuGet audit turns into restore errors under
TreatWarningsAsErrors. That is what has broken the weekly Database integration
workflow since 2026-10-19, and the main Build would fail the same way.

Pin the patched MIT package in Rotula.Data (flows to Rotula.Mcp) and in the
Oracle pack, and update the third-party notices.

Claude-Session: https://claude.ai/code/session_01M3Pqt8Ubw3fJwBV3ztYKQV
fix(deps): pin patched System.Security.Cryptography.Pkcs 10.0.13
…I runner

postgres.exe exits at once when the account has administrative rights,
which is the case on the GitHub Windows runner, so Start-TestServers failed
with "The server stopped right after starting". pg_ctl starts the server
with those rights removed. Start/timeout errors now also print the last
lines of the server log, since Stop-TestServers deletes it in CI.

Claude-Session: https://claude.ai/code/session_01M3Pqt8Ubw3fJwBV3ztYKQV
The gate reads the first coverage.cobertura.xml under coverage/raw, and the
folders are random GUIDs. Rotula.Data.Packs.Tests also produced a report,
with Rotula.Core almost uncovered, so the gate failed whenever that one
sorted first (0.54% on PR #4). Drop its coverlet.collector, the same as
Rotula.Data.Integration already does.

Claude-Session: https://claude.ai/code/session_01M3Pqt8Ubw3fJwBV3ztYKQV
fix(integration): start PostgreSQL through pg_ctl so it runs on the CI runner
…a job

Adds a UI-free decoder for the three ~HS host status strings, a status
reader for TCP and (best effort) the Windows spooler, and an opt-in check
in PrintService: a blocking fault (head open, paper out, ribbon out, head
too hot, diagnostics mode, buffer full, paused) stops the job before any
byte is sent, an unknown status never blocks, and a verification after the
job reports finished, fault after job or could not verify as a notice.
…ter settings

Adds a per-transport switch (on for network printers, off for Windows
printers), a Check printer status button that shows the decoded ~HS reply,
plain-language messages for a held-back job and for the verification after
it, and a single verification after the last job of a batch print. All new
texts are in English and Spanish.
Reads the ~HS status of an allowed Windows printer and returns what stops
it from printing in plain words. It sends a command to the printer, so it
needs the printing permission (allowPrinting and allowedPrinters), but no
confirmation token because nothing is printed.
…achable

Windows retries a refused TCP connection for about two seconds, so a closed
port often ends in the timeout instead of a SocketException. Report that as
Unreachable, not NoReply.
Three 5 ms polls took longer than the 300 ms verification limit on a busy CI
runner. These tests count polls, so they now use a 30 s limit; the scripted
reader still ends them after the expected reply.
…cing

Add StationConfigLoader (station.json next to the labels, a folder, a
single label or the ProgramData policy file), validated with
StationConfig.* message keys; StationPin (PBKDF2-SHA256 hash and
constant-time verify); and the UI-free ScanSequencer that turns Enter,
Tab and Shift+Tab into field moves, print and label selection.
A locked, print-only station window with label tiles and search, large
prompt fields with scanner input, quantity limits, live preview and a
big plain-words printer status. Printing goes through IPrintService with
the status check on, then the prompts reset keeping the configured
values. Exit is PIN-gated with a lockout. Entry points: --station,
--station-pin and two File menu items. Strings in en and es.
Add docs/print-station.md (entry points, station.json schema, problem
keys, status meanings, PIN), a decisions entry, the README feature rows
and sections in English and Spanish, and the changelog entry.
Rotula.Files.Policy reads SOFTWARE\Policies\P4Software\Rotula from HKLM and
HKCU (the computer wins, value by value) behind IPolicySource. Policies fix the
language and the update check, suggest a default printer, keep user-folder
add-ons from loading, and switch MCP printing or database access off. The MCP
server applies them on top of its settings file and can only be restricted,
never widened. ADMX with en-US and es-ES ADML ship in packaging/policies; tests
check the template value names against PolicyNames.
Settings fixed by a policy are disabled with a "Managed by your organization"
note. Help > Check for updates once a day is off by default; when the user or a
policy switches it on, Rotula sends at most one anonymous GET of the GitHub
latest-release API a day and shows a non-modal notice linking to the release
page. Nothing is downloaded or installed. Version compare, schedule, parsing and
the checker are UI-free with tests; HTTP is behind IReleaseFeed.
A WiX Toolset v5 project (packaging/msi, build tool only, not in Rotula.sln)
installs Rotula per machine to Program Files with a Start menu shortcut, the
.rotula association, ARP metadata and a fixed UpgradeCode with MajorUpgrade.
build-installer.ps1 builds it from the signed stage folder, signs and verifies
it like the setup exe, and publishes it. The Inno installer now accepts
/ALLUSERS and /CURRENTUSER on the command line.
The installer manifest offers the per-user Inno installer and the machine-scope
MSI (ProductCode read from the signed MSI, fixed UpgradeCode). Adds a Spanish
locale manifest.
New docs/deployment.md covers silent installs, the MSI, exit codes, Intune,
Group Policy, Configuration Manager and the policy settings. CONTRIBUTING and
decisions now state the update-check rule precisely: no network traffic unless
the user or an administrator enables it, and nothing is sent about the user.
…I runner

postgres.exe exits at once when the account has administrative rights,
which is the case on the GitHub Windows runner, so Start-TestServers failed
with "The server stopped right after starting". pg_ctl starts the server
with those rights removed. Start/timeout errors now also print the last
lines of the server log, since Stop-TestServers deletes it in CI.

Claude-Session: https://claude.ai/code/session_01M3Pqt8Ubw3fJwBV3ztYKQV
The gate reads the first coverage.cobertura.xml under coverage/raw, and the
folders are random GUIDs. Rotula.Data.Packs.Tests also produced a report,
with Rotula.Core almost uncovered, so the gate failed whenever that one
sorted first (0.54% on PR #4). Drop its coverlet.collector, the same as
Rotula.Data.Integration already does.

Claude-Session: https://claude.ai/code/session_01M3Pqt8Ubw3fJwBV3ztYKQV
Every key of Strings.en.resx, with the same placeholders, access keys
and technical tokens (ZPL commands, GS1 AIs, extensions, product name).
Every key of Strings.en.resx, following OQLF usage: courriel, and a
non-breaking space before : ; ! ? and inside guillemets. Placeholders,
access keys and technical tokens are kept as in English.
Loc.SupportedLanguages now lists en, es, pt-BR and fr-CA by full culture
name, and LanguageCode returns that name. On first run any Portuguese
desktop gets pt-BR, any French one fr-CA, Spanish es and the rest
English; saved en/es settings keep their meaning. View > Language lists
each language in its own name.

The parity tests now check every Strings.*.resx against English (keys,
placeholders, access keys, no empty value, French typography), and the
key-coverage tests run over Loc.SupportedLanguages instead of en/es.
README (en/es), CONTRIBUTING, the PR checklist, the changelog and the
design decisions now say every new string needs en, es, pt-BR and
fr-CA. The installer offers Brazilian Portuguese and French.
pt-BR and fr-CA for the 137 keys the printer status check, the print
station and the managed settings / update check added.
The Language policy reads every shipped language case-insensitively and
hands on the canonical culture name; the ADMX enum, both ADML files and
docs/deployment.md list all four. The managed lock covers the new menu
entries, and the tests of the earlier branches that looped over en/es
now use Loc.SupportedLanguages.
…utral status test

Archivo and Ayuda both took Alt+A, so Alt+A never reached Help. Help is now
Ay_uda (Alt+U). The printer-status text test now accepts the French « » quotes.
A UI-free model of label sheets for office printers (paper, margins, label
size, grid, pitch), validation with message keys, a small set of generic
Letter and A4 templates described by their dimensions, and the imposition
that fills sheets in order with a start position.
MergeBatch labels and LabelPrintRun now expose the resolved values of
every label (same record iteration and serial numbers as the ZPL), and the
renderer can draw a label from those values without resolving counters
again. SheetExporter places the labels on sheets at their physical size
and writes one PDF page per sheet, with optional crop marks or outlines.
claude and others added 27 commits October 24, 2026 14:15
An optional root "sheet" object; older builds ignore it, so it does not
raise the format version. A section that makes no sense is dropped.
File > Export > Label sheets (PDF) and a button in the Print dialog open a
dialog to pick a generic sheet template or type a custom layout, see the
positions, start at a later position on a partly used sheet and save the
labels (or the ticked records) as a PDF at the sheet's real size. Serial
numbers are reserved and confirmed as for printing; the chosen layout is
saved with the label. English and Spanish texts.
The Preview window opens on "As printed" for a label set to ^POI or ^PMY, explains why the picture is turned and offers an "As designed" switch. The renderer flips the whole label inside LabelRenderer.Draw; only LabelDocumentAdapter.AsPrinted sets it, so the canvas and PNG/JPEG/PDF exports stay as designed. The status bar names a label that prints upside down or mirrored. Adds renderer pixel tests, importer round-trip tests, a golden file and view-model tests; marks ^PO and ^PM as previewed in the command catalogue.
pt-BR and fr-CA for the 14 keys of the As printed / As designed preview
and the status bar note.
pt-BR and fr-CA for the 66 keys of the Label sheets dialog, its enums
and its layout checks.
The main window applies the saved language when it opens, which put the test
back in English before the sheet dialog built its template names.
feat(printing): check printer status (~HS) before and after printing
…int-station

feat: operator print station (locked print-only mode with scanner input)
…ployment

feat: enterprise deployment (machine-wide MSI, policies, opt-in update check, winget)
…calization

feat(l10n): add Portuguese (Brazil) and French (Canada)
…int-orientation

feat(preview): show print orientation (^PO) and mirror (^PM) as printed
…heet-layouts

feat: label sheet layouts for office printers (multi-up PDF export)
…strings

The English and Spanish connection-refused, port-validation and start-page
subtitle strings named one printer brand. They now say "most label printers"
and "ZPL-compatible printer", matching the pt-BR and fr-CA wording.
rotula.exe prints .rotula labels without the designer, for scripts and
warehouse systems, through the app's own print path (DPI conversion,
darkness/speed, variables, questions, data merge, serial numbers used once).

- rotula print / status / printers with JSON output and exit codes
- rotula serve: REST API (/api/v1/jobs, printer status and lists, OpenAPI),
  API keys stored as SHA-256 hashes, label-folder and printer allow-lists,
  size limits, strict settings and request bodies, Windows service hosting
- ~HS status check before every job and verification after the last;
  one job at a time per printer
- PrintEngine policy (ADMX/ADML) to switch it off
- 52 new strings in en, es, pt-BR and fr-CA
- docs/print-engine.md, README, CHANGELOG, notices, deployment, decisions
The request path, reference and printer name are written to the audit
log; control characters (CR, LF and others) now become spaces and long
values are cut, so a caller cannot start a fake log entry.
The tests now also reference rotula.exe, which runs on ASP.NET Core, so
the build leaves the Microsoft.Extensions.* files that framework provides
out of the test folder. Rotula.Mcp.exe runs on plain .NET and could not
start from there; the tests now use src/Rotula.Mcp/bin first.
fix(l10n): drop the printer vendor name from UI strings
…rint-engine

Add the headless print engine: rotula CLI and REST API
The release build now publishes the print engine next to the app and the
MCP server, signs it and verifies its signature, and the setup program,
the MSI and the portable zip install it beside Rotula.App.exe with
docs/print-engine.md. The CI release workflow publishes it too and checks
that it starts. Docs say where it is installed.
@glenntosco glenntosco self-assigned this Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Build, test and coverage failed in one test that this PR doesn't touch: DataConnectionStoreTests.OdbcPasswordInRawString_IsMovedToTheProtectedSlot. This PR changes no C# code.

Why it failed: the test checks that the saved file does not contain "Pwd", ignoring case. The file holds the encrypted password as random base64, and this run's ciphertext happened to contain …LypWDt…. That matches pwd, so the test fails about once in a few thousand runs.

What I did: I re-ran the failed job once.

Proposed fix, in a separate PR: assert on "Pwd=" (the connection-string key) instead of "Pwd". That keeps what the test means to check and removes the random match.


Generated by Claude Code

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