Files
zeekayandHanzo Dev 69c6e860d7 transplant onto tamagui v2.6.0, brand as a re-runnable transform
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.

This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.

174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.

Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
  - useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
    returns undefined there, so every Tamagui Dialog kills a prerender — the
    exact crash hanzo.app spent a day on.
  - data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.

Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.

Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.

Co-Authored-By: Hanzo Dev <[email protected]>
2026-07-30 18:25:06 -07:00

103 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
// crunch the sheet telemetry (frames.jsonl) into the few derived numbers that
// actually decide pass/fail — so the keyboard-avoidance loop reads geometry from
// disk instead of eyeballing slow simulator screenshots.
//
// usage: node sheet-analyze.mjs [path-to-frames.jsonl]
//
// metrics (all in CSS px, screen-relative):
// keyboardTop = vo + vv top edge of the soft keyboard / accessory bar
// input clear = keyboardTop - iB focused input bottom vs keyboard (+ = visible)
// post clear = keyboardTop - pB submit button bottom vs keyboard (+ = reachable)
// maxScroll = sC - sH how far the content can scroll
// the success bar: at full scroll the post button clears the keyboard by a margin,
// and the frame does NOT move when the keyboard toggles (no resize/jank).
import { readFileSync } from 'node:fs'
const path = process.argv[2] || '/tmp/sheet-frames/frames.jsonl'
const lines = readFileSync(path, 'utf8').split('\n').filter(Boolean)
const samples = []
for (const l of lines) {
try {
const s = JSON.parse(l)
if (s.k === 'pos') samples.push(s)
} catch {}
}
if (!samples.length) {
console.log('no pos samples in', path)
process.exit(0)
}
const kbTop = (s) => (s.vo || 0) + s.vv
const fin = (n) => typeof n === 'number' && !Number.isNaN(n)
const clr = (top, bottom) => (fin(bottom) ? top - bottom : null) // + = above kb
// a sheet is OPEN only when its frame is on-screen. closed/hidden sheets sit at
// or below the layout viewport (fT >= de, or the 10000 hidden sentinel) — exclude
// them so the jank check compares open-vs-open, not open-vs-closed.
const isOpen = (s) => fin(s.fT) && s.fT < (s.de || 1e9) - 20
const kbUp = samples.filter((s) => s.kb >= 80 && isOpen(s))
const kbDown = samples.filter((s) => s.kb < 80 && isOpen(s))
// pick the settled keyboard-up sample with the DEEPEST scroll — that's the worst
// case for the post button (furthest the user can push the content up).
const deepest = kbUp.length
? kbUp.reduce((a, b) => (b.sT > a.sT ? b : a))
: samples[samples.length - 1]
// best post-button clearance achieved across all kb-up samples (did it ever clear?)
let bestPost = null
for (const s of kbUp) {
const c = clr(kbTop(s), s.pB)
if (c !== null && (bestPost === null || c > bestPost.c)) bestPost = { c, s }
}
const fmt = (s) => {
const kt = kbTop(s)
const max = s.sC - s.sH
return [
` frame fT=${s.fT} fB=${s.fB} fH=${s.fH}`,
` scroll sT=${s.sT}/${max} (${max > 0 ? Math.round((s.sT / max) * 100) : 0}%) sH=${s.sH} sC=${s.sC}`,
` kb vv=${s.vv} vo=${s.vo} kb=${s.kb} -> keyboardTop=${kt} layoutVP=${s.de}`,
` input iT=${s.iT} iB=${s.iB} clearance=${clr(kt, s.iB)}`,
` post pT=${s.pT} pB=${s.pB} clearance=${clr(kt, s.pB)}`,
].join('\n')
}
console.log(
`== ${samples.length} pos samples (${kbUp.length} kb-up, ${kbDown.length} kb-down) ==\n`
)
console.log('LAST kb-up @ deepest scroll:')
console.log(fmt(deepest))
if (bestPost) {
console.log(
`\nbest post clearance across kb-up: ${bestPost.c}px (at sT=${bestPost.s.sT}/${bestPost.s.sC - bestPost.s.sH})`
)
}
// jank check: frame top/bottom should be IDENTICAL between a kb-down and a kb-up
// settled sample (the frame must not resize/move when the keyboard toggles).
if (kbDown.length && kbUp.length) {
const d = kbDown[kbDown.length - 1]
const u = kbUp[kbUp.length - 1]
const moved = d.fT !== u.fT || d.fB !== u.fB || d.fH !== u.fH
console.log(
`\njank check kb-down(fT=${d.fT} fB=${d.fB} fH=${d.fH}) vs kb-up(fT=${u.fT} fB=${u.fB} fH=${u.fH}) -> ${moved ? 'FRAME MOVED (jank!)' : 'stable (no jank)'}`
)
} else {
console.log(
'\njank check: need both kb-down and kb-up samples (capture an open before focusing)'
)
}
// verdict
if (bestPost) {
const ok = bestPost.c >= 12
console.log(
`\nVERDICT post-reachable: ${ok ? 'PASS' : 'FAIL'} (best clearance ${bestPost.c}px, want >= 12)`
)
}