Skip to content

Let a second call refine the config verbs instead of replacing it (#119) - #121

Merged
mstackhouse merged 1 commit into
developmentfrom
gh_issue_119
Jul 27, 2026
Merged

Let a second call refine the config verbs instead of replacing it (#119)#121
mstackhouse merged 1 commit into
developmentfrom
gh_issue_119

Conversation

@mstackhouse

Copy link
Copy Markdown
Member

Closes #119.

Your argument for merging is right, and it is a cleaner case than #113 for exactly the reason you gave: the state being merged is clinify's own config, where "not specified" is unambiguous, so there is nothing to guess.

ct |> clin_row_height(body = 15.35, title = 11.4, footnote = 11.4, rule = "atleast", unit = "pt") |>
      clin_row_height(header_leading = 0.75)
# body/title/footnote retained, header_leading added

It was not one function

Checking the siblings before fixing the reported one:

verb second call naming only one thing before
clin_row_height() title = 11.4 body lost — as reported
clin_header_pad() rule_to_body = 6 above lost
clin_spanner_rule() rows = 1 border lost (a dashed pen reverted to the default)
clin_table_align() fine, it carries a single value

All three now fold only the arguments a call actually names into what was already there. Fixing just the reported one would have left two known silent-data-loss bugs of the same shape.

Two things the fix turns on

Arguments with a default cannot be recognised by being NULL. rule and border are always populated, so a naive is.null() merge would still silently reset them. The merge keys off missing().

missing() has to be captured before match.arg() runs — assigning to a formal clears its missing status. Without that the fix looked correct everywhere except that a second call still reset rule to "atleast". Caught it in verification, and there is a test pinning it:

exact <- clin_row_height(base, body = 15.35, rule = "exact")
clin_row_height(exact, title = 11.4)          # rule stays "exact"
clin_row_height(exact, title = 11.4, rule = "atleast")   # named, so it wins
clin_row_height(base, body = 15.35)           # first call, default "atleast"

On rows, and why merging is still flat

clin_header_pad() and clin_spanner_rule() both carry a rows field that qualifies the values. Merging is field-wise, so a later call naming rows replaces the earlier rows for the whole setting rather than layering a second instruction. Documented as such. Layering different values on different header rows is the per-row vector form above = c(18, 34) from #113, which is a better fit than two calls anyway — I did not want two ways to express the same thing with different semantics.

Verification

  • 2,176 tests pass, zero snapshot changes.
  • devtools::check() — 0 errors, 0 warnings, and the unable to verify current time NOTE, which is the no-network note this repo's cran-comments.md documents as local-only.
  • Lints on the three changed files: 0, excluding a cross-file object_usage_linter false positive for the shared helper, which single-file lint() cannot resolve and R CMD check's own pass does not flag.
  • Tests cover each verb's merge, a named value still winning, rule surviving and being overridable, and a bare clin_spanner_rule() still ruling while border = FALSE still does not.

One correction to my own verification: I initially asserted isFALSE() on border = FALSE and a probe printed FALSE where I expected TRUE, which I glossed over before the test caught it. border = FALSE is normalised into a zero-width fp_border rather than kept as a logical — a sensible representation, and the assertion now checks width == 0 / style == "none".

Note

#120 is being investigated separately. DESCRIPTION left at 0.4.0 with the entry under # clinify (development version).

🤖 Generated with Claude Code

A second clin_row_height() replaced the first, so anything the earlier call had
set reverted to its default with no error or warning. The natural pattern - a
house wide call, then a per table exception - was therefore impossible without
restating every argument, and getting it wrong rendered a 3.189% pixel
difference silently.

The reporter's argument for merging is right and it is a cleaner case than the
composition question on #113: the state being merged is clinify's own config,
where "not specified" is unambiguous, so there is nothing to guess.

It was not one function. clin_header_pad() lost `above` the same way, and
clin_spanner_rule() lost `border`. All three now fold only the arguments a call
actually names into what was already there. clin_table_align() carries a single
value, so replacing was already correct there.

Two things the fix turns on:

Arguments with a default cannot be recognised by being NULL - `rule` and
`border` are always populated - so the merge keys off missing() rather than
is.null().

And missing() has to be captured before match.arg() runs, because assigning to
a formal clears its missing status. Without that, a second call still reset
`rule` to "atleast" while looking correct everywhere else. There is a test for
exactly that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mstackhouse
mstackhouse merged commit d318142 into development Jul 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant