#!/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=|" says how long the string should be, a marker "P" 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. The phone refused to save special.txt (spike run 2026-10-21), so the c-*.txt strings split it into one character group each, and aegis-json.txt is a complete fake unencrypted Aegis export: together they show which characters block saving and whether an Aegis file can be pasted at all. 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' ) # Test C, split: (file name, label, string). Each ends with " END". SPLIT = ( ("c-1-percent", "C1: percent", "C1 % 100% END"), ("c-2-angle", "C2: angle brackets", "C2 < > END"), ("c-3-backslash", "C3: backslash", "C3 \\ a\\/b END"), ("c-4-quote", "C4: double quote", 'C4 " "x" END'), ("c-5-braces", "C5: braces", "C5 { } {x} END"), ("c-6-latin", "C6: accented letters and euro sign", "C6 äöü ß € END"), ("c-7-emoji", "C7: emoji", "C7 😀 END"), ("c-8-json", "C8: JSON fragment", 'C8 {"name":"Fake","n":1} END'), ) AEGIS_JSON = ( '{"version":1,"header":{"slots":null,"params":null},"db":{"version":3,"entries":[' '{"type":"totp","uuid":"00000000-0000-4000-8000-000000000001","name":"alice@example.com",' '"issuer":"TestOne","note":"","icon":null,"info":{"secret":"JBSWY3DPEHPK3PXP",' '"algo":"SHA1","digits":6,"period":30}}]}}' ) 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)) entries.extend(SPLIT) entries.append(("aegis-json", "fake unencrypted Aegis export", AEGIS_JSON)) 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"

{html.escape(label)}

" f'' f"
" for name, label, value in entries ) page = f""" Settings spike test strings

Settings spike test strings

Tap Copy, then paste into one of the Text fields in the app settings. Expected lengths are in each heading.

{cards} """ (here / "index.html").write_text(page) print(f"wrote {len(entries)} test strings and index.html into {here}") if __name__ == "__main__": main()