diff options
37 files changed, 861 insertions, 5 deletions
@@ -69,7 +69,7 @@ DOCS_RUN = $(DOCKER) run --rm --init --label project=$(PROJECT) \ --read-only --tmpfs /tmp:rw,exec,nosuid,size=1g --shm-size 512m \ -v "$(ROOT)":/work -w /work --network none $(DOCS_IMAGE) -.PHONY: help image docs-image sdk key build test test-tools sim screenshots screenshots-approve perf memtest icons \ +.PHONY: help image docs-image sdk key build test test-tools sim screenshots screenshots-approve perf memtest icons vectors spike spike-sim \ lint package docs ci shell clean nuke lock products \ check-docker dirs need-image need-docs-image @@ -130,6 +130,15 @@ package: dirs need-image ## Signed .iq for the Store beta + per-device release . products: dirs need-image ## Check manifest products against the downloaded device files $(DEV_OFFLINE) python3 tools/gen_products.py --check +vectors: dirs need-image ## Regenerate test/data/Vectors.mc (cross-checked RFC/NIST test vectors) + $(DEV_OFFLINE) python3 tools/gen_vectors.py + +spike: dirs need-image ## Build the M1 settings spike (debug .prg + beta .iq) into build/spike + $(DEV_OFFLINE) build-spike.sh build + +spike-sim: dirs need-image ## Run the settings spike headless on DEVICE and screenshot it + $(DEV_OFFLINE) build-spike.sh sim $(DEVICE) + icons: dirs need-image ## Regenerate the launcher icons for every product (after changing manifest.xml) $(DEV_OFFLINE) sh -c 'python3 tools/make_icon.py --all --devices /home/dev/.Garmin/ConnectIQ/Devices && \ python3 tools/make_icon.py --size 60 --out resources/drawables/launcher_icon.png' diff --git a/docker/scripts/build-spike.sh b/docker/scripts/build-spike.sh new file mode 100755 index 0000000..32a5b89 --- /dev/null +++ b/docker/scripts/build-spike.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Author: cirakg & claude ai +# Summary: Build the M1 settings spike (debug .prg files and the signed beta .iq) and optionally run it in the simulator. +# +# Usage: build-spike.sh build debug builds for every spike product + build/spike/settings-spike.iq +# build-spike.sh sim [DEVICE] debug build, run it headless, screenshot (default DEVICE fr70) +# Output: build/spike/ only (debug/<DEVICE>.prg, settings-spike.iq, logs, screenshots). +# +# Same policy as the app: strict type checking (-l 3), any compiler warning +# fails. The .iq is the file to upload as a private beta (docs/SETTINGS_SPIKE.md). +set -euo pipefail +# shellcheck source=ciq-env.sh +source "$(dirname "${BASH_SOURCE[0]}")/ciq-env.sh" + +readonly SPIKE=/work/spike/settings +readonly JUNGLES="${SPIKE}/monkey.jungle;${SPIKE}/icons.jungle" +readonly OUT="${BUILD_DIR}/spike" +readonly DEVICES=(fr70 fr255s venusq2 fr965) +# Longest wait for the debug self-test's "SPIKE ready" line. +readonly READY_TIMEOUT_S=60 + +# Run monkeyc, keep its log, fail on errors and on warnings. +compile() { + local log_file="$1" + shift + if ! monkeyc "$@" -f "${JUNGLES}" -y "${DEV_KEY}" -l 3 -w >"${log_file}" 2>&1; then + cat "${log_file}" >&2 + die "spike compile failed (${log_file#/work/})" + fi + if grep -q "^WARNING" "${log_file}"; then + grep "^WARNING" "${log_file}" >&2 + die "compiler warnings in the spike build; warnings count as errors" + fi +} + +build_debug() { + local device="$1" + ciq_require_device "${device}" + mkdir -p "${OUT}/debug" + compile "${OUT}/debug/${device}.log" -d "${device}" -o "${OUT}/debug/${device}.prg" + log "built build/spike/debug/${device}.prg" +} + +build_all() { + local device + for device in "${DEVICES[@]}"; do + build_debug "${device}" + done + compile "${OUT}/settings-spike.log" -e -r -o "${OUT}/settings-spike.iq" + log "built build/spike/settings-spike.iq (upload this as the beta)" +} + +run_in_simulator() { + local device="$1" app_log pid _ + app_log="${OUT}/sim-${device}.log" + build_debug "${device}" + ciq_start_display + ciq_start_simulator + monkeydo "${OUT}/debug/${device}.prg" "${device}" >"${app_log}" 2>&1 & + pid=$! + for _ in $(seq 1 $((READY_TIMEOUT_S * 10))); do + grep -q "SPIKE ready" "${app_log}" && break + kill -0 "${pid}" 2>/dev/null || break + sleep 0.1 + done + grep -q "SPIKE ready" "${app_log}" || { cat "${app_log}" >&2; die "the spike did not report ready on ${device}"; } + sleep 1 + mkdir -p "${OUT}/raw" + import -window "$(ciq_simulator_window)" "${OUT}/raw/${device}-window.png" + kill "${pid}" 2>/dev/null || true + python3 /work/tools/sim_screenshot.py --raw "${OUT}/raw/${device}-window.png" \ + --device-dir "${CIQ_HOME}/Devices/${device}" --out "${OUT}/screen-${device}.png" + grep "SPIKE ready" "${app_log}" >&2 + log "screen saved to build/spike/screen-${device}.png" +} + +ciq_require_sdk +ciq_require_key +mkdir -p "${OUT}" +case "${1:-build}" in + build) build_all ;; + sim) run_in_simulator "${2:-fr70}" ;; + *) die "usage: build-spike.sh build | sim [DEVICE]" ;; +esac diff --git a/docker/scripts/lint.sh b/docker/scripts/lint.sh index 09b8410..998b2b9 100755 --- a/docker/scripts/lint.sh +++ b/docker/scripts/lint.sh @@ -19,8 +19,8 @@ log "project policy" python3 /work/tools/lint_project.py /work log "Python tools: ruff and mypy" -ruff check /work/tools -ruff format --check /work/tools +ruff check /work/tools /work/spike +ruff format --check /work/tools /work/spike (cd /work && mypy) log "product list" diff --git a/docs/SETTINGS_SPIKE.md b/docs/SETTINGS_SPIKE.md new file mode 100644 index 0000000..340670a --- /dev/null +++ b/docs/SETTINGS_SPIKE.md @@ -0,0 +1,92 @@ +<!-- + Author: cirakg & claude ai + Summary: Milestone M1: how to run the settings spike on your phone and Forerunner 70, and where to record the results. +--> +# Settings spike (M1) + +Garmin doesn't document how long a text setting may be, which characters the Garmin Connect app accepts, or whether values written by the watch show up on the phone. The inbox and import design (README sections 10 and 11) depends on those answers, so this milestone measures them with a small throwaway app, **Settings Spike**, on your Android phone and Forerunner 70. + +It takes about 30 minutes. Everything uses fake test strings; never paste a real secret here. + +## What the spike app does + +- Its settings have eight text fields with no length limit, one password field, a read-only status field, a switch, a list, a number and a long number. +- On the watch it shows, for each text field: the length in characters and bytes, the first 12 and last 8 characters, and which of `& < > \ " { } %` and non-ASCII characters arrived. +- Each time you save settings while the app is open, it counts the change, writes `seen N chars at HH:MM:SS, change #K` into the read-only status field, and clears the password field. That tests whether the phone shows values the watch wrote. +- Scroll with UP/DOWN or by swiping; START or a tap jumps back to the top. + +The test strings make cuts obvious: each starts with `N=<length>|`, has a marker `P<position>` every 50 characters, and ends with `END`. If the watch shows a different length or no `END`, the phone app cut the string; the last marker visible tells roughly where. + +## Step 1: build the package (on your computer) + +```sh +make spike +``` + +The file to upload is `build/spike/settings-spike.iq`. The spike has its own app ID, separate from OTP Wallet, so it can be deleted afterwards without affecting the real app. + +## Step 2: a Garmin developer account and the beta upload + +1. Open https://developer.garmin.com/connect-iq/ in a browser and sign in with your normal Garmin account. Accept the Connect IQ developer agreement if asked (the same agreement `make sdk` showed you). +2. Go to the developer dashboard (the "Upload an app" / "My apps" area of the Connect IQ developer site). +3. Upload `build/spike/settings-spike.iq` as a **new app** and tick **Beta App**. Fill in what the form requires (name "Settings Spike", any short description, any category). A beta app is visible only to your account. +4. Wait until the upload shows as available (usually minutes). + +Garmin changes these pages from time to time; if a label differs, look for "beta" in the upload form. + +## Step 3: install it on the Forerunner 70 + +1. On the phone, open the Connect IQ Store app (or Connect IQ inside Garmin Connect) while signed in to the same account. +2. Find **Settings Spike** among your apps (or open the beta's store link from the developer dashboard on the phone) and install it on the Forerunner 70. +3. On the watch, start **Settings Spike** from the apps list. It should show "Settings spike", "Changes seen: 0" and eight empty text fields. + +## Step 4: get the test strings onto the phone + +Copy `spike/settings/test-strings/index.html` to the phone (for example email it to yourself, or copy it over USB) and open it in Chrome. Each string has a **Copy** button that tells you how many characters it copied. The same strings are also in `spike/settings/test-strings/*.txt` (copy only the last line of a file). + +## Step 5: the tests + +Keep the spike app open on the watch for tests A to H, except where a test says otherwise. Open the settings on the phone through Garmin Connect: the Forerunner 70, then Connect IQ apps (or Activities & Apps), then Settings Spike, then Settings. Save after each change and read the watch. + +| Test | What to do | What to write down | +| --- | --- | --- | +| A. One long field | Paste the 100-character string into Text 1, save. Repeat with 255, 256, 500, 800, 801, 1000, 2000, 4000 and 8000 (always Text 1). | The longest length that arrives complete (length matches and ends with `END`). For the first one that fails: does the phone refuse to save, show an error, or cut it silently (then the length the watch shows)? | +| B. Total size | Put the longest string that worked in A into Text 1 to Text 8, save. If that fails, empty fields until it saves. | The largest total ("Total: N chars" on the watch) that saves, and what happens beyond it | +| C. Characters | Paste `special` into Text 2, save. | The "has:" line on the watch, and anything missing compared with the string (especially `&` and `\`) | +| D. Write-back | After any save, close the settings on the phone and open them again. | Does "Status written by the watch" show `seen ... change #N`? Is the password field empty? | +| E. Live changes | With the app open, change one field and save. Then close the app on the watch, change another field, save, and start the app again. | Does "Changes seen" increase while the app is open? After the restart, does the watch show the new value? | +| F. Password field | Type `fake1234` into the password field, save. | Does the watch show "got 8 chars"? When you reopen the settings, is the field masked, empty, or showing the text? | +| G. Timing | Use a stopwatch from tapping Save until the watch screen changes. | Seconds for a short string and for the longest that worked | +| H. Other types | Toggle the switch, pick option C, enter 0 and then 301 as the number, enter 9999999999 as the long number. | Values shown on the watch, and which inputs the phone rejects (and its message) | + +## Results + +Fill this in (or tell me the values and I'll record them). Date, phone model, Garmin Connect app version (Settings, About) and the watch firmware help explain later differences. + +| Item | Result | +| --- | --- | +| Date, phone, Garmin Connect version, watch firmware | | +| A. Longest single field that arrives complete | | +| A. What happens above that (refused / error / cut to N) | | +| B. Largest total across the eight fields | | +| C. Characters that didn't arrive or blocked saving | | +| D. Status written by the watch visible on the phone (yes/no) | | +| D. Password field empty after the watch cleared it (yes/no) | | +| E. Changes arrive while the app is open (yes/no) | | +| E. Changes arrive while the app is closed, seen at next start (yes/no) | | +| F. Password field: arrives on the watch, masked on the phone (yes/no each) | | +| G. Time from Save to the watch updating (short / longest) | | +| H. Phone-side validation of the number fields (message) | | +| Other observations | | + +## How the results feed the design + +- **A and B** set the number and size of the import fields (`impText1` to `impTextN`, README 10.2) and the multi-part import buffer (README 11.2). For example, if one field holds 4000 characters but the total stops at 8000, import uses two or three fields per sync and multi-part import for larger Aegis files. +- **C** decides whether `otpauth://` URIs can be pasted as they are on your phone and whether the parser needs the `;` fallback (README 11.2). +- **D** decides whether status messages and cleared secret fields are visible on the phone. The design works either way (forms are idempotent and results also show on the watch, README 10.3); this tells us which one you'll experience. +- **E and G** decide whether processing on `onSettingsChanged` is enough or whether the app must also process forms at every start (it will do both; this confirms it). +- **F** confirms what the password field protects (on-screen masking only). + +## Cleaning up + +When the results are recorded: uninstall Settings Spike from the watch (Connect IQ Store app or the watch's app settings), and delete the beta from the developer dashboard if you like. The spike's code stays in `spike/settings/` for reference. diff --git a/pyproject.toml b/pyproject.toml index ba959eb..1647937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ filterwarnings = ["error"] line-length = 120 target-version = "py310" cache-dir = "build/.ruff_cache" -include = ["tools/**/*.py"] +include = ["tools/**/*.py", "spike/**/*.py"] [tool.ruff.lint] select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF", "PL"] @@ -28,5 +28,5 @@ ignore = [ [tool.mypy] python_version = "3.10" strict = true -files = ["tools"] +files = ["tools", "spike/settings/test-strings"] cache_dir = "build/.mypy_cache" 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 |
