Commit Graph
3386 Commits
Author SHA1 Message Date
timothy 7e5d20be98 fix(583): gate every model-less dispatch, not just implementer-looking ones
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 14s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 17s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m37s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 55s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m23s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m22s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Independent review of d221a065 found the prompt-text heuristic both over- and
under-fired. Confirmed repros: a read-only recon brief merely mentioning
"worktree" nagged, while "author the change and open a PR", "land this on the
branch" and "make the changes and commit them" all passed silently — the gate
missed exactly the case it existed to catch.

Root cause behind all three findings: the hook contradicted its own rule. The
HARD CONSTRAINT says "every dispatched agent"; the hook gated only dispatches
whose prose looked like an implementer. Prompt text is not a reliable signal for
authority.

Inverted: ask whenever `model` is absent, exempting only `fork` (the tool ignores
a fork's model override, so a prompt could not be acted on). This dissolves all
three findings rather than patching the regex — no phrasing dependence, no
false-positive concept, and no "cannot commit" claim to be wrong about. The
reviewer was right that Explore/Plan/claude-code-guide all carry Bash, so the old
exemption rationale was false.

The noise objection is answered by the escape hatch, not by scoping: naming a
tier costs one parameter and the hook never fires. Self-eliminating for anyone
following the rule.

Decision record updated to record the rejected narrow design and why.

Re-verified: 11 payloads incl. all three findings, the invariants (model named ->
never fires; fork exempt; non-Agent passes), and robustness (malformed JSON,
empty payload, missing tool_input, embedded backticks/quotes/newlines) — never
crashes, never emits malformed JSON. decisions-validate: OK.
2026-07-25 11:19:31 +02:00
timothy d3c89d87aa feat(440): per-source weight steppers + exclude/add-untagged in Auto-Tune DetailPanel
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 21s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m48s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m29s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Wires the Auto-Tune DetailPanel's Content-sources pane to #425's backend: each
member row gains a 1..1000 weight stepper and an include/exclude toggle, and a
library typeahead adds a source that isn't in the axis's base set. Edits
accumulate in the screen's per-channel draft (the existing §8/§11 guard covers
them) and are flushed as the create request's `sources` array.

- Only genuinely customised rows are sent, mirroring the server's own
  `customized` predicate — an all-default array is a backend no-op, so the field
  is omitted entirely and the channel keeps the cheaper fair-share shape.
- Weights are clamped to 1..1000 on blur and again at save, so an out-of-range
  value never reaches the server as a raw 400 (spa-conventions §4a).
- The add-untagged picker compiles typed text to `title:*…*` rather than
  forwarding raw Lucene: the index's default field does not match bare title
  words, so a raw forward would silently find nothing.
- Removes the read-only #425 hint.

Docs: spa-conventions.md §11 records the per-source correction-row convention.

