The branch moved the `builtIn` discriminator from a bare filename to the full seeded path, but split how the two sites evaluate it: the API handler compares in memory (ordinal) while GetBuiltInElementId's new `.Where(e => e.Path == OnNowNextSeededPath)` compares in SQL. GraphicsElement.Path takes no explicit collation -- TvContext.OnModelCreating pins one only on the listed name/title columns -- so SQLite answers that case-sensitively and MySQL uses the server default, which is normally case-INsensitive. On MySQL the two discriminators could therefore disagree about the same row: AttachOnNowNextByDefault would resolve a case-variant user element as the built-in one while the API reported builtIn:false for it. Collapse both onto GraphicsElementDefaults.IsOnNowNext, ordinal, applied in memory. GetBuiltInElementId goes back to loading the Text candidates and filtering in memory (the shape it had before this branch), keeping only the `Kind` enum filter in SQL. The prose claimed more than the code did. "A filename-only comparison is case-sensitive-by-accident" appeared in four places as a defect the full-path fix removed; a full-path comparison is exactly as case-sensitive, so the clause said nothing and implied a fix that had not happened. Case sensitivity is now deliberate and stated as such -- the built-in element is the exact file the seeder wrote, at the exact path it wrote it to -- and the reason the comparison is kept out of SQL is recorded where the predicate lives. docs/decisions/records/graphics/channel-level-attachment.md said BuiltIn was "computed by comparing the row's `Path` to GraphicsElementDefaults. OnNowNextFileName", which was true of neither the pre-#568 rule (filename to filename) nor the current one; an active record resolved by key now states the current predicate in its own sentence rather than in a parenthetical. Two tests pin the ordinal rule against a loosening to OrdinalIgnoreCase, one per site. Measured: OrdinalIgnoreCase reddens exactly GetAllGraphicsElementsForApi_Should_Not_Mark_A_Case_Variant_Of_The_Seeded_Path_As_BuiltIn and Ignores_A_Case_Variant_Of_The_Seeded_Path, 2 failed / 77 passed of the 79 graphics tests. They do NOT pin provider independence -- under SQLite's BINARY collation an equivalent SQL comparison answers identically, so no test in this suite can distinguish the two. That is stated at each site rather than left for a reader to assume the tests cover it. Decisions-Edit: yes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
41 lines
2.4 KiB
C#
41 lines
2.4 KiB
C#
using System.IO;
|
|
|
|
namespace ErsatzTV.Core.Graphics;
|
|
|
|
public static class GraphicsElementDefaults
|
|
{
|
|
// Built-in "On Now / Next" text element filename -- a component of OnNowNextSeededPath below,
|
|
// never itself an identity check (#568: a filename-only comparison is folder-agnostic).
|
|
public const string OnNowNextFileName = "on-now-next.yml";
|
|
|
|
// Display name only. Never use it for identity -- that is IsOnNowNext below (#67 / #74 / #568).
|
|
public const string OnNowNextName = "On Now / Next";
|
|
|
|
// The full path the seeder writes the built-in template to (GraphicsElementSeeder.SeedOnNowNext
|
|
// builds `target` the same way). A `builtIn` discriminator must match THIS, not
|
|
// `Path.GetFileName(...) == OnNowNextFileName` -- a filename-only comparison is folder-agnostic:
|
|
// a user element named exactly `on-now-next.yml` in any of the other four template folders
|
|
// (image/motion/subtitle/script) would also report `builtIn:true` (#568). `Kind == Text` alone
|
|
// does not close this either, since a second text template could share the filename in principle.
|
|
public static string OnNowNextSeededPath =>
|
|
Path.Combine(FileSystemLayout.GraphicsElementsTextTemplatesFolder, OnNowNextFileName);
|
|
|
|
/// <summary>
|
|
/// The one identity test for the built-in On Now / Next element. Ordinal on purpose, and so
|
|
/// case-sensitive on purpose: the built-in element is the exact file the seeder wrote, at
|
|
/// the exact path it wrote it to.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Callers compare in memory rather than in a <c>Where</c> clause, because in SQL the answer
|
|
/// would be the PROVIDER's to give: <c>GraphicsElement.Path</c> takes no explicit collation
|
|
/// (<c>TvContext.OnModelCreating</c> pins one only on the listed name/title columns), so
|
|
/// SQLite compares it case-sensitively while MySQL uses the server default, which is
|
|
/// normally case-INsensitive. Evaluating one discriminator site in SQL and the other in
|
|
/// memory would let the two disagree on MySQL alone. The SQLite test suite cannot tell the
|
|
/// two apart -- BINARY collation and an ordinal comparison agree on every input -- so this
|
|
/// is held by keeping the comparison out of SQL, not by a test.
|
|
/// </remarks>
|
|
public static bool IsOnNowNext(string path) =>
|
|
string.Equals(path, OnNowNextSeededPath, StringComparison.Ordinal);
|
|
}
|