CLAUDE WATERMARK REMOVER · PRACTICAL TEST
Why can substring return text when slice returns an empty string for the same indexes?
Our ABCDE control gives BCD from substring(4, 1) and an empty string from slice(4, 1). substring also agrees with substring(1, 4). Cleanup can move the selected characters without making these APIs equivalent.
Open the Claude text cleaner · Full measured data
Measured inputs and outputs
These are locally constructed test strings, not evidence that Claude inserts these characters. We executed the saved homepage script snapshot with a minimal DOM harness and compared exact strings. The invisible-checkbox state and dash mode appear in each row. The observations below test this page's specific question. Destination observations run independently on the exact input and cleaned strings in the recorded Node runtime. We make no detector-score or statistical-watermark removal claim.
| Fixture and mode | Input string and code points | Output and cleaner status | Observed before / after |
|---|---|---|---|
| Marked sequence direct string / keep / invisible true | "ABCDE"U+0041 U+200B U+0042 U+0043 U+0044 U+0045 | "ABCDE"Removed 1 invisible character | {"substringReversed":"BC","sliceReversed":"","substringForward":"BC","length":6}{"substringReversed":"BCD","sliceReversed":"","substringForward":"BCD","length":5} |
| Plain sequence direct string / keep / invisible true | "ABCDE"U+0041 U+0042 U+0043 U+0044 U+0045 | "ABCDE"No selected invisible characters found | {"substringReversed":"BCD","sliceReversed":"","substringForward":"BCD","length":5}{"substringReversed":"BCD","sliceReversed":"","substringForward":"BCD","length":5} |
| Short sequence direct string / keep / invisible true | "AB"U+0041 U+0042 | "AB"No selected invisible characters found | {"substringReversed":"B","sliceReversed":"","substringForward":"B","length":2}{"substringReversed":"B","sliceReversed":"","substringForward":"B","length":2} |
| Empty sequence direct string / keep / invisible true | "" | ""No selected invisible characters found | {"substringReversed":"","sliceReversed":"","substringForward":"","length":0}{"substringReversed":"","sliceReversed":"","substringForward":"","length":0} |
Reversing the endpoints changes the API comparison
The observer deliberately supplies four as the first endpoint and one as the second endpoint to two methods on the identical string. For ABCDE, substring returns BCD while slice returns an empty string. A separate forward substring call also returns BCD, exposing the endpoint interchange in this case. The marked sequence places U+200B after A. Before cleanup its reversed substring contains that character followed by BC; afterwards it contains BCD. slice remains empty in both branches. The difference therefore cannot be explained solely by an invisible character: it is already present in the unmarked reference. We save each returned string and the source length rather than comparing only rendered output.
Short and empty strings show the boundary controls
For AB, the reversed substring returns B after its endpoints are bounded to the string, while slice still returns an empty string. For the empty source both results are empty. Identical empty outputs in that last row do not establish identical endpoint rules; the longer controls expose the distinction. Every numeric argument in this matrix is a small nonnegative integer. We do not test negative arguments, fractional coercion, custom receiver objects or Unicode grapheme slicing. Existing guides address negative-one results from indexOf and splitting surrogate pairs with slice. This page starts with explicit reversed endpoints and asks why the two named methods disagree, so it supplies a different diagnostic and a different set of controls.
Validate ranges before choosing a substring operation
If a data field requires an ordered start and end, check that ordering explicitly before extracting content. Decide whether reversal should be rejected, swapped deliberately or interpreted as an empty range. Selecting a different method without reviewing that requirement can hide a bug, because substring can produce plausible text from reversed endpoints. Recompute any offsets derived from the original value after a confirmed deletion. The local test makes no document-selection, editor-cursor or browser-layout claim; its arguments are constants in the reproducer. Use the measured strings to verify the API behavior in your runtime and keep the original range with the edited data. The cleaner itself performs neither extraction nor application range validation.
Reproduce this test
Save reproduce.cjs and tested-app.js in the same folder. Run the command below with Node.js. The harness prints its runtime, script SHA-256 and every measured row. Compare those rows with the original record. Using a newer script or runtime creates a new experiment; retain the version information with your rerun.
node reproduce.cjsReference and next check
ECMAScript substring provides the relevant primary definition. The table and fixture analysis are original measurements. For broader inspection, use our Unicode inspector. Read the scope distinction before interpreting cleanup as a watermark result.