{
  "schemaVersion": 1,
  "generatedAt": "2026-07-29",
  "license": "CC0-1.0",
  "environment": {
    "testDate": "2026-07-29",
    "appCommit": "86b9bd1cf7a3c535ccb1e7fa8dd8b0335c783392+working-tree",
    "browser": "Google Chrome 150.0.7871.182",
    "operatingSystem": "Windows 11",
    "hardware": "Measurements are fixture-specific; no universal performance claim is made.",
    "libraryVersions": {
      "pdf-lib": "1.17.1",
      "pdfjs-dist": "5.5.207",
      "piexifjs": "1.0.6",
      "heic2any": "0.0.4",
      "tesseract.js": "7.0.0",
      "qrcode": "1.5.4",
      "papaparse": "5.5.3",
      "@noble/hashes": "2.0.1"
    }
  },
  "studies": [
    {
      "id": "pdf-toolbox-study",
      "toolId": "pdf-toolbox",
      "articleSlug": "merge-split-rotate-pdf",
      "fixtureDirectory": "pdf-toolbox",
      "method": "Merge A, B, and C in that order; split the five-page result into 1-2, 3, and 4-5; inspect page tokens, rotation, counts, and document properties.",
      "independentCheck": "Open outputs in a second PDF viewer and extract text with PDF.js.",
      "observations": [
        [
          "merged_page_count",
          "5",
          "5",
          "pass"
        ],
        [
          "page_order",
          "A1,A2,B1,C1,C2",
          "A1,A2,B1,C1,C2",
          "pass"
        ],
        [
          "split_page_counts",
          "2,1,2",
          "2,1,2",
          "pass"
        ]
      ],
      "limitations": "The study does not claim preservation of signatures, forms, bookmarks, attachments, or every metadata object.",
      "fixtures": [
        {
          "id": "pdf-toolbox-expected-results-json",
          "path": "/research/fixtures/pdf-toolbox/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 382,
          "sha256": "3e2a884652eea7e81a8d669b871a2cb966531c2befc4940ac3107584c52e39e7",
          "purpose": "Expected machine-readable result"
        },
        {
          "id": "pdf-toolbox-source-a-pdf",
          "path": "/research/fixtures/pdf-toolbox/source-a.pdf",
          "license": "CC0-1.0",
          "sizeBytes": 1476,
          "sha256": "392ab3a23621910bc38c33f1c8b35502f253736e2e66a4a6474dd7ea5a66c222",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "pdf-toolbox-source-b-pdf",
          "path": "/research/fixtures/pdf-toolbox/source-b.pdf",
          "license": "CC0-1.0",
          "sizeBytes": 1092,
          "sha256": "9e69475cb0936ea1c74d4389bebc211dcdd433612d35ce86ef7a430ed499d8dc",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "pdf-toolbox-source-c-pdf",
          "path": "/research/fixtures/pdf-toolbox/source-c.pdf",
          "license": "CC0-1.0",
          "sizeBytes": 1473,
          "sha256": "0e221f47c430d04d7fb48a602da34986fa127f923ad6c8bcaa1dea9e99837356",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/pdf-toolbox-result.png",
        "sizeBytes": 67001,
        "sha256": "5a859e6e724c3f549c442a1b895cf8546a88c169d012e08700902cd2ff7ef103"
      }
    },
    {
      "id": "pdf-redact-study",
      "toolId": "pdf-redact",
      "articleSlug": "redact-pdf-safely",
      "fixtureDirectory": "pdf-redact",
      "method": "Cover the SECRET token on page one, leave page two unmarked, export, then try text selection and extraction on both pages.",
      "independentCheck": "Inspect the exported page content in a second PDF viewer and with PDF.js text extraction.",
      "observations": [
        [
          "marked_page_text_objects",
          "0",
          "0",
          "pass"
        ],
        [
          "unmarked_control_token",
          "PUBLIC-CONTROL-7152",
          "PUBLIC-CONTROL-7152",
          "pass"
        ],
        [
          "marked_page_representation",
          "raster_image",
          "raster_image",
          "pass"
        ]
      ],
      "limitations": "Only marked pages are rebuilt as raster pages; document-level objects and unmarked pages require separate review.",
      "fixtures": [
        {
          "id": "pdf-redact-expected-results-json",
          "path": "/research/fixtures/pdf-redact/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 297,
          "sha256": "4a05830e93af0cd21066ab5aa659e9f3f50a274285efd26d36532ec3e69a2dfa",
          "purpose": "Expected machine-readable result"
        },
        {
          "id": "pdf-redact-redaction-sample-pdf",
          "path": "/research/fixtures/pdf-redact/redaction-sample.pdf",
          "license": "CC0-1.0",
          "sizeBytes": 1522,
          "sha256": "a117eb12bf69ad5d25df202fde20824d0f65b0e67c2a13365cee3a4e530bfb07",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/pdf-redaction-result.png",
        "sizeBytes": 52341,
        "sha256": "c3de7b3297aa2f276ae724a67f275c761201c451df275cb652a92bc8025186d6"
      }
    },
    {
      "id": "photo-metadata-study",
      "toolId": "photo-safe",
      "articleSlug": "remove-photo-metadata",
      "fixtureDirectory": "photo-safe",
      "method": "Inspect the synthetic JPEG, use Strip All EXIF, download the result, then reopen it in the tool and compare visible pixels.",
      "independentCheck": "Read the original and output with a second EXIF parser.",
      "observations": [
        [
          "source_exif_groups",
          "0th,Exif,GPS",
          "0th,Exif,GPS",
          "pass"
        ],
        [
          "output_exif_segment",
          "absent",
          "absent",
          "pass"
        ],
        [
          "visible_test_caption",
          "present",
          "present",
          "pass"
        ]
      ],
      "limitations": "The check covers supported JPEG EXIF structures, not every proprietary metadata container or visible identifier.",
      "fixtures": [
        {
          "id": "photo-safe-metadata-sample-jpg",
          "path": "/research/fixtures/photo-safe/metadata-sample.jpg",
          "license": "CC0-1.0",
          "sizeBytes": 33732,
          "sha256": "fc9d6587811cc9c161d37ff8178d21e896d51e98f4b97211039e3a7fa864653c",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/photo-metadata-result.png",
        "sizeBytes": 78992,
        "sha256": "d964a48ae1755916b39f78c6f65e0f92d7390ceb925aa00a633bd472951f17a8"
      }
    },
    {
      "id": "heic-conversion-study",
      "toolId": "heic-converter",
      "articleSlug": "convert-heic-in-browser",
      "fixtureDirectory": "heic-converter",
      "method": "Convert the 640 by 400 synthetic color chart once to JPEG at the tool default quality and once to PNG.",
      "independentCheck": "Open both outputs in a second image decoder and compare dimensions and sampled color blocks.",
      "observations": [
        [
          "source_dimensions",
          "640x400",
          "640x400",
          "pass"
        ],
        [
          "jpeg_dimensions",
          "640x400",
          "640x400",
          "pass"
        ],
        [
          "png_dimensions",
          "640x400",
          "640x400",
          "pass"
        ]
      ],
      "limitations": "A single-image fixture does not test bursts, depth maps, motion resources, every color profile, or every HEIC encoder.",
      "fixtures": [
        {
          "id": "heic-converter-color-chart-heic",
          "path": "/research/fixtures/heic-converter/color-chart.heic",
          "license": "CC0-1.0",
          "sizeBytes": 12744,
          "sha256": "7c3e14505e02f64860518a38ef0a1c70c2d896ea145384ff5f7ee4a8b9ea54f0",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/heic-converter-result.png",
        "sizeBytes": 66592,
        "sha256": "ac6233a48b8570875dcec3b965772f257cf817a67f3e685d9b1b397174e370ff"
      }
    },
    {
      "id": "ocr-accuracy-study",
      "toolId": "ocr-tool",
      "articleSlug": "extract-text-from-images",
      "fixtureDirectory": "ocr-tool",
      "method": "Run the four generated cards through the current English Tesseract worker and compare normalized output with ground truth.",
      "independentCheck": "Calculate character error rate from the downloaded ground-truth text.",
      "observations": [
        [
          "language_model",
          "eng",
          "eng",
          "pass"
        ],
        [
          "clear_32px_cer",
          "recorded_not_thresholded",
          "2.7%",
          "note"
        ],
        [
          "small_16px_cer",
          "recorded_not_thresholded",
          "1.3%",
          "note"
        ],
        [
          "low_contrast_cer",
          "recorded_not_thresholded",
          "0.0%",
          "note"
        ],
        [
          "rotated_noisy_cer",
          "recorded_not_thresholded",
          "0.0%",
          "note"
        ]
      ],
      "limitations": "The values describe these fixtures on the recorded browser; they are not an accuracy guarantee for other images or languages.",
      "fixtures": [
        {
          "id": "ocr-tool-clear-32px-png",
          "path": "/research/fixtures/ocr-tool/clear-32px.png",
          "license": "CC0-1.0",
          "sizeBytes": 12590,
          "sha256": "6e98fb316e8fbd33a6145514a2f72efa4266926bb8780c352e1973785a98185c",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "ocr-tool-expected-results-json",
          "path": "/research/fixtures/ocr-tool/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 274,
          "sha256": "a07edf2a4d83a276f85a33a0597708f0b4c4b53485b84600ea738e638b74f178",
          "purpose": "Expected machine-readable result"
        },
        {
          "id": "ocr-tool-ground-truth-txt",
          "path": "/research/fixtures/ocr-tool/ground-truth.txt",
          "license": "CC0-1.0",
          "sizeBytes": 77,
          "sha256": "b4be7e476d6f3bc20920dbb22951189935ca4504198b246597db707f8cb7f2a1",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "ocr-tool-low-contrast-png",
          "path": "/research/fixtures/ocr-tool/low-contrast.png",
          "license": "CC0-1.0",
          "sizeBytes": 10132,
          "sha256": "8130a0e3de635e72222cff570294d8d4d36f36d55acfd582301b4e3799e524e0",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "ocr-tool-rotated-noisy-png",
          "path": "/research/fixtures/ocr-tool/rotated-noisy.png",
          "license": "CC0-1.0",
          "sizeBytes": 40577,
          "sha256": "9eafdbb4cbd3fccccb55143b6f1d99431ca2e13a313716e1a5f74b782fe1c422",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "ocr-tool-small-16px-png",
          "path": "/research/fixtures/ocr-tool/small-16px.png",
          "license": "CC0-1.0",
          "sizeBytes": 6609,
          "sha256": "1c99efffc3c7b1e64b295697668f9cbe5d4fae67880fc816874e66ae16a75a34",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/ocr-result.png",
        "sizeBytes": 70220,
        "sha256": "931e9f262f076c76dbfc1f467789bd424bc27400c924ba7f1dba0bb42df84e59"
      }
    },
    {
      "id": "qr-reliability-study",
      "toolId": "qr-generator",
      "articleSlug": "create-reliable-qr-codes",
      "fixtureDirectory": "qr-generator",
      "method": "Generate all three payloads with the production 400-pixel and two-module-margin settings, then decode the clean PNGs and resized copies.",
      "independentCheck": "Decode with an independent QR reader rather than the generation library.",
      "observations": [
        [
          "clean_payloads_decoded",
          "3/3",
          "3/3",
          "pass"
        ],
        [
          "width_pixels",
          "400",
          "400",
          "pass"
        ],
        [
          "margin_modules",
          "2",
          "2",
          "pass"
        ]
      ],
      "limitations": "Digital decoding does not represent every phone camera, print process, glare condition, or damaged surface.",
      "fixtures": [
        {
          "id": "qr-generator-dense-text-png",
          "path": "/research/fixtures/qr-generator/dense-text.png",
          "license": "CC0-1.0",
          "sizeBytes": 12196,
          "sha256": "0f88081170b72ef954d0c83dcf79b04303de8cc209f4d01282f9167c5629cf3c",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "qr-generator-expected-results-json",
          "path": "/research/fixtures/qr-generator/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 1475,
          "sha256": "01bafb4bb98b8c18b64c3f8a560bbd63f9a51a06ce4142b1c693f52e1e5b1c2b",
          "purpose": "Expected machine-readable result"
        },
        {
          "id": "qr-generator-payloads-json",
          "path": "/research/fixtures/qr-generator/payloads.json",
          "license": "CC0-1.0",
          "sizeBytes": 1202,
          "sha256": "096ae67349ef6cd1d7b8dd52b827136b574d8a1fe9139a6b5e26868960e45294",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "qr-generator-short-text-png",
          "path": "/research/fixtures/qr-generator/short-text.png",
          "license": "CC0-1.0",
          "sizeBytes": 1926,
          "sha256": "3438c3df48f7f7243d42d9012890d0fb640be1ceecf455c731c5f6178199b7c0",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "qr-generator-url-png",
          "path": "/research/fixtures/qr-generator/url.png",
          "license": "CC0-1.0",
          "sizeBytes": 2852,
          "sha256": "01ad298807f2cdecd98a7a3ec1c517725e6eaedcaf12d77d17cb27c12614a674",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/qr-generator-result.png",
        "sizeBytes": 78158,
        "sha256": "338236e83b70bda36cf238eacdc9d64568f8a92ab20948de562872e8de698642"
      }
    },
    {
      "id": "json-scrub-study",
      "toolId": "json-formatter",
      "articleSlug": "format-and-scrub-json",
      "fixtureDirectory": "json-formatter",
      "method": "Run the nested fixture with every default pattern and key masking enabled, then compare each output path with the expectation file.",
      "independentCheck": "Search the formatted output for every original sentinel value and review unmatched free text manually.",
      "observations": [
        [
          "key_masking_targets",
          "2",
          "2",
          "pass"
        ],
        [
          "pattern_target_groups",
          "6",
          "6",
          "pass"
        ],
        [
          "documented_false_negative",
          "1",
          "1",
          "pass"
        ],
        [
          "documented_false_positive_risk",
          "1",
          "1",
          "pass"
        ]
      ],
      "limitations": "Regex matching cannot understand business meaning, validate every identifier, or find secrets hidden in unknown encodings.",
      "fixtures": [
        {
          "id": "json-formatter-expected-results-json",
          "path": "/research/fixtures/json-formatter/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 390,
          "sha256": "95e12bdde0948acc5e84c67ed462c432e9ea95c4c82963278a3f1e9ac49c3af8",
          "purpose": "Expected machine-readable result"
        },
        {
          "id": "json-formatter-input-json",
          "path": "/research/fixtures/json-formatter/input.json",
          "license": "CC0-1.0",
          "sizeBytes": 473,
          "sha256": "eb14ab83e2401ffa2404bed6ead0a76b0332dd15d290ad69b938021a04d42255",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/json-scrub-result.png",
        "sizeBytes": 88672,
        "sha256": "a65c8351683ca9122b682392ffdc9bb0c4a25ef170f5af02baa348a3dc0df92d"
      }
    },
    {
      "id": "csv-streaming-study",
      "toolId": "csv-viewer",
      "articleSlug": "inspect-large-csv-files",
      "fixtureDirectory": "csv-viewer",
      "method": "Open the edge-case CSV, verify headers and records, jump between indexed ranges, edit a copy, and compare the export.",
      "independentCheck": "Parse the source and export with Papa Parse in a standalone verification script.",
      "observations": [
        [
          "data_rows_including_blank",
          "4",
          "4",
          "pass"
        ],
        [
          "unique_headers",
          "id,name,name (2),notes,city",
          "id,name,name (2),notes,city",
          "pass"
        ],
        [
          "embedded_newline",
          "preserved",
          "preserved",
          "pass"
        ],
        [
          "utf8_values",
          "preserved",
          "preserved",
          "pass"
        ]
      ],
      "limitations": "Large-file speed depends on hardware, browser memory, row width, quoting, storage, and other active tabs.",
      "fixtures": [
        {
          "id": "csv-viewer-edge-cases-csv",
          "path": "/research/fixtures/csv-viewer/edge-cases.csv",
          "license": "CC0-1.0",
          "sizeBytes": 141,
          "sha256": "a132bbf75afe38e0e2daf7c132b9ee2309a1d8355efc25c575609071b3fa57b9",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "csv-viewer-expected-results-json",
          "path": "/research/fixtures/csv-viewer/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 298,
          "sha256": "329b2c6ea201a4d400d68f83790fffcb2506b42693f01087c40657344482fe05",
          "purpose": "Expected machine-readable result"
        },
        {
          "id": "csv-viewer-generate-large-csv-mjs",
          "path": "/research/fixtures/csv-viewer/generate-large-csv.mjs",
          "license": "CC0-1.0",
          "sizeBytes": 343,
          "sha256": "8ed6668a9f0dd6a337a0f5690052e2adce34cd2e0aa47ddf3c996faa51316d57",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/csv-viewer-result.png",
        "sizeBytes": 78075,
        "sha256": "0cd4cc6c77fe5591801ff91accc930da0072ea21b84b827deedb6c17338d51ce"
      }
    },
    {
      "id": "checksum-study",
      "toolId": "hash-tool",
      "articleSlug": "verify-file-checksums",
      "fixtureDirectory": "hash-tool",
      "method": "Hash the UTF-8 fixture with all five production algorithms and compare every hexadecimal result byte-for-byte.",
      "independentCheck": "Compare cryptographic digests with Node.js crypto and CRC-32 with crc-32.",
      "observations": [
        [
          "crc32",
          "expected_results_json",
          "exact_match",
          "pass"
        ],
        [
          "md5",
          "expected_results_json",
          "exact_match",
          "pass"
        ],
        [
          "sha1",
          "expected_results_json",
          "exact_match",
          "pass"
        ],
        [
          "sha256",
          "expected_results_json",
          "exact_match",
          "pass"
        ],
        [
          "sha512",
          "expected_results_json",
          "exact_match",
          "pass"
        ]
      ],
      "limitations": "A matching checksum verifies byte equality against the chosen reference; it does not establish trustworthiness or authenticity.",
      "fixtures": [
        {
          "id": "hash-tool-checksum-vector-txt",
          "path": "/research/fixtures/hash-tool/checksum-vector.txt",
          "license": "CC0-1.0",
          "sizeBytes": 60,
          "sha256": "2af43999792cb485bf4bf7b98620723b8ca3dbe9d092e9a6f154440841de860c",
          "purpose": "Synthetic reproducible input"
        },
        {
          "id": "hash-tool-expected-results-json",
          "path": "/research/fixtures/hash-tool/expected-results.json",
          "license": "CC0-1.0",
          "sizeBytes": 364,
          "sha256": "dad83ddbc1453127840d2efd451fbc5029e20b7a161903506f9ff922c27b4d1c",
          "purpose": "Expected machine-readable result"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/hash-result.png",
        "sizeBytes": 80140,
        "sha256": "a8c54028a706c339385983b172dbf642ef5d398981430b26e4157960d2e7d559"
      }
    },
    {
      "id": "base-encoding-study",
      "toolId": "base-converter",
      "articleSlug": "understand-base64-and-hex",
      "fixtureDirectory": "base-converter",
      "method": "Encode four Unicode strings to Base64, Base64URL, and hexadecimal, then reverse every value through the production converter.",
      "independentCheck": "Compare the UTF-8 bytes and encodings with Node.js Buffer.",
      "observations": [
        [
          "unicode_vectors",
          "4",
          "4",
          "pass"
        ],
        [
          "base64_round_trips",
          "4/4",
          "4/4",
          "pass"
        ],
        [
          "base64url_round_trips",
          "4/4",
          "4/4",
          "pass"
        ],
        [
          "hex_round_trips",
          "4/4",
          "4/4",
          "pass"
        ]
      ],
      "limitations": "The interface converts text through UTF-8; it is not a general binary-file encoder and encoding does not encrypt data.",
      "fixtures": [
        {
          "id": "base-converter-vectors-json",
          "path": "/research/fixtures/base-converter/vectors.json",
          "license": "CC0-1.0",
          "sizeBytes": 549,
          "sha256": "0616d04879538f2b4a014c87fc8d62f878abdaa6b10caaa2c1d7e7d852de26ea",
          "purpose": "Synthetic reproducible input"
        }
      ],
      "screenshot": {
        "path": "/research/screenshots/base-encoder-result.png",
        "sizeBytes": 52057,
        "sha256": "28e583f515b7c95a8749a10d25e1bf1f0d18a8ebaa493ce160f550a1e10c019e"
      }
    }
  ]
}
