@cplieger/web-terminal-ui 5.4.1 → 5.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "5.4.1",
3
+ "version": "5.4.3",
4
4
  "type": "module",
5
5
  "description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
6
6
  "license": "MPL-2.0",
@@ -43,9 +43,9 @@ import {
43
43
  } from "./model.js";
44
44
  import { browserNotifierEnv, createNotifier } from "./notify.js";
45
45
  import {
46
- REORDER_LEAN_PX,
47
46
  REORDER_MOVE_EPS_PX,
48
47
  REORDER_REST_MS,
48
+ REORDER_STILL_MS,
49
49
  REORDER_SETTLE_MS,
50
50
  REORDER_SHIFT_TRANS,
51
51
  REORDER_SLOT_FADE_MS,
@@ -667,10 +667,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
667
667
  let activeId: string | null = null;
668
668
  let draggingEl: HTMLElement | null = null;
669
669
  // --- Desktop reorder preview state (mechanism below, near dragTargetBefore) ---
670
- // The chips currently carrying an inline transform: the lean during a hold,
671
- // or the slide that commits it. One set, because both stages write the same
672
- // two properties on the same elements and one settle function has to be able
673
- // to hand every one of them back to the stylesheet.
670
+ // The chips currently carrying an inline displacement from the commit slide.
671
+ // A set plus one settle function, so whatever a slide wrote can always be handed
672
+ // back to the stylesheet — including when a second drag interrupts the first.
674
673
  const shifted = new Set<HTMLElement>();
675
674
  let shiftTimer: ReturnType<typeof setTimeout> | null = null;
676
675
  // The slot waiting to open and the rest window that will open it.
@@ -681,6 +680,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
681
680
  let restTimer: ReturnType<typeof setTimeout> | null = null;
682
681
  let restBefore: HTMLElement | null = null;
683
682
  let restX: number | null = null;
683
+ // When the pointer last actually MOVED. A stationary dragover this long after it
684
+ // is believed as a stop (see trackRest).
685
+ let restMovedAt = 0;
684
686
  // Whether a `drop` fired for the drag in flight. It is the exact signal for
685
687
  // "the user released deliberately" as opposed to "the drag was abandoned":
686
688
  // Escape and a refused release fire dragend with no drop at all. dragend
@@ -1833,33 +1835,30 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1833
1835
  // in. The chip you were dragging looked like the slot it left, the slot it
1834
1836
  // was over, and the slot it would end up in.
1835
1837
  //
1836
- // The preview splits that into two different answers for two different
1837
- // actions, and the dragged chip stays in the flow throughout as the slot it
1838
- // will land in (`.wt-tab-dragging`, which 30-tabs.css renders as an empty
1839
- // dashed outline rather than a dimmed copy of the tab — the solid copy is
1840
- // the drag image under the pointer, and one of the two had to stop
1841
- // pretending to be the tab):
1838
+ // The preview answers a MOVING pointer and a STOPPED one differently, and the
1839
+ // dragged chip stays in the flow throughout as the slot it will land in
1840
+ // (`.wt-tab-dragging`, which 30-tabs.css renders as an empty dashed outline
1841
+ // rather than a dimmed copy of the tab — the solid copy is the drag image
1842
+ // under the pointer, and one of the two had to stop pretending to be the tab):
1842
1843
  //
1843
- // - SWEEPING across the strip only LEANS the chips a commit would displace,
1844
- // a few px toward where they would go (leanToward). Instant, and it is
1845
- // what says where the tab will fall while you are still looking for the
1846
- // spot. Nothing is rearranged, so crossing five tabs does not reshuffle
1847
- // the strip five times.
1848
- // - COMING TO REST opens the slot for real (trackRest -> commitSlot): one
1849
- // DOM move, the displaced chips slide from their old positions to their
1850
- // new ones, and the slot fades in at its new home.
1844
+ // - SWEEPING across the strip rearranges NOTHING. Crossing five tabs used to
1845
+ // move all five, which is the whole complaint.
1846
+ // - COMING TO REST opens the slot (trackRest -> commitSlot): one DOM move,
1847
+ // the displaced chips slide from their old positions to their new ones, and
1848
+ // the slot fades in at its new home.
1851
1849
  //
1852
- // Rest is DETECTED, not waited out: REORDER_REST_MS is re-armed by movement,
1853
- // so it expires a rest window after the last movement rather than a fixed
1854
- // time after a decision. An earlier version made every commit serve a fixed
1855
- // hold, which is the wrong shape for a direct manipulation — it delayed the
1856
- // one case that should be instant (you found the spot and stopped) to
1857
- // protect a case the lean already covers (you are still moving). It also
1858
- // grew a progress bar to explain the wait, which is the tell that the wait
1859
- // should not have been there.
1850
+ // Rest is DETECTED, not waited out: REORDER_REST_MS is re-armed by movement, so
1851
+ // it expires a rest window after the last MOVEMENT rather than a fixed time
1852
+ // after a decision. Two shapes were tried and removed before this one, and both
1853
+ // failures are worth keeping: a fixed one-second HOLD (wrong shape for a direct
1854
+ // manipulation — it delayed the one case that should be instant, and grew a
1855
+ // progress bar to explain the wait, which is the tell that the wait should not
1856
+ // have been there), and a sweep-time LEAN of the chips a commit would displace
1857
+ // (it read as a second, competing preview and its distance from the real move
1858
+ // was too small to tell them apart).
1860
1859
  //
1861
1860
  // A release never waits either: `drop` commits whatever slot is pending
1862
- // (flushRest), so dropping mid-sweep lands the tab where the lean said.
1861
+ // (flushRest), so dropping mid-sweep still lands the tab where it was headed.
1863
1862
  //
1864
1863
  // dropTargetBefore returns the first tab whose horizontal midpoint is past
1865
1864
  // x (the element the dragged tab should sit before), or null to drop at the
@@ -1869,8 +1868,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1869
1868
  //
1870
1869
  // It hit-tests LAYOUT geometry (offsetLeft/offsetWidth), never
1871
1870
  // getBoundingClientRect. That is not an optimisation, it is what makes an
1872
- // animated reorder hit-testable at all: a rect read while a chip is mid-lean or
1873
- // mid-slide returns the INTERPOLATED position, so the preview's own motion feeds
1871
+ // animated reorder hit-testable at all: a rect read while a chip is mid-slide
1872
+ // returns the INTERPOLATED position, so the preview's own motion feeds
1874
1873
  // back into the decision that produced it and the strip oscillates between two
1875
1874
  // slots for as long as the pointer sits near a boundary. Layout offsets are the
1876
1875
  // chip's position in the FLOW, which a transform does not affect at all.
@@ -1942,87 +1941,25 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1942
1941
  shifted.clear();
1943
1942
  }
