fix(734): a fifth wrong copy claim, and the N5 replacement was vacuous too
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 8s
review-verdict/h10 Review-verdict: MERGEABLE @ f3d003b (base: main)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 16s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 25s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 16s
Review verdict / Set review-verdict status (pull_request_target) Successful in 10s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 5m50s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m27s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m54s
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 / CI toolchain image resolves (pull_request) Successful in 8s
review-verdict/h10 Review-verdict: MERGEABLE @ f3d003b (base: main)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 16s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 25s
PR Gates / decisions lifecycle (pull_request) Successful in 22s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 16s
Review verdict / Set review-verdict status (pull_request_target) Successful in 10s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 5m50s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m27s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m54s
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
Third cold review: BLOCKED on N8, with N9/N10 non-blocking. N8 — the hardware-acceleration paragraph told the operator to worry about whether the FFmpeg build supports a kind. It does not have to: `GetSupportedHardwareAccelerationKindsHandler` adds a kind only when `FFmpegCapabilities.HasHardwareAcceleration` is already true, so an unsupported kind never reaches the dropdown and cannot fail at playback. On this host the dropdown offers exactly ["None","VideoToolbox"]. The device-passthrough half was right, and is now the whole claim: nothing checks at save time whether the device is present and passed through, and that is what fails at playback. That is the FIFTH wrong copy claim across three review rounds, in nine paragraphs — three fixed in round two, a fourth found in round three, this one found after I asked for a fifth. The decision record now says so plainly, because the pattern is the lesson: prose about behaviour is not cheaper to get right than code, and one pass over it is demonstrably not enough. N9 — the test I wrote to replace the vacuously-arranged one was itself vacuous. Deleting the panel's onMouseEnter/onMouseLeave left 14/14 green, for two reasons the reviewer's mutation established: there was no assertion between the panel enter and leave, so the "keeps open" half was never checked; and React propagates portal events through the React tree, so mouseLeave(panel) also fires the WRAPPER's onMouseLeave — with `hoveredTrigger` never cleared, the closing assertion passed on the wrapper's signal alone. The test now leaves the wrapper first, so `hoveredPanel` is the only thing holding the panel open, and asserts the panel is still present at that point. Re-run with the reviewer's exact mutation: it now kills exactly that test, and the restored control is 14/14. N10 — `.ctv-field-help-panel-top`/`-bottom` were emptied by the bridge's removal and are now pure state hooks. Commented as such: do not hang an offset off them, because that is how the bridge started. Follow-ups filed rather than bolted on at round four: - #839 — panel placement (unclipped, auto-flip) is verified by live measurement, not by a committed test; jsdom has no layout and cannot see it. - #840 — the portal puts a panel-internal `docsHref` link at the end of the tab order. Trigger: the first screen that passes `docsHref`. Both linked from §15's adopting checklist and from the decision record, so an adopter meets them where it matters. Gate: lint clean, build clean, 1220/1220, decisions-validate OK, mirror 6/6 byte-identical (checker asserts it read all six before reporting). refs #734
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
videoBufferSize:
|
||||
"How much bitrate deviation the encoder may bank before it has to correct. Roughly 2x the bitrate is the usual starting point. Very small values force a near-constant bitrate and hurt quality on scene changes.",
|
||||
hardwareAcceleration:
|
||||
"Offloads decode and encode to the GPU. Whether an option actually works depends on the container having the device passed through and the FFmpeg build supporting it — a mismatch fails at playback time, not when you save.",
|
||||
"Offloads decode and encode to the GPU. The list only offers what this FFmpeg build supports, so an unsupported kind never appears here. What nothing checks when you save is whether the device itself is present and passed through to the container — that is the mismatch that fails at playback time.",
|
||||
normalizeLoudnessMode:
|
||||
"Levels volume across content from different sources. Off leaves each item at its own level, so volume jumps between them; loudnorm retargets everything to one integrated loudness, which evens that out at the cost of an extra filter in the graph and a less faithful dynamic range.",
|
||||
};
|
||||
|
||||
@@ -35,7 +35,8 @@ severe and non-obvious rather than backfilled across every screen.
|
||||
The portal costs two things, both accepted knowingly: the panel leaves any dialog's stacking
|
||||
context, so it needs `z-index: 110` to clear the `100` scrims, and a focusable element inside it
|
||||
lands at the end of the document tab order rather than beside its trigger (latent — no screen
|
||||
passes `docsHref` yet).
|
||||
passes `docsHref` yet; tracked in #840, triggered by the first screen that does). Panel placement
|
||||
itself is verified by live measurement rather than a committed test — #839.
|
||||
- **Copy lives in the screen's own `FIELD_HELP`, declared `as const`.** Not in the shared component
|
||||
(it would collect copy for screens it knows nothing about) and not from the API (copy shipping
|
||||
separately from the field it describes drifts). `as const` rather than `Record<string, string>`,
|
||||
@@ -69,4 +70,11 @@ severe and non-obvious rather than backfilled across every screen.
|
||||
warning lives in `QsvPipelineBuilder`, and because the save path normalises the value first, the
|
||||
log it promised cannot be produced by anything a user does on that screen. Copy describing options
|
||||
the user cannot choose, or behaviour the code does not have, is the exact drift this convention
|
||||
exists to prevent — and one pass over it is demonstrably not enough.
|
||||
exists to prevent. A fifth then survived the round that fixed the fourth: the hardware-acceleration
|
||||
paragraph told the operator to worry about whether the FFmpeg build supports a kind, but
|
||||
`GetSupportedHardwareAccelerationKindsHandler` only offers kinds for which
|
||||
`FFmpegCapabilities.HasHardwareAcceleration` is already true, so an unsupported kind never reaches
|
||||
the dropdown — the surviving failure is the device not being passed through, which nothing checks
|
||||
at save time. **Five wrong claims across three review rounds, in nine paragraphs.** Prose about
|
||||
behaviour is not cheaper to get right than code; check each sentence against the enum, the handler
|
||||
or the record it describes, and expect more than one pass.
|
||||
|
||||
@@ -1074,7 +1074,12 @@ do.
|
||||
4. Reuse the `ctv-field-help*` classes in `components.css`; do not restyle per screen.
|
||||
5. If the field needs a `docsHref`, remember the link is reached by pinning, and that the portal puts
|
||||
it at the END of the document's tab order rather than immediately after the trigger — a known
|
||||
limitation, tracked rather than fixed because no screen passes `docsHref` yet.
|
||||
limitation tracked in **#840**, whose trigger is exactly this: the first screen to pass
|
||||
`docsHref`. Pick it up rather than working around it locally.
|
||||
|
||||
Panel placement (not clipped, flips when it would run off the bottom) is currently verified by live
|
||||
measurement, not by a committed test — **#839** tracks pinning it in the browser harness. Re-measure
|
||||
if you change the offset, the placement logic or `.ctv-card`'s overflow.
|
||||
|
||||
Backfilling every screen is deliberately **not** this convention's job — adopt it where a field's
|
||||
consequences are severe and non-obvious, which is what makes the icon meaningful rather than
|
||||
|
||||
@@ -1264,6 +1264,11 @@
|
||||
}
|
||||
|
||||
|
||||
/* `.ctv-field-help-panel-top` / `-bottom` intentionally carry no rules: placement is inline (the
|
||||
layout effect in fieldHelp.tsx computes it), and these remain purely as a state hook for tests
|
||||
and live measurement. Do not hang an offset off them — that is how the withdrawn hover bridge
|
||||
started. See spa-conventions.md §15. */
|
||||
|
||||
.ctv-field-help-detail {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -116,10 +116,17 @@ describe('FieldHelp', () => {
|
||||
const wrap = trigger.parentElement as HTMLElement;
|
||||
|
||||
fireEvent.mouseEnter(wrap);
|
||||
const panel = screen.getByRole('note');
|
||||
fireEvent.mouseEnter(panel);
|
||||
fireEvent.mouseEnter(screen.getByRole('note'));
|
||||
|
||||
fireEvent.mouseLeave(panel);
|
||||
// Leave the WRAPPER first, so `hoveredPanel` is the only signal still holding the panel open.
|
||||
// Without this the wrapper's own hover keeps it up and the assertions below pass with the
|
||||
// panel's handlers deleted — React propagates portal events through the React tree, so
|
||||
// mouseLeave(panel) also fires the wrapper's onMouseLeave.
|
||||
fireEvent.mouseLeave(wrap);
|
||||
expect(screen.getByRole('note')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /Learn more/ })).toBeInTheDocument();
|
||||
|
||||
fireEvent.mouseLeave(screen.getByRole('note'));
|
||||
expect(screen.queryByRole('note')).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const FIELD_HELP = {
|
||||
videoBufferSize:
|
||||
'How much bitrate deviation the encoder may bank before it has to correct. Roughly 2x the bitrate is the usual starting point. Very small values force a near-constant bitrate and hurt quality on scene changes.',
|
||||
hardwareAcceleration:
|
||||
'Offloads decode and encode to the GPU. Whether an option actually works depends on the container having the device passed through and the FFmpeg build supporting it — a mismatch fails at playback time, not when you save.',
|
||||
'Offloads decode and encode to the GPU. The list only offers what this FFmpeg build supports, so an unsupported kind never appears here. What nothing checks when you save is whether the device itself is present and passed through to the container — that is the mismatch that fails at playback time.',
|
||||
qsvExtraHardwareFrames:
|
||||
`Extra surfaces in the QSV upload pool. A pool of 0 is measured to fail outright — the channel serves nothing at all — while values between 1 and ${MINIMUM_QSV_EXTRA_HARDWARE_FRAMES - 1} are untested rather than known-bad. Rather than trust them, the server raises anything smaller to ${MINIMUM_QSV_EXTRA_HARDWARE_FRAMES} as it saves.`,
|
||||
qsvPreferNativeDecoder:
|
||||
|
||||
Reference in New Issue
Block a user