Apply trace_options in every datasource section (issue #80) - #86
Merged
Conversation
`trace_options` was validated in any datasource section and written by the XLSX sentinel row for any datasource, but read at exactly one site — inside `other::` handling. Put a block on `servo_u` or `eit` and it validated cleanly, warned about nothing, and did nothing. Two constants hid the gap by sharing a string: `DatabaseOptions.TRACE_OPTIONS` is what a config file writes, `SourceOptions.TRACE_OPTIONS` what a module's options.py ships, and only the latter was ever read. - Merge the two in `Signal._build_trace_options`, user block over module defaults, key by key. Per-signal `color` / `line_dash` / `visible` in the `signals` block stay the last word. - Drop `_source_options_for_file` from the `other` source: it pre-merged the per-file block into `source_options` before the call, which the merge above now covers for every scope. - Cover the merge in `tests/unit/test_signal_container.py` (precedence in both directions, plus the two malformed inputs validation lets through) and on a module datasource in `tests/datasource/test_mindray_respi_waves.py`, which ships its own trace style to override. Behaviour change: a config that already sets `trace_options` on a device datasource starts taking effect, and where that datasource ships its own trace style the config now wins per key. Recorded in the changelog. - Document the block in the user guide, which had no `trace_options` row in the per-source reference at all — it existed only in the "Generic Other" narrative, while the Excel column table already promised it worked datasource-wide. - Set `opacity` on `eit` in the demo database, so the shipped example exercises the block outside `other::`: the nine overlaid impedance curves are legible where they cross. The `.json` is regenerated from the `.xlsx`. - Add a **Trace** entry to CONTEXT.md. The glossary listed "trace" only as a word to avoid, as a synonym for Signal, leaving `trace_options` looking like a violation of it; a Trace is the drawn form of a Signal, which is why the key is not `signal_options`. Flag the two-tier name collision alongside the existing `numerics` and `process` entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 tasks
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves the silent-ignore described in #80, taking the "make it work everywhere" branch of the decision that issue asked for.
Why that option
The three subsystems already disagreed, and two of them implemented "everywhere":
database_options_parser.pyvalidatestrace_optionsin every datasource section.database_options_xlsx.pywrites it from the sentinel row for any datasource, with tests pinning that.trace_modeas "for every trace in this datasource" — the published docs promised behaviour that did not exist.Restricting to
other::would have meant removing working behaviour from two places and adding a rejection path to each. Making it work everywhere is a 4-line merge that renders a 14-line bridge redundant.The gap
Two constants share the string
"trace_options", which is what hid this:DatabaseOptions.TRACE_OPTIONSdatabase_optionssectionother/SourceOptions.TRACE_OPTIONSoptions.pySignal._build_trace_options_build_trace_optionsalready received both dicts and read only the second, so a user block never reached the renderer.otherworked because_source_options_for_filepre-merged the user's keys intosource_optionsbefore the call.Changes
signal_container.py— merge the two, user block over module defaults, key by key. A non-dict block is tolerated, since validation reports it as an error without aborting the run.other/find_load_format.py— drop_source_options_for_file(−20 lines). Becauseotherpasses the per-file block asdatabase_options_specific, the merge above is the same operation on the same two dicts in the same order.constants.py— bothTRACE_OPTIONScomments rewritten; the old one asserted "Only 'other' reads it", andSourceOptionshad no docstring at all.Behaviour change
A config that already sets
trace_options(or the Exceltrace_mode/line_width/opacity/marker_symbolcolumns) on a device datasource starts taking effect, where it was previously ignored. Where that datasource ships its own trace style —fluxmed_parameters,mindray_respi_numerics,mindray_respi_waves,other— the user's block now wins per key; keys left unset keep the shipped value.That direction matches the existing tier ordering (explicit config beats shipped default) and is how
otheralready behaved. Nothing changes for a config that only styledother::<stem>files. Recorded inCHANGELOG.mdunder Unreleased.Tests
tests/unit/test_signal_container.py::TestTraceOptionsPrecedence— six cases: precedence in both directions, the two malformed inputs validation lets through (misspelled key, non-dict block), and the invariant that per-signalline_dashstill wins.tests/datasource/test_mindray_respi_waves.py::TestTraceOptions— the acceptance criterion's non-otherdatasource. Chosen because it ships its owntrace_options, so it proves both directions at once.tests/datasource/test_other.py::TestPerFileTraceOptionsis the regression guard on the deletion and is unchanged.ruff check+ruff format --checkclean; 878 passed. No snapshot regeneration needed — trace styling is well downstream of the parquet goldens.Docs and demo
trace_optionsrow in the Per-Source Fields Reference at all; the key existed only inside the "Generic Other" narrative. Added the row, the per-source JSON example, and a### trace_options Blocksection modelled on thenumericsone. Theother::passage now cross-links instead of duplicating the key list.eitnow setsopacity: 0.7, so the shipped example exercises the block outsideother::. Its nine overlaid impedance curves (Global + Local 1–4 + %Local 1–4) stay legible where they cross. Verified end-to-end —opacity=[1.0]without the block,opacity=[0.7]with it. The.jsonis regenerated from the.xlsxwith the one-liner intest_example_assets.py.CONTEXT.mdgains a Trace entry. The glossary previously listed "trace" only as a word to avoid (as a synonym for Signal), which lefttrace_optionslooking like a violation of the project's own vocabulary. A Trace is the drawn form of a Signal — which is exactly why the styling key is notsignal_options. The two-tier name collision is flagged alongside the existingnumericsandprocessentries.Acceptance criteria
trace_optionsin a non-othersection either takes effect or produces a warning — it now takes effect.otherdatasource.🤖 Generated with Claude Code