You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking issue for Sheetwrite 0.4.0. Two themes — performance and delivery size — plus the rendering-correctness defects found in the macOS / worker-renderer audit.
The sections below are ordered: each one unblocks the next. Nothing here is set in stone — every performance and size item is gated on measurement, and an item whose benchmark or bun run size:report does not show the win is dropped rather than shipped on an estimate.
A prior audit found no dead code and no unused dependencies under the checks run so far (evidence and method limits), so deletion is not currently available as a size lever. Optionality and compiler settings are.
Land the scenario benchmark set: formula-dense window, text-heavy scroll, fractional scroll, unresized 1M-row geometry (PR #48, results)
Produce per-function/section byte attribution for the WASM artifact (twiggy, or wasm-opt --print-function-sizes — already in the wasm-pack cache). eval + calc is 238,064 shallow bytes: 79.33% of named Sheetwrite function bytes and 32.34% of the name-preserving Twiggy total (results)
4. Performance — each item ships only if §3 justifies it
Remove the redundant formula-entry map probes in visible-window decoding
Profile and cut WASM-to-JS window allocation and copy cost
Remove per-frame render-signature string building
Replace clear-everything string-cache eviction — dropped after profiling: cache/window decoding was not the long-text bottleneck
Make row geometry allocation-free while no row has been resized (PR #49, results)
Cut paint clipping cost without breaking intentional text spill
Compiler settings — cheapest lever, no code change
Re-measure the release-profile tradeoff. Interleaved raw-backed captures keep opt-level = 3: 2 saves 2.08% Brotli but slows resolved 100k formula paths 5.72–8.46%; "s" saves 6.72% but slows them 8.60–11.71% (results, PR perf(bench): record wasm profile tradeoff #47)
Run the build matrix: {crate opt-level3, "s"} × {wasm-opt-O3, -Os, -Oz}, scored on size:reportand the existing bench gates. wasm-opt ([package.metadata.wasm-pack.profile.release]) is a separate post-link knob needing no crate boundary — try it first
Update the Cargo.toml justification comment with whatever the re-measurement actually shows
Bundle attribution — attribution first, cutover only if proven
Attribute the Next.js vs Vite initial-bundle gap per module across both fixtures, after confirming they are comparable (mode, minifier, externals)
Add dedicated subpaths for sync, collaboration, rebase, persistence; measure both fixtures again. An earlier subpath experiment proved a subpath alias alone saved 226 bytes against a 7,680-byte threshold — because GridImpl statically retains the stock chrome. These four modules differ (they are barrel-only, not reachable from GridImpl), which is exactly what the attribution must confirm
Only if attribution proves the root re-exports are retained in the Next graph: open a follow-up to drop them from index.ts. That is a breaking API cutover — own changeset, migration note, consumer sweep. Not pre-approved here
Decide the source-map publication policy for @sheetwrite/core
Split first-paint-only code out of the eager grid.ts import chain
Regenerate delivery-size reports; raise no existing ceiling
6. Maintainability
Split store/data-engine.ts (3,691 LOC, fan-in 1 — no public API impact)
Un-export the 41 internal-only symbols that are not part of the published surface
Add an unused-export gate (knip or equivalent); neither tsc nor Biome detects unused exports today, so the current finding rests on a heuristic scan
7. Spikes — time-boxed, may not land in 0.4.0
Store-only WASM artifact via --no-default-features. Gate 1: does it compile (FormulaEntry stores a calc::Ast; store owns eval::DepIndex). Gate 2: what does size:report say. Kill it if gate 1 is expensive
Cold-crate extraction for a per-package opt-level override. Only if §3 attribution shows enough cold bytes to be worth the dependency inversion. Note lto = "fat" + codegen-units = 1 re-optimizes across crate boundaries and generic monomorphization emits into the instantiating crate, so the boundary is not a guaranteed size lever even after the split
Exit criteria
Main-thread canvas verified on macOS Safari and Chromium at DPR 2
Before/after artifacts published for every performance claim
bun run size:report regenerated; every size number traced to it
Formula-recalc latency gated by bench/src/formula-bench.ts before any size-oriented compiler setting ships
No public API or wire-format change ships without its consumers migrated
Explicitly out of scope
Splitting the WASM crate purely to enable lazy loading. Two crates still link into one artifact; only separately loaded artifacts reduce first load, and that needs a serializable boundary across a cycle recalc crosses constantly. Superseded by the §7 spikes.
New npm packages for the optional modules. Subpath exports get the same result without version skew across five lockstep packages.
Decomposing grid.ts (3,146 LOC, fan-out 42). Real debt, and the earlier subpath experiment shows it is what blocks a minimal delivery boundary — but it is a 0.5.0 project. Doing it inside a performance release is how regressions ship.
Tracking issue for Sheetwrite 0.4.0. Two themes — performance and delivery size — plus the rendering-correctness defects found in the macOS / worker-renderer audit.
The sections below are ordered: each one unblocks the next. Nothing here is set in stone — every performance and size item is gated on measurement, and an item whose benchmark or
bun run size:reportdoes not show the win is dropped rather than shipped on an estimate.A prior audit found no dead code and no unused dependencies under the checks run so far (evidence and method limits), so deletion is not currently available as a size lever. Optionality and compiler settings are.
1. Foundations — blocks everything below
0.4.0(PR fix(ci): gate every pull request against its base #39)KIND_*wire contract one source of truth (PR refactor(core): share WASM cell-kind wire tags #42)2. Rendering correctness — blocking
Evidence and acceptance criteria
VisibleWindowView(PR fix(core): preserve cached worker view buffers #43)devicePixelRatiochanges without a CSS-size change (PR fix(core): repaint when device pixel ratio changes #46)OffscreenCanvascontext-loss policy (PR fix(core): fail closed when paint workers fail #44 — fatal fallback)3. Measurement — gate for every item in §4 and §5
Evidence and acceptance criteria
twiggy, orwasm-opt --print-function-sizes— already in the wasm-pack cache).eval+calcis 238,064 shallow bytes: 79.33% of named Sheetwrite function bytes and 32.34% of the name-preserving Twiggy total (results)4. Performance — each item ships only if §3 justifies it
5. Delivery size
Baseline numbers and acceptance criteria
Compiler settings — cheapest lever, no code change
opt-level = 3:2saves 2.08% Brotli but slows resolved 100k formula paths 5.72–8.46%;"s"saves 6.72% but slows them 8.60–11.71% (results, PR perf(bench): record wasm profile tradeoff #47)opt-level3,"s"} × {wasm-opt-O3,-Os,-Oz}, scored onsize:reportand the existing bench gates.wasm-opt([package.metadata.wasm-pack.profile.release]) is a separate post-link knob needing no crate boundary — try it firstCargo.tomljustification comment with whatever the re-measurement actually showsBundle attribution — attribution first, cutover only if proven
sync,collaboration,rebase,persistence; measure both fixtures again. An earlier subpath experiment proved a subpath alias alone saved 226 bytes against a 7,680-byte threshold — becauseGridImplstatically retains the stock chrome. These four modules differ (they are barrel-only, not reachable fromGridImpl), which is exactly what the attribution must confirmindex.ts. That is a breaking API cutover — own changeset, migration note, consumer sweep. Not pre-approved here@sheetwrite/coregrid.tsimport chain6. Maintainability
store/data-engine.ts(3,691 LOC, fan-in 1 — no public API impact)knipor equivalent); neithertscnor Biome detects unused exports today, so the current finding rests on a heuristic scan7. Spikes — time-boxed, may not land in 0.4.0
--no-default-features. Gate 1: does it compile (FormulaEntrystores acalc::Ast; store ownseval::DepIndex). Gate 2: what doessize:reportsay. Kill it if gate 1 is expensiveopt-leveloverride. Only if §3 attribution shows enough cold bytes to be worth the dependency inversion. Notelto = "fat"+codegen-units = 1re-optimizes across crate boundaries and generic monomorphization emits into the instantiating crate, so the boundary is not a guaranteed size lever even after the splitExit criteria
bun run size:reportregenerated; every size number traced to itbench/src/formula-bench.tsbefore any size-oriented compiler setting shipsExplicitly out of scope
grid.ts(3,146 LOC, fan-out 42). Real debt, and the earlier subpath experiment shows it is what blocks a minimal delivery boundary — but it is a 0.5.0 project. Doing it inside a performance release is how regressions ship.