Adversarial review fixes for the #97 endpoint:
- nowPlaying now resolves like the XMLTV guide: when the wall-clock item
is pre/mid-roll filler, surface the guide group's program item (title +
full program bounds) instead of the filler; a guide entry with no
program item (e.g. fallback loop) reports null. Previously an ad break
would retitle the channel and reset progress every 30 seconds.
- covering-item lookup now runs one cheap projected query per distinct
playout offset instead of hydrating the full metadata include tree for
a window widened by the largest offset across ALL channels (up to 24h);
metadata is fetched in a second query scoped to the covering guide
groups, time-bounded to stay safe against GuideGroup recycling (mod
10000). Regression caught while testing: covering items are keyed by
(source channel, lookup time) so a source channel no longer inherits
its offset mirror's lookup result.
- GetChannelStatesForApi now carries Now explicitly (controller passes
DateTime.UtcNow), making boundary semantics testable: new tests pin
now == Start (playing) and now == Finish (not playing), plus mid-roll,
filler-only, and mirror-source-row cases.
- ChannelNowPlayingResponseModel title is non-nullable in the schema
(#nullable enable) — GetDisplayTitle always returns a string.
- GetState uses the typed-return controller convention; Playouts.Mapper
reverted to internal (handler lives in the same assembly).
- Spec doc notes the OnDemand drift caveat and filler semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An off-air channel has no current playout item, so the handler returns
null for NowPlaying; the DTO now declares that (file-scoped #nullable —
ErsatzTV.Core has NRT disabled project-wide). Regenerated OpenAPI and
web types: nowPlaying keeps its nullable oneOf schema (matching the
schema contract test) and channelNumber loses a spurious nullable
annotation left over from an earlier draft — the field is required.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the Auto-tuning wizard as hero screen 5 (#69) and annotates the
Channels-list and Schedule-editor sections + prompts with the borrowed
enrichments: lineup health + resume badge (#72, #68), weighted
distribution / reroll / pad-to-boundary / seasonal (#70, #71, #77, #73).
Refs #59#69
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the inherited 4-editor channel-creation chain with a single
library-to-lineup builder: browse/search library with artwork, drag
shows into the lineup, friendly toggles (shuffle, always-on), one image
for logo+bug, technical settings via built-in/custom Channel Templates
with advanced override. Lists prerequisite backend gaps (composite
create endpoint, Channel Templates, library browse API, full image-type
support incl. transparent re-encode, unified logo/bug, always-on vs
on-demand) and a Claude Design prompt for the screen.
Refs #59
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records the locked design direction for #59: React SPA on the REST API,
API-first (design pulls the API), Komodo-slick look, ChicoryTV dark +
chicory-blue brand. Includes the 4 hero screens with real domain
vocabulary and ready-to-paste Claude Design prompts.
Refs #59
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First slice of the REST API (ersatztv#2). Idiomatic REST over existing MediatR handlers; design in docs/rest-api.md.
Foundation: NotFoundError : BaseError (Core) to split 404 from 422; ApiResults mapping helpers (Either/Option -> 201+Location / 200 / 204 / 404 / 422), additive (existing *ToActionResult untouched); ApiKeyAuthorizationFilter (optional X-Api-Key on mutating actions, gated by Api:WriteKey, no-op when unset) — independent of the IPTV JWT toggle so Jellyfin/Dispatcharr reads are unaffected, applied at controller-class level so every mutating action incl. ResetPlayout is covered fail-safe.
Channels: POST /api/channels (201+Location), PUT/{id} (200/404), DELETE/{id} (204/404), GET/{id} (200/404). Request DTOs -> existing commands; responses reuse ChannelViewModel. Ported page-only validations into handlers (ShowInEpg-when-disabled, external-logo-URL, Group NotEmpty) + FFmpegProfile/Watermark/Filler existence on update for create-parity. Fixed a latent 500: the .Filter(c>0) existence pattern threw TaskCanceledException on not-found; rewritten to AnyAsync.
Tests (NUnit, TZ=UTC): handler success/404/422, ApiResults mapping, API-key filter, controller status/Location/mapping, a controller-security regression net, and a create->read->delete EF integration test on a new in-memory SQLite harness. ErsatzTV.Tests 46/46, Architecture 5/5.
Refs #34
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ErsatzTV.Tests had NUnit3TestAdapter + Shouldly but lacked Microsoft.NET.Test.Sdk and NSubstitute, so 'dotnet test' ran zero tests (no test host); the pre-existing MultiSelectBaseTests never executed. Add Microsoft.NET.Test.Sdk, NSubstitute, and Testably.Abstractions.Testing by name — versions already centralized in Directory.Packages.props (CPM preserved, no Version= attributes).
Surfaced while wiring tests for #34. Refs #34
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Should_Not_Crash_Mid_Roll_{Zero,One}_Chapters passed 'new PlayoutItem()'
to AddFiller, whose Start defaults to DateTime.MinValue. The mid-roll Pad
path (PlayoutModeSchedulerBase.cs:609) subtracts 'currentMinute' minutes
from StartOffset; since StartOffset does ToLocalTime(), a non-zero local
offset makes currentMinute non-zero, underflowing DateTimeOffset.MinValue
and throwing ArgumentOutOfRangeException. Under UTC the offset is 0 so it
stays in range (hence CI was green). Unreachable in production — real
playout items never start at MinValue — so this is a test-data artifact,
not a product bug.
Fix: seed the fixtures with a real Start (startState.CurrentTime.UtcDateTime),
matching the convention in the passing AddFiller tests. Verified across
UTC, Europe/Brussels, and Asia/Kolkata (+5:30); full Core.Tests now passes
under any timezone (484/0). Updated docs/contributing.md §8 per its §10
deviation policy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive the real GetChannelGuideHandler against a Testably fake filesystem
(seeded channels.xml + per-channel fragments) and a SQLite-backed
IDbContextFactory with one visible + one ShowInEpg=false channel, then
snapshot ChannelGuide.ToXml(). Covers the {RequestBase}/{AccessTokenUri}
substitution (the #1 regression surface), etv:-tag stripping, hidden-channel
exclusion, and assembly — with/without access token and with a base URL.
The volatile ?v={mtime} cache-buster is normalized. Same [CallerFilePath]
Goldens + ETV_UPDATE_GOLDENS harness as the M3U goldens (#11).
Core.Tests now references ErsatzTV.Application (handler lives there).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two-phase handoff for a parallel session: Phase 1 investigates the
existing API/CQRS/validation patterns and produces a design + increment
split (Channels-first); Phase 2 implements per vertical slice via
workflows/ultracode. refs #2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-investigated ersatztv#1 against the live stack: ErsatzTV emits
request-host-driven URLs (44 cached guide fragments use {RequestBase},
zero baked localhost), and Jellyfin no longer tuners ErsatzTV directly —
it funnels through Dispatcharr. The historical localhost came from a
client fetching with Host: localhost:8409, a path that no longer exists.
Document the live topology + evidence + resolution; reframe the optional
base-URL setting as not-built; note the latent fragility. Remove the now
spent handoff doc.
refs #1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docker-build.yml: the image `build` job now `needs: [test, migrations]`, so a
broken migration blocks the image build (not just the test gate). main branch
protection also now requires the "EF migration integrity (SQLite + MySql)" check
on PR merges. (follow-through on #13)
- docs/handoffs/m3u-tvg-logo-fix.md: working brief for the next task (#1, M3U
tvg-logo localhost), incl. the finding that the issue's GenerateChannelLogoUrl
root-cause is a red herring (that localhost is the ffmpeg watermark path, fetched
in-container; investigate the request flow / forwarded headers / cached values
instead). References the #11 golden + #16 E2E regression nets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs/contributing.md — a descriptive guide to the established patterns, derived from
the existing code: architecture/layering, CQRS handler conventions, LanguageExt
functional style, Blazor/MudBlazor UI, EF Core + dual-provider migrations, the FFmpeg
pipeline pattern, naming/formatting/analyzers, testing, and build/CI — each with
concrete file references — plus the deviation policy (match the established style;
diverge only with a concrete, stated reason). Several sections are now CI-enforced
(#12 layering, #15 analyzers, #13 migrations, #11 golden tests). CLAUDE.md points to
it so it's loaded as project guidance.
Survey across the sections done with parallel read-only agents; all file references
verified to exist.
Refs #10
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the image smoke test from "serves HTTP" to a real E2E: after HTTP readiness,
assert the Jellyfin-facing surfaces on the freshly built image — /iptv/channels.m3u
returns 2xx containing #EXTM3U, and /iptv/xmltv.xml returns 2xx containing a <tv root.
xmltv.xml needs channels.xml (the scheduler writes it a few seconds after boot), so each
endpoint is polled with a deadline; container logs are dumped on failure. Catches
routing / base-URL (#1) / migration regressions that leave the app "up" but serving
broken playlists/guides.
Validated the exact checks against the real :latest image on a fresh container (both 200
with the expected shape) before committing.
Refs #16
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New `migrations` job in docker-build.yml runs on every push/PR and, per provider:
- `dotnet ef migrations has-pending-model-changes` — fails on model drift (an entity
changed without a matching migration).
- `dotnet ef database update` against a fresh empty DB — applies all migrations,
catching broken/un-orderable ones.
SQLite (the prod provider) uses a throwaway file; MySql uses ServerVersion.AutoDetect
(connects at config time), so the job runs a `services: mysql:8.4` container (the
act_runner uses Docker execution on network downloadswarm, so the service is reachable
as `mysql:3306`). Independent gate for now (not a `needs:` of the image build) so the
MySql-service dependency can't block image builds until proven stable.
Validated both providers locally against real DBs: SQLite 787 migrations -> 139 tables;
MySql 8.4 305 migrations -> 137 tables; both model-drift checks clean.
Docs: ci-cd.md "Migration integrity" (dual-provider discipline, EF CLI pattern, the
non-transactional caveat) + CLAUDE.md convention.
Refs #13
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lock the M3U shape Jellyfin/Kodi consume (ChannelPlaylist.ToM3U) so a regression
— e.g. while implementing the paused #1 (tvg-logo base URL) — fails CI instead of
only surfacing as Jellyfin misbehaving in prod.
Six scenarios snapshot today's output: simple channel (generated logo), internal
artwork logo, external-URL logo, multi-channel (sorted by number), Kodi user-agent
(KODIPROP lines), and base-URL + access-token threading. Deterministic: fixed
UniqueIds, and a guard skips (Assume) when ETV_INSTANCE_ID is set (it feeds the
tvg-id). Goldens live next to the test under Goldens/, located via [CallerFilePath];
regenerate via the explicit Regenerate_goldens test or ETV_UPDATE_GOLDENS=1.
Verified: 6/6 pass, and proven to have teeth (tampering a golden fails the test).
XMLTV guide + channel-logo image golden coverage split to #28 (DB/filesystem
fixtures / non-deterministic across platforms).
Refs #11
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ErsatzTV.Architecture.Tests asserting the solution's layering, so a
"just import it here" violation fails the build instead of eroding the
architecture over time:
- Core must not depend on Application/Infrastructure*/Scanner, nor on
EF Core / Pomelo / Microsoft.Data.Sqlite / Dapper.
- FFmpeg (lowest layer) must not depend on any other ErsatzTV layer.
- Application must not depend on the concrete Infrastructure.Sqlite/.MySql
providers (only the Infrastructure abstraction).
- Infrastructure must not depend on Application or the concrete providers.
Uses NetArchTest.eNhancedEdition — the maintained fork; the original
NetArchTest.Rules is unmaintained since 2021 and its older Mono.Cecil
doesn't reliably parse .NET 10 assemblies. Runs via the existing `dotnet
test` in CI. Rules verified with a negative control (a known-true
dependency asserted forbidden fails as expected — so they're not vacuous).
Refs #12
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Roslynator, SonarAnalyzer.CSharp, Meziantou.Analyzer, and AsyncFixer as central
analyzer references (Directory.Build.targets, CPM-versioned, guarded on
ManagePackageVersionsCentrally so the gitignored .mcp tool isn't affected).
.editorconfig defaults dotnet_analyzer_diagnostic.severity to `suggestion` so the packs
surface findings without failing the TreatWarningsAsErrors (TWAE) build; rules are
promoted to warning/error incrementally (promotion = enforcement via the existing TWAE
build, so no separate lint step is needed).
StyleCop.Analyzers is intentionally excluded: its latest stable (1.1.118) crashes
(AD0001) on C# records and overlaps the existing .editorconfig/Roslynator.
Blazor .razor: editorconfig severity overrides don't reach analyzer diagnostics in Razor
@code (source-generator limitation; dotnet format can't fix them either), so the
currently-firing SonarAnalyzer rules are temporarily NoWarn-ed in ErsatzTV.csproj and
burned down rule-by-rule in #25. The same rules run at suggestion on .cs.
Formatting normalization (mixed UTF-8 BOM + whitespace) is deferred to its own PR.
Full-solution Release build green (0 errors, 0 warnings).
Refs #15
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previously-deferred dockerfile manager is live: it manages docker/Dockerfile
(mcr dotnet bases + internal ersatztv-ffmpeg) and reads the HTTP-only Gitea
registry via a RENOVATE_HOST_RULES host rule (insecureRegistry + read creds) set
in the workflow env. Vestigial arm/ffmpeg-tests Dockerfiles excluded; compose
manager unused (build-only).
refs server-management#484
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn on the `dockerfile` manager so Renovate also proposes base-image bumps for
docker/Dockerfile (mcr.microsoft.com/dotnet/* and our internal
192.168.1.95:3000/timothy/ersatztv-ffmpeg). The internal registry is HTTP-only,
so the workflow passes a host rule (insecureRegistry + registry read creds reused
from REGISTRY_USER/REGISTRY_PASSWORD) via RENOVATE_HOST_RULES — kept in the
workflow env, not in the committed renovate.json.
- Scope: only the built amd64 docker/Dockerfile; the vestigial upstream
arm32v7/arm64/ffmpeg-tests Dockerfiles (archived ghcr base) are disabled.
- Group mcr.microsoft.com/dotnet/* base images into one PR.
- Compose files are build-only (no image tags) -> docker-compose manager not needed.
refs server-management#484
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update ci-cd.md: the scheduled vuln scan is green now that #8 cleared the
advisories (red = a new one); note the NU1901-1903 demotion is kept by design
(criticals still block; lower severities surface via the scan + Renovate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NCalcSync 5.11.0 -> 6.3.2 clears CVE-2026-55254 / GHSA-3w5p-95mh-gq75 (the
factorial-DoS advisory on NCalc.Core/NCalcSync). NCalc 6 split its assemblies and
renamed the custom-function API, so port OpacityExpressionHelper:
FunctionArgs -> FunctionEventArgs, and args.Parameters[i].Evaluate() ->
args.Parameters.Evaluate(i) (FunctionData.Count / Evaluate(index)). Add a
regression test covering the migrated opacity wiring (the feature had no tests).
NCalc 6 transitively requires Microsoft.Extensions.Logging.Abstractions >= 10.0.7,
so bump the centrally-pinned Microsoft.Extensions.* family 10.0.2 -> 10.0.7 to
avoid the NU1605 downgrade error (a .NET 10 servicing patch bump).
SQLitePCLRaw: EF Core 9's Sqlite provider pulls the vulnerable bundle 2.1.10
(GHSA-2m69-gcr7-jv3q, outdated bundled SQLite). Directly pin
SQLitePCLRaw.bundle_e_sqlite3 3.0.3 in Infrastructure.Sqlite to override the
transitive version with the patched native (lib.e_sqlite3 3.50.3); core 3.0.3
satisfies Microsoft.Data.Sqlite's >= 2.1.10 requirement under EF Core 9.
Verified: `dotnet list package --vulnerable --include-transitive` reports 0
vulnerable projects; restore + Release build clean; full test suite green under
UTC. (2 pre-existing PlayoutModeSchedulerBase filler tests fail only under
non-UTC local timezones, unrelated to these deps; they pass in CI.)
Refs #8
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Renovate is now live (PR #18/#19): self-hosted weekly Gitea Actions job that
opens dependency-update + OSV vuln-fix PRs and a Dependency Dashboard, with
patch-level auto-merge scoped to test/dev-only packages. Document it under
docs/ci-cd.md → Dependency management, plus a CLAUDE.md conventions pointer.
refs server-management#484
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two follow-ups to the initial Renovate setup (merged in PR #18):
- Gitea reserves the GITHUB_ secret-name prefix (same as GitHub Actions), so the
GITHUB_COM_TOKEN secret was rejected (HTTP 400). Rename to GH_COM_TOKEN and map
it to RENOVATE_GITHUB_COM_TOKEN in the workflow.
- Pin renovate/renovate:43 (current major; 41 was already stale — latest is 43.x).
refs server-management#484
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stand up Renovate for this repo via a scheduled Gitea Actions workflow on the
shared act_runner. Managers: nuget (Central Package Management) + github-actions.
Dockerfile/docker-compose managers deferred until the HTTP Gitea-registry
handling is verified.
- renovate.json: config:recommended, dependency dashboard, OSV vulnerability
alerts, family grouping (Microsoft.Extensions/AspNetCore/EF Core/Serilog/
Refit/Lucene.Net), and patch-level auto-merge scoped to test/dev-only packages
(NUnit/NSubstitute/Shouldly/coverlet/Test.Sdk/Testably/threading analyzer).
- .gitea/workflows/renovate.yml: weekly cron + workflow_dispatch (defaults to a
safe dry run); bot identity + tokens from repo Actions secrets.
Supersedes the *proposing* half that dependency-scan.yml (ersatztv#14) left out;
the scan stays as a cheap in-repo detector for now.
refs server-management#484
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ci-cd.md: new "Dependency management" section (CPM, NuGet audit, scheduled
dependency-scan.yml); Dockerfile notes now list Directory.Packages.props and
why it's required before restore.
- CLAUDE.md: dependency convention (edit central Directory.Packages.props, never
re-add Version=); correct the test framework note (NUnit, not xUnit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Gitea-native stand-in for Dependabot (#14): a weekly (cron) + workflow_dispatch
job running `dotnet list package --vulnerable --include-transitive` over the full
solution, failing the run when advisories are present (the command itself exits 0,
so the marker line is parsed). Detection only; automated update PRs are tracked in
server-management#484 (self-hosted Renovate).
Expect RED until #8 clears the current NCalcSync/SQLitePCLRaw advisories; after
that a red run signals a NEW advisory.
Part of #14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move all 154 PackageReference versions (96 distinct packages) out of the 14
project files into a single central Directory.Packages.props with
ManagePackageVersionsCentrally=true. No version changes — every package was
already pinned identically across projects (no conflicts detected), so this is a
pure relocation: updates become one-line and cross-project version drift is
structurally impossible.
Also copy Directory.Packages.props into the Docker image build before restore:
with CPM the csproj carry no versions, so the image's `dotnet restore` fails
without the central manifest (verified: NU1015 across every project).
Restore + Release build verified locally, plus a simulation of the image's
restore layer under linux-x64 (0 errors; only the pre-existing
NCalcSync/SQLitePCLRaw advisories remain, demoted to warnings, tracked in #8).
Part of #14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-trigger: Gitea dropped the run records for the prior push/tag of this
same tree (runner online, workflow active, but no ActionRun created).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document upstream's vYY.<release-seq>.<patch> scheme (year, sequential
release-in-year, patch) in docs/ci-cd.md + CLAUDE.md so we follow it going
forward: <release-seq> is NOT the calendar month (v25.2.0 shipped in June,
v26.3.0 in Feb), it's a per-year counter that resets each January. v26.3.1
= our infra-only rebuild of upstream 26.3.0; v26.4.0 reserved for the first
release with app changes. Also notes the [skip ci]-on-a-tagged-commit gotcha.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add concurrency group so the single jazz runner can't run the
push-main-then-push-tag release flow in parallel (shared :buildcache
+ smoke container would collide).
- Add pull_request trigger running the test job only (PRs had no gate);
skip the build job on PRs.
- Only push images from main or a v* tag (workflow_dispatch from other
refs now builds without publishing, instead of clobbering :latest/:prod).
- Replace the log-grep smoke check with a real HTTP readiness probe
(docker exec python3 -> http://localhost:8409/), unique container name,
and trap-based cleanup to avoid leaks on cancel.
- dotnet test now runs -c Release --no-build (was rebuilding in Debug).
- Directory.Build.props: WarningsAsErrors=NU1904 so critical NuGet
advisories block in every project, not just ones with
TreatWarningsAsErrors.
- Dockerfile copies global.json + .editorconfig too, so the image build
matches CI's SDK pin and analyzer severities.
- Remove dead .github/dependabot.yml + FUNDING.yml (upstream-pointed).
- Rewrite docs/ci-cd.md to the implemented pipeline.
Refs #4, #3, #8.
ErsatzTV logs via Serilog and never emits Kestrel's 'Now listening on'
line, so the smoke test grep never matched even though the app booted
fully in ~6s. Match ErsatzTV's actual startup logs instead ('… service
started', 'Located ffmpeg'). The image build itself was already passing.
Refs #4.
.NET 10's NuGet audit promotes vulnerable transitive packages to NU1901-1904
warnings during restore; the app projects set TreatWarningsAsErrors=true, so
restore failed on NCalcSync 5.11.0 (moderate) and SQLitePCLRaw 2.1.10 (high).
Demote NU1901/NU1902/NU1903 to warnings in Directory.Build.props (NU1904
critical still errors). Also COPY Directory.Build.props/.targets in the
Dockerfile before restore so the image build honors the same props as
local/CI builds (it previously copied only the .sln). Underlying deps tracked
in #8.
Refs #4, #8.
Adds .gitea/workflows/docker-build.yml: a test-gated, amd64-only Docker
build that pushes the fork's own image to the Gitea container registry
(192.168.1.95:3000). The .NET test job gates the image build; the build
job stamps INFO_VERSION from git, uses buildx registry layer caching, and
smoke-tests that the built image boots before finishing.
Tagging:
- push to main -> :latest + :<short-sha> (test image; prod untouched)
- push tag v* -> :prod + :<version> + :<short-sha> (prod release)
Updates docker/Dockerfile to pull the FFmpeg base image from our Gitea
registry (192.168.1.95:3000/timothy/ersatztv-ffmpeg:7.1.1) instead of the
archived upstream ghcr.io image.
Removes the upstream .github/workflows/* — they target GHCR/DockerHub and
Azure/Apple signing secrets we don't have, call reusable workflows at dead
ersatztv/ersatztv@main paths, and were running as noise (incl. a daily
stale-issue cron) on the Gitea runner. Upstream is archived, so there are
no future merges to preserve them for.
Refs #4, #3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live Host-header test against the running container proves tvg-logo follows
the request Host header — there is no hardcoded localhost in the M3U/XMLTV
path (the localhost in GenerateChannelLogoUrl only feeds the FFmpeg watermark
overlay). Record the confirmed cause, the operational vs. durable fixes, and
the key architectural fact for future work: ErsatzTV has no advertised/base-URL
setting, so every absolute URL derives from the request Host; ConfigElement is
key/value so adding a setting needs no EF migration. Code fix for #1 deferred
until CI/CD test/prod envs (#3, #4) exist.
Refs #1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Moves ci-cd.md (59 lines) from Claude memory into docs/ alongside
existing architecture docs. Slims MEMORY.md from 42 to 18 lines by
removing sections duplicated in CLAUDE.md (Tech Stack, Key Patterns,
Architecture Docs index).
Total memory load per session: 101 → 18 lines.
Fixes#7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace informal implementer workflow with structured 7-step protocol
including mandatory root cause analysis for bug fixes. References /done
skill for automated enforcement.
Part of adversarial-reviewer #260.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document channel architecture, M3U/XMLTV integration with Jellyfin,
and fork maintenance strategy for the archived upstream. Also includes
CLAUDE.md updates for implementer workflow and project boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CLAUDE.md with architecture overview and development guide
- .mcp.json with docker, ssh, gitea, csharp-lsp, and nuget MCP servers
- Skills for ersatztv and jellyfin
- .gitignore: exclude .mcp/ (built MCP tools)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>