1944
1943
 
1945
- // leanToward tips the chips a commit would displace a few px toward their
1946
- // destination, for as long as the pointer is still choosing. No settle timer: the
1947
- // lean is a held state, not an animation with an end, and a timer that expired
1948
- // under it would snap it away mid-sweep.
1944
+ // trackRest is the whole gate, called on every dragover over the strip.
1949
1945
  //
1950
- // This is the SWEEP preview and it is the only thing a moving pointer produces:
1951
- // it says where the tab will fall without rearranging anything, which is what lets
1952
- // a drag cross five tabs without the strip reshuffling five times.
1946
+ // Sweeping and settling are DIFFERENT actions and get different answers. A
1947
+ // travelling pointer rearranges nothing at all; the moment it comes to REST the
1948
+ // slot opens.
1953
1949
  //
1954
- // Chips that were leaning and no longer would be are written back to zero in
1955
- // the SAME pass, so a changed candidate eases from one lean into the other
1956
- // rather than snapping through rest first.
1957
- function leanToward(before: HTMLElement | null): void {
1958
- if (!draggingEl) {
1959
- return;
1960
- }
1961
- // The preference is read live, so it can flip mid-gesture. Clearing here rather
1962
- // than returning bare is what honours a mid-hold opt-in: a bare return would
1963
- // leave the lean already applied under the previous preference sitting there.
1964
- if (prefersReduce()) {
1965
- endShift();
1966
- return;
1967
- }
1968
- const chips = [...scroller.querySelectorAll<HTMLElement>(".wt-tab")];
1969
- const from = chips.indexOf(draggingEl);
1970
- const to = before === null ? chips.length : chips.indexOf(before);
1971
- if (from < 0 || to < 0) {
1972
- return;
1973
- }
1974
- // Moving right past the chips in between shifts them LEFT, and moving left
1975
- // shifts the chips it displaces RIGHT — the direction the user reads as
1976
- // "that tab is getting out of the way".
1977
- const movers = from < to ? chips.slice(from + 1, to) : chips.slice(to, from);
1978
- const dx = from < to ? -REORDER_LEAN_PX : REORDER_LEAN_PX;
1979
- const next = new Map<HTMLElement, number>();
1980
- for (const el of movers) {
1981
- next.set(el, dx);
1982
- }
1983
- for (const el of shifted) {
1984
- if (!next.has(el)) {
1985
- next.set(el, 0);
1986
- }
1987
- }
1988
- applyShift(next, REORDER_SHIFT_TRANS);
1989
- }
1990
- // releaseLean eases every leaning chip back to rest and settles it, for when
1991
- // a candidate is withdrawn (the pointer came back to the committed slot, or left
1992
- // the strip). endShift would be wrong here: it strips the transition in the same
1993
- // write, so the lean would snap home.
1994
- function releaseLean(): void {
1995
- if (shifted.size === 0) {
1996
- return;
1997
- }
1998
- // ...except under reduced motion, where snapping IS the requested behaviour and
1999
- // easing back would animate the very motion the preference opted out of.
2000
- if (prefersReduce()) {
2001
- endShift();
2002
- return;
2003
- }
2004
- const rest = new Map<HTMLElement, number>();
2005
- for (const el of shifted) {
2006
- rest.set(el, 0);
2007
- }
2008
- applyShift(rest, REORDER_SHIFT_TRANS);
2009
- shiftTimer = setTimeout(endShift, REORDER_SETTLE_MS);
2010
- }
2011
- // trackRest is the whole gate, called on every dragover over the strip.
1950
+ // Rest is detected from TWO signals, and separating them is what lets the slot
1951
+ // open promptly without a fast sweep committing every slot it crosses:
2012
1952
  //
