Skip to content

Pre-release fixes: playback version warning, O(1) same-name re-add, line_width compat - #760

Merged
brentyi merged 8 commits into
mainfrom
pre-release-fixes
Aug 16, 2026
Merged

Pre-release fixes: playback version warning, O(1) same-name re-add, line_width compat#760
brentyi merged 8 commits into
mainfrom
pre-release-fixes

Conversation

@brentyi

@brentyi brentyi commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Fixes from a pre-release regression audit:

  • Playback of a .viser recording saved with a different viser version now shows a version-mismatch notification instead of silently rendering with possible format drift.
  • Same-name scene node re-adds purge the old node's buffered state through a new entity-keyed buffer index instead of an O(buffer) scan, keeping re-add-per-frame loops constant-time in scene size (~4.5ms → ~0.5ms per re-add at 2k nodes).
  • The deprecated line_width handle setter now forces thickness_units="screen" so assigned values keep their historical pixel meaning on handles created with world-space defaults.
  • add_arrows accepts the removed line_width argument with a deprecation warning instead of raising TypeError, and ArrowsHandle.line_width warns instead of silently writing a dead attribute.
  • Share tunnel teardown threads tolerate BrokenPipeError/ConnectionResetError from dead manager proxies, matching close() and avoiding spurious tracebacks at interpreter exit after a failed tunnel.
  • The line fringe material's antialiasing define is applied via callback ref so it survives material remounts from segments/dashed branch flips.
  • Folder/tab context-guard errors now explain that the handle may be entered concurrently from another thread, not just self-nested.
  • The per-entity message taxonomy is consolidated into a single Message.entity_state_key() definition, with new tests covering the buffer index, the re-add purge, overlapping container-context entry, and the arrows/line-width deprecations.

Live connections reject client/server version mismatches via the websocket
subprotocol, but .viser recordings only console.log'd the version they were
saved with — an old recording played back silently wrong when the message
format had drifted (e.g. the line_width -> thickness rename). Show a
persistent notification when the recording's version differs from the
viewer's. Embeds from as_html() bundle the client build that wrote them, so
they can't mismatch.
…sage

Three fixes from the pre-release regression audit:

- Same-name scene node re-add scanned the entire persistent message buffer
  to purge the old node's per-entity state, making re-add-per-frame
  animation loops quadratic in scene size (~4.5ms per re-add at 2k nodes).
  Add an entity-state index to AsyncMessageBuffer -- the materialized form
  of Message.targets_entity_state -- maintained by a single deletion
  primitive (pop_message_locked), and route the replacement purge, push()'s
  remove-time update purge, and the GC's second pass through it. Re-add is
  now O(per-entity messages): flat ~0.5ms regardless of scene size.

- The deprecated line_width handle setter wrote thickness without pinning
  units, so a v1.0.x '3 px' intent became 3 world units on handles created
  with the new world-space defaults. The setter now forces
  thickness_units='screen', preserving the alias's historical pixel
  meaning on every handle.

- The folder/tab context-guard error claimed 're-entered inside itself'
  for what can also be a concurrent enter from another thread; say so.
- Folder/tab handles: overlapping 'with' entry (self-nested or from
  another thread) raises; sequential re-entry stays legal and elements
  land in the container.
- Same-name scene node replacement: end-to-end check that the old node's
  buffered state is purged, the replacement's forced default-pose
  broadcast survives, bystander nodes are untouched, and the buffer's
  entity-state index stays consistent.
…ath cleanups

Applying a reuse/simplification/efficiency/altitude review of the recent
changes:

- The entity-state taxonomy now has one definition: Message.entity_state_key()
  (next to its data), with targets_entity_state reduced to a predicate view
  of it. The buffer's parallel _entity_state_key copy -- and the test loop
  that existed only to police their agreement -- are gone. push()'s remove
  purge filters by 'lifecycle_phase is not None' instead of restating the
  phase tuple a third time.
- push() hot path: entity key computed outside the buffer lock (matching the
  existing pre-compute pattern) and the per-push throwaway set() from
  setdefault() eliminated.
- The four copy-pasted line_width alias bodies now delegate to shared
  module-level helpers carrying the warning text and units-first rationale
  once.
- Test hygiene: assert_entity_index_consistent moved to tests/infra_utils
  (the declared shared home) instead of a cross-module private import; new
  tests use the viser_server() context manager and broadcast_messages()
  helpers rather than leaking bare servers and hand-walking the buffer.

