diff options
Diffstat (limited to 'spike')
32 files changed, 671 insertions, 0 deletions
diff --git a/spike/settings/icons.jungle b/spike/settings/icons.jungle new file mode 100644 index 0000000..de24aea --- /dev/null +++ b/spike/settings/icons.jungle @@ -0,0 +1,10 @@ +# Author: cirakg & claude ai +# Summary: Per-device launcher-icon resources; generated by tools/make_icon.py --all (do not edit). +# +# Each product gets the icon folder matching the launcher-icon size in its +# device definition, so the compiler never has to scale (and warn). + +fr255s.resourcePath = $(fr255s.resourcePath);resources-icons/40x40 +venusq2.resourcePath = $(venusq2.resourcePath);resources-icons/40x40 +fr70.resourcePath = $(fr70.resourcePath);resources-icons/54x54 +fr965.resourcePath = $(fr965.resourcePath);resources-icons/65x65 diff --git a/spike/settings/manifest.xml b/spike/settings/manifest.xml new file mode 100644 index 0000000..3144ffa --- /dev/null +++ b/spike/settings/manifest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Author: cirakg & claude ai + Summary: Manifest of the M1 settings spike, a throwaway app that measures Connect IQ settings limits. + + * Its own app id, separate from OTP Wallet, so it can be uploaded as its own + private beta and deleted afterwards. + * No permissions, so no network. + * Products: the four test devices, including your Forerunner 70. +--> +<iq:manifest version="3" xmlns:iq="http://www.garmin.com/xml/connectiq"> + <iq:application id="9e455c06fab44b58b6d431a2d8cf2900" type="watch-app" name="@Strings.AppName" + entry="SpikeApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="5.0.0"> + <iq:products> + <iq:product id="fr255s"/> + <iq:product id="fr70"/> + <iq:product id="fr965"/> + <iq:product id="venusq2"/> + </iq:products> + <iq:permissions/> + <iq:languages> + <iq:language>eng</iq:language> + </iq:languages> + <iq:barrels/> + </iq:application> +</iq:manifest> diff --git a/spike/settings/monkey.jungle b/spike/settings/monkey.jungle new file mode 100644 index 0000000..6a70a0b --- /dev/null +++ b/spike/settings/monkey.jungle @@ -0,0 +1,8 @@ +# Author: cirakg & claude ai +# Summary: Build configuration of the M1 settings spike (used with icons.jungle by build-spike.sh). +# +# Paths are relative to this folder. +project.manifest = manifest.xml + +base.sourcePath = source +base.resourcePath = resources diff --git a/spike/settings/resources-icons/40x40/drawables.xml b/spike/settings/resources-icons/40x40/drawables.xml new file mode 100644 index 0000000..6d638f8 --- /dev/null +++ b/spike/settings/resources-icons/40x40/drawables.xml @@ -0,0 +1,7 @@ +<!-- + Author: cirakg & claude ai + Summary: Launcher icon at 40x40 px for the devices listed in icons.jungle (generated by tools/make_icon.py). +--> +<drawables> + <bitmap id="LauncherIcon" filename="launcher_icon.png" /> +</drawables> diff --git a/spike/settings/resources-icons/40x40/launcher_icon.png b/spike/settings/resources-icons/40x40/launcher_icon.png Binary files differnew file mode 100644 index 0000000..39193ac --- /dev/null +++ b/spike/settings/resources-icons/40x40/launcher_icon.png diff --git a/spike/settings/resources-icons/54x54/drawables.xml b/spike/settings/resources-icons/54x54/drawables.xml new file mode 100644 index 0000000..b703a0e --- /dev/null +++ b/spike/settings/resources-icons/54x54/drawables.xml @@ -0,0 +1,7 @@ +<!-- + Author: cirakg & claude ai + Summary: Launcher icon at 54x54 px for the devices listed in icons.jungle (generated by tools/make_icon.py). +--> +<drawables> + <bitmap id="LauncherIcon" filename="launcher_icon.png" /> +</drawables> diff --git a/spike/settings/resources-icons/54x54/launcher_icon.png b/spike/settings/resources-icons/54x54/launcher_icon.png Binary files differnew file mode 100644 index 0000000..a6cd756 --- /dev/null +++ b/spike/settings/resources-icons/54x54/launcher_icon.png diff --git a/spike/settings/resources-icons/65x65/drawables.xml b/spike/settings/resources-icons/65x65/drawables.xml new file mode 100644 index 0000000..9450580 --- /dev/null +++ b/spike/settings/resources-icons/65x65/drawables.xml @@ -0,0 +1,7 @@ +<!-- + Author: cirakg & claude ai + Summary: Launcher icon at 65x65 px for the devices listed in icons.jungle (generated by tools/make_icon.py). +--> +<drawables> + <bitmap id="LauncherIcon" filename="launcher_icon.png" /> +</drawables> diff --git a/spike/settings/resources-icons/65x65/launcher_icon.png b/spike/settings/resources-icons/65x65/launcher_icon.png Binary files differnew file mode 100644 index 0000000..892b731 --- /dev/null +++ b/spike/settings/resources-icons/65x65/launcher_icon.png diff --git a/spike/settings/resources/drawables/drawables.xml b/spike/settings/resources/drawables/drawables.xml new file mode 100644 index 0000000..4425922 --- /dev/null +++ b/spike/settings/resources/drawables/drawables.xml @@ -0,0 +1,7 @@ +<!-- + Author: cirakg & claude ai + Summary: Default launcher icon of the spike (per-size icons come from resources-icons/ via icons.jungle). +--> +<drawables> + <bitmap id="LauncherIcon" filename="launcher_icon.png" /> +</drawables> diff --git a/spike/settings/resources/drawables/launcher_icon.png b/spike/settings/resources/drawables/launcher_icon.png Binary files differnew file mode 100644 index 0000000..a4b5902 --- /dev/null +++ b/spike/settings/resources/drawables/launcher_icon.png diff --git a/spike/settings/resources/settings/properties.xml b/spike/settings/resources/settings/properties.xml new file mode 100644 index 0000000..ff444cf --- /dev/null +++ b/spike/settings/resources/settings/properties.xml @@ -0,0 +1,23 @@ +<!-- + Author: cirakg & claude ai + Summary: Properties behind the spike's settings: eight text fields plus one of each other field type. +--> +<properties> + <!-- Length tests: no maxLength, so the phone app's own limit shows. --> + <property id="text1" type="string"></property> + <property id="text2" type="string"></property> + <property id="text3" type="string"></property> + <property id="text4" type="string"></property> + <property id="text5" type="string"></property> + <property id="text6" type="string"></property> + <property id="text7" type="string"></property> + <property id="text8" type="string"></property> + <!-- Masked input; the watch clears it after reading (write-back test). --> + <property id="secretField" type="string"></property> + <!-- Read-only on the phone; the watch writes a status line here. --> + <property id="statusField" type="string"></property> + <property id="switchField" type="boolean">false</property> + <property id="listField" type="number">0</property> + <property id="numberField" type="number">30</property> + <property id="longField" type="long">0</property> +</properties> diff --git a/spike/settings/resources/settings/settings.xml b/spike/settings/resources/settings/settings.xml new file mode 100644 index 0000000..967e27c --- /dev/null +++ b/spike/settings/resources/settings/settings.xml @@ -0,0 +1,58 @@ +<!-- + Author: cirakg & claude ai + Summary: Settings screen of the spike as shown in Garmin Connect: length-test fields and one of each field type. +--> +<settings> + <setting propertyKey="@Properties.statusField" title="@Strings.Status"> + <settingConfig type="alphaNumeric" readonly="true" /> + </setting> + + <group id="lengthTests" title="@Strings.GroupLength" description="@Strings.GroupLengthHelp"> + <setting propertyKey="@Properties.text1" title="@Strings.Text1" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text2" title="@Strings.Text2" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text3" title="@Strings.Text3" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text4" title="@Strings.Text4" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text5" title="@Strings.Text5" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text6" title="@Strings.Text6" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text7" title="@Strings.Text7" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + <setting propertyKey="@Properties.text8" title="@Strings.Text8" prompt="@Strings.TextPrompt"> + <settingConfig type="alphaNumeric" /> + </setting> + </group> + + <group id="otherTypes" title="@Strings.GroupOther" description="@Strings.GroupOtherHelp"> + <setting propertyKey="@Properties.secretField" title="@Strings.Secret" prompt="@Strings.SecretPrompt"> + <settingConfig type="password" /> + </setting> + <setting propertyKey="@Properties.switchField" title="@Strings.Switch"> + <settingConfig type="boolean" /> + </setting> + <setting propertyKey="@Properties.listField" title="@Strings.ListTitle"> + <settingConfig type="list"> + <listEntry value="0">@Strings.ListA</listEntry> + <listEntry value="1">@Strings.ListB</listEntry> + <listEntry value="2">@Strings.ListC</listEntry> + </settingConfig> + </setting> + <setting propertyKey="@Properties.numberField" title="@Strings.NumberTitle"> + <settingConfig type="numeric" min="1" max="300" errorMessage="@Strings.NumberError" /> + </setting> + <setting propertyKey="@Properties.longField" title="@Strings.LongTitle"> + <settingConfig type="numeric" min="0" /> + </setting> + </group> +</settings> diff --git a/spike/settings/resources/strings/strings.xml b/spike/settings/resources/strings/strings.xml new file mode 100644 index 0000000..302ffb0 --- /dev/null +++ b/spike/settings/resources/strings/strings.xml @@ -0,0 +1,31 @@ +<!-- + Author: cirakg & claude ai + Summary: Strings of the settings spike (app name and settings titles). +--> +<strings> + <string id="AppName">Settings Spike</string> + <string id="Status">Status written by the watch</string> + <string id="GroupLength">Length tests</string> + <string id="GroupLengthHelp">Paste one test string per field, then save.</string> + <string id="Text1">Text 1</string> + <string id="Text2">Text 2</string> + <string id="Text3">Text 3</string> + <string id="Text4">Text 4</string> + <string id="Text5">Text 5</string> + <string id="Text6">Text 6</string> + <string id="Text7">Text 7</string> + <string id="Text8">Text 8</string> + <string id="TextPrompt">Paste a test string</string> + <string id="GroupOther">Other field types</string> + <string id="GroupOtherHelp">Password, switch, list and numbers.</string> + <string id="Secret">Password field</string> + <string id="SecretPrompt">Type anything (fake)</string> + <string id="Switch">Switch</string> + <string id="ListTitle">List</string> + <string id="ListA">Option A</string> + <string id="ListB">Option B</string> + <string id="ListC">Option C</string> + <string id="NumberTitle">Number (1-300)</string> + <string id="NumberError">Enter 1 to 300</string> + <string id="LongTitle">Long number</string> +</strings> diff --git a/spike/settings/source/DebugSelfTest.mc b/spike/settings/source/DebugSelfTest.mc new file mode 100644 index 0000000..ef50148 --- /dev/null +++ b/spike/settings/source/DebugSelfTest.mc @@ -0,0 +1,35 @@ +// Author: cirakg & claude ai +// Summary: Debug-only self-test: fills one field and runs the settings-change path once, so the simulator shows real output. +// +// The simulator has no phone, so this stands in for one save from Garmin +// Connect: if Text 1 is empty it receives a fake 100-character test string, +// then the normal onSettingsChanged path runs (analysis plus write-back). +// It prints a "SPIKE ready" line that build-spike.sh waits for. Release +// builds compile the empty (:release) version, so the beta you upload never +// changes your settings by itself. + +import Toybox.Application.Properties; +import Toybox.Lang; +import Toybox.System; + +module DebugSelfTest { + + // Fake 100-character test string in the same format as the phone tests. + (:debug) + const SAMPLE = "N=100|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghEND"; + + (:debug) + function run(app as SpikeApp) as Void { + var current = Properties.getValue("text1"); + if (current == null || (current instanceof String && current.length() == 0)) { + Properties.setValue("text1", SAMPLE); + app.onSettingsChanged(); + } + System.println("SPIKE ready total_chars=" + app.report.totalChars + " changes=" + app.report.changeCount + + " status='" + Properties.getValue("statusField") + "'"); + } + + (:release) + function run(app as SpikeApp) as Void { + } +} diff --git a/spike/settings/source/SpikeApp.mc b/spike/settings/source/SpikeApp.mc new file mode 100644 index 0000000..c5adcb2 --- /dev/null +++ b/spike/settings/source/SpikeApp.mc @@ -0,0 +1,43 @@ +// Author: cirakg & claude ai +// Summary: Settings spike entry point: analyses every setting on start and on each change, and writes a status back. +// +// The spike measures what Garmin doesn't document (README section 16, M1): +// how long a text setting may be, which characters survive, whether values +// the watch writes back reach the phone, and when onSettingsChanged fires. +// It is a throwaway app with its own app id; nothing here is reused by OTP +// Wallet except the measured numbers. + +import Toybox.Application; +import Toybox.Lang; +import Toybox.WatchUi; + +class SpikeApp extends Application.AppBase { + + // The analysis shown on screen, refreshed on start and on every change. + var report as SpikeReport; + + function initialize() { + AppBase.initialize(); + report = new SpikeReport(); + } + + // Read the values the watch already has (from the last sync). + function onStart(state as Dictionary?) as Void { + report.analyseOnStart(); + DebugSelfTest.run(self); + } + + function onStop(state as Dictionary?) as Void { + } + + function getInitialView() as [Views] or [Views, InputDelegates] { + var view = new SpikeView(report); + return [view, new SpikeDelegate(view)]; + } + + // Called when you save the settings on the phone while the app runs. + function onSettingsChanged() as Void { + report.settingsChanged(); + WatchUi.requestUpdate(); + } +} diff --git a/spike/settings/source/SpikeDelegate.mc b/spike/settings/source/SpikeDelegate.mc new file mode 100644 index 0000000..94b3ece --- /dev/null +++ b/spike/settings/source/SpikeDelegate.mc @@ -0,0 +1,35 @@ +// Author: cirakg & claude ai +// Summary: Input of the spike screen: UP/DOWN or swipes scroll, START/tap jumps back to the top. + +import Toybox.Lang; +import Toybox.WatchUi; + +class SpikeDelegate extends WatchUi.BehaviorDelegate { + + // Lines moved per button press or swipe. + private const SCROLL_STEP = 3; + // Large enough to reach the first line from anywhere. + private const TO_TOP = -1000; + + private var _view as SpikeView; + + function initialize(view as SpikeView) { + BehaviorDelegate.initialize(); + _view = view; + } + + function onNextPage() as Boolean { + _view.scroll(SCROLL_STEP); + return true; + } + + function onPreviousPage() as Boolean { + _view.scroll(-SCROLL_STEP); + return true; + } + + function onSelect() as Boolean { + _view.scroll(TO_TOP); + return true; + } +} diff --git a/spike/settings/source/SpikeReport.mc b/spike/settings/source/SpikeReport.mc new file mode 100644 index 0000000..09f27fa --- /dev/null +++ b/spike/settings/source/SpikeReport.mc @@ -0,0 +1,144 @@ +// Author: cirakg & claude ai +// Summary: Reads every spike setting, measures lengths and characters, writes the status back, builds the screen lines. +// +// Per text field it reports: length in characters and UTF-8 bytes, the first +// 12 and last 8 characters (the test strings start with "N=<length>|" and +// end with "END", so truncation is obvious), and which of the characters +// & < > \ " { } % and non-ASCII it contains. +// +// Write-back test: on every settings change the watch writes a status line +// into the read-only "statusField" and clears "secretField". Opening the +// settings on the phone afterwards shows whether the phone displays values +// the watch wrote. + +import Toybox.Application; +import Toybox.Application.Properties; +import Toybox.Lang; +import Toybox.System; + +module SpikeKeys { + const TEXT_FIELDS = ["text1", "text2", "text3", "text4", "text5", "text6", "text7", "text8"]; + const SECRET = "secretField"; + const STATUS = "statusField"; + const OTHER_FIELDS = ["switchField", "listField", "numberField", "longField"]; + const HEAD_CHARS = 12; + const TAIL_CHARS = 8; + const ASCII_BACKSLASH = 92; + const ASCII_QUOTE = 34; +} + +class SpikeReport { + + // Lines for SpikeView, rebuilt by analyse(). + var lines as Array<String> = [] as Array<String>; + // onSettingsChanged calls since the app started, and the time of the last. + var changeCount as Number = 0; + var lastChange as String = "never"; + // Totals over the eight text fields, from the latest analysis. + var totalChars as Number = 0; + var totalBytes as Number = 0; + // Length of the password field when it was last read (before clearing). + var passwordReceived as Number = 0; + // The status line the watch wrote last ("" before the first change). + var lastWriteBack as String = ""; + + function initialize() { + } + + // On start: no write-back, just show what the last sync delivered. + function analyseOnStart() as Void { + passwordReceived = readString(SpikeKeys.SECRET).length(); + analyse(); + } + + // Settings were saved on the phone: measure, write back, show. + function settingsChanged() as Void { + changeCount++; + lastChange = clockText(); + passwordReceived = readString(SpikeKeys.SECRET).length(); + analyse(); + lastWriteBack = "seen " + totalChars + " chars at " + lastChange + ", change #" + changeCount; + Properties.setValue(SpikeKeys.STATUS, lastWriteBack); + Properties.setValue(SpikeKeys.SECRET, ""); + analyse(); + } + + // Rebuild every screen line from the current property values. + function analyse() as Void { + lines = [] as Array<String>; + totalChars = 0; + totalBytes = 0; + var fieldLines = [] as Array<String>; + for (var i = 0; i < SpikeKeys.TEXT_FIELDS.size(); i++) { + describeText(fieldLines, "T" + (i + 1), readString(SpikeKeys.TEXT_FIELDS[i])); + } + lines.add("Settings spike"); + lines.add("Changes seen: " + changeCount); + lines.add("Last change: " + lastChange); + lines.add("Total: " + totalChars + " chars, " + totalBytes + " B"); + lines.addAll(fieldLines); + lines.add("Password: got " + passwordReceived + " chars,"); + lines.add(" now " + readString(SpikeKeys.SECRET).length() + " (watch clears it)"); + for (var i = 0; i < SpikeKeys.OTHER_FIELDS.size(); i++) { + lines.add(SpikeKeys.OTHER_FIELDS[i] + ": " + valueText(SpikeKeys.OTHER_FIELDS[i])); + } + lines.add("Status now: " + readString(SpikeKeys.STATUS)); + lines.add("Wrote: " + (lastWriteBack.equals("") ? "nothing yet" : lastWriteBack)); + } + + // Length, ends and special characters of one text field. + private function describeText(out as Array<String>, label as String, value as String) as Void { + var chars = value.length(); + var bytes = value.toUtf8Array().size(); + totalChars += chars; + totalBytes += bytes; + out.add(label + ": " + chars + " chars, " + bytes + " B"); + if (chars == 0) { + return; + } + var head = part(value, 0, chars < SpikeKeys.HEAD_CHARS ? chars : SpikeKeys.HEAD_CHARS); + var tailStart = chars > SpikeKeys.TAIL_CHARS ? chars - SpikeKeys.TAIL_CHARS : 0; + out.add(" " + head + " .. " + part(value, tailStart, chars)); + out.add(" has: " + specialsIn(value, chars != bytes)); + } + + // Which of the characters the plan worries about occur in the value. + private function specialsIn(value as String, nonAscii as Boolean) as String { + var probes = [ + "&", "<", ">", SpikeKeys.ASCII_BACKSLASH.toChar().toString(), + SpikeKeys.ASCII_QUOTE.toChar().toString(), "{", "}", "%" + ]; + var found = ""; + for (var i = 0; i < probes.size(); i++) { + if (value.find(probes[i]) != null) { + found += probes[i] + " "; + } + } + if (nonAscii) { + found += "non-ASCII"; + } + return found.equals("") ? "none" : found; + } + + // A property as a String; anything missing or of another type reads as "". + private function readString(key as String) as String { + var value = Properties.getValue(key); + return value instanceof String ? value : ""; + } + + // Any property as display text. + private function valueText(key as String) as String { + // getValue throws for unknown keys and never returns null for declared ones. + return Properties.getValue(key).toString() as String; + } + + private function part(value as String, startIndex as Number, endIndex as Number) as String { + var result = value.substring(startIndex, endIndex); + return result == null ? "" : result; + } + + private function clockText() as String { + var time = System.getClockTime(); + return time.hour.format("%02d") + ":" + time.min.format("%02d") + ":" + time.sec.format("%02d"); + } +} diff --git a/spike/settings/source/SpikeView.mc b/spike/settings/source/SpikeView.mc new file mode 100644 index 0000000..68bacc7 --- /dev/null +++ b/spike/settings/source/SpikeView.mc @@ -0,0 +1,53 @@ +// Author: cirakg & claude ai +// Summary: Scrollable text screen showing the spike's measurements. +// +// Lines come from SpikeReport. The first visible line moves with +// SpikeDelegate (UP/DOWN buttons or swipes); text stays in the middle band of +// round screens so nothing is cut off at the top or bottom. + +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class SpikeView extends WatchUi.View { + + // Share of the screen height left free at the top and at the bottom. + private const MARGIN_FRACTION = 0.14; + + private var _report as SpikeReport; + private var _firstLine as Number = 0; + + function initialize(report as SpikeReport) { + View.initialize(); + _report = report; + } + + // Move the first visible line by delta lines, staying within the text. + function scroll(delta as Number) as Void { + var last = _report.lines.size() - 1; + _firstLine += delta; + if (_firstLine > last) { + _firstLine = last; + } + if (_firstLine < 0) { + _firstLine = 0; + } + WatchUi.requestUpdate(); + } + + function onUpdate(dc as Graphics.Dc) as Void { + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK); + dc.clear(); + var font = Graphics.FONT_XTINY; + var lineHeight = dc.getFontHeight(font); + var top = (dc.getHeight() * MARGIN_FRACTION).toNumber(); + var bottom = dc.getHeight() - top; + var x = dc.getWidth() / 2; + var y = top; + var lines = _report.lines; + for (var i = _firstLine; i < lines.size() && y + lineHeight <= bottom; i++) { + dc.drawText(x, y, font, lines[i], Graphics.TEXT_JUSTIFY_CENTER); + y += lineHeight; + } + } +} diff --git a/spike/settings/test-strings/index.html b/spike/settings/test-strings/index.html new file mode 100644 index 0000000..d330b52 --- /dev/null +++ b/spike/settings/test-strings/index.html @@ -0,0 +1,30 @@ +<!doctype html> +<!-- Author: cirakg & claude ai --> +<!-- Summary: Settings spike test strings with copy buttons; open on the phone, copy, paste into a settings field. --> +<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Settings spike test strings</title> +<style>body{font-family:sans-serif;margin:16px}textarea{width:100%;font-family:monospace} +button{font-size:1.1em;padding:6px 16px;margin:4px 0 16px}</style></head> +<body><h1>Settings spike test strings</h1> +<p>Tap Copy, then paste into one of the Text fields in the app settings. Expected lengths are in each heading.</p> +<section><h2>100 characters</h2><textarea id="len-0100" readonly rows="3">N=100|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghEND</textarea><button onclick="copyText('len-0100')">Copy</button></section> +<section><h2>255 characters</h2><textarea id="len-0255" readonly rows="3">N=255|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrEND</textarea><button onclick="copyText('len-0255')">Copy</button></section> +<section><h2>256 characters</h2><textarea id="len-0256" readonly rows="3">N=256|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsEND</textarea><button onclick="copyText('len-0256')">Copy</button></section> +<section><h2>500 characters</h2><textarea id="len-0500" readonly rows="3">N=500|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0250qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0300jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0350cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0400vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP0450opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdEND</textarea><button onclick="copyText('len-0500')">Copy</button></section> +<section><h2>800 characters</h2><textarea id="len-0800" readonly rows="3">N=800|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0250qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0300jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0350cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0400vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP0450opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP0500hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP0550abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP0600tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP0650mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP0700fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP0750yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnEND</textarea><button onclick="copyText('len-0800')">Copy</button></section> +<section><h2>801 characters</h2><textarea id="len-0801" readonly rows="3">N=801|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0250qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0300jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0350cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0400vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP0450opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP0500hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP0550abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP0600tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP0650mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP0700fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP0750yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoEND</textarea><button onclick="copyText('len-0801')">Copy</button></section> +<section><h2>1000 characters</h2><textarea id="len-1000" readonly rows="3">N=1000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkEND</textarea><button onclick="copyText('len-1000')">Copy</button></section> +<section><h2>2000 characters</h2><textarea id="len-2000" readonly rows="3">N=2000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP1000opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP1050hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP1100abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP1150tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP1200mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP1250fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP1300yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP1350rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP1400klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP1450defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP1500wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP1550pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP1600ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP1650bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP1700uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP1750nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP1800ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP1850zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP1900stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP1950lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaEND</textarea><button onclick="copyText('len-2000')">Copy</button></section> +<section><h2>4000 characters</h2><textarea id="len-4000" readonly rows="3">N=4000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP1000opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP1050hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP1100abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP1150tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP1200mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP1250fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP1300yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP1350rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP1400klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP1450defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP1500wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP1550pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP1600ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP1650bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP1700uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP1750nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP1800ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP1850zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP1900stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP1950lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP2000efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP2050xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP2100qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP2150jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP2200cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP2250vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP2300opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP2350hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP2400abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP2450tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP2500mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP2550fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP2600yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP2650rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP2700klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP2750defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP2800wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP2850pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP2900ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP2950bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP3000uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP3050nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP3100ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP3150zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP3200stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP3250lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP3300efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP3350xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP3400qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP3450jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP3500cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP3550vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP3600opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP3650hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP3700abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP3750tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP3800mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP3850fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP3900yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP3950rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgEND</textarea><button onclick="copyText('len-4000')">Copy</button></section> +<section><h2>8000 characters</h2><textarea id="len-8000" readonly rows="3">N=8000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP1000opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP1050hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP1100abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP1150tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP1200mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP1250fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP1300yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP1350rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP1400klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP1450defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP1500wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP1550pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP1600ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP1650bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP1700uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP1750nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP1800ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP1850zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP1900stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP1950lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP2000efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP2050xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP2100qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP2150jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP2200cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP2250vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP2300opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP2350hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP2400abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP2450tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP2500mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP2550fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP2600yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP2650rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP2700klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP2750defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP2800wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP2850pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP2900ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP2950bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP3000uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP3050nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP3100ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP3150zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP3200stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP3250lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP3300efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP3350xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP3400qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP3450jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP3500cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP3550vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP3600opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP3650hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP3700abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP3750tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP3800mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP3850fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP3900yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP3950rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP4000klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP4050defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP4100wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP4150pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP4200ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP4250bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP4300uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP4350nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP4400ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP4450zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP4500stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP4550lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP4600efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP4650xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP4700qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP4750jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP4800cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP4850vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP4900opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP4950hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP5000abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP5050tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP5100mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP5150fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP5200yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP5250rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP5300klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP5350defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP5400wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP5450pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP5500ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP5550bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP5600uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP5650nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP5700ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP5750zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP5800stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP5850lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP5900efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP5950xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP6000qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP6050jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP6100cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP6150vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP6200opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP6250hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP6300abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP6350tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP6400mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP6450fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP6500yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP6550rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP6600klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP6650defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP6700wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP6750pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP6800ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP6850bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP6900uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP6950nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP7000ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP7050zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP7100stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP7150lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP7200efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP7250xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP7300qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP7350jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP7400cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP7450vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP7500opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP7550hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP7600abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP7650tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP7700mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP7750fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP7800yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP7850rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP7900klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP7950defghijklmnopqrstuvwxyzabcdefghijklmnopqrsEND</textarea><button onclick="copyText('len-8000')">Copy</button></section> +<section><h2>special characters</h2><textarea id="special" readonly rows="3">special: & < > \ " { } % | non-ASCII: äöü ß € 😀 | otpauth://totp/Fake:alice?secret=JBSWY3DPEHPK3PXP&issuer=Fake&digits=6 | {"name":"Fake","path":"a\/b"} END</textarea><button onclick="copyText('special')">Copy</button></section> +<script> +function copyText(id) { + const area = document.getElementById(id); + area.select(); + const done = () => alert("Copied " + area.value.length + " characters"); + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(area.value).then(done, () => { document.execCommand("copy"); done(); }); + } else { document.execCommand("copy"); done(); } +} +</script></body></html> diff --git a/spike/settings/test-strings/len-0100.txt b/spike/settings/test-strings/len-0100.txt new file mode 100644 index 0000000..a4f972e --- /dev/null +++ b/spike/settings/test-strings/len-0100.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 100 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=100|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghEND diff --git a/spike/settings/test-strings/len-0255.txt b/spike/settings/test-strings/len-0255.txt new file mode 100644 index 0000000..b8cc080 --- /dev/null +++ b/spike/settings/test-strings/len-0255.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 255 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=255|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrEND diff --git a/spike/settings/test-strings/len-0256.txt b/spike/settings/test-strings/len-0256.txt new file mode 100644 index 0000000..bbd0ca2 --- /dev/null +++ b/spike/settings/test-strings/len-0256.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 256 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=256|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsEND diff --git a/spike/settings/test-strings/len-0500.txt b/spike/settings/test-strings/len-0500.txt new file mode 100644 index 0000000..80f5d25 --- /dev/null +++ b/spike/settings/test-strings/len-0500.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 500 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=500|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0250qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0300jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0350cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0400vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP0450opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdEND diff --git a/spike/settings/test-strings/len-0800.txt b/spike/settings/test-strings/len-0800.txt new file mode 100644 index 0000000..bc90b28 --- /dev/null +++ b/spike/settings/test-strings/len-0800.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 800 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=800|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0250qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0300jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0350cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0400vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP0450opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP0500hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP0550abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP0600tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP0650mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP0700fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP0750yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnEND diff --git a/spike/settings/test-strings/len-0801.txt b/spike/settings/test-strings/len-0801.txt new file mode 100644 index 0000000..38ef08d --- /dev/null +++ b/spike/settings/test-strings/len-0801.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 801 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=801|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0050stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0100lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0150efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0200xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0250qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0300jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0350cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0400vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP0450opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP0500hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP0550abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP0600tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP0650mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP0700fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP0750yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoEND diff --git a/spike/settings/test-strings/len-1000.txt b/spike/settings/test-strings/len-1000.txt new file mode 100644 index 0000000..0979c3c --- /dev/null +++ b/spike/settings/test-strings/len-1000.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 1000 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=1000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkEND diff --git a/spike/settings/test-strings/len-2000.txt b/spike/settings/test-strings/len-2000.txt new file mode 100644 index 0000000..0358ab2 --- /dev/null +++ b/spike/settings/test-strings/len-2000.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 2000 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=2000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP1000opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP1050hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP1100abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP1150tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP1200mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP1250fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP1300yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP1350rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP1400klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP1450defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP1500wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP1550pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP1600ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP1650bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP1700uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP1750nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP1800ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP1850zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP1900stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP1950lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaEND diff --git a/spike/settings/test-strings/len-4000.txt b/spike/settings/test-strings/len-4000.txt new file mode 100644 index 0000000..934e0d5 --- /dev/null +++ b/spike/settings/test-strings/len-4000.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 4000 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=4000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP1000opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP1050hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP1100abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP1150tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP1200mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP1250fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP1300yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP1350rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP1400klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP1450defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP1500wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP1550pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP1600ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP1650bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP1700uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP1750nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP1800ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP1850zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP1900stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP1950lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP2000efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP2050xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP2100qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP2150jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP2200cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP2250vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP2300opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP2350hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP2400abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP2450tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP2500mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP2550fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP2600yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP2650rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP2700klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP2750defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP2800wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP2850pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP2900ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP2950bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP3000uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP3050nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP3100ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP3150zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP3200stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP3250lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP3300efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP3350xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP3400qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP3450jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP3500cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP3550vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP3600opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP3650hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP3700abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP3750tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP3800mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP3850fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP3900yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP3950rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgEND diff --git a/spike/settings/test-strings/len-8000.txt b/spike/settings/test-strings/len-8000.txt new file mode 100644 index 0000000..1f75f8d --- /dev/null +++ b/spike/settings/test-strings/len-8000.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: 8000 characters. +# Copy ONLY the last line (the test string) into a settings field. + +N=8000|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP0050rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP0100klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP0150defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP0200wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP0250pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP0300ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP0350bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP0400uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP0450nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP0500ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP0550zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP0600stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP0650lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP0700efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP0750xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP0800qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP0850jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP0900cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP0950vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP1000opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP1050hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP1100abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP1150tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP1200mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP1250fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP1300yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP1350rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP1400klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP1450defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP1500wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP1550pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP1600ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP1650bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP1700uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP1750nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP1800ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP1850zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP1900stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP1950lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP2000efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP2050xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP2100qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP2150jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP2200cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP2250vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP2300opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP2350hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP2400abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP2450tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP2500mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP2550fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP2600yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP2650rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP2700klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP2750defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP2800wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP2850pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP2900ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP2950bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP3000uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP3050nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP3100ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP3150zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP3200stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP3250lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP3300efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP3350xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP3400qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP3450jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP3500cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP3550vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP3600opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP3650hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP3700abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP3750tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP3800mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP3850fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP3900yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP3950rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP4000klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP4050defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP4100wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP4150pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP4200ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP4250bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP4300uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP4350nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP4400ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP4450zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP4500stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP4550lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP4600efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP4650xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP4700qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP4750jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP4800cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP4850vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP4900opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP4950hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP5000abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP5050tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP5100mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP5150fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP5200yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP5250rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP5300klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP5350defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP5400wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP5450pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP5500ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP5550bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP5600uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP5650nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP5700ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP5750zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP5800stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP5850lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP5900efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP5950xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP6000qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP6050jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP6100cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP6150vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP6200opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP6250hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP6300abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP6350tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP6400mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP6450fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP6500yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP6550rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP6600klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP6650defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvP6700wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnoP6750pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghP6800ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzaP6850bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstP6900uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmP6950nopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefP7000ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyP7050zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrP7100stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkP7150lmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdP7200efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwP7250xyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopP7300qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiP7350jklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabP7400cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuP7450vwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnP7500opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgP7550hijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzP7600abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsP7650tuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklP7700mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeP7750fghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxP7800yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqP7850rstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijP7900klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcP7950defghijklmnopqrstuvwxyzabcdefghijklmnopqrsEND diff --git a/spike/settings/test-strings/make_test_strings.py b/spike/settings/test-strings/make_test_strings.py new file mode 100644 index 0000000..8d43977 --- /dev/null +++ b/spike/settings/test-strings/make_test_strings.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# Author: cirakg & claude ai +# Summary: Generate the settings-spike test strings: one .txt per string and an index.html with copy buttons. +"""Generate the test strings for the M1 settings spike (docs/SETTINGS_SPIKE.md). + +Length strings look like + N=1000|abcdef...P0050...P0100...xyzEND +"N=<length>|" says how long the string should be, a marker "P<position>" +sits at every 50th character, and "END" closes it. If the phone app cuts a +string, the watch shows a wrong length and no "END"; the last marker it shows +tells roughly where the cut happened. Only letters, digits, "=", "|" are +used, so the length tests don't also test character handling. + +special.txt holds the characters that may be rejected (& < > \\ " { } %), +non-ASCII text, a fake otpauth URI and a JSON fragment. + +Usage: python3 make_test_strings.py (writes into its own folder) +""" + +from __future__ import annotations + +import html +from pathlib import Path + +LENGTHS = (100, 255, 256, 500, 800, 801, 1000, 2000, 4000, 8000) +SPECIAL = ( + 'special: & < > \\ " { } % | non-ASCII: äöü ß € 😀 | ' + "otpauth://totp/Fake:alice?secret=JBSWY3DPEHPK3PXP&issuer=Fake&digits=6 | " + '{"name":"Fake","path":"a\\/b"} END' +) +HEADER = ( + "# Author: cirakg & claude ai\n# Summary: {summary}\n" + "# Copy ONLY the last line (the test string) into a settings field.\n\n" +) +LETTERS = "abcdefghijklmnopqrstuvwxyz" + + +def marker_string(length: int) -> str: + """A string of exactly `length` characters with position markers.""" + text = f"N={length}|" + i = 0 + while len(text) < length - 3: + pos = len(text) + if pos % 50 == 0 and pos + 5 <= length - 3: + text += f"P{pos:04d}" + else: + text += LETTERS[i % len(LETTERS)] + i += 1 + text += "END" + assert len(text) == length, (length, len(text)) + return text + + +def main() -> None: + here = Path(__file__).resolve().parent + entries = [(f"len-{n:04d}", f"{n} characters", marker_string(n)) for n in LENGTHS] + entries.append(("special", "special characters", SPECIAL)) + for name, label, value in entries: + (here / f"{name}.txt").write_text(HEADER.format(summary=f"Settings spike test string: {label}.") + value + "\n") + cards = "\n".join( + f"<section><h2>{html.escape(label)}</h2>" + f'<textarea id="{name}" readonly rows="3">{html.escape(value)}</textarea>' + f"<button onclick=\"copyText('{name}')\">Copy</button></section>" + for name, label, value in entries + ) + page = f"""<!doctype html> +<!-- Author: cirakg & claude ai --> +<!-- Summary: Settings spike test strings with copy buttons; open on the phone, copy, paste into a settings field. --> +<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Settings spike test strings</title> +<style>body{{font-family:sans-serif;margin:16px}}textarea{{width:100%;font-family:monospace}} +button{{font-size:1.1em;padding:6px 16px;margin:4px 0 16px}}</style></head> +<body><h1>Settings spike test strings</h1> +<p>Tap Copy, then paste into one of the Text fields in the app settings. Expected lengths are in each heading.</p> +{cards} +<script> +function copyText(id) {{ + const area = document.getElementById(id); + area.select(); + const done = () => alert("Copied " + area.value.length + " characters"); + if (navigator.clipboard && window.isSecureContext) {{ + navigator.clipboard.writeText(area.value).then(done, () => {{ document.execCommand("copy"); done(); }}); + }} else {{ document.execCommand("copy"); done(); }} +}} +</script></body></html> +""" + (here / "index.html").write_text(page) + print(f"wrote {len(entries)} test strings and index.html into {here}") + + +if __name__ == "__main__": + main() diff --git a/spike/settings/test-strings/special.txt b/spike/settings/test-strings/special.txt new file mode 100644 index 0000000..38d0d79 --- /dev/null +++ b/spike/settings/test-strings/special.txt @@ -0,0 +1,5 @@ +# Author: cirakg & claude ai +# Summary: Settings spike test string: special characters. +# Copy ONLY the last line (the test string) into a settings field. + +special: & < > \ " { } % | non-ASCII: äöü ß € 😀 | otpauth://totp/Fake:alice?secret=JBSWY3DPEHPK3PXP&issuer=Fake&digits=6 | {"name":"Fake","path":"a\/b"} END |
