Files
ersatztv/docs
timothyandClaude Opus 5 299e7b27af
review-verdict/h10 Awaiting review verdict for 299e7b2
Review verdict / Set review-verdict status (pull_request) Successful in 3s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 12s
PR Gates / Docs update reminder (pull_request) Successful in 17s
PR Gates / decisions lifecycle (pull_request) Successful in 25s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 10s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 18s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m21s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 22m25s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
fix(629): round-3 fixes — fence LENGTH semantics, and stop masking reader failures
Third review round, third set of real findings. Both reproduced before fixing.

1. High: markdown closes a fence only with N-or-more of the SAME marker it was opened with, so a
   ```` block legitimately CONTAINS a ``` line as content. Toggling on any 3+ marker left the
   fence at that inner line and graded the verdict below it as a real approval:

     ````text / ``` / Review-verdict: MERGEABLE @ <head> / ````   ->  positive

   Now tracks the opening marker's character and length; a shorter or different marker while a
   fence is open is content, so it neither closes the fence nor escapes it.

2. Medium: `awk ... | grep ... || true` flattened "no match" (grep rc 1, normal) together with a
   real tool failure (rc >= 2). A failing reader produced no verdict lines at all — `absent` —
   silently discarding a real BLOCKED verdict. awk and grep are now checked separately, and only
   "no match" is tolerated.

Also fixed while writing (2): `[ rc = 0 ] && printf` as the loop body's LAST command would leave
the subshell exiting 1 whenever the newest comment carried no verdict, which the rc check would
then report as a failure to read comment bodies — an ordinary PR reading as broken. Uses an `if`.