2013
- // Sweeping and settling are DIFFERENT actions and get different answers. While the
2014
- // pointer is still travelling the strip only LEANS the chips it would displace
2015
- // (leanToward): instant, cheap, and it is what says where the tab will fall while
2016
- // you are still looking for the spot. Nothing is rearranged, so crossing five tabs
2017
- // does not rearrange the strip five times. The moment the pointer comes to REST the
2018
- // slot opens for real.
1953
+ // - a `dragover` at an unchanged position is POSITIVE evidence of a stop, so it
1954
+ // commits after a short REORDER_STILL_MS confirmation. This is the signal that
1955
+ // normally decides, and the reason the delay is short.
1956
+ // - the absence of events is only a FALLBACK (armRestNet), for a browser that
1957
+ // stops delivering dragover entirely. That one has to out-wait the drag loop's
1958
+ // 350ms cadence, so it is long — and it is rarely what decides.
2019
1959
  //
2020
- // Rest is detected, not waited out. An earlier version made every commit wait a
2021
- // fixed hold, which is the wrong shape for a direct manipulation: it delayed the
2022
- // one case that should be instant (you have found the spot and stopped) in order to
2023
- // protect the case that never needed protecting (you are still moving, and the lean
2024
- // already covers it). The timer here is re-armed by MOVEMENT, so it expires a rest
2025
- // window after the last movement rather than a fixed time after a decision.
1960
+ // One quiet window had to serve both jobs before this, which is why it could not be
1961
+ // shortened: the timer deciding responsiveness was the same one that had to survive
1962
+ // the cadence.
2026
1963
  function trackRest(clientX: number): void {
2027
1964
  const dragged = draggingEl;
2028
1965
  if (!dragged) {
@@ -2037,30 +1974,40 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2037
1974
  cancelRest();
2038
1975
  return;
2039
1976
  }
2040
- // Lean toward a NEW candidate. The test is guarded on a pending rest rather than
2041
- // on `before !== restBefore` alone, because null is a legitimate candidate here
2042
- // ("past the last chip") and would otherwise be indistinguishable from the
2043
- // initial "nothing pending" state — which is exactly how the first sweep of a
2044
- // drag toward the end of the strip ended up leaning nothing at all.
2045
- if (restTimer === null || before !== restBefore) {
2046
- restBefore = before;
2047
- leanToward(before); // the sweep preview: where this will fall
2048
- }
2049
- // Movement pushes the commit out; stillness lets the armed timer run down to it.
2050
- // A stationary pointer that has nothing armed still needs one — a browser may
2051
- // deliver dragover repeatedly at an unchanged position, and the first of those is
2052
- // the only signal that the pointer arrived and stopped.
2053
- if (moved || restTimer === null) {
2054
- if (restTimer !== null) {
2055
- clearTimeout(restTimer);
2056
- }
2057
- restTimer = setTimeout(() => {
2058
- restTimer = null;
2059
- const target = restBefore;
2060
- restBefore = null;
2061
- commitSlot(target);
2062
- }, REORDER_REST_MS);
1977
+ restBefore = before;
1978
+ const now = Date.now();
1979
+ if (moved) {
1980
+ // Still travelling. Push the net out and wait.
1981
+ restMovedAt = now;
1982
+ armRestNet();
1983
+ return;
1984
+ }
1985
+ // A dragover at an UNCHANGED position is positive evidence that the pointer has
1986
+ // stopped, which is a far stronger signal than the absence of events — and it is
1987
+ // what lets the slot open promptly instead of out-waiting the drag loop's 350ms
1988
+ // cadence. The elapsed check filters the coincidence where one event of a sweep
1989
+ // lands within REORDER_MOVE_EPS_PX of the previous (a reversal, or a frame whose
1990
+ // motion was almost all vertical); a real stop clears it on the next event.
1991
+ if (now - restMovedAt >= REORDER_STILL_MS) {
1992
+ flushRest();
1993
+ return;
1994
+ }
1995
+ armRestNet(); // too soon to believe; keep the net alive
1996
+ }
1997
+ // armRestNet is the no-events fallback: if dragover stops arriving altogether, no
1998
+ // stationary event will ever confirm the stop, so this commits anyway. Sized to
1999
+ // out-wait the cadence (see REORDER_REST_MS) because it is the one timer that a
2000
+ // fast sweep could otherwise expire between two of its own events.
2001
+ function armRestNet(): void {
2002
+ if (restTimer !== null) {
2003
+ clearTimeout(restTimer);
2063
2004
  }
2005
+ restTimer = setTimeout(() => {
2006
+ restTimer = null;
2007
+ const target = restBefore;
2008
+ restBefore = null;
2009
+ commitSlot(target);
2010
+ }, REORDER_REST_MS);
2064
2011
  }
2065
2012
  function cancelRest(): void {
2066
2013
  if (restTimer === null) {
@@ -2069,11 +2016,10 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2069
2016
  clearTimeout(restTimer);
2070
2017
  restTimer = null;
2071
2018
  restBefore = null;
2072
- releaseLean();
2073
2019
  }
2074
2020
  // flushRest commits a pending slot at once, because a release is a decision. It is
2075
2021
  // what stops the rest window from ever being felt as a floor on the gesture: drop
2076
- // mid-sweep and the tab lands where the lean said it would. Only a `drop` calls it;
2022
+ // mid-sweep and the tab still lands where it was headed. Only a `drop` calls it;
2077
2023
  // an abandoned drag reaches dragend without one and reverts.
2078
2024
  function flushRest(): void {
2079
2025
  if (restTimer === null) {
@@ -2103,18 +2049,18 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2103
2049
  // belongs HERE and not only in the CSS, because these transitions are written
2104
2050
  // inline and no stylesheet gate can reach them; the switcher's release reel
2105
2051
  // guards its own call site the same way. Read live, so toggling the OS setting
2106
- // mid-drag takes effect on the next commit and strips any existing lean.
2052
+ // mid-drag takes effect on the very next commit.
2107
2053
  if (prefersReduce()) {
2108
2054
  endShift();
2109
2055
  mutate();
2110
2056
  return;
2111
2057
  }
2112
- // FIRST — where each chip is right now, LEAN INCLUDED, so a slide continues
2113
- // out of the lean instead of jumping back through rest first. The switcher's
2114
- // release reel captures its live swipe preview the same way, and for the
2115
- // same reason. Rects, not layout offsets: this is VISUAL position, which is
2116
- // what the animation interpolates (hit-testing wants the opposite, see
2117
- // dropTargetBefore).
2058
+ // FIRST — where each chip is right now, MID-SLIDE INCLUDED, so a second commit
2059
+ // continues from wherever the first one got to instead of jumping back through
2060
+ // rest first. The switcher's release reel captures its live swipe preview the
2061
+ // same way, and for the same reason. Rects, not layout offsets: this is VISUAL
2062
+ // position, which is what the animation interpolates (hit-testing wants the
2063
+ // opposite, see dropTargetBefore).
2118
2064
  const first = new Map<HTMLElement, number>();
2119
2065
  for (const el of scroller.querySelectorAll<HTMLElement>(".wt-tab")) {
2120
2066
  if (el !== hold) {
@@ -2159,14 +2105,14 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2159
2105
  // an OSC title) renders the committed order rather than flickering through
2160
2106
  // whatever the pointer is hovering.
2161
2107
  //
2162
- // Every early return releases the lean, and that is not belt-and-braces. The
2163
- // caller nulls `restTimer` BEFORE calling in (both the rest timer and flushRest
2164
- // do), so by the time control arrives here `cancelRest` has already become a no-op
2165
- // and nothing else would ever hand those chips back.
2108
+ // The caller nulls `restTimer` BEFORE calling in (both the rest timer and
2109
+ // flushRest do), so `cancelRest` is already a no-op by the time control arrives
2110
+ // here: an early return must leave nothing behind that only cancelRest would
2111
+ // Nothing exists pre-commit for it to have cleared, and a slide already in flight
2112
+ // is owned by its own settle timer.
2166
2113
  function commitSlot(before: HTMLElement | null): void {
2167
2114
  const dragged = draggingEl;
2168
2115
  if (!dragged?.isConnected) {
2169
- releaseLean();
2170
2116
  return; // the dragged session was closed elsewhere mid-gesture
2171
2117
  }
2172
2118
  // The pending slot was chosen a rest window ago, and a session closed in another
@@ -2176,11 +2122,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2176
2122
  // child, which would abandon the drop mid-way and leave DOM order and tabList
2177
2123
  // disagreeing. null stays legal: it means "past the last chip".
2178
2124
  if (before !== null && before.parentNode !== scroller) {
2179
- releaseLean();
2180
2125
  return;
2181
2126
  }
2182
2127
  if (before === dragged || before === dragged.nextElementSibling) {
2183
- releaseLean();
2184
2128
  return; // already in that slot
2185
2129
  }
2186
2130
  flipTo(() => {
@@ -2270,8 +2214,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2270
2214
  }
2271
2215
  }
2272
2216
 
2273
- // endReorderPreview ends the GESTURE's state: the pending hold (easing any
2274
- // lean home as it goes) and the slot's fade.
2217
+ // endReorderPreview ends the GESTURE's state: the pending rest and the slot's
2218
+ // fade.
2275
2219
  //
2276
2220
  // It deliberately does NOT stop a slide that is already playing. A slide is an
2277
2221
  // animation with its own settle timer, and cutting it here is exactly what
@@ -2291,9 +2235,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2291
2235
  // sweep). Two removals matter, for different reasons:
2292
2236
  //
2293
2237
  // - the pending reference chip: commitSlot would hand a detached node to
2294
- // insertBefore. It guards that too, but standing the hold down here means the
2295
- // user's lean comes off at the moment the target vanishes rather than a second
2296
- // later, and a fresh dragover can arm a slot that still exists.
2238
+ // insertBefore. It guards that too, but withdrawing the pending slot here means
2239
+ // a fresh dragover arms against a chip that still exists rather than waiting out
2240
+ // a rest window against one that does not.
2297
2241
  // - the DRAGGED chip: the gesture is over and there is no source left to deliver
2298
2242
  // a dragend. A browser is not obliged to fire one for a removed source, and
2299
2243
  // without this the feature would keep `draggingEl` set to a detached node for
@@ -3353,7 +3297,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
3353
3297
  return false;
3354
3298
  });
3355
3299
  // Drive the reorder preview while dragging a tab over the strip. A sweep only
3356
- // leans the chips; the slot opens when the pointer comes to rest (trackRest).
3300
+ // rearranges nothing; the slot opens when the pointer comes to rest (trackRest).
3357
3301
  bar.addEventListener("dragover", (e) => {
3358
3302
  if (!draggingEl) {
3359
3303
  return;
@@ -21,37 +21,41 @@ import { statusPhrase, statusRevealsDot } from "./model.js";
21
21
  // motion is spelled in — index.ts owns the mechanism, strip.ts owns the values,
22
22
  // the same split switcher.ts keeps for the mobile swipe.
23
23
 
24
- /** The quiet window (ms) that means the pointer has STOPPED. When it elapses with no
25
- * movement, the strip opens the slot the pointer is over.
24
+ /** How long (ms) the pointer must have been un-moved before a stationary `dragover` is
25
+ * believed as "stopped".
26
26
  *
27
- * This is a rest detector, not a hold. The difference is the whole interaction: a hold
28
- * makes you wait a fixed time whatever you do, which is exactly the wrong shape here —
29
- * a reorder is a direct manipulation and the answer to "when does it move" is "when you
30
- * stop looking for the spot". Sweeping across the strip only leans the chips (cheap,
31
- * instant, and it is what says where the tab will fall); coming to rest is what commits.
27
+ * This is the number that decides how fast the slot opens, and it is small because a
28
+ * `dragover` at an UNCHANGED position is positive evidence of rest — far better
29
+ * evidence than the absence of events, which is all the fallback below has. The drag
30
+ * loop keeps delivering events while the pointer is held still, so a genuinely stopped
31
+ * pointer announces itself; this only filters the coincidence where one event of a
32
+ * sweep happens to land within REORDER_MOVE_EPS_PX of the previous one (a direction
33
+ * reversal, or a frame where the motion was almost entirely vertical). */
34
+ export const REORDER_STILL_MS = 90;
35
+
36
+ /** Fallback guard (ms): commit the pending slot this long after the last MOVEMENT, for
37
+ * the case where `dragover` stops arriving altogether and no stationary event ever
38
+ * confirms the stop.
32
39
  *
33
- * Sized to sit above the gap between two dragover events during a real sweep and below
34
- * human perception of delay, so stopping reads as immediate. If a browser's dragover
35
- * cadence is ever slower than this, the failure mode is benign: the strip commits the
36
- * slot the pointer is over slightly eagerly, which is what it would have done anyway.
40
+ * **Its floor is set by the platform, not by taste, and this is the timer that floor
41
+ * applies to.** HTML5 drag-and-drop only guarantees a `dragover` every 350ms — the drag
42
+ * loop runs on that cadence, not per mouse movement — so a guard at or below it can
43
+ * expire BETWEEN two events of a fast sweep, and the strip then commits every slot the
44
+ * pointer crosses. Measured in production: at 120ms a quick pass over five tabs moved
45
+ * all five.
37
46
  *
38
- * A release never waits for it either — a drop commits whatever is pending (flushRest). */
39
- export const REORDER_REST_MS = 120;
47
+ * A pure quiet-window design has to make this ONE number carry both jobs, which is why
48
+ * it could not be shortened — the same timer that decides responsiveness was the one
49
+ * that had to out-wait the cadence, so 500ms was safe and felt slow. Splitting rest
50
+ * DETECTION (the stationary event above) from the no-events NET (here) lets each be
51
+ * sized for its own job. This one is rarely the deciding signal at all. */
52
+ export const REORDER_REST_MS = 450;
40
53
 
41
54
  /** Movement (px) between two dragover events below which the pointer counts as still.
42
55
  * A hand resting on a mouse is never perfectly still, and treating a 1px tremor as a
43
56
  * sweep would keep pushing the commit out for as long as someone held the tab. */
44
57
  export const REORDER_MOVE_EPS_PX = 3;
45
58
 
46
- /** How far (px) each chip a commit would displace leans toward its destination while
47
- * the pointer is still sweeping.
48
- *
49
- * Small enough to read as a lean rather than a move, and large enough to be
50
- * unambiguous at a glance: it is what answers "where will this fall" during the sweep,
51
- * before anything has actually moved. The same purpose the mobile switcher's
52
- * PREVIEW_PEEK_MAX serves for its live swipe. */
53
- export const REORDER_LEAN_PX = 7;
54
-
55
59
  /** The one transition both preview stages use (the lean, and the slide that
56
60
  * commits it).
57
61
  *