Skip to content

Group snippets into sections in the ^B panel - #40

Merged
MarcelInTO merged 1 commit into
mainfrom
snippet-sections
Aug 30, 2026
Merged

MarcelInTO merged 1 commit into
mainfrom
snippet-sections

Conversation

@MarcelInTO

Copy link
Copy Markdown
Owner

Closes #38.

Why

Twenty-seven snippets across four schemas were being told apart by their names alone —
Divider (rule), Plate - own page, Part (division), Appendix (division) — which is
the naming convention doing a grouping job badly. The config already grouped them, in
banner comments (BLOCKS., ILLUSTRATIONS., DIVISIONS., BACK MATTER and FRAMING.)
that the panel could not see.

What

A <snippet> may now sit inside a <section name="…">:

<snippets>
  <snippet name="Footnote"><block kind="footnote"/></snippet>

  <section name="Illustrations">
    <snippet name="Plate - own page">
      <illustration role="plate" print-placement="own-page" src="" alt=""/>
    </snippet>
    <snippet name="Headpiece">
      <illustration role="headpiece" src="" alt=""/>
    </snippet>
  </section>
</snippets>
┌──────────────┤Snippet at <block>├──────────────┐
│ ◉ Below  ○ Above  ○ Child                      │
│                                                │
│ Blocks                                         │   <- vim Title, orange
│   Paragraph                                    │
│   Argument                                     │
│   Footnote                                     │
│ Illustrations                                  │
│   Plate - own page                             │
│   Headpiece                                    │
│                          ⟦ Cancel ⟧  ⟦► OK ◄⟧  │
└────────────────────────────────────────────────┘
  • File order throughout. Sections group, they do not sort, so ordering the file still
    orders the panel.
  • A heading is a label, not a choice. The selection steps over it in whichever
    direction it was travelling, so ^B-arrow-Enter still lands on something insertable
    and OK can never commit a row that inserts nothing. One hook rather than arrow-key
    bindings, so arrows, Home/End, PageUp/PageDown, the mouse and type-ahead are all covered
    — and so the drill can exercise it, since injected keys reach app-scope bindings only.
  • Headings are coloured (vim's Title → orange, the one accent the tree does not
    already spend on a node kind), so a row at column 0 cannot be misread as an unindented
    snippet.
  • Nothing changes without sections. Same rows, no indent, no headings — the feature is
    invisible to a config that has not asked for it, the way an absent config draws no group.
  • A section written wrong never costs its snippets. Unnamed leaves them ungrouped,
    nested folds them into the parent, both reported alongside the other skips — the rule the
    rest of the loader already follows.

Three things found on the way

The panel has been under-sizing itself. It reserved 6 rows for chrome and spends 10, so
a short config got a two-row list inside a twelve-row dialog. Long configs hid it — they
clamp to the screen and scroll — and sections make short ones the case that matters, since
every heading is another row to fit. The number is pinned in §14b now, so a framework change
to the chrome fails there rather than silently shrinking the list again.

ValueChanging.NewValue is a trap in Terminal.Gui 2.4.17. It is writable and reads
exactly like a redirect hook; ListView ignores it. (Handled does cancel, but that only
pins the selection where it is, so Down onto a heading would do nothing.) Re-setting from
ValueChanged behind a re-entrancy guard is what actually moves it. The first version sat
on the heading and the drill caught it — verified through MoveDown, not assumed.

Type-ahead would have broken silently. Typing a letter jumps to the next entry starting
with it; the stock matcher compares against the row as drawn, so every sectioned snippet
would have stopped answering to its own first letter. That jump is documented nowhere,
which is exactly why it was worth confirming it worked before changing the rows —
measured first (typing l reaches the off-screen Letter (division)), then preserved with
SnippetMatcher.

Verification

  • §14b gains 22 checks — the parse, the row mapping, the skipping, the colour read back
    off the draw buffer, the panel height, and the matcher both as a function and as actually
    attached to the live list.
  • Ten mutations, each killing its own check and no others. Two checks were rewritten
    after surviving a first pass.
  • Drill passes on all four CI fixtures (460 / 447 / 434 / 439 checks).
  • A drill probe that throws now fails the run instead of wedging it: the exception used
    to escape the timeout callback, the Esc never fired, and the modal sat there until the
    harness deadline. Cost a mutation sweep to find. The probe also derives its row indices
    from the rows rather than hard-coding them, so a build with the headings mutated away
    goes red instead of indexing off the end.

Not in this change

Splitting Blocks further — Text / Verse / Dividers — would help more than the four
sections the banner comments already imply, but that is a judgement about the vocabulary
rather than about fux, so the conversion stays faithful to what the file already says.

🤖 Generated with Claude Code

https://claude.ai/code/session_0146fhu1MJdAWimomPpjX6WY

Closes #38.

A config long enough to be worth having is long enough to be hard to read. Twenty-seven
snippets across four schemas were being told apart by their names alone — "Divider
(rule)", "Plate - own page", "Part (division)" — which is the naming convention doing a
grouping job badly, and it does not scale.

A `<snippet>` may now sit inside a `<section name="...">`. The panel heads each group and
indents what is under it; blocks stay in file order, because sections group rather than
sort and ordering the file is how the panel is ordered.

A heading is a label, not a choice. The selection steps over it in whichever direction it
was travelling, so ^B-arrow-Enter still lands on something insertable, OK can never commit
a row that inserts nothing, and typing a letter to jump works as before. Headings are drawn
in vim's Title colour (orange — the one accent the tree does not already spend on a node
kind), so a row at column 0 cannot be misread as an unindented snippet.

Nothing changes for a config without sections: same rows, no indent, no headings. Getting
a section wrong never costs the snippets inside it — an unnamed one leaves them ungrouped,
a nested one folds them into its parent, both reported alongside the other skips.

Three things found on the way, each fixed rather than worked around:

* The panel has been under-sizing itself. It reserved 6 rows for chrome and spends 10, so
  a short config got a two-row list inside a twelve-row dialog. Long configs hid it — they
  clamp to the screen and scroll — and sections make short ones the case that matters,
  since every heading is another row to fit. §14b pins the number now.

* Terminal.Gui 2.4.17's `ValueChanging` exposes a writable `NewValue` that reads exactly
  like a redirect hook and is ignored by ListView; its `Handled` cancels but only pins the
  selection where it is. Re-setting from `ValueChanged` behind a re-entrancy guard is what
  actually moves it. Verified through MoveDown, not assumed — the drill caught the first
  version sitting on the heading.

* Type-ahead compares against the row as drawn, so the indent would have stopped every
  sectioned snippet answering to its own first letter. That jump is undocumented, which is
  why it was worth confirming it worked before breaking it; SnippetMatcher keeps it.

A drill probe that throws now fails the run instead of wedging it: the exception used to
escape the timeout callback, the Esc never fired, and the modal sat there until the
harness deadline. Cost a mutation sweep to learn.

§14b gains 22 checks. Ten mutations, each killing its own check and no others.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146fhu1MJdAWimomPpjX6WY
Comment thread src/Fux/Drill.cs
{
probed = true;
try { probe(); }
catch (Exception ex) { _lastDialogDiag += " PROBE THREW: " + ex.Message; }
Comment thread src/Fux/Drill.cs
{
probed = true;
try { probe(); }
catch (Exception ex) { _lastDialogDiag += " PROBE THREW: " + ex.Message; }
@MarcelInTO
MarcelInTO merged commit 9b6ec30 into main Aug 30, 2026
5 checks passed
@MarcelInTO
MarcelInTO deleted the snippet-sections branch August 30, 2026 00:13
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.

Snippet sections/categories

2 participants