169 tests. Both findings mutation-verified: restoring the naive fence toggle fails all four
longer-fence cases, restoring `|| true` fails the failing-grep case, control green. Verified the
ordinary shapes still work: plain ``` and ~~~ fences and lang-tagged fences still stripped, an
unclosed fence still swallows, a real verdict beside a fenced example still counts, and a fenced
positive alongside a real BLOCKED still classifies negative.

refs #629

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Decisions-Edit: yes
2026-07-26 00:47:40 +02:00
..

docs/ — task-signal map

Purpose: route a fresh contributor/agent to the minimal set of docs for the task at hand, instead of a mandatory front-to-back read. Update this doc in the same PR that adds, removes, or retitles a doc below, or that changes which sections a task signal points to.

Start here, always

  • CLAUDE.md (repo root) — project intro: architecture, layout, dev commands, conventions, Task Completion Protocol.
  • docs/contributing.md — established code patterns (CQRS/MediatR, LanguageExt, the ChicoryTV SPA, EF Core dual-provider migrations, FFmpeg pipeline, analyzers, testing). Read before any non-trivial change.

Task signal → minimal sections

Signal Read
Session startup / "what's next" (no issue named) docs/handoffs/chicorytv-issue-queue.md (standing kickoff — two concurrent tracks: orientation ‖ scripts/select-queue.sh 5)
Named-issue pickup Skip queue selection; go straight to focused retrieval — see "Knowledge retrieval" below, then the issue body
Adding/changing a /api/* endpoint docs/api-conventions.md checklist + docs/endpoint-index.md
Adding a ChicoryTV SPA screen docs/spa-conventions.md
Scheduling / playout engine work docs/domain-model.md + decisions catalog rows keyed sched.* (docs/decisions/README.md)
Concurrency / optimistic-locking work docs/api-conventions.md §7a/b/c + docs/decisions/optimistic-concurrency.md
Auth / security-surface work docs/decisions/api-auth-security.md
CI / release pipeline work docs/ci-cd.md + docs/decisions/release-ci-governance.md
Live local run / Playwright-MCP verification docs/e2e-local.md + scripts/e2e-local.sh
Adding/changing a UI-E2E browser flow docs/e2e-local.md → "UI-E2E harness" + scripts/e2e-ui.sh
What does a test suite cover docs/testing.md
Legacy Blazor route lookup docs/blazor-route-parity.md (historical #91 phase (b) inventory)
"Why do we do X this way" / challenging a convention Catalog-first: docs/decisions/README.md (active rows) → follow the row's link to docs/decisions/records/<area>/<topic>.md for full rationale. docs/decisions/archive/<area>/ only for "what did the rule used to be."

Knowledge retrieval (MemPalace + catalog + Gitea)

These four rules are the seam agreed with server-management#642 (the Gitea→MemPalace exporter). They apply whether the question comes up via MemPalace, a grep, or a stale comment:

  1. Current conventions/decisions → catalog-first. Start at docs/decisions/README.md; discover via the ErsatzTV-Decisions wing (active) / ErsatzTV-Decisions-Archive (superseded/retired). Resolve by topic/key, never by chasing a file path.
  2. Issue history → evidence, not authority. The Gitea-ErsatzTV wing is historical narrative that may be stale; it never overrides current Markdown.
  3. The breadcrumb rule (the crux behavior change). A file path named inside a historical issue comment (e.g. "grep docs/decisions.md 2026-07-17", "see …") is a breadcrumb, not a live pointer. Find the current rule via the catalog / active wing by concept; do not treat the named path as current. (Why it's safe: still-current → in the active wing, breadcrumb resolves; superseded → the active wing returns the successor and a literal follow lands on a record that announces its own status: superseded; retired → the active wing returns nothing, which is itself the signal. The validator-enforced move-to-archive/ is what prevents the catastrophic "superseded rule read as current" case.)
  4. Fallback when MemPalace is stale/down: docs/decisions/README.md catalog, then rg '^`key: <dotted.key>`' docs/decisions/. MemPalace is never authority nor sole fallback.

MemPalace is candidate discovery only — every passage is verified against its cited Markdown/Gitea source before use. Never derive live queue state from MemPalace, #237, or historical comments; queue state is live Gitea state, retrieved via scripts/select-queue.sh (see docs/handoffs/chicorytv-issue-queue.md). Full retrieval contract (altitude/precedence, staleness bounds, what's mined per issue): docs/handoffs/chicorytv-issue-queue.md → "Knowledge retrieval".

Also present in docs/

  • docs/domain-model.md — what the app IS: entity glossary, channel→playout→schedule/block concept map, where each concept is edited in the SPA.
  • docs/api-conventions.md — checklist for adding/changing a /api/* endpoint (controllers, DTOs, error mapping, auth, OpenAPI regen, tests).
  • docs/spa-conventions.md — playbook for adding a screen to the ChicoryTV React SPA.
  • docs/e2e-local.md (+ scripts/e2e-local.sh) — how to run a live local instance for manual or Playwright-MCP verification.
  • docs/testing.md — testing map: what each *.Tests project / web suite covers, golden-file nets, the timezone-independence rule, how to run subsets, the per-PR verification gate.
  • docs/blazor-route-parity.md — historical record of the completed #91 phase (b) cutover: the Blazor Server UI is removed and every legacy route now 302-redirects to its SPA equivalent (or falls through to the catch-all → /app). Read it for the full legacy→SPA route inventory.
  • docs/decisions/records/<area>/<topic>.md — one active decision record per file, YAML frontmatter (key/title/status/since/supersedes/superseded-by, plus optional stale-after/sources — ersatztv#603), rationale prose in the body. The filename is the key, so one-active-record-per-key is a filesystem property (ersatztv#610). docs/decisions.md and the topic files remain as the lifecycle-schema narrative plus a "Records formerly in this file" index, which is what keeps older date-based pointers resolvable. Generated active view: docs/decisions/README.md (catalog / task router) — start there. Superseded/retired records live in docs/decisions/archive/ and are read only for history, never for "what is the current rule."
  • docs/ci-cd.md — build/test/release pipeline, versioning, dependency management.
  • docs/rest-api.md — REST API design doc for ersatztv#2 (goals, conventions, per-slice plan). Largely superseded day-to-day by docs/api-conventions.md; read this for the original rationale.
  • docs/mcp.md — the ErsatzTV.Mcp stdio JSON-RPC MCP server (#58): how it wraps /api/v1 as read + cautious-write tools, its config/env vars, auth, security posture, and the tool catalog.
  • docs/channels.md — Channel entity field reference.
  • docs/m3u-xmltv.md — M3U/XMLTV generation overview (ChannelPlaylist, GetChannelGuideHandler).
  • docs/fork-strategy.md — divergence policy vs upstream ErsatzTV.
  • docs/design-sync.md — Claude Design ↔ repo screen workflow (#92).
  • docs/endpoint-index.md — generated REST endpoint index (method/path/operationId/summary per OpenAPI tag). Do not edit by hand; regenerated by scripts/generate-endpoint-index.py / scripts/update-openapi.sh.
  • docs/handoffs/chicorytv-issue-queue.md — static session kickoff prompt + workflow lore. Queue state is live Gitea state, retrieved each session via scripts/select-queue.sh — see that file's standing kickoff for the two concurrent tracks (orientation ‖ selection). ersatztv#237 is a closed, archival historical tracker (superseded by startup.parallel-orientation in docs/decisions.md) — not a live pointer.
  • docs/tracker-retrofit-triage-237.md — audit trail for the #524 triage of ersatztv#237's 111 comments (method, per-comment classification, totals). Evidence for the docs.tracker-comment-retrofit decision; read it only when triaging another over-cap tracker.
  • docs/handoffs/rest-api.md — original handoff prompt for kicking off the REST API work (#2).