Sweep all .cshtml files for encoding corruption; add pre-commit guard

Replace all corruption variants with HTML entities across 226 view files:
- 3-char UTF-8-as-Win1252 sequences (ae-corruption)
- Standalone smart/curly quotes that break C# Razor expressions
- Partially re-corrupted variants where the 3rd byte was normalised to ASCII

tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the
script itself never contains a literal non-ASCII character; supports -DryRun

.githooks/pre-commit: blocks commits containing the ae-corruption byte
signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the
hook is repo-committed and active for all future work on this machine.

Build clean; 225 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 21:37:10 -04:00
parent 21b39161a3
commit a0bdd2b5b4
252 changed files with 1785 additions and 1633 deletions
@@ -20,7 +20,7 @@
<div class="alert alert-permanent alert-warning d-flex gap-2 mb-4" role="alert">
<i class="bi bi-exclamation-triangle-fill flex-shrink-0 mt-1"></i>
<div>
<strong>Catalog item prices are fixed.</strong> The price you enter here is exactly what gets charged when this item is added to a quote or job no markup, no prep service charges, and no complexity adjustments are added on top. Make sure your price already includes labor, materials, and margin.
<strong>Catalog item prices are fixed.</strong> The price you enter here is exactly what gets charged when this item is added to a quote or job &mdash; no markup, no prep service charges, and no complexity adjustments are added on top. Make sure your price already includes labor, materials, and margin.
</div>
</div>
@@ -41,7 +41,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Item Name"
data-bs-content="Use a specific, recognizable name. The name appears on quotes, invoices, and in the picker dropdown. Good names include material and size where relevant e.g., 'Steel Bracket (6in)', '18in Alloy Wheel'.">
data-bs-content="Use a specific, recognizable name. The name appears on quotes, invoices, and in the picker dropdown. Good names include material and size where relevant &mdash; e.g., 'Steel Bracket (6in)', '18in Alloy Wheel'.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -75,7 +75,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Pricing"
data-bs-content="The Default Price is the exact amount charged when this item is added to a quote no markup, prep services, or complexity adjustments are applied on top. Set the all-in price you want to bill. Approximate Area is optional if set, it helps estimate powder needed for reporting purposes.">
data-bs-content="The Default Price is the exact amount charged when this item is added to a quote &mdash; no markup, prep services, or complexity adjustments are applied on top. Set the all-in price you want to bill. Approximate Area is optional &mdash; if set, it helps estimate powder needed for reporting purposes.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -87,7 +87,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Default Price"
data-bs-content="This is the final price charged to the customer no markup, prep services, or complexity charges are added on top. Enter the all-in amount you want to bill for this item. Staff can still override it on individual quotes if needed.">
data-bs-content="This is the final price charged to the customer &mdash; no markup, prep services, or complexity charges are added on top. Enter the all-in amount you want to bill for this item. Staff can still override it on individual quotes if needed.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -122,7 +122,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Financial Accounts"
data-bs-content="Links this item to your chart of accounts for accounting exports. Revenue Account is credited when invoiced; COGS Account is debited when materials are consumed. Leave both blank to use the company default accounts most shops only need to set these for items with special accounting treatment.">
data-bs-content="Links this item to your chart of accounts for accounting exports. Revenue Account is credited when invoiced; COGS Account is debited when materials are consumed. Leave both blank to use the company default accounts &mdash; most shops only need to set these for items with special accounting treatment.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -140,7 +140,7 @@
<select asp-for="CogsAccountId" class="form-select" asp-items="ViewBag.CogsAccounts"
data-quick-add-url="/Accounts/Create?preSubType=40" data-quick-add-title="Add COGS Account">
<option value="">(Default COGS account)</option>
<option value="__new__">+ Add New Account</option>
<option value="__new__">+ Add New Account&hellip;</option>
</select>
<small class="form-text text-muted">Account debited when materials are consumed.</small>
</div>
@@ -164,7 +164,7 @@
<div class="mb-3">
<label for="image" class="form-label fw-semibold">Upload Image</label>
<input type="file" class="form-control" id="image" name="image" accept="image/jpeg,image/png,image/gif,image/webp" onchange="previewCatalogImage(this)" />
<div class="form-text">Accepted formats: jpg, jpeg, png, gif, webp max 10 MB. A 200×200 thumbnail is generated automatically.</div>
<div class="form-text">Accepted formats: jpg, jpeg, png, gif, webp &mdash; max 10 MB. A 200×200 thumbnail is generated automatically.</div>
<div id="imagePreview" class="mt-2 d-none">
<img id="imagePreviewImg" src="" alt="Preview" style="max-width:200px;max-height:200px;object-fit:contain;border:1px solid #dee2e6;border-radius:6px;" />
</div>