From 94cb5abd77f746354c5c04aec3ffd610876f8217 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Thu, 13 Aug 2026 15:47:42 +0100 Subject: [PATCH] DOC-6968 Omit a clients-example tab when that client lacks the requested step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Splits the shortcode's whole-file fallback three ways, so that a step a client does not have omits its tab instead of dumping the client's entire file into the pane. 63 panes site-wide were doing that, the worst rendering 258 lines of Go — package declaration, imports and several unrelated examples — where the reader expected a snippet for one command. The condition has three clauses and each one is load-bearing: step != "" AND step not in named_steps AND len(named_steps) > 0 Dropping the last would strip every client tab from commands/set.md and commands/get.md, whose files have no STEP markers at all, so the whole file genuinely is the example. Dropping the first would break the deliberate step="" usage on data-store.md. Both are verified as untouched below. It tests membership in named_steps rather than $sliceable on purpose. A step that exists but whose line range is malformed or out of bounds should still fall through to the whole-file path — that is precisely the drift the $sliceable guard was written to absorb, and conflating the two would turn a data problem into a missing tab. The continue fires before any content is built, not merely before the tab is appended. The legacy path mutates .Page.Store and inlines the file once per page, so building it for a tab we then drop would leave orphaned markup and a wasted copy of the file in the output. Each omission emits a warnf naming the page, client, step and set, which is self-extinguishing: it goes to zero as the missing examples get written. CI runs plain `hugo -d output` with no --panicOnWarning, and the build still exits 0, so this surfaces the gaps without turning them into a failure. Verified before and after on identical data: 2711 panes -> 2648 (63 removed), legacy panes 100 -> 37, and the 63 warnings correspond one-to-one with the 63 removed panes. What remains on the legacy path is exactly the two protected classes: 25 cosmetic panes in zero-step files and 12 from step="". All 594 tab groups survive with at least one pane, so nothing renders empty; selector options still match panes exactly on the worst-affected group (scan1, 6 and 6), so no tab button is left without a pane; commands/incr.md is untouched at 12 clients; and vector3/vector4 degrade to a CLI-only tab as agreed, a shape that already exists elsewhere on the site. One measurement trap cost me a false discrepancy first time round. data/examples.json is gitignored build output, so after checking out a branch it still describes the previous tree: my first run reported 65 warnings against 63 known panes, purely because Ruby's newly merged hexpire steps were absent from the stale data. Rebuilding it reconciled both numbers exactly. Learned: data/examples.json is gitignored build output, so it must be regenerated after any branch switch before rendering is measured, or the measurement describes the tree you were on before Constraint: keep all three clauses of the omit condition — dropping the len(named_steps) > 0 test strips every client tab from commands/set.md and commands/get.md, which have no STEP markers at all Constraint: the continue must fire before content is built, because the legacy path mutates .Page.Store and inlines the whole file once per page Rejected: gating omission on $sliceable rather than named_steps membership | a step whose range is malformed should still fall back to the whole file, which is what that guard exists for Directive: do not add a whole-file last resort for groups that lose every client tab — vector3/vector4 intentionally degrade to CLI-only, and a 1-pane group is an existing shape Gaps: scan.md still loses 7 of its 12 client tabs, and the h* pages 2 each, until the cmds_generic and cmds_hash steps are written; the warnf output is the worklist Ticket: DOC-6968 Co-Authored-By: Claude Opus 5 (1M context) --- layouts/partials/tabbed-clients-example.html | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/layouts/partials/tabbed-clients-example.html b/layouts/partials/tabbed-clients-example.html index 84b0ac7d92..bdbded19ce 100644 --- a/layouts/partials/tabbed-clients-example.html +++ b/layouts/partials/tabbed-clients-example.html @@ -140,6 +140,35 @@ {{ end }} {{ end }} + {{/* A step was requested that this client's file does not contain. Three cases hide + behind that, and only the middle one is a defect: + + step="" (explicitly empty) -> the whole file IS the example. Keep it. + file has other steps, not this one -> an AUTHORING GAP. Omit the tab: the + legacy path below would otherwise dump the client's entire file — imports, + connection setup and every unrelated example — into a pane the reader + opened for one command. Up to 258 lines where a snippet belongs. + file has NO steps at all -> predates STEP markers, so the whole file is + still the closest thing to an example. Keep it, or pages like + commands/set.md and commands/get.md lose every client tab. + + The len > 0 clause is what separates the last two, and it is load-bearing. + Note this tests membership in named_steps rather than $sliceable: a step that + exists but whose range is malformed or out of bounds should still fall through + to the whole-file path, which is the safety net that guard was written for. */}} + {{ $omitTab := false }} + {{ if and (ne $step "") (isset $example "named_steps") }} + {{ $steps := index $example "named_steps" }} + {{ if and (gt (len $steps) 0) (not (isset $steps $step)) }} + {{ $omitTab = true }} + {{ warnf "[tabbed-clients-example] %q: client %q has no step %q in set %q — tab omitted. Add the step to that client's example file, or name the supporting clients in lang_filter." $.Page $client $step $id }} + {{ end }} + {{ end }} + {{/* Skip before any content is built: the legacy path mutates .Page.Store and inlines + the whole file once per page, so building it for a tab we then drop would leave + orphaned markup behind. */}} + {{ if $omitTab }}{{ continue }}{{ end }} + {{ $content := "" }} {{ $fullFileKey := "" }} {{ $hlRange := "" }}