PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Failing after 23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m29s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 16m5s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
168 records -> docs/decisions/records/<area>/<topic>.md (163 active, 23 dirs) and docs/decisions/archive/<area>/<topic>.md (5 archived). The filename IS the key, so one-active-record-per-key becomes a filesystem property rather than a validator check, and supersession becomes a `git mv`. WHY: the monolith was a concurrency problem before an aesthetic one. A 3,900-line append target made parallel sessions collide -- PR #605 and PR #614 both hit append-vs-append conflicts during routine rebases, and hand-resolving those inside the corpus is exactly the operation the rationale-rewrite guard exists to police. HOW IT IS VERIFIED: a ~170-file diff cannot be meaningfully read, so correctness does not rest on reading it. The parser was taught BOTH formats first, so the body-diff guard parses the old form at the merge-base and the new form at head -- the migration validates itself, no bypass. The proof is a field-level equivalence harness: 168 records before and after, zero lost, zero gained, zero field mismatches, zero rationale bodies differing. Reviewers should scrutinise the harness; it is the actual evidence. What measuring caught that reading would not have: - ~500 lines sit OUTSIDE any record -- decisions.md's lifecycle schema and each topic file's preamble, mostly the only copy. Source files are kept and stripped, never deleted. They also cannot be filed per-area: topic files hold several areas and 4 of 23 areas span several files. - Archive discovery was a non-recursive glob; after the split it found ZERO archived records, surfacing as four bogus "supersedes points to unknown key" errors rather than an obvious failure. - ~32 live docs point into the corpus BY DATE, which the split dangles. Each stripped file now ends with a generated "Records formerly in this file" index, which also rescues the identical breadcrumbs in old issue comments. - decisions.md's "In this file:" list was 97 same-file anchor bullets that the split makes WRONG, not merely stale. Dropped; the generated index replaces them with links that resolve. The equivalence harness now runs against a checked-in FIXTURE, not the live corpus. The earlier version migrated the real tree, which made it a one-shot: the moment the migration landed there was nothing left to move and the tests failed for reasons unrelated to the code. A fixture keeps them testing the SCRIPT rather than the repo's current state. Keys preserved verbatim, warts included: `sched` (12) and `scheduling` (1) remain two directories for one concept. Renaming a key is not a move -- it changes identity, breaks the equivalence proof, and invalidates MemPalace's per-key drawers. Taxonomy normalisation is separate work. refs #610
3.3 KiB
3.3 KiB
key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
| key | title | status | since | supersedes | superseded-by | rule | signals | mechanics |
|---|---|---|---|---|---|---|---|---|
| spa.channel-editor-create-logo | 2026-07-11 — Channel editor: bare-create entry point + external-logo mutual exclusion (#212) | active | 2026-07-11 | none | none | Bare-channel create is a "New blank channel" action on the channels list (reusing Blazor's add-mode defaults) that navigates into the full editor, and an external logo URL always wins over an uploaded logo, matching `ChannelEditViewModel` precedence. | `ChannelEditor.razor` add-mode defaults, `ArtworkContentTypeModel.isExternalUrl`, `EMPTY_LOGO` · paths: `web/src/App.tsx`, `ChannelEditScreen.tsx` · issues: #212 | `ChannelEditScreen.tsx` |
Two Blazor-parity decisions closing the channel-editor gaps (ChannelEditor.razor +
ChannelEditViewModel):
- Bare-channel create lives on the channels list, not a form-first route. Blazor's
/channels(noId) is a full add form; the SPA instead adds a "New blank channel" action next to "Add Channel" onChannelsScreen(web/src/App.tsx) that POSTsCreateChannelRequestwith Blazor's computed add-mode defaults ((max existing int-parsed channel number) + 1,name: "New Channel",group: "ErsatzTV",ffmpegProfileId=GET /api/settings/ffmpeg'sdefaultFFmpegProfileId,streamingMode: "TransportStreamHybrid",isEnabled/showInEpg: true, every other field at its C#default(T)— seeChannelEditor.razor'selsebranch for the source of truth) directly, then navigates to/app/edit-channel/{id}for the rest of the fields. This is a deliberate equivalent, not a parity gap: it reuses the existing full editor instead of duplicating its ~20 fields into a second form. "Add Channel" (/app/new-channel, the library-to-lineupChannelBuilderflow) is unrelated and untouched. - External logo URL wins over an uploaded logo, mirroring
ChannelEditViewModel.ToUpdate/ToCreate. The channel's logo isArtworkContentTypeModel { path, contentType, isExternalUrl }; on hydration,isExternalUrl: truepopulates a separate "External logo URL" field and the uploaded-logo draft state is treated as empty (EMPTY_LOGO = { path: '', contentType: '' }inChannelEditScreen.tsx) so the two never disagree. On submit, a non-blank URL always wins:logo: { path: url, contentType: '' }, exactly matchingExternalLogoUrl's precedence in the C# view model. Uploading a file clears the URL field (the last-set field wins, Blazor parity viaUploadLogo's_model.ExternalLogoUrl = null). The URL is validated as http(s) client-side before save is enabled (Blazor has no equivalent validation; added because the field is free text with no server-side format check surfaced to the SPA).
Also landed with #212: preferredAudioLanguageCode/preferredSubtitleLanguageCode,
musicVideoCreditsTemplate, and streamSelector moved from free-text Inputs to Selects fed by
GET /api/languages / /api/channels/music-video-credits-templates /
/api/channels/stream-selectors. Each keeps the channel's currently-stored value selectable even if
it's absent from the reference list (optionsKeepingCurrent in ChannelEditScreen.tsx) so loading
an existing channel never silently changes the value out from under an unmanaged language code or a
template/selector file removed from disk since save.