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
@@ -52,11 +52,11 @@
<small class="text-muted">
@if ((bool)ViewBag.UseAi)
{
<i class="bi bi-cloud me-1 text-primary"></i><span class="text-primary fw-medium">Application Insights</span><span class="ms-1"> Warning+ events &mdash;</span>
<i class="bi bi-cloud me-1 text-primary"></i><span class="text-primary fw-medium">Application Insights</span><span class="ms-1">&mdash; Warning+ events &mdash;</span>
}
else
{
<i class="bi bi-database me-1"></i><span>SQL table Warning+ events &mdash;</span>
<i class="bi bi-database me-1"></i><span>SQL table &mdash; Warning+ events &mdash;</span>
}
@if (totalCount > 0) { <span>@totalCount.ToString("N0") entries</span> }
else { <span>No entries yet</span> }
@@ -74,7 +74,7 @@
{
<div class="alert alert-info alert-permanent">
<i class="bi bi-info-circle me-2"></i>
The <code>SystemLogs</code> table hasn't been created yet it will appear automatically after the first Warning or Error is logged.
The <code>SystemLogs</code> table hasn't been created yet &mdash; it will appear automatically after the first Warning or Error is logged.
</div>
}
else if (ViewBag.QueryError != null)
@@ -88,7 +88,7 @@
<form method="get" class="row g-2 align-items-end">
<div class="col-md-3">
<input name="search" value="@ViewBag.Search" class="form-control form-control-sm"
placeholder="Search message or exception" />
placeholder="Search message or exception&hellip;" />
</div>
<div class="col-md-2">
<select name="level" class="form-select form-select-sm" onchange="this.form.submit()">
@@ -101,7 +101,7 @@
</div>
<div class="col-md-3">
<input name="source" value="@ViewBag.Source" class="form-control form-control-sm"
placeholder="Source context" />
placeholder="Source context&hellip;" />
</div>
<div class="col-md-1">
<input name="from" type="date" value="@ViewBag.From" class="form-control form-control-sm" title="From date" />
@@ -153,7 +153,7 @@
<td class="text-nowrap small text-muted">@row.Timestamp.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd HH:mm:ss")</td>
<td><span class="badge @LevelBadge(row.Level)">@row.Level</span></td>
<td class="small text-truncate" style="max-width:180px" title="@row.SourceContext">
@(row.SourceContext?.Split('.').LastOrDefault() ?? "")
@(row.SourceContext?.Split('.').LastOrDefault() ?? "&mdash;")
</td>
<td class="small text-truncate" style="max-width:400px">
@row.Message
@@ -162,8 +162,8 @@
<i class="bi bi-bug text-danger ms-1" title="Has exception"></i>
}
</td>
<td class="small text-muted">@(row.UserName ?? "")</td>
<td class="small text-muted text-center">@(row.CompanyId?.ToString() ?? "")</td>
<td class="small text-muted">@(row.UserName ?? "&mdash;")</td>
<td class="small text-muted text-center">@(row.CompanyId?.ToString() ?? "&mdash;")</td>
<td class="text-center"><i class="bi bi-zoom-in text-muted small"></i></td>
</tr>
}
@@ -172,7 +172,7 @@
</div>
</div>
@* Mobile card view shown on screens < 992px *@
@* Mobile card view &mdash; shown on screens < 992px *@
<div class="mobile-card-view d-lg-none">
<div class="mobile-card-list">
@foreach (var row in Model)
@@ -183,7 +183,7 @@
var cardIcon = row.Level == "Error" || row.Level == "Fatal" ? "bi-x-circle"
: row.Level == "Warning" ? "bi-exclamation-triangle"
: "bi-info-circle";
var msgTruncated = row.Message?.Length > 80 ? row.Message.Substring(0, 80) + "" : row.Message ?? "";
var msgTruncated = row.Message?.Length > 80 ? row.Message.Substring(0, 80) + "&hellip;" : row.Message ?? "&mdash;";
<div class="mobile-data-card log-row"
style="cursor:pointer"
data-bs-toggle="modal" data-bs-target="#logModal"
@@ -208,7 +208,7 @@
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Source</span>
<span class="mobile-card-value small">@(row.SourceContext?.Split('.').LastOrDefault() ?? "")</span>
<span class="mobile-card-value small">@(row.SourceContext?.Split('.').LastOrDefault() ?? "&mdash;")</span>
</div>
@if (row.UserName != null)
{
@@ -238,7 +238,7 @@
{
<nav class="mt-3 d-flex justify-content-between align-items-center">
<small class="text-muted">
Showing @((page - 1) * pageSize + 1)@Math.Min(page * pageSize, totalCount) of @totalCount.ToString("N0")
Showing @((page - 1) * pageSize + 1)&ndash;@Math.Min(page * pageSize, totalCount) of @totalCount.ToString("N0")
</small>
<ul class="pagination pagination-sm mb-0">
<li class="page-item @(page <= 1 ? "disabled" : "")">