[INFO] fetching crate diffly 0.2.0...
[INFO] testing diffly-0.2.0 against 1.99.0-beta.8+cargoflags=--release for beta-release-1.100-2
[INFO] extracting crate diffly 0.2.0 into /workspace/builds/worker-2-tc1/source
[INFO] started tweaking crates.io crate diffly 0.2.0
[INFO] removed 0 missing examples
[INFO] finished tweaking crates.io crate diffly 0.2.0
[INFO] tweaked toml for crates.io crate diffly 0.2.0 written to /workspace/builds/worker-2-tc1/source/Cargo.toml
[INFO] validating manifest of crates.io crate diffly 0.2.0 on toolchain 1.99.0-beta.8
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+1.99.0-beta.8" "metadata" "--manifest-path" "Cargo.toml" "--no-deps", kill_on_drop: false }`
[INFO] crate crates.io crate diffly 0.2.0 already has a lockfile, it will not be regenerated
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+1.99.0-beta.8" "fetch" "--manifest-path" "Cargo.toml", kill_on_drop: false }`
[INFO] [stderr]     Updating crates.io index
[INFO] [stderr]  Downloading crates ...
[INFO] [stderr]   Downloaded unicode-ident v1.0.23
[INFO] [stderr]   Downloaded spin v0.9.8
[INFO] [stderr]   Downloaded vte_generate_state_changes v0.1.2
[INFO] [stderr]   Downloaded tabled_derive v0.9.0
[INFO] [stderr]   Downloaded libredox v0.1.12
[INFO] [stderr]   Downloaded ansitok v0.2.0
[INFO] [stderr]   Downloaded vte v0.10.1
[INFO] [stderr]   Downloaded ansi-str v0.8.0
[INFO] [stderr]   Downloaded redox_syscall v0.7.1
[INFO] [stderr]   Downloaded tempfile v3.25.0
[INFO] [stderr]   Downloaded tokio-stream v0.1.18
[INFO] [stderr]   Downloaded crossbeam-queue v0.3.12
[INFO] [stderr]   Downloaded zmij v1.0.20
[INFO] [stderr]   Downloaded tinyvec v1.10.0
[INFO] [stderr]   Downloaded papergrid v0.13.0
[INFO] [stderr]   Downloaded rustls-webpki v0.103.9
[INFO] [stderr]   Downloaded cc v1.2.55
[INFO] [stderr]   Downloaded zerocopy-derive v0.8.39
[INFO] [stderr]   Downloaded tracing-subscriber v0.3.22
[INFO] [stderr]   Downloaded webpki-roots v1.0.6
[INFO] [stderr]   Downloaded tabled v0.17.0
[INFO] [stderr]   Downloaded zerocopy v0.8.39
[INFO] [stderr]   Downloaded regex-syntax v0.8.9
[INFO] [stderr]   Downloaded rustls v0.23.36
[INFO] [stderr]   Downloaded libc v0.2.181
[INFO] [stderr]   Downloaded sailfish v0.10.1
[INFO] [stderr]   Downloaded sailfish-compiler v0.10.1
[INFO] [stderr]   Downloaded winnow v0.7.14
[INFO] [stderr]   Downloaded sailfish-macros v0.10.1
[INFO] [stderr]   Downloaded dirs-sys v0.4.1
[INFO] [stderr]   Downloaded filetime v0.2.27
[INFO] [stderr]   Downloaded redox_users v0.4.6
[INFO] [stderr]   Downloaded itoap v1.0.1
[INFO] [stderr]   Downloaded dirs v5.0.1
[INFO] [stderr]   Downloaded config v0.15.19
[INFO] [stderr]   Downloaded toml v0.9.12+spec-1.1.0
[INFO] [stderr]   Downloaded toml_parser v1.0.7+spec-1.1.0
[INFO] [stderr]   Downloaded toml_datetime v0.7.5+spec-1.1.0
[INFO] [stderr]   Downloaded serde_spanned v1.0.4
[INFO] [stderr]   Downloaded unicode-segmentation v1.12.0
[INFO] [stderr]   Downloaded colored v2.2.0
[INFO] [stderr]   Downloaded clap_derive v4.5.55
[INFO] [stderr]   Downloaded anstyle v1.0.13
[INFO] [stderr]   Downloaded clap v4.5.58
[INFO] [stderr]   Downloaded anstream v0.6.21
[INFO] [stderr]   Downloaded clap_lex v1.0.0
[INFO] [stderr]   Downloaded clap_builder v4.5.58
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-2-tc1/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-2-tc1/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-m" "1610612736" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:77db811e55d90add9212f6832c23229e3de5f1c1f9905e8cb07319ea15df73ac" "sleep" "infinity", kill_on_drop: false }`
[INFO] [stdout] cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325
[INFO] running `Command { std: "docker" "start" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-w" "/opt/rustwide/workdir" "--user" "0:0" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325" "/opt/rustwide/cargo-home/bin/cargo" "+1.99.0-beta.8" "metadata" "--no-deps" "--format-version=1", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325" "/opt/rustwide/cargo-home/bin/cargo" "+1.99.0-beta.8" "build" "--frozen" "--message-format=json" "--release", kill_on_drop: false }`
[INFO] [stderr]    Compiling proc-macro2 v1.0.106
[INFO] [stderr]    Compiling quote v1.0.44
[INFO] [stderr]    Compiling unicode-ident v1.0.23
[INFO] [stderr]    Compiling libc v0.2.181
[INFO] [stderr]    Compiling serde_core v1.0.228
[INFO] [stderr]    Compiling smallvec v1.15.1
[INFO] [stderr]    Compiling litemap v0.8.1
[INFO] [stderr]    Compiling typenum v1.19.0
[INFO] [stderr]    Compiling writeable v0.6.2
[INFO] [stderr]    Compiling generic-array v0.14.7
[INFO] [stderr]    Compiling cc v1.2.55
[INFO] [stderr]    Compiling serde v1.0.228
[INFO] [stderr]    Compiling pin-project-lite v0.2.16
[INFO] [stderr]    Compiling crossbeam-utils v0.8.21
[INFO] [stderr]    Compiling zeroize v1.8.2
[INFO] [stderr]    Compiling equivalent v1.0.2
[INFO] [stderr]    Compiling hashbrown v0.16.1
[INFO] [stderr]    Compiling autocfg v1.5.0
[INFO] [stderr]    Compiling rustls-pki-types v1.14.0
[INFO] [stderr]    Compiling toml_write v0.1.2
[INFO] [stderr]    Compiling zmij v1.0.20
[INFO] [stderr]    Compiling getrandom v0.3.4
[INFO] [stderr]    Compiling winnow v0.7.14
[INFO] [stderr]    Compiling once_cell v1.21.3
[INFO] [stderr]    Compiling futures-core v0.3.31
[INFO] [stderr]    Compiling bytes v1.11.1
[INFO] [stderr]    Compiling memchr v2.8.0
[INFO] [stderr]    Compiling rustls v0.23.36
[INFO] [stderr]    Compiling foldhash v0.1.5
[INFO] [stderr]    Compiling cfg-if v1.0.4
[INFO] [stderr]    Compiling num-traits v0.2.19
[INFO] [stderr]    Compiling sailfish-compiler v0.10.1
[INFO] [stderr]    Compiling serde_json v1.0.149
[INFO] [stderr]    Compiling hashbrown v0.15.5
[INFO] [stderr]    Compiling webpki-roots v1.0.6
[INFO] [stderr]    Compiling tracing-core v0.1.36
[INFO] [stderr]    Compiling indexmap v2.13.0
[INFO] [stderr]    Compiling ring v0.17.14
[INFO] [stderr]    Compiling concurrent-queue v2.5.0
[INFO] [stderr]    Compiling syn v2.0.114
[INFO] [stderr]    Compiling filetime v0.2.27
[INFO] [stderr]    Compiling itoa v1.0.17
[INFO] [stderr]    Compiling crc-catalog v2.4.0
[INFO] [stderr]    Compiling cpufeatures v0.2.17
[INFO] [stderr]    Compiling pin-utils v0.1.0
[INFO] [stderr]    Compiling futures-sink v0.3.31
[INFO] [stderr]    Compiling futures-io v0.3.31
[INFO] [stderr]    Compiling futures-task v0.3.31
[INFO] [stderr]    Compiling log v0.4.29
[INFO] [stderr]    Compiling crc v3.4.0
[INFO] [stderr]    Compiling event-listener v5.4.1
[INFO] [stderr]    Compiling parking_lot_core v0.9.12
[INFO] [stderr]    Compiling getrandom v0.2.17
[INFO] [stderr]    Compiling errno v0.3.14
[INFO] [stderr]    Compiling mio v1.1.1
[INFO] [stderr]    Compiling socket2 v0.6.2
[INFO] [stderr]    Compiling futures-util v0.3.31
[INFO] [stderr]    Compiling block-buffer v0.10.4
[INFO] [stderr]    Compiling crypto-common v0.1.7
[INFO] [stderr]    Compiling parking_lot v0.12.5
[INFO] [stderr]    Compiling signal-hook-registry v1.4.8
[INFO] [stderr]    Compiling hashlink v0.10.0
[INFO] [stderr]    Compiling digest v0.10.7
[INFO] [stderr]    Compiling futures-intrusive v0.5.0
[INFO] [stderr]    Compiling sha2 v0.10.9
[INFO] [stderr]    Compiling crossbeam-queue v0.3.12
[INFO] [stderr]    Compiling webpki-roots v0.26.11
[INFO] [stderr]    Compiling sailfish v0.10.1
[INFO] [stderr]    Compiling option-ext v0.2.0
[INFO] [stderr]    Compiling unicode-segmentation v1.12.0
[INFO] [stderr]    Compiling anyhow v1.0.101
[INFO] [stderr]    Compiling base64 v0.22.1
[INFO] [stderr]    Compiling dirs-sys v0.4.1
[INFO] [stderr]    Compiling pathdiff v0.2.3
[INFO] [stderr]    Compiling itoap v1.0.1
[INFO] [stderr]    Compiling dirs v5.0.1
[INFO] [stderr]    Compiling convert_case v0.6.0
[INFO] [stderr]    Compiling toml_parser v1.0.7+spec-1.1.0
[INFO] [stderr]    Compiling serde_spanned v1.0.4
[INFO] [stderr]    Compiling uuid v1.20.0
[INFO] [stderr]    Compiling toml_datetime v0.7.5+spec-1.1.0
[INFO] [stderr]    Compiling toml v0.9.12+spec-1.1.0
[INFO] [stderr]    Compiling config v0.15.19
[INFO] [stderr]    Compiling synstructure v0.13.2
[INFO] [stderr]    Compiling zerofrom-derive v0.1.6
[INFO] [stderr]    Compiling yoke-derive v0.8.1
[INFO] [stderr]    Compiling zerovec-derive v0.11.2
[INFO] [stderr]    Compiling serde_derive v1.0.228
[INFO] [stderr]    Compiling displaydoc v0.2.5
[INFO] [stderr]    Compiling tokio-macros v2.6.0
[INFO] [stderr]    Compiling tracing-attributes v0.1.31
[INFO] [stderr]    Compiling thiserror-impl v2.0.18
[INFO] [stderr]    Compiling async-trait v0.1.89
[INFO] [stderr]    Compiling tokio v1.49.0
[INFO] [stderr]    Compiling zerofrom v0.1.6
[INFO] [stderr]    Compiling tracing v0.1.44
[INFO] [stderr]    Compiling yoke v0.8.1
[INFO] [stderr]    Compiling zerovec v0.11.5
[INFO] [stderr]    Compiling zerotrie v0.2.3
[INFO] [stderr]    Compiling thiserror v2.0.18
[INFO] [stderr]    Compiling tinystr v0.8.2
[INFO] [stderr]    Compiling potential_utf v0.1.4
[INFO] [stderr]    Compiling icu_collections v2.1.1
[INFO] [stderr]    Compiling icu_locale_core v2.1.1
[INFO] [stderr]    Compiling icu_provider v2.1.1
[INFO] [stderr]    Compiling icu_normalizer v2.1.1
[INFO] [stderr]    Compiling icu_properties v2.1.2
[INFO] [stderr]    Compiling chrono v0.4.43
[INFO] [stderr]    Compiling either v1.15.0
[INFO] [stderr]    Compiling idna_adapter v1.2.1
[INFO] [stderr]    Compiling idna v1.1.0
[INFO] [stderr]    Compiling serde_spanned v0.6.9
[INFO] [stderr]    Compiling toml_datetime v0.6.11
[INFO] [stderr]    Compiling toml_edit v0.22.27
[INFO] [stderr]    Compiling url v2.5.8
[INFO] [stderr]    Compiling rustls-webpki v0.103.9
[INFO] [stderr]    Compiling tokio-stream v0.1.18
[INFO] [stderr]    Compiling toml v0.8.23
[INFO] [stderr]    Compiling sailfish-macros v0.10.1
[INFO] [stderr]    Compiling sqlx-core v0.8.6
[INFO] [stderr]    Compiling sqlx v0.8.6
[INFO] [stderr]    Compiling diffly v0.2.0 (/opt/rustwide/workdir)
[INFO] [stdout] warning: unnecessary braces around block return value
[INFO] [stdout]  --> /opt/rustwide/target/release/build/sailfish-compiler-9a8a04fbf6282741/out/templates/73645b80920e49f9-8187ed16487acca2:2:19186
[INFO] [stdout]   |
[INFO] [stdout] 2 | ... ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __...
[INFO] [stdout]   |       ^^                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ^^
[INFO] [stdout]   |
[INFO] [stdout]   = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stdout] help: remove these braces
[INFO] [stdout]   |
[INFO] [stdout] 2 - { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stdout] 2 + { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stdout]   |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stderr]     Finished `release` profile [optimized] target(s) in 1m 35s
[INFO] running `Command { std: "docker" "inspect" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325" "/opt/rustwide/cargo-home/bin/cargo" "+1.99.0-beta.8" "test" "--frozen" "--no-run" "--message-format=json" "--release", kill_on_drop: false }`
[INFO] [stderr]    Compiling rustix v1.1.3
[INFO] [stderr]    Compiling linux-raw-sys v0.11.0
[INFO] [stderr]    Compiling bitflags v2.10.0
[INFO] [stderr]    Compiling fastrand v2.3.0
[INFO] [stdout] warning: unnecessary braces around block return value
[INFO] [stdout]  --> /opt/rustwide/target/release/build/sailfish-compiler-9a8a04fbf6282741/out/templates/73645b80920e49f9-8187ed16487acca2:2:19186
[INFO] [stdout]   |
[INFO] [stdout] 2 | ... ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __...
[INFO] [stdout]   |       ^^                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ^^
[INFO] [stdout]   |
[INFO] [stdout]   = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stdout] help: remove these braces
[INFO] [stdout]   |
[INFO] [stdout] 2 - { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stdout] 2 + { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stdout]   |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stderr]    Compiling tempfile v3.25.0
[INFO] [stderr]    Compiling diffly v0.2.0 (/opt/rustwide/workdir)
[INFO] [stdout] warning: unnecessary braces around block return value
[INFO] [stdout]  --> /opt/rustwide/target/release/build/sailfish-compiler-9a8a04fbf6282741/out/templates/73645b80920e49f9-8187ed16487acca2:2:19186
[INFO] [stdout]   |
[INFO] [stdout] 2 | ... ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __...
[INFO] [stdout]   |       ^^                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ^^
[INFO] [stdout]   |
[INFO] [stdout]   = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stdout] help: remove these braces
[INFO] [stdout]   |
[INFO] [stdout] 2 - { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stdout] 2 + { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stdout]   |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stderr]     Finished `release` profile [optimized] target(s) in 19.64s
[INFO] running `Command { std: "docker" "inspect" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325" "/opt/rustwide/cargo-home/bin/cargo" "+1.99.0-beta.8" "test" "--frozen" "--release", kill_on_drop: false }`
[INFO] [stderr] warning: unnecessary braces around block return value
[INFO] [stderr]  --> /opt/rustwide/target/release/build/sailfish-compiler-9a8a04fbf6282741/out/templates/73645b80920e49f9-8187ed16487acca2:2:19186
[INFO] [stderr]   |
[INFO] [stderr] 2 | ... ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __...
[INFO] [stderr]   |       ^^                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ^^
[INFO] [stderr]   |
[INFO] [stderr]   = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stderr] help: remove these braces
[INFO] [stderr]   |
[INFO] [stderr] 2 - { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stderr] 2 + { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stderr]   |
[INFO] [stderr] 
[INFO] [stderr] warning: `diffly` (lib) generated 1 warning (run `cargo fix --lib -p diffly` to apply 1 suggestion)
[INFO] [stderr] warning: `diffly` (lib test) generated 1 warning (1 duplicate)
[INFO] [stderr]     Finished `release` profile [optimized] target(s) in 0.25s
[INFO] [stderr]      Running unittests src/lib.rs (/opt/rustwide/target/release/deps/diffly-6c529fa445af57fd)
[INFO] [stdout] 
[INFO] [stdout] running 80 tests
[INFO] [stdout] test application::conflict::tests::clean_when_no_base_snapshot ... ok
[INFO] [stdout] test application::conflict::tests::no_conflict_when_different_rows_changed ... ok
[INFO] [stdout] test application::diff::tests::test_diff_columns_nested_json ... ok
[INFO] [stdout] test application::diff::tests::table_differ_detects_insert_update_delete ... ok
[INFO] [stdout] test application::diff::tests::test_diff_columns_ignores_object_key_order ... ok
[INFO] [stdout] test domain::fingerprint::tests::empty_table_is_deterministic ... ok
[INFO] [stdout] test application::conflict::tests::detects_conflict_on_same_row_same_column ... ok
[INFO] [stdout] test application::diff::tests::no_diff_when_rows_identical ... ok
[INFO] [stdout] test domain::fingerprint::tests::order_independent ... ok
[INFO] [stdout] test application::diff::tests::test_diff_columns_no_change ... ok
[INFO] [stdout] test domain::fingerprint::tests::same_rows_same_fingerprint ... ok
[INFO] [stdout] test application::conflict::tests::clean_when_fingerprints_match ... ok
[INFO] [stdout] test application::diff::tests::test_diff_columns_one_change ... ok
[INFO] [stdout] test application::diff::tests::test_diff_columns_float_tolerance ... ok
[INFO] [stdout] test domain::fingerprint::tests::different_rows_different_fingerprint ... ok
[INFO] [stdout] test application::diff::tests::test_json_equal_strings_differ ... ok
[INFO] [stdout] test application::diff::tests::test_json_equal_float_within_epsilon ... ok
[INFO] [stdout] test infrastructure::config::tests::load_defaults_applied ... ok
[INFO] [stdout] test application::diff::tests::test_extract_pk_single ... ok
[INFO] [stdout] test application::diff::tests::test_extract_pk_composite ... ok
[INFO] [stdout] test application::diff::tests::test_json_equal_identical_numbers ... ok
[INFO] [stdout] test infrastructure::config::tests::load_table_config_parsed ... ok
[INFO] [stdout] test infrastructure::config::tests::load_invalid_toml_errors ... ok
[INFO] [stdout] test infrastructure::config::tests::url_mariadb ... ok
[INFO] [stdout] test infrastructure::config::tests::url_multibyte_utf8_encoded ... ok
[INFO] [stdout] test infrastructure::config::tests::load_defaults_overridden_by_file ... ok
[INFO] [stdout] test infrastructure::config::tests::load_missing_explicit_file_errors ... ok
[INFO] [stdout] test infrastructure::config::tests::url_postgres ... ok
[INFO] [stdout] test infrastructure::config::tests::url_sqlite ... ok
[INFO] [stdout] test infrastructure::config::tests::url_special_chars_in_password_are_encoded ... ok
[INFO] [stdout] test infrastructure::config::tests::load_missing_required_field_errors ... ok
[INFO] [stdout] test infrastructure::config::tests::load_env_does_not_affect_unset_keys ... ok
[INFO] [stdout] test infrastructure::config::tests::env_source_key_mapping_probe ... ok
[INFO] [stdout] test infrastructure::config::tests::load_explicit_overrides_base_values ... ok
[INFO] [stdout] test infrastructure::config::tests::url_special_chars_in_user_are_encoded ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_mysql_quote_ident ... ok
[INFO] [stdout] test application::diff::tests::test_json_equal_ignores_object_key_order ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_from_driver_names ... ok
[INFO] [stdout] test infrastructure::config::tests::url_unreserved_chars_not_encoded ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_mysql_native_types ... ok
[INFO] [stdout] test infrastructure::config::tests::load_env_port_parsed_as_integer ... ok
[INFO] [stdout] test infrastructure::config::tests::url_mysql ... ok
[INFO] [stdout] test infrastructure::config::tests::load_env_overrides_file ... ok
[INFO] [stdout] test infrastructure::config::tests::url_unknown_driver_falls_back_to_postgres ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_postgres_cast_to_text ... ok
[INFO] [stdout] test infrastructure::config::tests::load_explicit_path ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sqlite_quote_ident ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_postgres_quote_ident ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sqlite_schema_prefix_empty ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_select_query_mysql ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sql_literal_json_postgres_has_cast ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_needs_introspection ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_mysql_schema_prefix ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sql_literal_string_escapes ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_select_query_no_pk_omits_order_by ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_postgres_schema_prefix ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sql_literal_null ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_postgres_native_types ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sql_literal_json_mysql_no_cast ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_select_query_postgres ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_mysql_cast_to_text ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sql_literal_bool ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_select_query_sqlite ... ok
[INFO] [stdout] test infrastructure::db::dialect::tests::test_sql_literal_json_sqlite_no_cast ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_typed_select_query_array_gets_text_cast ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_typed_select_query_mysql_uses_convert ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_typed_select_query_no_pk_omits_order_by ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_pk_where_clause_mysql_backticks ... ok
[INFO] [stdout] test infrastructure::db::sql_utils::tests::test_build_typed_select_query_postgres_casts_non_primitives ... ok
[INFO] [stdout] test presentation::writers::json::tests::json_output_contains_sql_field_for_each_change ... ok
[INFO] [stdout] test presentation::writers::json::tests::json_output_sql_uses_correct_dialect_quoting ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_pk_where_clause_null_is_null ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_pk_where_clause_value ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_bool ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_json_mysql_no_cast ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_jsonb_postgres ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_null ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_number ... ok
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_string_escapes_quotes ... ok
[INFO] [stderr]    Doc-tests diffly
[INFO] [stdout] test presentation::writers::sql::tests::test_sql_literal_json_sqlite_no_cast ... ok
[INFO] [stdout] 
[INFO] [stdout] test result: ok. 80 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
[INFO] [stdout] 
[INFO] [stderr] warning: unnecessary braces around block return value
[INFO] [stderr]  --> /opt/rustwide/target/release/build/sailfish-compiler-9a8a04fbf6282741/out/templates/73645b80920e49f9-8187ed16487acca2:2:19186
[INFO] [stderr]   |
[INFO] [stderr] 2 | ... ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __...
[INFO] [stderr]   |       ^^                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ^^
[INFO] [stderr]   |
[INFO] [stderr]   = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stderr] help: remove these braces
[INFO] [stderr]   |
[INFO] [stderr] 2 - { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stderr] 2 + { let changeset = self . changeset ; __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Diffly - Changeset ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</title>\n\n<style>\n  :root {\n    --bg: #0d1117; --surface: #161b22; --border: #30363d; --text: #e6edf3; --muted: #8b949e;\n    --green-bg: #0d2818; --green-border: #238636; --green-text: #3fb950;\n    --red-bg: #2d1117; --red-border: #da3633; --red-text: #f85149;\n    --orange-bg: #2a1e00; --orange-border: #d29922; --orange-text: #e3b341;\n    --blue: #58a6ff;\n  }\n  html[data-theme=\"light\"] {\n      --bg:#ffffff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --muted:#636c76;\n      --green-bg: #dafbe1; --green-border: #1f883d; --green-text: #1a7431;\n      --red-bg: #ffebe9; --red-border: #c93c37; --red-text: #b3231a;\n      --orange-bg: #fff8c5; --orange-border: #9a6700; --orange-text: #825600;\n      --blue: #0550ae;\n    }  body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); padding:2rem; line-height:1.5; }\n  .container { max-width:1200px; margin:0 auto; }\n  h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }\n  .meta { color:var(--muted); font-size: 0.9rem; margin-bottom: 2rem; }\n  button.toggle { background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.3rem 0.7rem; cursor:pointer; }\n  .summary { display:flex; gap:1rem; margin-bottom:2rem; flex-wrap:wrap; }\n  .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1rem 1.5rem; min-width: 140px; }\n  .stat .num { font-size:2rem; font-weight:700; }\n  .stat .label { color:var(--muted); font-size: 0.85rem; text-transform:uppercase; letter-spacing:.05em; }\n  .stat.insert .num { color: var(--green-text); }\n  .stat.update .num { color: var(--orange-text); }\n  .stat.delete .num { color: var(--red-text); }\n  .table-section { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }\n  .table-header { padding:1rem 1.5rem; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items: center; }\n  .table-header h2 { font-size: 1.1rem; font-weight: 600; }\n  .badges { display:flex; gap:.5rem; }\n  .badge { padding:.2rem .6rem; border-radius:12px; font-size:.75rem; font-weight:600; }\n  .badge.insert { background:var(--green-bg); color:var(--green-text); border:1px solid var(--green-border); }\n  .badge.update { background:var(--orange-bg); color:var(--orange-text); border:1px solid var(--orange-border); }\n  .badge.delete { background:var(--red-bg); color:var(--red-text); border:1px solid var(--red-border); }\n  .change-group { padding:.75rem 1.5rem; border-bottom:1px solid var(--border); }\n  .change-group:last-child { border-bottom: none; }\n  .change-group h3 { font-size: 0.85rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; }\n  .change-group--insert h3 { color: var(--green-text); }\n  .change-group--update h3 { color: var(--orange-text); }\n  .change-group--delete h3 { color: var(--red-text); }\n  table { width:100%; border-collapse:collapse; font-size: 0.85rem; }\n  th { text-align: left; padding: .5rem .75rem; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer; }\n  th.asc::after, th.desc::after { content: ''; display: inline-block; margin-left: 0.5em; font-size: 0.8em; }\n  th.asc::after { content: '▲'; }\n  th.desc::after { content: '▼'; }\n  td { padding:.5rem .75rem; border-bottom:1px solid var(--border); font-family:'SF Mono',monospace; font-size: 0.8rem; word-break: break-all; }\n  tr:last-child td { border-bottom: none; }\n  .val-before { color: var(--red-text); text-decoration: line-through; background:rgba(248,81,73,.1); }\n  .val-after { color: var(--green-text); background:rgba(63,185,80,.1); }\n  .pk-cell { color:var(--blue); }\n  .op-insert { border-left: 3px solid var(--green-border); }\n  .op-update { border-left: 3px solid var(--orange-border); }\n  .op-delete { border-left: 3px solid var(--red-border); }\n  .search-box { padding:0.3rem 0.5rem; border:1px solid var(--border); border-radius:6px; background:var(--surface); color:var(--text); }\n  .actions { display:flex; gap:.5rem; align-items:center; }\n  /* ─── Performance section ─────────────────────────────────── */\n  .perf-section { margin-bottom: 2rem; }\n  .perf-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem; }\n  .perf-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; display: flex; gap: 0.5rem; }\n  .perf-op { color: var(--muted); font-family: 'SF Mono', monospace; font-size: 0.8rem; }\n  .perf-fast .perf-duration { color: var(--green-text); }\n  .perf-medium .perf-duration { color: var(--orange-text); }\n  .perf-slow .perf-duration { color: var(--red-text); font-weight: 700; }\n  @media print { body { background:white; color:black; } .toggle { display:none; } .table-section { page-break-inside: avoid; } }\n</style>\n\n<script>\nfunction toggleTheme() {\n  const html = document.documentElement;\n  html.dataset.theme = html.dataset.theme === \"light\" ? \"dark\" : \"light\";\n}\nfunction sortTable(th) {\n  const table = th.closest(\"table\");\n  const wasAsc = th.classList.contains(\"asc\");\n\n  // Reset all other headers in the same table\n  table.querySelectorAll(\"thead th\").forEach(h => h.classList.remove(\"asc\", \"desc\"));\n\n  // Set new state on the clicked header\n  if (wasAsc) {\n    th.classList.add(\"desc\");\n  } else {\n    th.classList.add(\"asc\");\n  }\n\n  // Perform sorting\n  const isAsc = th.classList.contains(\"asc\");\n  const idx = Array.from(th.parentNode.children).indexOf(th);\n  const rows = Array.from(table.querySelectorAll(\"tbody tr\"));\n\n  rows.sort((a,b) => {\n    const A = a.children[idx].innerText;\n    const B = b.children[idx].innerText;\n    return isAsc\n      ? A.localeCompare(B, undefined, {numeric: true, sensitivity: 'base'})\n      : B.localeCompare(A, undefined, {numeric: true, sensitivity: 'base'});\n  });\n\n  rows.forEach(tr => table.querySelector(\"tbody\").appendChild(tr));\n}\nfunction filterTable(input) {\n  const table = input.closest(\".table-section\").querySelector(\"table\");\n  const term = input.value.toLowerCase();\n  table.querySelectorAll(\"tbody tr\").forEach(row => {\n    row.style.display = row.innerText.toLowerCase().includes(term) ? \"\" : \"none\";\n  });\n}\nfunction exportTableCSV(btn) {\n  const table = btn.closest(\".table-section\").querySelector(\"table\");\n  let csv = [];\n  table.querySelectorAll(\"tr\").forEach(row => {\n    let cols = Array.from(row.children).map(td => `\"${td.innerText.replace(/\"/g,'\"\"')}\"`);\n    csv.push(cols.join(\",\"));\n  });\n  const blob = new Blob([csv.join(\"\\n\")], { type: \"text/csv\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"table_export.csv\";\n  a.click();\n  URL.revokeObjectURL(url);\n}\n</script>\n</head>\n<body>\n<div class=\"container\">\n  <div style=\"display:flex;justify-content:space-between;align-items:center;\">\n    <h1>Diffly</h1>\n    <button class=\"toggle\" onclick=\"toggleTheme()\">🌗 Theme</button>\n  </div>\n  <h2>📋 Changeset | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . driver) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n  <div class=\"meta\">\n    <strong>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . changeset_id) ; __sf_rt :: render_text ! (__sf_buf , "</strong><br>\n    ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . source_schema) ; __sf_rt :: render_text ! (__sf_buf , " → ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , " | ") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . created_at) ; __sf_rt :: render_text ! (__sf_buf , "\n  </div>\n  <div class=\"summary\">\n    <div class=\"stat insert\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_inserts) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Inserts</div></div>\n    <div class=\"stat update\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_updates) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Updates</div></div>\n    <div class=\"stat delete\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . total_deletes) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Deletes</div></div>\n    <div class=\"stat\"><div class=\"num\">") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . summary . tables_affected) ; __sf_rt :: render_text ! (__sf_buf , "</div><div class=\"label\">Tables</div></div>\n  </div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; for table in & changeset . tables { __sf_rt :: render_text ! (__sf_buf , "\n  ") ; if ! table . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"table-section\">\n  <div class=\"table-header\">\n    <h2>") ; __sf_rt :: render_escaped ! (__sf_buf , changeset . target_schema) ; __sf_rt :: render_text ! (__sf_buf , ".") ; __sf_rt :: render_escaped ! (__sf_buf , table . table_name) ; __sf_rt :: render_text ! (__sf_buf , "</h2>\n    <div class=\"actions\">\n      <input class=\"search-box\" placeholder=\"Filter…\" onkeyup=\"filterTable(this)\">\n      <button class=\"toggle\" onclick=\"exportTableCSV(this)\">⬇ CSV</button>\n      <div class=\"badges\">\n        ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge insert\">+") ; __sf_rt :: render_escaped ! (__sf_buf , table . inserts . len ()) ; __sf_rt :: render_text ! (__sf_buf , " insert") ; __sf_rt :: render_escaped ! (__sf_buf , if table . inserts . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge update\">~") ; __sf_rt :: render_escaped ! (__sf_buf , table . updates . len ()) ; __sf_rt :: render_text ! (__sf_buf , " update") ; __sf_rt :: render_escaped ! (__sf_buf , if table . updates . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n          <span class=\"badge delete\">-") ; __sf_rt :: render_escaped ! (__sf_buf , table . deletes . len ()) ; __sf_rt :: render_text ! (__sf_buf , " delete") ; __sf_rt :: render_escaped ! (__sf_buf , if table . deletes . len () > 1 { "s" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "</span>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </div>\n    </div>\n  </div>\n\n  ") ; if ! table . inserts . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Inserts" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . inserts ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . updates . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . updates ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { __sf_rt :: render_text ! (__sf_buf , "<div class=\"change-group change-group--update\">\n  <h3>Updates</h3>\n  <table>\n    <thead>\n      <tr>\n        <th>PK</th><th>Column</th><th>Before</th><th>After</th>\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        ") ; let pk_str = table . primary_key . iter () . filter_map (| k | row . pk . get (k) . map (| v | format ! ("{}={}" , k , v))) . collect :: < Vec < _ > > () . join (", ") ; __sf_rt :: render_text ! (__sf_buf , "\n        ") ; for (i , col_diff) in row . changed_columns . iter () . enumerate () { __sf_rt :: render_text ! (__sf_buf , "\n          <tr class=\"op-update\">\n            ") ; if i == 0 { __sf_rt :: render_text ! (__sf_buf , "\n              <td class=\"pk-cell\" rowspan=\"") ; __sf_rt :: render_escaped ! (__sf_buf , row . changed_columns . len ()) ; __sf_rt :: render_text ! (__sf_buf , "\">") ; __sf_rt :: render_escaped ! (__sf_buf , pk_str) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            ") ; } __sf_rt :: render_text ! (__sf_buf , "\n            <td>") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . column) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-before\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . before . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n            <td class=\"val-after\">") ; __sf_rt :: render_escaped ! (__sf_buf , & col_diff . after . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n  ") ; if ! table . deletes . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let group_name = "Deletes" ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; let rows = & table . deletes ; __sf_rt :: render_text ! (__sf_buf , "\n    ") ; { let pk_cols = & table . primary_key ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let non_pk_cols : Vec < & String > = rows [0] . data . keys () . filter (| k | ! pk_cols . contains (k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let pk_present : Vec < & String > = pk_cols . iter () . filter (| k | rows [0] . data . contains_key (* k)) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n") ; let all_cols : Vec < & String > = pk_present . iter () . copied () . chain (non_pk_cols . iter () . copied ()) . collect () ; __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"change-group change-group--") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n  <h3>") ; __sf_rt :: render_escaped ! (__sf_buf , group_name) ; __sf_rt :: render_text ! (__sf_buf , "</h3>\n  <table>\n    ") ; if ! rows . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n    <thead>\n      <tr>\n        ") ; for col in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n          <th class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col) { "pk-cell" } else { "" }) ; __sf_rt :: render_text ! (__sf_buf , "\" onclick=\"sortTable(this)\">") ; __sf_rt :: render_escaped ! (__sf_buf , col) ; __sf_rt :: render_text ! (__sf_buf , "</th>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tr>\n    </thead>\n    <tbody>\n      ") ; for row in rows { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"op-") ; __sf_rt :: render_escaped ! (__sf_buf , if group_name == "Inserts" { "insert" } else { "delete" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n          ") ; for col_name in & all_cols { __sf_rt :: render_text ! (__sf_buf , "\n            ") ; let v = row . data . get (* col_name) . unwrap_or (& serde_json :: Value :: Null) ; __sf_rt :: render_text ! (__sf_buf , "\n            <td class=\"") ; __sf_rt :: render_escaped ! (__sf_buf , if pk_cols . contains (col_name) { "pk-cell" } else if group_name == "Inserts" { "val-after" } else { "val-before" }) ; __sf_rt :: render_text ! (__sf_buf , "\">\n              ") ; __sf_rt :: render_escaped ! (__sf_buf , v . to_string ()) ; __sf_rt :: render_text ! (__sf_buf , "\n            </td>\n          ") ; } __sf_rt :: render_text ! (__sf_buf , "\n        </tr>\n      ") ; } __sf_rt :: render_text ! (__sf_buf , "\n    </tbody>\n    ") ; } __sf_rt :: render_text ! (__sf_buf , "\n  </table>\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n</div>") ; } __sf_rt :: render_text ! (__sf_buf , "\n  ") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; if let Some (perf) = & changeset . perf { __sf_rt :: render_text ! (__sf_buf , "\n") ; if ! perf . timings . is_empty () { __sf_rt :: render_text ! (__sf_buf , "\n<div class=\"perf-section\">\n  <h2 class=\"perf-title\">⏱ Performance</h2>\n  <div class=\"perf-meta\">\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_rows_fetched) ; __sf_rt :: render_text ! (__sf_buf , "</strong> row(s) fetched</span>\n    <span>·</span>\n    <span><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , perf . total_ms) ; __sf_rt :: render_text ! (__sf_buf , " ms</strong> total elapsed</span>\n  </div>\n  <div class=\"table-section\" style=\"margin-bottom:1.5rem;\">\n    <table id=\"perf-table\">\n      <thead>\n        <tr>\n          <th onclick=\"sortTable(this)\">Operation</th>\n          <th onclick=\"sortTable(this)\">Table</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Rows</th>\n          <th onclick=\"sortTable(this)\" style=\"text-align:right\">Time (ms)</th>\n        </tr>\n      </thead>\n      <tbody>\n        ") ; for t in & perf . timings { __sf_rt :: render_text ! (__sf_buf , "\n        <tr class=\"") ; if t . duration_ms >= 1000 { __sf_rt :: render_text ! (__sf_buf , "perf-slow") ; } else if t . duration_ms >= 100 { __sf_rt :: render_text ! (__sf_buf , "perf-medium") ; } else { __sf_rt :: render_text ! (__sf_buf , "perf-fast") ; } __sf_rt :: render_text ! (__sf_buf , "\">\n          <td class=\"perf-op\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . operation) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td><strong>") ; __sf_rt :: render_escaped ! (__sf_buf , t . table) ; __sf_rt :: render_text ! (__sf_buf , "</strong></td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . rows) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n          <td style=\"text-align:right;font-family:'SF Mono',monospace\" class=\"perf-duration\">") ; __sf_rt :: render_escaped ! (__sf_buf , t . duration_ms) ; __sf_rt :: render_text ! (__sf_buf , "</td>\n        </tr>\n        ") ; } __sf_rt :: render_text ! (__sf_buf , "\n      </tbody>\n    </table>\n  </div>\n</div>\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n") ; } __sf_rt :: render_text ! (__sf_buf , "\n\n") ; { __sf_rt :: render_text ! (__sf_buf , "</div>\n</body>\n</html>") ; } }
[INFO] [stderr]   |
[INFO] [stderr] 
[INFO] [stderr] warning: 1 warning emitted
[INFO] [stderr] 
[INFO] [stdout] 
[INFO] [stdout] running 3 tests
[INFO] [stdout] test src/domain/snapshot.rs - domain::snapshot::MapSnapshotProvider (line 12) ... ok
[INFO] [stdout] test src/lib.rs - snapshot (line 129) ... ok
[INFO] [stdout] test src/domain/fingerprint.rs - domain::fingerprint::fingerprint (line 16) ... ok
[INFO] [stdout] 
[INFO] [stdout] test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
[INFO] [stdout] 
[INFO] running `Command { std: "docker" "inspect" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325", kill_on_drop: false }`
[INFO] [stdout] cfca8e9988ccf002bc7f4b786d4abebb1a3208e4fe021a7d4159b825f2aee325
