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:
@@ -24,7 +24,7 @@
|
||||
<p class="mb-3" style="font-family:var(--font-display);font-size:1.35rem;font-weight:500;line-height:1.4;color:var(--pcl-ink);">
|
||||
@if (_attnCount > 0)
|
||||
{
|
||||
<span>Shop is </span><span style="color:var(--pcl-bad);">running hot</span><span> — @_attnCount item@(_attnCount == 1 ? "" : "s") need attention.</span>
|
||||
<span>Shop is </span><span style="color:var(--pcl-bad);">running hot</span><span> — @_attnCount item@(_attnCount == 1 ? "" : "s") need attention.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -70,7 +70,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* PWA install banner — rendered by JS only on mobile, hidden once dismissed or already installed *@
|
||||
@* PWA install banner — rendered by JS only on mobile, hidden once dismissed or already installed *@
|
||||
<div id="pwa-install-banner" class="row mb-4" style="display:none!important;">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-permanent mb-0 d-flex align-items-start gap-3 py-3"
|
||||
@@ -115,7 +115,7 @@
|
||||
@await Html.PartialAsync("_ShopProgressWidget", shopProgressWidget)
|
||||
}
|
||||
|
||||
@* Config health alert — only shown when there are setup gaps *@
|
||||
@* Config health alert — only shown when there are setup gaps *@
|
||||
@if (configHealth != null && !configHealth.IsHealthy)
|
||||
{
|
||||
<div class="row mb-4">
|
||||
@@ -417,15 +417,15 @@
|
||||
}
|
||||
@if (Model.AgingDays1To30 > 0)
|
||||
{
|
||||
<span><span class="me-1" style="display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--pcl-warn);"></span>1–30d @Model.AgingDays1To30.ToString("C0")</span>
|
||||
<span><span class="me-1" style="display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--pcl-warn);"></span>1–30d @Model.AgingDays1To30.ToString("C0")</span>
|
||||
}
|
||||
@if (Model.AgingDays31To60 > 0)
|
||||
{
|
||||
<span><span class="me-1" style="display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--pcl-bad);"></span>31–60d @Model.AgingDays31To60.ToString("C0")</span>
|
||||
<span><span class="me-1" style="display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--pcl-bad);"></span>31–60d @Model.AgingDays31To60.ToString("C0")</span>
|
||||
}
|
||||
@if (Model.AgingDays61To90 > 0)
|
||||
{
|
||||
<span><span class="me-1" style="display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--pcl-bad);"></span>61–90d @Model.AgingDays61To90.ToString("C0")</span>
|
||||
<span><span class="me-1" style="display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--pcl-bad);"></span>61–90d @Model.AgingDays61To90.ToString("C0")</span>
|
||||
}
|
||||
@if (Model.AgingDaysOver90 > 0)
|
||||
{
|
||||
@@ -547,7 +547,7 @@
|
||||
@if (line.EstCost.HasValue)
|
||||
{<span>@line.EstCost.Value.ToString("C")</span>}
|
||||
else
|
||||
{<span class="text-muted">—</span>}
|
||||
{<span class="text-muted">—</span>}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm btn-outline-danger mark-ordered-btn text-nowrap"
|
||||
@@ -563,7 +563,7 @@
|
||||
<tr>
|
||||
<td colspan="2">Vendor Total</td>
|
||||
<td class="text-end">@vendorGroup.TotalLbsNeeded.ToString("N2") lbs</td>
|
||||
<td class="text-end">@(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
||||
<td class="text-end">@(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -582,7 +582,7 @@
|
||||
<div class="card border-0 shadow-sm dashboard-card" style="border-left: 4px solid #0d6efd !important;">
|
||||
<div class="card-header bg-body border-0 d-flex justify-content-between align-items-center pt-4 pb-3">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="bi bi-box-arrow-in-down me-2 text-muted"></i>Powder Ordered — Awaiting Receipt
|
||||
<i class="bi bi-box-arrow-in-down me-2 text-muted"></i>Powder Ordered — Awaiting Receipt
|
||||
<span class="ms-2 text-muted fw-normal small" id="placed-count-label">@Model.PowderOrdersPlacedCount item@(Model.PowderOrdersPlacedCount == 1 ? "" : "s")</span>
|
||||
</h5>
|
||||
<small class="text-muted">Grouped by vendor · Enter lbs received to update inventory</small>
|
||||
@@ -641,13 +641,13 @@
|
||||
@if (line.EstCost.HasValue)
|
||||
{<span>@line.EstCost.Value.ToString("C")</span>}
|
||||
else
|
||||
{<span class="text-muted">—</span>}
|
||||
{<span class="text-muted">—</span>}
|
||||
</td>
|
||||
<td class="text-muted small">
|
||||
@if (line.OrderedAt.HasValue)
|
||||
{<span title="@line.OrderedAt.Value.Tz(ViewBag.CompanyTimeZone as string).ToString("g")">@line.OrderedAt.Value.Tz(ViewBag.CompanyTimeZone as string).ToString("MMM d")</span>}
|
||||
else
|
||||
{<span>—</span>}
|
||||
{<span>—</span>}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="d-flex align-items-center gap-1 justify-content-center receive-form-@line.CoatId">
|
||||
@@ -680,7 +680,7 @@
|
||||
<tr>
|
||||
<td colspan="2">Vendor Total</td>
|
||||
<td class="text-end">@vendorGroup.TotalLbsNeeded.ToString("N2") lbs</td>
|
||||
<td class="text-end">@(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
||||
<td class="text-end">@(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -750,7 +750,7 @@
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Category</label>
|
||||
<select class="form-select" id="apm-categoryId" name="inventoryCategoryId">
|
||||
<option value="">— Select category —</option>
|
||||
<option value="">— Select category —</option>
|
||||
@if (ViewBag.InventoryCategories != null)
|
||||
{
|
||||
foreach (var cat in (IEnumerable<dynamic>)ViewBag.InventoryCategories)
|
||||
@@ -764,7 +764,7 @@
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Primary Vendor</label>
|
||||
<select class="form-select" id="apm-vendorId" name="primaryVendorId">
|
||||
<option value="">— Select vendor —</option>
|
||||
<option value="">— Select vendor —</option>
|
||||
@if (ViewBag.VendorList != null)
|
||||
{
|
||||
foreach (var v in (IEnumerable<dynamic>)ViewBag.VendorList)
|
||||
@@ -838,12 +838,12 @@
|
||||
@section Scripts {
|
||||
<script src="~/js/shop-progress-widget.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
// Start Intake — pushes SignalR event to front-desk tablet
|
||||
// Start Intake — pushes SignalR event to front-desk tablet
|
||||
document.getElementById('btnStartIntake')?.addEventListener('click', async function () {
|
||||
const btn = this;
|
||||
const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Sending…';
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Sending…';
|
||||
try {
|
||||
const res = await fetch('/Kiosk/StartSession', {
|
||||
method: 'POST',
|
||||
@@ -928,7 +928,7 @@
|
||||
|
||||
const esc = s => s ? s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') : '';
|
||||
const estCost = (c.lbsToOrder && c.costPerLb) ? (c.lbsToOrder * c.costPerLb) : null;
|
||||
const orderedDate = c.orderedAt ? new Date(c.orderedAt).toLocaleDateString('en-US',{month:'short',day:'numeric'}) : '—';
|
||||
const orderedDate = c.orderedAt ? new Date(c.orderedAt).toLocaleDateString('en-US',{month:'short',day:'numeric'}) : '—';
|
||||
const lbsFmt = c.lbsToOrder ? parseFloat(c.lbsToOrder).toFixed(2) : '0.00';
|
||||
|
||||
// Find or create vendor group
|
||||
@@ -973,7 +973,7 @@
|
||||
${c.finish ? `<span class="badge bg-light text-dark border ms-1">${esc(c.finish)}</span>` : ''}
|
||||
</td>
|
||||
<td class="text-end fw-medium">${lbsFmt} lbs</td>
|
||||
<td class="text-end">${estCost ? '$' + estCost.toFixed(2) : '<span class="text-muted">—</span>'}</td>
|
||||
<td class="text-end">${estCost ? '$' + estCost.toFixed(2) : '<span class="text-muted">—</span>'}</td>
|
||||
<td class="text-muted small">${orderedDate}</td>
|
||||
<td class="text-center">
|
||||
<div class="d-flex align-items-center gap-1 justify-content-center receive-form-${c.coatId}">
|
||||
@@ -1024,7 +1024,7 @@
|
||||
}
|
||||
qtyInput.classList.remove('is-invalid');
|
||||
|
||||
// Custom powder (no inventory item) → open modal to add to inventory
|
||||
// Custom powder (no inventory item) â†' open modal to add to inventory
|
||||
if (!hasInv) {
|
||||
const modal = document.getElementById('addPowderModal');
|
||||
// Pre-fill hidden + text fields
|
||||
@@ -1069,7 +1069,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Inventory item exists → receive directly
|
||||
// Inventory item exists â†' receive directly
|
||||
const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value
|
||||
?? document.querySelector('meta[name="__RequestVerificationToken"]')?.content;
|
||||
|
||||
@@ -1110,7 +1110,7 @@
|
||||
?? document.querySelector('meta[name="__RequestVerificationToken"]')?.content;
|
||||
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving…';
|
||||
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving…';
|
||||
|
||||
try {
|
||||
const resp = await fetch('@Url.Action("AddCustomPowderToInventory", "Dashboard")', {
|
||||
@@ -1139,7 +1139,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ── AI Lookup for Add Powder modal ───────────────────────────────────────
|
||||
// -- AI Lookup for Add Powder modal ---------------------------------------
|
||||
(function () {
|
||||
const apmBtn = document.getElementById('apm-ai-btn');
|
||||
const apmStatusEl = document.getElementById('apm-ai-status');
|
||||
@@ -1189,7 +1189,7 @@
|
||||
|
||||
const hasInput = manufacturer || colorName || colorCode || partNumber || itemName;
|
||||
if (!hasInput) {
|
||||
apmShowStatus('warning', '<i class="bi bi-exclamation-triangle me-1"></i>Fill in at least one field — Manufacturer, Color Name, Color Code, or Item Name — then try again.');
|
||||
apmShowStatus('warning', '<i class="bi bi-exclamation-triangle me-1"></i>Fill in at least one field — Manufacturer, Color Name, Color Code, or Item Name — then try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1198,7 +1198,7 @@
|
||||
document.getElementById('apm-bad-match-btn')?.remove();
|
||||
apmBtn.disabled = true;
|
||||
apmBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Looking up...';
|
||||
apmShowStatus('info', '<i class="bi bi-hourglass-split me-1"></i>Searching for product specifications…');
|
||||
apmShowStatus('info', '<i class="bi bi-hourglass-split me-1"></i>Searching for product specifications…');
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -1265,7 +1265,7 @@
|
||||
: '';
|
||||
apmShowStatus('success', `<i class="bi bi-check-circle me-1"></i>Auto-filled: ${filled.join(', ')}.${reasoning}`);
|
||||
} else {
|
||||
apmShowStatus('warning', '<i class="bi bi-info-circle me-1"></i>No new fields to fill — they may already be populated, or the product wasn\'t found.');
|
||||
apmShowStatus('warning', '<i class="bi bi-info-circle me-1"></i>No new fields to fill — they may already be populated, or the product wasn\'t found.');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
@@ -1319,7 +1319,7 @@
|
||||
(function () {
|
||||
var DISMISSED_KEY = 'pcl_pwa_banner_dismissed';
|
||||
|
||||
// Already installed as standalone — never show
|
||||
// Already installed as standalone — never show
|
||||
var isStandalone = window.navigator.standalone === true ||
|
||||
window.matchMedia('(display-mode: standalone)').matches;
|
||||
if (isStandalone) return;
|
||||
@@ -1343,7 +1343,7 @@
|
||||
var isSafari = /webkit/i.test(ua) && !/crios|chrome|fxios|opios/i.test(ua);
|
||||
if (isSafari) {
|
||||
titleEl.textContent = 'Add to Home Screen';
|
||||
msgEl.innerHTML = 'For the best experience — and so the camera only asks once — open the ' +
|
||||
msgEl.innerHTML = 'For the best experience — and so the camera only asks once — open the ' +
|
||||
'<strong>Share menu</strong> <span style="font-size:1.1em">▲</span> at the bottom of Safari ' +
|
||||
'and tap <strong>Add to Home Screen</strong>.';
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user