Kept: remove_from_buffer stays (public infra API for downstream servers,
now index-consistent); the coalesce-path double key computation and a
single-message thickness update were considered and skipped as marginal or
behavior-changing.
…e on remount

Three fixes from the code-review pass:

- add_arrows was the one line_width site migrated without a deprecation
  shim: passing the old kwarg raised TypeError and the handle alias was
  silently dead. It now warns-and-ignores (arrows have no thickness
  equivalent -- the old prop only fed a client fallback rendering path that
  no longer exists), with the same Never-typed overload pattern as the
  sibling APIs and a warn+no-op handle property.

- The share tunnel's wait_job/on_disconnect daemon threads caught only
  EOFError around dead-manager proxy reads, but those also raise
  BrokenPipeError/ConnectionResetError -- the same trio close() already
  tolerates -- printing a spurious traceback at interpreter exit after a
  failed tunnel.

- Line's fringe material applied its VISER_LINE_FRINGE define in an effect
  keyed on [showFringe], so a material remounted by a segments/dashed
  branch flip while showFringe stayed true missed the define and lost the
  antialiasing pass. The define is now applied via callback ref at attach
  time. Not reachable through viser's own wrappers (constant props), but
  Line is exported for reuse.
The prior commit accidentally included lockfile churn from a local npm
version rewriting peer flags; no dependency actually changed.
@brentyi
brentyi force-pushed the pre-release-fixes branch from 41d80ca to 7403b0c Compare August 14, 2026 05:47
The slider number box limits displayed/typed decimals via decimalScale,
fed by a server-computed precision that was derived from step alone. A
slider's legal values are min + k*step clamped to max, so an off-grid
min or max (e.g. min=0.5, step=1.0) produced values whose decimals the
box could neither display (2.5 rendered as "3") nor accept as input.
Precision is now the max decimal digits across min, max, and step, which
exactly bounds the grid's digits; integer sliders keep precision 0, so
the fractional-input rejection that motivated decimalScale is preserved
(pinned by the existing e2e test). New unit tests cover the grid rule
and a new e2e test verifies exact display and typed round-trip in a real
browser.
…unt, orbit-bound snap

Four fixes:

- Setting a camera pose whose up direction is parallel to the view (the
  canonical top-down/bird's-eye setup with default +Z up) raised
  ValueError from the new degenerate-basis hardening; inside
  exception-isolated on_client_connect callbacks the pose silently never
  applied. The server now degrades like the client's orbit controls
  (pole clamp): it falls back to the previous orientation's up vector,
  then to a world axis, and still sends the pose. Garbage input (NaN,
  zero up, zero look distance) still raises.

- Numeric-input display precision (decimalScale) was derived from step
  alone. Sliders now cover their value grid (min + k*step clamped to
  max), and number/vector inputs additionally cover creation-time
  values and bounds, via a shared _compute_precision_digits_covering
  helper -- so add_number(initial_value=0.25, step=0.5) no longer
  displays "0.2". Integer grids keep precision 0.

- Crossing the mobile breakpoint swapped the element type wrapping the
  canvas (dock surface mounted/unmounted), remounting the entire R3F
  subtree: new WebGL context and camera snapped to the initial pose on
  every phone rotation. The dock surface now stays mounted in websocket
  mode as a passthrough container and toggles an enabled flag (panes
  emptied, sync nodes dropped -- both siblings after the canvas, so the
  canvas never reparents). Spec section 3.6 updated to match.

- A camera legitimately placed beyond max_orbit_distance (large-
  coordinate scenes under the 1e4 default) was teleported to the bound
  by the first scroll tick, since camera-controls clamps every user
  dolly. The effective bound now ratchets to the current distance while
  outside the configured one: zooming in is smooth, zooming further out
  stays blocked, and the configured clamp restores once inside. An
  explicit server-set bound still pulls the camera in smoothly.

New coverage: top-down pose unit tests, precision-grid unit tests for
all five numeric widgets, and browser e2e tests asserting canvas
identity + camera pose survive breakpoint crossings and that far
cameras dolly smoothly instead of snapping.
@brentyi
brentyi merged commit d837cb7 into main Aug 16, 2026
23 checks passed
@brentyi
brentyi deleted the pre-release-fixes branch August 16, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant