One key in the source link, yet two in the output
Before measuring, the odd thing looked simple: write UTM_SOURCE=a in all caps in the original link, put b in the form source box, and does the result hold two utm_source pairs or one? The second thread came from the panel: all five boxes were filled, yet the entry count field reported 6. Only after a round of pure Node did the picture settle into two separate ledgers: the first is a claim step that folds case, and the second is that the five numbers are each read with a different ruler.
The page under test is the UTM link builder, a template measured at 30156 bytes and 620 lines of body. The UB state object is constructed directly, and DOM traversal stops where ubEl returns null, so rendered table rows, button states and copy feedback are all marked as not measured at the DOM layer. Everything below is measured at the string layer, and the length unit is UTF-16 code units.
The fold happens at the claim step
ubAssemble (L277-320) takes each key-value pair split out of the source link and matches it against the entries the form wants to write, and the match lowercases both sides before comparing. Testing the source link UTM_SOURCE=a against a form source of b, the query in the result keeps only utm_source=b, and the res string carries a single equals sign; the key name is emitted in the canonical lowercase defined by UB_FIELDS, the uppercase form is not left behind, and there is no pair of coexisting groups either. The tighter sample puts two same-key entries that differ only in case into one link, UTM_source=old and utm_source=older: the first is claimed and overwrites, the second falls into the cleared bucket, and the three table rows read overwrote, cleared, overwrote.
Six states, two counts
Each parameter lands in exactly one of six st values in the end. The sixth comes when a form value strikes a lowercase key already used: fill source with b, then attach a custom utm_source=x, and the second attempt is judged a duplicate and ignored, so the result holds only one utm_source.
| State | Trigger condition |
|---|---|
| Overwrote original | The lowercased source key hits a form value, and it has not been used yet |
| Cleared | The source row is utm, keep is unchecked, and it was not claimed |
| Kept original | A non-utm key, or an unclaimed utm with keep checked |
| Added / added custom | The form value is not set, and the lowercase key has not been used either |
| Duplicate ignored | The form value is not set, but that lowercase key was already used |
The two counters in the check area are not on the same scale as the states: dropped counts only rows marked cleared, kept counts only rows marked kept original that also hit /^utm_/i, and carried-over non-utm rows enter neither tally.
The keep switch changes only where old values go
Take a source link that already carries three utm values and fill only source in the form. With keep off, the query is 14 code units, cnt is 1, dropped is 2, and the other two rows are judged cleared. With keep on, the query is 48 code units, cnt is 3, kept is 2, and the very same two rows turn into kept original. The table keeps 3 rows either way; only the final string differs.
Figure 1: where the three parameters land under the two keep settings, and the difference.
Five fields, five rulers
The five panel fields, in order, are the code-unit counts of the whole url, of the base and of the query; the query split into segments on & (an empty query reports 0); and the number of table rows whose key hits /^utm_/i, including rows that were cleared.
The sample that disagrees: a source link that carries 3 old utm keys plus one non-utm key id=7, all five form boxes filled, keep off. Measured, total is 97, base is 25, query is 71, cnt is 6, and utm is 5. The gut reading of "five utm keys joined" expects 5, yet cnt reports 6, and the extra segment is exactly the carried-over id. In the paired sample where that id is removed, base 25 plus the question mark plus query 67 gives 93, matching total line by line.
Figure 2: the ruler behind each of the five fields, and where cnt and utm diverge.
An empty value takes two roads
When a custom value is left empty, the result is a flag with a key but no equals sign, like ?gdt_vid; but a blank space in one of the standard five boxes makes the whole box skipped. Fill source with three spaces and the query keeps only utm_medium=email, leaving a link of 42 characters.
Decoding swaps the plus sign first
ubDecode (L238-241) first turns every plus sign into a space, then hands the string to decodeURIComponent, and echoes it as-is when URIError is thrown. Measured: %2B decodes to a single plus sign, a lone + decodes to a space, %ZZ echoes back unchanged at 3 code units, and %E4%B8AD echoes back at 8 code units because its third segment is a bare AD, while the correctly written %E4%B8%AD decodes back into a single Chinese character. The forward and reverse of the same %xx string are handled on the URL encode and decode page.
Two hard caps that live in different functions
UB_MAX_CUSTOM measures 8, and it lands in two places. When the custom list has already reached 8, adding a custom entry shows a notice first and then returns, so custom stays at 8 entries. When the split link carries parameters beyond 8, it takes the truncation path: a sample of 10 non-utm keys plus one utm_term yields other 10, dropped 2, and custom trimmed down to 8. UB_MAX_VARIANTS measures 30, and inside ubVarLines (L434-445) it de-duplicates first and only then checks length: 31 unique lines give lines 30 and over 1, and at 40 lines over reads 10, while the result always takes the first 30. De-duplication is case-sensitive, so Banner and banner count as two. A truly empty line is counted neither as a duplicate nor as a blank value; only a line of spaces or tabs is counted as blank.
A preset label is not its value
UB_PRESETS measures 18 entries, and the label column is never equal to the source or medium column. The entry 微信公众号 pairs with weixin and social; the entries 百度自然 and 百度竞价 share the same baidu source and differ only by organic and cpc. The highlight rule requires source and medium to match at the same time, so editing baidu alone lights nothing up. UB_MEDIUMS measures 13 tiers and touches only medium. Within this page these names are just string facts.
Copy and code differ in three places
The 18 statements this tool carries in its page configuration were checked line by line against measured behaviour, giving 15 matches, 3 partial matches and 0 conflicts. The state list in the copy names only five, while the code has six st values; a blank campaign returns the notice tier, while a blank medium is the one that returns the caution tier; the paid-channel whitelist in the copy lists 5 tiers, while the code regex actually holds 6. What each of those five keys means, and why case splits one report into two rows, is already covered in an older on-site article and is not repeated here.
Frequently asked questions
Q: Are the uppercase key names in the source link preserved?
Not on the claimed entry: it is rebuilt in the canonical lowercase from the form side and does not coexist with the new value. Only rows that take the kept-original path preserve their original spelling.
Q: Why is the entry count one more than the utm count?
The entry count splits the query on &, so a carried-over non-utm parameter still forms one segment; the utm count only tallies table rows whose key hits the utm_ prefix, and rows that were cleared are counted as well.
Q: When variants exceed 30 lines, is it a hard cut or just a notice?
It de-duplicates first and then cuts, so the result holds only the first 30 entries while a trailing note states how many lines were cut. At 40 unique values, over reports 10.