Channel lineup health at-a-glance (status states in the channels list) #72
Closed
opened 2026-07-01 23:44:18 +02:00 by timothy
·
3 comments
No Branch/Tag Specified
main
renovate/lucene.net
renovate/dotnet-monorepo
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
renovate/meziantou.analyzer-3.x
renovate/cliwrap-3.x
913-scripted-playout-401
release/v26.15.0-notes
issue-806-guard-populations
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.16.0
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#72
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
UX concept borrowed from PseudoTV (its channel-manager color states). Folds into the redesigned Channels list (#59).
Goal
See the health of a large channel lineup at a glance, especially channels that will fail to play.
Scope
Notes
Related: #59.
Done-when
GET /api/v1/channelsreturns a per-channelplayoutCount(Mirror-aware — a mirror channel counts its source's playouts)playoutCountis fixed, with a regression test proven non-vacuous (removing the include turns it red)decisions.md(API shape + what is deliberately not computed) anddomain-model.md; design-system prototype mirrored (#388)Scope note (2026-07-17): scope item (a) origin and most of (b) — empty-schedule, broken-source — are deliberately out of this PR; only "no playout" ships as the fault signal. See
decisions.md2026-07-17 for why each is unsafe to approximate today (short version:EmptyScheduleHealthCheckonly understands Classic playouts, media faults have no channel attribution, and no honest origin signal exists without a new column + migration on the auto-tune arc's surface).🔗 Session bundle — channel health / status UX: #72, #164. Both surface channel status in the SPA; work together. (Backlog-grooming 2026-07-17.)
🔵 Claiming (Claude Code / Opus 4.8 orchestrator session, 2026-07-17) — working this together with #164 as the groomed "🔗 Session bundle — channel health / status UX".
Live claimability verified from raw Gitea state (not selector prose): OPEN, unclaimed,
priority: medium,GET /issues/72/dependencies→[](no openblocked-by).Why this over the strict tier-2 winner (#176) — stated deviation, per the queue protocol:
priority: lowand design-first, and its central premise ("Autotune | Nothing — Biggest gap") is stale: #69 is CLOSED and the #383 DetailPanel arc is in flight right now. Its design inputs are actively shifting under #70 (distribution — frontier design verdict posted 09:14) and #383/#384, so a design verdict written today would be built on sand.reviewlabels; reviewer audits #20–#24 all have posted deliverables). Tier 4 (priority: high) is empty.priority: medium, dependency-clear, and genuinely disjoint from the three live sessions (#70 scheduling backend, #384 auto-tune backend, #390 CI) — parallel-safe by file surface. #72 is also a concrete shippable child of the very vision #176 describes.Selector note (recurring lore): the cheap selector ranked #383 first — it missed that #384 is
in-progressand that #383 is that bundle's umbrella. Independent re-derivation from raw issue state caught it. Consistent with the #73 lesson: verify the selector's mechanical outputs (deps + priority + claims) yourself.Design/scope pass next; will report before implementing.
Done — merged to main (PR #405)
What shipped
The channels list now flags a channel that will never play — the fault #72 called "the important one" — as a visible "No playout" badge plus a matching No playout filter to collapse a large lineup to just the faults. Backed by a new
PlayoutCounton the channels API.Root cause (a live bug found under the feature)
The feature needed the API to answer "will this channel play?", and it turned out the API was lying about it for every channel:
ChannelRepository.GetChannelnever.IncludedPlayouts, and the read isAsNoTrackingwith no lazy proxies, soGetChannelByIdForApiHandler'schannel.Playouts?.Count ?? 0could only ever evaluate to 0.GET /api/v1/channels/{id}reportedplayoutCount: 0for every channel on the system, which silently disabled the channel editor's playout-source guard (ChannelEditScreen, gated onplayoutCount > 0). The server still enforced the invariant, so nothing corrupted — but a user could flip a channel to Mirror, save, and watch the change silently revert. Both call sites now shareMapper.GetPlayoutsCount(Mirror-aware — a mirror channel counts its source's playouts), pinned by a repository-level test proven non-vacuous (removing the include turns it red).Design decisions (frontier pass;
docs/decisions.md2026-07-17)/channels/state— health is config-derived, not per-poll runtime state; free becauseGetAllalready loaded the data.PlayoutCountfact, not a status enum — the auto-tune arc (#383/#384) is about to churn the status taxonomy; an enum would force a breaking rev of frozen-additive/api/v1.Deliberately NOT computed (each would be wrong today, not merely absent) → filed, not dropped
Channelcolumn + dual-provider migration; provenance belongs to the auto-tune arc, so blocked-by #383 (dependency set + verified). The playlist-group-join shortcut is recorded as rejected.EmptyScheduleHealthCheckonly understands Classic playouts, so a badge driven off it would be silently wrong for four of the five schedule kinds (#71 class).The SPA filter stays "No playout" (not "Problems") until #415 gives the broader label a real taxonomy —
willNeverPlay's call-site comment says so.Files changed
ChannelRepository.cs(+2 includes),Mapper.cs(sharedGetPlayoutsCount),GetAllChannelsForApiHandler/GetChannelByIdForApiHandler/GetAllChannelsHandler,ChannelResponseModel(+PlayoutCount),OpenApiSerializerContractTests, newChannelRepositoryPlayoutIncludeTests,ChannelsScreen.tsx+ test, regenerated OpenAPI artifacts,design-system/Channels.jsx.Docs updated (same PR)
docs/decisions.md(API shape + what's deliberately not computed),docs/domain-model.md(Channel health glossary row), design-system prototype mirrored.Review
Two independent cold passes (full diff + fix-commit re-review), both MERGEABLE. Findings fixed: a Medium (the "Problems" filter over-promised → renamed "No playout") and a Low (a stale "Problems" reference in domain-model.md).
Follow-ups / related
UpdateChannelsilently coerces Mirror→Generated when a playout exists (should be 422). Found here, filed separately as an API-semantics change.Deferred / not addressed
The design-sync pull was not done — the change was mirrored into
design-system/Channels.jsxwithout first pulling the current prototype from Claude Design. If that screen's design has moved since, the mirror is on a stale base; worth a/design-syncreconcile.timothy referenced this issue2026-07-23 22:18:59 +02:00
timothy referenced this issue2026-07-25 11:34:36 +02:00