fixes #440
2026-07-25 11:15:20 +02:00
timothy d221a06522 chore(583): make per-agent model routing a hard constraint + PreToolUse gate
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 14s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 5m42s
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 6s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m53s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 15m2s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The kickoff tells the orchestrator to route agents by capability, but that rule
lived only in a prose paragraph. On 2026-07-25 a session dispatched two
implementers (#436, #440) with `model` omitted, silently inheriting the
orchestrator tier — while following every bullet in HARD CONSTRAINTS in the same
session. The bulleted list is what functions as the checklist; prose above it
reads as background.

- HARD CONSTRAINTS: keyed routing bullet requiring the tier to be stated in the
  dispatch itself, so an invisible omission becomes visible output. Prose
  paragraph tightened to point at the key rather than restate the table (the
  kickoff is pasted into every session — duplication is a per-session tax, #542).
- .claude/hooks/pretooluse-agent-model.sh: PreToolUse on Agent, `ask` when a
  committing agent is dispatched with no explicit `model`. Narrow by design —
  passes through read-only/recon types, `fork` (model override ignored by the
  tool), and any dispatch already naming a tier, because a gate that fires on
  every fan-out trains one-shot dismissal. `ask` not `deny`: routing is a
  judgment call with no derivable right answer, unlike the H6/H10 merge gate.
- New decision record `process.per-agent-model-routing`; catalog regenerated.

Decision matrix verified against 9 payloads incl. a replay of the dispatch that
missed. decisions-validate: OK.

fixes #583
2026-07-25 11:02:42 +02:00
timothy 6cf99718ee feat(436): arbitrary-depth rule-builder group nesting
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 34s
PR Gates / Docs update reminder (pull_request) Successful in 38s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 42s
PR Gates / decisions lifecycle (pull_request) Successful in 1m1s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 17m3s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 17m14s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m32s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The rule builder's Group nesting was capped at one level (#176's Kodi
model). Generalize it to recursive nesting bounded by a single shared
constant, MAX_GROUP_DEPTH (types.ts, = 5, root group is depth 0):

- parse.ts: replace the allowNested boolean with a depth counter that
  recurses to the cap; deeper input stays out of subset (null -> raw-text
  fallback), so parse remains the exact inverse of compile. Sub-group
  detection now requires the leading '(' to be the one closed by the
  trailing ')' (quote/escape aware), so '(a)x(b)' can't be mistaken for
  one wrapped group.
- RuleBuilder.tsx: 'Add group' is offered while depth < MAX_GROUP_DEPTH
  instead of only at the root; nested group boxes get box-sizing:
  border-box so per-level padding can't overflow (no global reset).
- roundtrip.test.ts: the 500-tree generator nests to the cap and asserts
  the corpus actually reached it; explicit depth-3 cases added to
  compile/parse/validation tests and a depth-gate test to RuleBuilder.

compile.ts and validation.ts already recursed correctly and are unchanged.
No backend/OpenAPI change.

Docs: spa-conventions.md §12; decisions lifecycle — new active record
spa.rulebuilder-nesting, predecessor spa.smartcollection-rule-builder
relocated to docs/decisions/archive/spa.md as superseded.

fixes #436
2026-07-25 11:01:35 +02:00
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
timothy aca70b8398 Merge pull request 'feat(438,435,434): RuleBuilder validation, relative-date operators, DB-sourced facet typeahead' (#577) from feat/rulebuilder-bundle 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 / Build & test (.NET) (push) Successful in 33s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 34s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 12m5s
Merge PR #577 (feat/rulebuilder-bundle): RuleBuilder validation (#438), relative-date operators (#435), DB-sourced facet typeahead (#434)
2026-07-23 19:16:55 +00:00
timothyandClaude Opus 4.8 7e6861a928 fix(434): accurate endpoint description (DB-sourced) + tests for case-insensitivity, tag exclusion, distinct
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 19s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m24s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m43s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13m40s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 15m38s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m50s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:55:32 +02:00
timothyandClaude Opus 4.8 cc5ec712e0 chore(434): restore ISearchIndex/LuceneSearchIndex to main (revert incidental BOM strip)
The GetFieldValues additions were reverted in the DB-sourcing rework; these two
files had only an incidental BOM strip left, which pulled unrelated pre-existing
whitespace debt into the scoped format gate. Restore byte-identical to origin/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:44:15 +02:00
timothyandClaude Opus 4.8 a654ae554a fix(434): actually strip the BOM this time (previous commit staged before stripping)
ISearchIndex.cs / LuceneSearchIndex.cs still carried a BOM after the prior
commit — the strip ran after `git add`, so the staged (BOM'd) content was
what got committed. No content change beyond the BOM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:44:15 +02:00
timothyandClaude Opus 4.8 9cb51f6a73 docs(434): [decisions-edit] update field-values decision + api-conventions for DB sourcing
Edits the api.search-field-values decision record's rationale prose in
place (same key, same date, not a reversal) to describe the DB-sourced
per-field distinct-values design and the narrowed allow-list, replacing
the superseded Lucene-term-dictionary description. Updates the endpoint's
api-conventions.md entry the same way. Regenerated docs/decisions/README.md
via build_decisions_catalog.py; decisions_validate.py passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:44:15 +02:00
timothyandClaude Opus 4.8 fb5f609cef rework(434): source facet typeahead from DB distinct values, not Lucene analyzed tokens
The Lucene term dictionary stores lowercased word tokens for analyzed text
fields ("Science Fiction" -> science/fiction), so the typeahead was
suggesting fragments instead of whole values. GetSearchFieldValuesHandler
now injects IDbContextFactory<TvContext> and resolves an explicit
per-field-name distinct-values query (genre/studio/director/writer/actor/
artist/tag/network/collection/video_codec/album), with state and
video_dynamic_range computed in memory and content_rating split on '/' to
match what search actually matches on. title/show_title/album_artist have
no distinct source and now correctly 404 (free-text fallback), same as
before. Reverts the GetFieldValues additions to ISearchIndex/
LuceneSearchIndex/ElasticSearchIndex back to their pre-#434 state (BOM
stripped per #311, otherwise byte-identical). Endpoint shape, DTO,
controller, and OpenAPI are unchanged (no diff from
./scripts/update-openapi.sh).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:44:15 +02:00
timothyandClaude Opus 4.8 a1d3adda9c fix(435): reject non-integer relative-date N (1e2/7.0) — strict digit-string validation
- validation.ts: ruleError now uses /^\d+$/ regex instead of Number.isInteger
- dateMacro.ts: isValidN now uses /^\d+$/ regex instead of Number.isInteger
- Prevents accepting scientific/decimal notation (e.g. '1e2', '7.0') that break compile↔parse contract
- Added tests for both invalid cases returning null/error

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:44:15 +02:00
timothyandClaude Opus 4.8 c2fb62dc88 docs(434,435,438): decisions records, spa-conventions §12, api-conventions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:44:15 +02:00
timothyandClaude Opus 4.8 13bfcebd04 feat(434): facet-value typeahead combobox for text fields
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:27 +02:00
timothyandClaude Opus 4.8 8d805ecbd8 fix(434): ElasticSearch field-value typeahead returns empty list, not a 500
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:27 +02:00
timothyandClaude Opus 4.8 b4ae0bde18 feat(434): distinct-values search endpoint (text fields, Lucene term enumeration)
Adds GET /api/v1/search/fields/{name}/values?q=&limit= — the backend slice of the
visual rule builder's facet-value typeahead (#434). Enumerates distinct Lucene term
values for a text field via MultiFields.GetTerms + TermsEnum, filtered by a
case-insensitive prefix, limit clamped to [1,50]. 404s when the field is absent from
SearchFieldCatalog or is not type "text". ElasticSearchIndex (the optional external
backend) throws NotSupportedException for this method — its text fields are analyzed,
not keyword-mapped, so a terms aggregation isn't safe to guess at without verifying
against a live cluster.

Regenerated OpenAPI trio (v1.json, v1.d.ts, endpoint-index.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:27 +02:00
timothyandClaude Opus 4.8 b66a3400ae feat(438,435): RuleBuilder UI — validation surface, relative-date inputs, single-child toggle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 30a8a5752c feat(435): parse relative-date macros; round-trip against normalizeGroup
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 d487ce3601 fix(438): drop empty nested subgroups instead of emitting "()" in compiled Lucene
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 575f652760 feat(438,435): compile relative-date macros; drop invalid rules instead of malformed Lucene
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 000a987a48 test(435): cover all relative-date combos, invalid-input rejection, composed round-trip
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 e91891a453 feat(435): dateMacro compile/parse mapping for relative-date operators
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +02:00
timothyandClaude Opus 4.8 c301db10c4 feat(438,435): extend rule types; validation + single-child normalization helpers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:26 +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
timothyandtimothy 8b9a7ed541 feat(414): stamp immutable Channel.Origin (auto-tuned vs user-created) and surface it (#575)
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 6m23s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 19m9s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 12m1s
Co-authored-by: Timothy <timothy.look@gmail.com>
Co-committed-by: Timothy <timothy.look@gmail.com>
2026-07-23 18:12:25 +00:00
timothyandtimothy 7a7c611267 fix(458): reject duplicate names on playlist &amp; playlist-group rename (#576)
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 & push image (amd64) (push) Has been cancelled
Build ErsatzTV Image / Build & test (.NET) (push) Has been cancelled
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Has been cancelled
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Has been cancelled
Co-authored-by: Timothy <timothy.look@gmail.com>
Co-committed-by: Timothy <timothy.look@gmail.com>
2026-07-23 18:07:43 +00:00
timothyandtimothy 0c063c23fb harden(421,559): percent-encode access_token in IPTV URLs, redact from logs, no-store on tokened manifests (#574)
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) Successful in 7m45s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m19s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 18m27s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m10s
Co-authored-by: Timothy <timothy.look@gmail.com>
Co-committed-by: Timothy <timothy.look@gmail.com>
2026-07-23 16:30:59 +00:00
timothy 41291b3686 Merge pull request #573 (feat/392): per-schedule clock-boundary padding toggle + 60-min increment
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 / Functional E2E (curl contracts) (push) Successful in 32s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 33s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m40s
2026-07-23 07:10:29 +00:00
timothy bb3e245b3c docs(392): drop predecessor cross-ref line (append-only decisions diff)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 12s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m53s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 4m44s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m58s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 19m45s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
2026-07-23 08:27:02 +02:00
timothyandClaude Opus 4.8 60fecd18b7 chore(392): regenerate migration + OpenAPI + decisions catalog after rebase onto main
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 27s
PR Gates / Docs update reminder (pull_request) Successful in 38s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 34s
PR Gates / decisions lifecycle (pull_request) Failing after 43s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14m55s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 18m23s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m58s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 23m49s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The rebase onto origin/main (which merged #74's ChannelGraphicsElement
migration) left the PadToNearestMinute migration's embedded Designer.cs
model snapshot stale — it still reflected the pre-#74 model, so EF's
diff against it produced an empty Up()/Down() when naively regenerated.
Reset TvContextModelSnapshot.cs to origin/main's true post-#74 state,
then re-ran scripts/add-migration.sh so the migration's Designer.cs
correctly folds in ChannelGraphicsElement and the migration's Up() adds
only the PadToNearestMinute column. Verified has-pending-model-changes
is clean for both providers.

OpenAPI (v1.json/v1.d.ts/endpoint-index.md) and docs/decisions/README.md
regenerated identically to the auto-merged state, so nothing to commit
there — confirmed both padToNearestMinute and #74's graphics-elements
endpoints/decision key are present.

Stripped a UTF-8 BOM this dotnet-ef/dotnet-format toolchain wrote into
the regenerated migration + snapshot files (known BOM trap, ersatztv#311).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:18:15 +02:00
timothy 767f96802e fix(392): apply schedule-level pad to Fill-With-Group items (reverse nav lost in DeepCopy) 2026-07-23 08:03:31 +02:00
timothyandClaude Opus 4.8 6a05363e2a design(392): mirror 60-min pad option into FillerPresets prototype
Schedule-level pad control has no prototype counterpart (the schedule-level
scalar form is not modeled in Schedules.jsx); nothing to mirror there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:03:31 +02:00
timothy e99e72ee71 docs(392): record per-schedule clock-padding decision + domain-model field 2026-07-23 08:03:31 +02:00
timothy f4ba219000 feat(392): add pad-to-clock-boundary control to the schedule editor 2026-07-23 08:03:31 +02:00
timothyandClaude Opus 4.8 beab219a90 test(392): cover schedule pad through Flood/Duration/Multiple; make day-seam clamp precise
Adds parameterized invariant tests (Schedule_clock_padded_offline_multimode)
exercising schedule-level clock pad + offline advance across a 2-day window
(two midnight crossings) through Flood, Duration, and Multiple — previously
only PlayoutModeSchedulerOne had any coverage. Fixture uses sub-15-min content
so each padded item occupies one :15 slot and Duration's fill-the-block
contract tiles exactly (no off-boundary packing).

Part 2 (precision): replaces the day-boundary anchor-clamp magnitude heuristic
(overrun <= one pad interval on a padded schedule) with a precise signal — the
last scheduler now reports the exact offline-pad target it advanced CurrentTime
to (transient PlayoutSchedulerResult.ClockPadOfflineTarget, never persisted),
and the clamp exempts only when CurrentTime equals that target exactly. A
non-offline overrun (Duration/Flood/Multiple ending short of its natural end, a
hard-stop, a tail advance) changes CurrentTime away from the target and still
clamps, so persisted NextStart no longer shifts by up to an interval. No
persisted-schema/migration change. Output byte-identical for all existing
goldens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 08:03:31 +02:00
timothy 7efe38dd04 feat(392): honor ProgramSchedule.PadToNearestMinute in the Classic builder 2026-07-23 08:03:31 +02:00
timothy 9ea2750cbf feat(392): expose ProgramSchedule.padToNearestMinute on the REST API 2026-07-23 08:03:31 +02:00
timothy 75d5c385b2 feat(392): add ProgramSchedule.PadToNearestMinute column (dual-provider migration) 2026-07-23 08:03:31 +02:00
timothy 2f1ede6030 feat(392): add 60-minute option to filler-preset pad increment 2026-07-23 08:03:31 +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 5a7a4ed7e0 Merge pull request 'fix(570): On Now/Next overlay YAML renders (font_family + format_datetime)' (#571) from fix/74-overlay-render 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 / Build & test (.NET) (push) Successful in 15s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 29s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 9m39s
Merge pull request '#571 fix(570): On Now/Next overlay YAML renders (font_family + format_datetime)' (#571) from fix/74-overlay-render into main

fixes #570
2026-07-22 21:39:32 +00:00
timothyandClaude Opus 4.8 892f4e354b fix(570): On Now/Next overlay YAML renders — font_family + format_datetime
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 17s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 15m46s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m50s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The #74 seeded on-now-next.yml never rendered at transcode time:
- format_datetime was called with 2 args; it needs 3 (DateTimeOffset, timeZoneId,
  format) and does the tz conversion itself, so the Scriban render threw. Drop the
  NEXT start-time (avoids hardcoding a timezone in a shipped default).
- styles had no font_family; CustomFontMapper.TypefaceFromStyle crashes on a null
  family before its default-font fallback. Add font_family: Noto Sans.
Verified live on ersatztv-test via frame capture. Adds a seeder test that
deserializes the YAML and asserts base_style resolves + every style sets a font.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 23:10:35 +02:00
timothy 7080f63ebe Merge pull request 'feat(74): per-channel On Now/Next transient overlay' (#569) from feat/74-on-now-next-overlay 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 / Build & test (.NET) (push) Successful in 15s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 29s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m45s
Merge pull request '#569 feat(74): per-channel On Now/Next transient overlay' (#569) from feat/74-on-now-next-overlay into main

fixes #74
2026-07-22 20:36:41 +00:00
timothyandClaude Opus 4.8 b7d58bbf32 docs(74): tighten overlay suppression rule (Merge-during-filler also clears)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 15s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 17s
PR Gates / Docs update reminder (pull_request) Successful in 20s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 14m25s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 16m45s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m51s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 21m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Folds whole-branch review finding #2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 22:11:46 +02:00
timothy c80839b338 docs(74): channel-level graphics attachment + On Now/Next overlay 2026-07-22 22:11:46 +02:00