Commit Graph
62 Commits
Author SHA1 Message Date
timothy be1070de51 Merge pull request 'feat(437): inline RuleBuilder smart-query authoring in Channel Builder' (#579) from feat/437-rulebuilder-channelbuilder into main
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 15m56s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 16m3s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 20m47s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 11m29s
2026-07-23 21:17:41 +00:00
timothyandtimothy 65c0e09179 feat(415): per-channel fault detection — server-derived health object + Problems filter (#581)
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 15m30s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 15m48s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 7m32s
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Closes #415. Server-derived health object on the channel list + detail DTOs (built-timeline detection, kind-agnostic across all 5 PlayoutScheduleKind; assessable gate keyed to the owning channel's mode), single "Problems" SPA filter with per-fault badges. Supersedes #72's api.channel-health-signal decision.

Co-authored-by: Timothy <timothy.look@gmail.com>
Co-committed-by: Timothy <timothy.look@gmail.com>
2026-07-23 20:45:19 +00:00
timothyandClaude Opus 4.8 5fba6187ce feat(437): inline RuleBuilder smart-query authoring in Channel Builder
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
PR Gates / Docs update reminder (pull_request) Successful in 13s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m0s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m0s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m38s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m4s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 18m49s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Adopt the reusable RuleBuilder (#176) for inline query authoring in the Channel
Builder (/app/new-channel). Extract CollectionsScreen's smart-collection dialog
into a shared, self-contained component (SmartCollectionDialog) and consume it in
both screens; the Channel Builder's Collections source gains a "New smart query"
action that persists the authored query as a real SmartCollection and adds it to
the lineup by smartCollectionId. Pure frontend — no REST/MCP surface change (the
MCP already exposes ersatztv_create_smart_collection).

The Auto-Tune half of #437 is a different primitive (group-by, not single-query
filtering) and a backend epic; it is designed separately in
docs/superpowers/specs/2026-07-23-auto-tune-arbitrary-field-design.md and filed as
its own issue rather than wired here.

Verification: web typecheck + lint clean; full vitest suite green (981, incl. a new
inline-smart-query test); cold-context review clean; live-E2E on a real instance
(query authored in the SPA persisted as SmartCollection "Action Picks" and added to
the lineup, 0 console errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:08:41 +02:00
timothyandClaude Opus 4.8 5618ad12ea plan(434,435,438): RuleBuilder bundle implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 4e2183d9a3 design(434,435,438): RuleBuilder bundle spec — validation, relative-date operators, facet typeahead
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothy 0b814abf41 docs(392): extract shared ComputePadBoundary helper in plan (DRY) 2026-07-23 08:03:31 +02:00
timothyandClaude Opus 4.8 9385bc8acd docs(392): implementation plan for per-schedule clock-boundary padding
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:03:31 +02:00
timothyandClaude Opus 4.8 425b4b65f9 docs(392): design spec for per-schedule clock-boundary padding toggle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:03:31 +02:00
timothy ed3acb931c docs(74): fix Task 3 test to Testably MockFileSystem API 2026-07-22 22:11:45 +02:00
timothyandClaude Opus 4.8 6ad2f83ea6 docs(74): implementation plan for On Now/Next overlay
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 22:11:45 +02:00
timothyandClaude Opus 4.8 2901b5293a docs(74): design spec for per-channel On Now/Next transient overlay
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 22:11:45 +02:00
timothy 6838979780 fix(60): re-review fixups for channel preview
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 18s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14m11s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 17m26s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m44s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 22m57s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
- ChannelPreviewPanel: a manual play-button click on a video already
  showing a fatal error was clearing the error, silently hiding the
  fault the panel exists to reveal. onPlaying now ignores the event
  while a fatal error is showing (tracked via a ref, reset in an
  effect keyed on channel.id); Retry remains the only way to clear it.
- shell.css: .ctv-preview-facts spacing was dead — equal-specificity
  .ctv-detail-infogrid{margin:0} later in the file won. Raised
  specificity with a compound selector instead of touching
  .ctv-detail-infogrid, which MediaDetailScreen also relies on.
- ChannelPreviewTests: added two cases exercising two simultaneously-
  true Unavailable causes, so the documented guard precedence in
  Mapper.GetPreview is actually pinned by a test.
- design doc: fixed a garbled sentence describing which DTO gained
  the Preview field.
2026-07-21 23:13:27 +02:00
timothy 54610fe0af docs(60): correct stale channel-preview design/decisions statements
The design spec claimed ChannelDetailResponseModel also gained the Preview
field; only ChannelResponseModel did (deliberate — nothing consumes it on the
detail DTO). Also record the two new Unavailable causes (disabled channel,
zero playouts) added to Mapper.GetPreview in the api.channel-preview-capability
decisions.md record.
2026-07-21 23:13:27 +02:00
timothy 11fb60469a docs(60): correct generated-artifact paths in the plan
The real paths are ErsatzTV/wwwroot/openapi/v1.json and
web/src/api/generated/v1.d.ts.

Refs #60
2026-07-21 23:13:08 +02:00
timothy 2595a134f8 docs(60): implementation plan for in-browser channel preview
Also simplifies the spec's guide correlation: ChannelStateResponseModel
already carries NowPlaying and ChannelsScreen already holds it, so the
panel needs no /api/v1/guide fetch and no timer.

Refs #60
2026-07-21 23:13:07 +02:00
timothy b892911400 docs(60): design spec for in-browser channel preview
Verification-oriented HLS preview on the channels list: server-declared
per-channel Preview capability (Availability/ManifestUrl/UnavailableReason),
a SlideOver panel over the existing HlsPlayer, and an onError extension so
playback failures stop being silent.

Refs #60
2026-07-21 23:13:07 +02:00
timothy 21a4e4d34e docs(plan): resolve IRemoteImageValidator layering (Core has no ImageSharp)
Core interface returns Task (throws-on-invalid); render path uses a static
Infra method returning Image. Drops the GraphicsEngine threading. refs #525
2026-07-21 13:13:21 +02:00
timothy 185de69930 docs(plan): external-logo download-on-save implementation plan
10 tasks, TDD, no schema change. Extracts RemoteImageDecodeBudget (Core) +
RemoteImageValidator (Infra), adds IRemoteLogoCacher, wires the 3 channel
handlers + UploadArtworkHandler, a startup migration for existing URL rows,
and the SPA preview/copy changes.

refs #525
2026-07-21 13:13:21 +02:00
timothy f1e7ee17ee docs(spec): resolve open questions — fold in upload budget check, keep fetcher namespace
refs #511
2026-07-21 13:13:21 +02:00
timothy 44199d3dba docs(spec): external channel-logo URLs become download-on-save
Design for reversing #511's render-time fetch: an external logo URL is
downloaded once at save time into the artwork cache (content-hash name,
same as an upload), validated against #511's decode budgets, and stored
so nothing downstream knows it came from a URL. Bad URL fails in the
editor, not at 3am. No refresh button — re-add the URL. One-time startup
migration converts existing URL rows; failures leave the row and warn.

refs #511 #502
2026-07-21 13:13:21 +02:00
timothyandClaude Opus 4.8 7bdb83fb16 docs(520): carry #642 orientation bullets into PR2 plan (from a814dd16)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:11:01 +02:00
timothyandClaude Opus 4.8 46ea8f0e92 docs(521): carry archive<->archive reciprocity gap into PR2 Task 6 (PR1 whole-branch review, non-blocking)
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 12s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m22s
Build ErsatzTV Image / decisions lifecycle (pull_request) Successful in 1m20s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 17m44s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m47s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 25m34s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 03:00:15 +02:00
timothyandClaude Opus 4.8 d58b373463 docs(520): record MemPalace issue-discovery vs queue-state boundary + closing-record-as-artifact (PR2 retrieval contract)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 03:00:15 +02:00
timothyandClaude Opus 4.8 428ebc0c81 docs(520,521): record #642 MemPalace conventions + archive-placement/bounded-strip invariants
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 02:58:58 +02:00
timothyandClaude Opus 4.8 404ec95479 docs(520,521): fold Fable review — body-diff guard, two-PR split, key/backstop fixes
Refs #520 #521

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 02:58:58 +02:00
timothyandClaude Opus 4.8 0637fa46ac docs(520,521): implementation plan — decision lifecycle + startup rewrite
Refs #520 #521

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 02:58:58 +02:00
timothyandClaude Opus 4.8 2af3cd024e docs(520,521): design spec — decision lifecycle + retrieval-efficient startup
Design for the decision-knowledge lifecycle schema/catalog/validator (#521) and
the #237-retirement / parallel-startup kickoff rewrite (#520), as one arc.

Refs #520 #521

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 02:58:58 +02:00
timothyandClaude Opus 4.8 cdbb685d22 docs(498): fold Fable review fixes into plan + spec
- Blocker 1: VA-API decode to SOFTWARE frames (drop -hwaccel_output_format,
  new DecoderVaapiToSoftware) so the proven hwupload/vpp_qsv branch bridges to
  the QSV encoder — the naive hardware-surface path emits a bare vpp_qsv on
  VA-API frames and fails on ~all content.
- Blocker 2: bool? domain property + != false coercion (DeinterlaceVideo
  pattern) so create-with-false actually persists false.
- High 3: REST DTOs bool?=null + ?? true for /api/v1 additive-compat.
- Medium 4: correct Task 4 test scaffolding (DefaultHardwareCapabilities).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:53:16 +02:00
timothyandClaude Opus 4.8 369ba3332e docs(498): implementation plan — VA-API decode on the QSV pipeline
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:53:16 +02:00
timothyandClaude Opus 4.8 32593a2263 docs(498): design spec — VA-API decode on the QSV pipeline
Mirrors Jellyfin's default-on "prefer native decoder" hybrid via a new
QsvPreferNativeDecoder profile boolean. Reverified against code before
designing; records the rejected decode-family-enum alternative (Option C).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:53:16 +02:00
timothy 71bb2c23f3 docs(67): revise plan+spec per independent review — fix toggle blocker, seed marker, real preview geometry
Fable review (cold, review-only) found a blocker in the plan's own code:
Task 5/6 selected the logo-bug preset by searching for the first
imageSource==='ChannelLogo' entry, but getWatermarks() sorts by name
(pickers.ts:14), so with a second logo-driven preset -- which this design
explicitly invites users to create -- the toggle would read OFF for a
logo-driven channel and REPOINT it on tick, from a control documented as a
pure reflection of stored state. Now resolved by id lookup, with the
tick-on target chosen by a shared findLogoBugWatermark() helper and pinned
by a two-preset regression test.

Also adopted:
- ConfigElement seed marker (watermark.channel_bug_seeded): ChannelWatermark
  has no IsSystem flag and Initialize runs every startup, so a name-only
  guard resurrected a deliberately deleted preset forever.
- Channel-editor preview now fetches the referenced preset's REAL geometry
  via the existing GET /api/v1/watermarks/{id} instead of hardcoding the
  seeded defaults -- which would have been wrong for exactly the users who
  tuned theirs.
- The 're-save untouched leaves watermarkId unchanged' test the spec
  promised and the plan had omitted, plus a create-path degrade test.
- Reversed the ChannelBuilder exclusion (operator decision): fresh installs
  stamp the preset onto the templates the seed creates; existing installs
  are untouched.
- External-URL logos never render a bug (File.Exists against a URL,
  WatermarkSelector.cs:269-286) -- verified, filed as #502, preview no
  longer promises it.
- Dropped Task 2's InternalsVisibleTo branch: already present
  (ErsatzTV.Application.csproj:30-32).

Refs #67 #502
2026-07-20 20:04:56 +02:00
timothy 9ff5580d49 docs(67): implementation plan + spec correction (ChannelBuilder inherits from template, excluded)
Self-review against the spec caught that the spec named both SPA creation
paths, but ChannelBuilder does not hardcode a watermark -- it inherits
watermarkId from the selected ChannelTemplate. Defaulting it would mean
mutating the seeded Standard/Music videos template rows, which the seed's
name-guard deliberately skips on existing installs, so it would no-op on
prod while changing fresh installs. Excluded in both documents with the
reason recorded.

Refs #67
2026-07-20 19:48:55 +02:00
timothy afbd50e3f0 docs(67): design spec — unified logo/bug via shared ChannelLogo watermark preset
Recon showed ChannelWatermarkImageSource.ChannelLogo already resolves the
channel's logo artwork at render time, and prod already runs that pattern
by hand (43 channels -> one 'Channel Bug' preset). So #67 is productizing
a proven mechanism, not building one: seed the preset idempotently in
DbInitializer (adopting any existing same-name row untouched), default the
SPA creation paths to it, and add a shared <BugPreview> that renders the
resolved geometry in both the channel editor and WatermarksScreen.

No schema change, no WatermarkSelector change. One additive DTO field
(WatermarkResponseModel.imageSource) under the frozen-additive /api/v1.

Refs #67
2026-07-20 19:39:37 +02:00
timothyandtimothy 56dff2c49f docs(489): spec + implementation plan for Jellyfin mixed-content libraries (#490)
Build ErsatzTV Image / Docs update reminder (push) Has been skipped
Build ErsatzTV Image / CI image pin matches docker/ci (push) Has been skipped
Build ErsatzTV Image / decisions.md append-only (push) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 5m16s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Has started running
Build ErsatzTV Image / Build & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Design spec and bite-sized TDD implementation plan for #489, whose
implementation landed in #493.

Docs-only. Kept as the record of how the design was reached: that Jellyfin
classifies mixed-library items server-side via includeItemTypes (so no
inference is needed), that MediaItem is TPT keyed on LibraryPathId (so no
migration is needed), that MediaKind is dispatch + presentation only, and why
the feature is deliberately scoped to Jellyfin rather than local libraries.

Also records the open risk the plan carried -- the music-video scanner's
untraced reconciliation -- which #494 subsequently answered.

Refs #489

Co-authored-by: Timothy <timothy.look@gmail.com>
Co-committed-by: Timothy <timothy.look@gmail.com>
2026-07-20 16:36:02 +00:00
timothy 8483af41fc docs(176): plan Task 8 — guard Builder toggle + resync query (matches shipped fix) 2026-07-18 02:41:50 +02:00
timothy 279c0d6ce5 docs(176): plan — hook exposes clean non-null RuleField (Core DTOs are nullable-disabled) 2026-07-18 02:41:50 +02:00
timothy 5186bed910 docs(176): plan — broaden round-trip generator to special chars; escaping now total 2026-07-18 02:41:50 +02:00
timothy 218e1810cb fix(176): total escaping so wildcard/quoted values round-trip (parse review) 2026-07-18 02:41:50 +02:00
timothy 7f7062729d docs(176): fix plan operator model — split fulltext matches/notMatches from text contains (Task 3 review Critical) 2026-07-18 02:41:50 +02:00
timothy aa4127fcef docs(176): fix plan test-project path + parse.ts self-review nits; add SDD ledger 2026-07-18 02:41:50 +02:00
timothyandClaude Opus 4.8 8c7af0e202 docs(176): implementation plan — SmartCollection visual rule builder
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:41:50 +02:00
timothyandClaude Opus 4.8 71159c1a8a docs(176): design spec — SmartCollection visual rule builder (sub-project #1)
Scopes #176 down after recon (Autotune #69 + composite create-channel #63
already shipped): the remaining leg is the visual WYSIWYG rule builder.
Kodi one-level-nested model, compile-only to a closed Lucene subset (no
schema change), backed by a new read-only GET /api/v1/search/fields catalog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:41:50 +02:00
timothyandClaude Opus 4.8 1f4341a347 docs(71): implementation plan for per-playout reshuffle + seed surfacing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:54:37 +02:00
timothyandClaude Opus 4.8 9a7ac28f9f docs(71): design spec for per-playout reshuffle + shuffle-state surfacing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:43:29 +02:00
timothy f31476e012 fix(69): auto-tune review fixes — null channels, orphaned SmartCollection, oversized preview names, doc drift
- CreateAutoTunedChannelsRequest.ToCommand(): guard null Channels (was NREing on
  a request body that omits "channels", causing HTTP 500).
- CreateAutoTunedChannelsHandler.CreateOne: when CreateChannelFromLineup returns
  Left (Skipped/Failed), roll back the just-created SmartCollection via
  DeleteSmartCollection so retries don't fail on SmartCollection-name uniqueness.
  Best-effort; the delete result does not change the outcome.
- PreviewAutoTuneChannelsHandler: filter out proposals whose generated name
  exceeds the 50-char Channel.Name limit before number allocation, so numbers
  aren't wasted on proposals that can never be created.
- docs/superpowers/specs/2026-07-16-auto-tuning-design.md: fix field-name drift
  in JSON examples (proposedNumber -> number, error -> reason) to match the
  actual AutoTuneProposal/AutoTuneChannelOutcome DTOs.

Refs #69
2026-07-16 22:18:52 +02:00
timothy 0320735f47 docs(69): auto-tune OpenAPI regen + api/domain/decisions docs
Refs #69
2026-07-16 22:18:52 +02:00
timothyandClaude Opus 4.8 2352604836 docs(69): PR1 backend implementation plan
Task-by-task TDD plan for the auto-tuning backend: axis map, number
allocator, preview query + EF enumeration, bulk-create orchestration,
REST endpoints, OpenAPI + docs.

Refs #69

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:16:43 +02:00
timothyandClaude Opus 4.8 35ad7df6a4 docs(69): auto-tuning 3-axis MVP design spec
Design for #69 auto-tuning: enumerate library metadata (TV Show / TV
Genre / Movie Genre) -> preview -> select -> bulk-create channels.
Additive/non-destructive, enumerate-via-EF + persist-via-SmartCollection,
server-side query generation, reserve-block numbering.

Refs #69

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:16:43 +02:00
timothyandOpenAI Codex d1a52eaa8b docs(api): use versioned channel routes
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m14s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 3m49s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m47s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m35s
Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 15:03:53 +02:00
timothyandOpenAI Codex 7dee5194f4 fix(api): report direct streams as on air
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 22s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 2m24s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m2s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 6m30s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
fixes #99

Co-Authored-By: OpenAI Codex <codex@openai.com>
2026-07-16 14:57:45 +02:00