Notes › Tech Notes › The Interface Order of the Eight Checkboxes Runs Backwards from the Code: Hard-Coded Eight-Step Order, Pairwise Swap Truth Table, and Code-Unit Metrics on the Text Cleaner Page

The Interface Order of the Eight Checkboxes Runs Backwards from the Code: Hard-Coded Eight-Step Order, Pairwise Swap Truth Table, and Code-Unit Metrics on the Text Cleaner Page

The eight checkboxes on this page run in an order hard-coded in reverse of the interface, so the order you tick them changes nothing: a pairwise swap truth table, the blank-line and merge-line cancellation, the code-unit metric behind the four statistics, and the outcome spread over all 256 combinations.

A while back I copied a few rows out of Excel and pasted them as text that mixed half-width spaces, full-width spaces, tabs and blank lines into one heap. I opened the text cleaner page in the toolbox: a row of eight checkboxes across the top, and one refresh of the result per tick. The trouble showed up when I wanted one specific order, spaces stripped first and only then full-width spaces converted to half-width.

The eight boxes are listed in one order and run in another

The subject of this article is the Text Cleaner page, the file application/index/view/tools/text-trim.html, a template of 144 lines / 5885 bytes: the eight opt-* checkboxes sit in L12-L33, and the cleanup logic of process() occupies L92-L130, exactly eight steps. Reading the interface from top to bottom and reading the code from first to last give two order tables, set side by side below. I lifted process() verbatim into a plain Node probe, tmp/tt_probe.js, rewriting not one character, fixing no bug, and feeding the tick state through an ids object. The real execution order that comes out is: fullwidth → html → tab → all-space → trim-line → empty-line → merge-empty → no-newline.

Interface (markup) orderCode execution order
trim-linefullwidth
all-spacehtml
empty-line (checked by default)tab
merge-emptyall-space
no-newlinetrim-line
html-tagempty-line
tabmerge-empty
fullwidth-spaceno-newline

The two tables are almost exact reversals of each other: fullwidth-space, which sits last on the interface, always runs first, and no-newline always runs last. In other words, the order in which you tick the boxes has no effect on the result at all, because the execution order is hard-coded in the code.

Swapping the order is not on the table either

I built a pairwise swap truth table: enable two boxes together, then manually exchange the two steps inside the probe and run each version once.

  • fullwidth ↔ all-space: fed a\u3000b\u3000c, the page order outputs abc (3 code units); the swapped order outputs a b c (5 code units), a code-unit difference = 4, not commutative. all-space always sits behind fullwidth, so "spaces first, then full-width to half-width" has no operational path on this page.
  • html-tag ↔ trim-line: fed <b>hello </b>, the page order gives hello (5 code units) and the swap gives hello (6 code units), a code-unit difference = 1, equally not commutative.
  • The three commutative pairs: trim-line ↔ empty-line, merge-empty ↔ empty-line, tab ↔ all-space, each with a code-unit difference of 0.

Numbering lines or prefixing them belongs to a different page, and I did that work on the Line Operations page.

Two boxes ticked together cancel each other out

In the code empty-line is at L118 and merge-empty at L123: empty-line runs first and drops every line where l.trim()==="", so by the time the string reaches merge-empty no \n{3,} run survives, and its hit count is always 0. Three inputs measured, all with 0 hits: A\n\n\nB → A\nB; A\n\n\n\n\nB\n\n\nC → A\nB\nC; the default sample → the string with its blank lines gone. Blank lines as a topic are covered in that note, so I will not repeat it here.

With merge-empty ticked alone, the rule is \n{3,} → \n\n: runs of 3, 4 and 5 newlines all collapse into 2 newlines, while exactly 2 newlines, that is 1 blank line, do not move at all.

What the four numbers in the statistics area measure

origLen takes raw.length, so it is a UTF-16 count; the byte column in the same table is measured separately through Buffer.byteLength under utf8:

CJK text 4 units 12 bytes

Figure 1: two rulers over the same stretch of text, code units and bytes.

The code for reduced even carries a negative branch, reduced>=0?reduced:0. The default sample has origLen=56, and over all 2^8=256 tick combinations reduced stays inside [0,16], with the negative branch hit 0/256 times. Line counts come from split("\n").length (L139/L140): the trailing-newline a\n gives 2 original lines; a\r\nb gives 2 original lines and leaves \r inside the line; \n\n gives 3 original lines; the default sample gives 4. With empty-line ticked alone the default sample ends at 3 result lines, the middle blank line being deleted; \n\n ends at 1 result line, an empty string.

How the default sample spreads over 256 ticks

Exhausting all 256 combinations of the default sample and deduplicating the output strings leaves 8 distinct results, with group sizes 64/64/32/32/16/16/16/16 that add up to 256. The four largest groups, representative strings verbatim:

  • 64 combinations: Hello World Hello JavaScript Hello Everyone
  • 64 combinations: HelloWorld HelloJavaScript HelloEveryone
  • 32 combinations: HelloWorld\nHelloJavaScript\n\nHelloEveryone
  • 16 combinations: Hello World \n Hello JavaScript \n Hello Everyone .
64 64 32 32 16 16 16 16

Figure 2: how many combinations each of the 8 deduplicated results covers.

Q: I ticked remove all spaces, so why is a space still in the output?

all-space only runs replace(/ /g,""), and what it deletes are half-width spaces. On a b\u00A0c\u3000d\te it removes that one half-width space and leaves U+00A0, U+3000 and the tab exactly where they were.

Q: With remove blank lines ticked alone, why did a line of only spaces vanish too?

The test inside empty-line is l.trim()!==="" . A line of only spaces, only tabs, only U+00A0 or only U+3000 falls to empty-line in one step: under [empty-line], [trim-line+empty-line], [fullwidth+trim-line+empty-line] and [fullwidth+empty-line] the result is X\nY every time.

Q: Why is the statistics area empty when the page opens, and only fills in once I type?

process is called only from the checkbox onchange, the textarea oninput and selectAll; grepping process( in the template hits 11 places, and not one of them runs on load, so both the output box and the statistics area are empty when you open the page. That is a string-layer judgement: no browser was opened for this batch, so the DOM layer stays unmeasured.

Q: I ticked merge blank lines and nothing changed at all. Why?

While empty-line is ticked at the same time, empty-line deletes every blank line first, merge-empty scores 0 hits, and the second tick is worth nothing; with merge-empty alone it only collapses \n{3,} into \n\n, and a run of exactly 1 blank line never reaches 3 newlines, so it stays as it was.

📎 Related articles

Decoding Runs Four Steps in a Row: Of 1194 Corpus Lines on the unicode-convert Page Only 798 Come Back Unchanged 2026-11-09 The Keep-Set Shrinks to 66 Characters: Character-by-Character Readings of Three Encoding Modes over 128 Code Points, Plus a 9-Cell Encode/Decode Matrix 2026-11-09 The color picker parse gate accepts only six-digit hex, making the three-digit branch unreachable on the page 2026-11-08 Editing the Salt Changes Nothing Until You Click: Three-Segment Concatenation and Multiline Input Measured on the MD5 Page 2026-11-08 A 60-bit Timestamp Leaves 28 Positions on This Page: Shifts and Masks in the UUID Generator v1 Branch, Measured Millisecond by Millisecond 2026-11-08