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
@@ -1,4 +1,4 @@
@model PowderCoating.Application.DTOs.Scheduling.OvenSchedulerViewModel
@model PowderCoating.Application.DTOs.Scheduling.OvenSchedulerViewModel
@{
ViewData["Title"] = "Oven Scheduler";
var dateStr = Model.ScheduledDate.ToString("yyyy-MM-dd");
@@ -8,7 +8,7 @@
@section Styles {
<style>
/* ─── Layout ─────────────────────────────────────── */
/* --- Layout --------------------------------------- */
.scheduler-layout {
display: flex;
gap: 1rem;
@@ -39,7 +39,7 @@
flex-shrink: 0;
}
/* ─── Batch cards ────────────────────────────────── */
/* --- Batch cards ---------------------------------- */
.batch-card {
border-radius: 10px;
border: 2px solid transparent;
@@ -52,14 +52,14 @@
.batch-card.status-completed { border-color: #198754; opacity: .85; }
.batch-card.drag-over { border-color: #0d6efd !important; box-shadow: 0 0 12px rgba(13,110,253,.4) !important; }
/* ─── Capacity bar ───────────────────────────────── */
/* --- Capacity bar --------------------------------- */
.capacity-bar-wrap { height: 6px; border-radius: 3px; background: #e9ecef; overflow: hidden; }
.capacity-bar-fill { height: 100%; border-radius: 3px; transition: width .3s; }
.cap-ok { background: #198754; }
.cap-warn { background: #ffc107; }
.cap-over { background: #dc3545; }
/* ─── Queue items ────────────────────────────────── */
/* --- Queue items ---------------------------------- */
.queue-job-card {
border-radius: 8px;
cursor: grab;
@@ -90,7 +90,7 @@
.batch-item-row:hover { background: var(--bs-secondary-bg); }
.batch-item-row.dragging { opacity: .5; }
/* ─── AI panel ───────────────────────────────────── */
/* --- AI panel ------------------------------------- */
.ai-suggestion-panel {
position: fixed;
top: 0; right: 0;
@@ -115,7 +115,7 @@
}
.ai-panel-backdrop.open { display: block; }
/* ─── Drop zone hint ─────────────────────────────── */
/* --- Drop zone hint ------------------------------- */
.drop-zone-empty {
min-height: 80px;
border: 2px dashed var(--bs-border-color);
@@ -275,7 +275,7 @@
<p class="small text-muted mb-2">
The Oven Scheduler helps you plan which jobs go into which oven on a given day.
Jobs waiting to be coated appear in the <strong>Queue</strong> on the left.
Each oven gets its own column — create <strong>batches</strong> and drag jobs into them
Each oven gets its own column &mdash; create <strong>batches</strong> and drag jobs into them
to build your day's run sheet.
</p>
<p class="small text-muted mb-0">
@@ -292,8 +292,8 @@
<li class="mb-1">Use the <strong>date arrows</strong> to navigate to the day you want to schedule.</li>
<li class="mb-1">Click <strong>New Batch</strong> and pick an oven to create a batch slot.</li>
<li class="mb-1"><strong>Drag job coat items</strong> from the Queue into a batch, or drag between batches to reorder.</li>
<li class="mb-1">Set a <strong>start time</strong> on each batch and monitor the capacity bar — it turns yellow at 80 % and red when over capacity.</li>
<li class="mb-1">Use the batch <strong>status buttons</strong> (Planned ↠Loading ↠In Progress ↠Completed) to track real-time progress.</li>
<li class="mb-1">Set a <strong>start time</strong> on each batch and monitor the capacity bar &mdash; it turns yellow at 80 % and red when over capacity.</li>
<li class="mb-1">Use the batch <strong>status buttons</strong> (Planned â†' Loading â†' In Progress â†' Completed) to track real-time progress.</li>
<li class="mb-0">Drag an item back to the Queue to unschedule it.</li>
</ol>
</div>
@@ -303,23 +303,23 @@
<h6 class="fw-semibold mb-2"><i class="bi bi-gear-fill text-warning me-1"></i>Setup for best results</h6>
<ul class="small text-muted mb-0 ps-3">
<li class="mb-1">
<strong>Ovens in Equipment</strong> — each oven must exist as an Equipment record
<strong>Ovens in Equipment</strong> &mdash; each oven must exist as an Equipment record
with <em>Type = Oven</em> and Status = Operational for it to appear as a column.
</li>
<li class="mb-1">
<strong>Oven capacity</strong> — set a <em>capacity (sq ft)</em> on each oven so
<strong>Oven capacity</strong> &mdash; set a <em>capacity (sq ft)</em> on each oven so
the capacity bar can warn you before you overload a batch.
</li>
<li class="mb-1">
<strong>Job items with surface area</strong> — enter <em>surface area (sq ft)</em>
<strong>Job items with surface area</strong> &mdash; enter <em>surface area (sq ft)</em>
on each job item so the scheduler can calculate load accurately.
</li>
<li class="mb-1">
<strong>Coat quantities</strong> — job items need at least one coat defined
<strong>Coat quantities</strong> &mdash; job items need at least one coat defined
(powder color + quantity) so they appear as draggable coat rows in the Queue.
</li>
<li class="mb-0">
<strong>Due dates &amp; priorities</strong> — set due dates and priorities on
<strong>Due dates &amp; priorities</strong> &mdash; set due dates and priorities on
jobs so the AI and sorting tools can recommend the most urgent work first.
</li>
</ul>
@@ -341,7 +341,7 @@
<!-- Main layout -->
<div class="scheduler-layout">
<!-- ── JOB QUEUE (left sidebar) ───────────────── -->
<!-- -- JOB QUEUE (left sidebar) ----------------- -->
<div class="scheduler-queue">
<div class="card shadow-sm">
<div class="card-header d-flex align-items-center py-2">
@@ -408,7 +408,7 @@
<span class="color-dot" style="background:@GetColorHex(coat.ColorName, coat.ColorCode)"></span>
}
<span class="fw-medium">@coat.CoatName</span>
<span class="text-muted ms-1">— @coat.ItemDescription</span>
<span class="text-muted ms-1">&mdash; @coat.ItemDescription</span>
</div>
<div class="text-muted" style="font-size:.75rem;">
Pass @coat.CoatPassNumber · @coat.SurfaceAreaSqFt.ToString("F1") sqft
@@ -426,7 +426,7 @@
</div>
</div>
<!-- ── OVEN COLUMNS ───────────────────────────── -->
<!-- -- OVEN COLUMNS ----------------------------- -->
<div class="scheduler-board">
<div class="oven-columns" id="ovenColumns">
@foreach (var oven in Model.Ovens)
@@ -547,7 +547,7 @@
@if (!Model.QueuedJobs.Any())
{
<div class="alert alert-info alert-permanent small">
<i class="bi bi-info-circle me-1"></i>The queue is empty — no jobs need oven scheduling right now.
<i class="bi bi-info-circle me-1"></i>The queue is empty &mdash; no jobs need oven scheduling right now.
</div>
}
else
@@ -561,7 +561,7 @@
@section Scripts {
<script>
// ── Info panel (always visible on load; dismiss hides for this visit only) ─
// -- Info panel (always visible on load; dismiss hides for this visit only) -
document.getElementById('btnDismissInfo').addEventListener('click', function () {
document.getElementById('schedulerInfoPanel').style.display = 'none';
});
@@ -41,7 +41,7 @@
@Model.ScheduledStartTime.Value.ToString("h:mm tt")
@if (Model.EstimatedEndTime.HasValue)
{
<span> @Model.EstimatedEndTime.Value.ToString("h:mm tt")</span>
<span>&ndash; @Model.EstimatedEndTime.Value.ToString("h:mm tt")</span>
}
<span class="ms-1">(@Model.CycleMinutes min)</span>
</div>