Skip to content

Commit d33f3b4

Browse files
committed
fix(examples): capture control baseline early on user interaction
The 2s settle window was meant to let example async-init `data.set` calls land in the baseline before user diffs start tracking. But if the user changes a control inside that window (e.g. pasting a Scene.url into gaussian-splatting/lod-streaming right after the page loads), the modified value gets folded into the baseline at settle end and so never appears in the shared URL — even though subsequent changes outside the window do. Capture the baseline as soon as the user interacts with #controlPanel (via document-level capture-phase pointerdown/focusin), whichever comes first. Async-init writes that fire before the user touches anything still flow into the baseline as intended; anything after the first interaction is treated as a real user diff.
1 parent a413d22 commit d33f3b4

1 file changed

Lines changed: 28 additions & 9 deletions

File tree

examples/src/app/components/Example.mjs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ class Example extends TypedComponent {
237237
/** @type {ReturnType<typeof setTimeout> | null} */
238238
_settleTimer = null;
239239

240+
/** @type {((e: Event) => void) | null} */
241+
_earlyCaptureHandler = null;
242+
240243
_applying = 0;
241244

242245
/**
@@ -717,7 +720,8 @@ class Example extends TypedComponent {
717720
/**
718721
* Apply URL-provided control overrides to the observer and arm the settle window.
719722
* Baseline is captured once at the end of the window so async-init `data.set` calls
720-
* don't pollute it.
723+
* don't pollute it — or as soon as the user touches the controls panel, whichever
724+
* comes first, so user input during the window isn't folded into the baseline.
721725
*
722726
* @param {Observer} observer - Example observer.
723727
*/
@@ -727,10 +731,16 @@ class Example extends TypedComponent {
727731
flattenLeaves(readState().controls, '', flat);
728732
this._loadControls = flat;
729733
this._baseline = {};
730-
if (this._settleTimer) {
731-
clearTimeout(this._settleTimer);
732-
}
734+
this._clearSettle();
733735
this._settleTimer = setTimeout(this._captureBaseline, SETTLE_WINDOW_MS);
736+
this._earlyCaptureHandler = (e) => {
737+
const target = /** @type {Node | null} */ (e.target);
738+
if (target && document.getElementById('controlPanel')?.contains(target)) {
739+
this._captureBaseline();
740+
}
741+
};
742+
document.addEventListener('pointerdown', this._earlyCaptureHandler, true);
743+
document.addEventListener('focusin', this._earlyCaptureHandler, true);
734744
this._applying++;
735745
for (const path of Object.keys(this._loadControls)) {
736746
if (observer.has(path)) {
@@ -740,8 +750,20 @@ class Example extends TypedComponent {
740750
this._applying--;
741751
}
742752

753+
_clearSettle() {
754+
if (this._settleTimer !== null) {
755+
clearTimeout(this._settleTimer);
756+
this._settleTimer = null;
757+
}
758+
if (this._earlyCaptureHandler) {
759+
document.removeEventListener('pointerdown', this._earlyCaptureHandler, true);
760+
document.removeEventListener('focusin', this._earlyCaptureHandler, true);
761+
this._earlyCaptureHandler = null;
762+
}
763+
}
764+
743765
_captureBaseline() {
744-
this._settleTimer = null;
766+
this._clearSettle();
745767
const { observer } = this.state;
746768
if (!observer) {
747769
return;
@@ -759,10 +781,7 @@ class Example extends TypedComponent {
759781
if (!observer) {
760782
this._baseline = {};
761783
this._loadControls = {};
762-
if (this._settleTimer) {
763-
clearTimeout(this._settleTimer);
764-
this._settleTimer = null;
765-
}
784+
this._clearSettle();
766785
}
767786
if (observer) {
768787
this._observerHandle = observer.on('*:set', this._handleControlSet);

0 commit comments

Comments
 (0)