Skip to content

Sheetwrite 0.4.0 Release Tracker #33

Description

@chh-ay

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.

1. Foundations — blocks everything below

2. Rendering correctness — blocking

Evidence and acceptance criteria

3. Measurement — gate for every item in §4 and §5

Evidence and acceptance criteria

  • 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

5. Delivery size

Baseline numbers and acceptance criteria

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-level 3, "s"} × {wasm-opt -O3, -Os, -Oz}, scored on size:report and 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
  • Worker rendering either paints correctly or falls back observably (PRs fix(core): preserve cached worker view buffers #43 and fix(core): fail closed when paint workers fail #44)
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or requestreleaseRelease coordination and verification

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions