Skip to content

feat(core): gestures attach for every pointer (spec:AST-094 FR43) - #8665

Draft
vjeux wants to merge 1701 commits into
meta:mainfrom
vjeux:uikit/f2-every-pointer
Draft

vjeux wants to merge 1701 commits into
meta:mainfrom
vjeux:uikit/f2-every-pointer

Conversation

@vjeux

@vjeux vjeux commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #8360. Review only this pull request's change: vjeux/astryx@045f666...c6c02fc

Why

On a laptop or a desktop, Astryx's gestures ignored the mouse, the trackpad and the pen: a bottom sheet could not be dragged with a mouse, and on a touchscreen laptop a finger did not claim as it does on a phone. The gesture host attached only where the primary pointer is coarse.

What led to it

The approved gesture spec says gestures attach for every pointer, a mouse, a pen and a finger alike (spec:AST-094 FR43).

Options and tradeoffs

  • Keep gestures coarse-only: simplest, and it contradicts the spec.
  • Install the host's page-wide listeners on every page: every desktop page pays for touch handling.
  • Attach for every pointer, but under a fine pointer let only an element that uses a gesture listen, with one passive pointerdown that installs the page listeners for that press only.

Why this choice

The third gives every pointer the same gestures while a page without gestures registers nothing (FR24). A mouse press on a control is a press, painted at once (FR11, DEC-21); a right click or a macOS ctrl-click starts none. A finger's or a pen's press brings the cancelable touchend and the menu canceller, so a claimed lift clicks nothing (FR21), and a kept press keeps its finger. A claimed mouse or pen drag takes no new text selection; a press claimed at its own pointerdown keeps the page's, so an action group's Copy still has it (FR41). The screen-edge pan stays a finger's (FR43).

How to check it

  1. Mouse-drag a bottom sheet's handle. Expected: it follows.
  2. Select text, then click an action group's Copy. Expected: the text is copied.
  3. Mouse-drag across text on a draggable element. Expected: nothing gets selected.
  4. On a touchscreen laptop, hold a stepper's button. Expected: it repeats; the page does not scroll.
  5. Inspect a desktop page with no gesture. Expected: no listeners from Astryx.

Evidence

  • Unit cases fail before each change: the fine-pointer listener budget, the mouse press, the context click, the armed press's claiming listeners, the kept press, the hidden page, the drag's selection hold, the selection a press claimed at its pointerdown keeps.
  • Real input in desktop Chromium and WebKit: a mouse drag pans; no touch listener exists without touch.

Writing rubric results

R1 narration: FR41's new sentence said "today's behavior"; now "its released behavior".
R2 research: none.
R3 evidence: the FR41 verification row names cases, not changes.
R4 rejected alternatives: none added.
R5 change references: none.
R6 open questions: whether a drag keeps a prior selection is now answered in the sentence.

cixzhang and others added 30 commits October 26, 2026 18:02
meta#8056 landed squashed; main's motion README, animator and its tests equal
meta#8056's final head (2b264d3), so this PR's versions, built on that head,
stand.
… listening

uninstall() removed the scroll and scrollend listeners but kept
fingerScrollers and endedWhileDown, so a finger scroll still running when
the host stopped listening (a per-event press lifting under a fine
pointer, or the pointer turning fine mid-momentum) never ended, and
isFingerScroll answered true for later program scrolls. Both sets now
clear with the listeners. The new test fails on the previous head.

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
…ning) into the arena fixes

Here the finger-scroll watch lives in fingerScroll.ts, whose listen(false)
already clears both sets, so the host keeps this PR's version; the new
test reads the watch through fingerScroll's functions.
…nce, so a still finger's long press begins

A browser with Touch Events sends them beside the pointer events for one
finger. With inputMode 'pointer-events' the host still read both, so the
finger was two touches and a long press (one touch required) never
began; a moving finger's pan still worked, which hid it. The host now
registers no Touch Event listener in that mode: the finger comes from its
pointer events alone.

Test (failing first): under 'pointer-events', a touch pointerdown plus its
touchstart is one touch, and a long press held past its duration begins
and ends.
spec:AST-094 FR20: a watcher registers only passive listeners, but the
host gave every tracked finger's target a cancelable touchend. The host
now counts, per target, the fingers a participant could claim (a
recognizer that claims touches, or a press consumer): touchend is
cancelable only while that count is above zero and passive otherwise,
swapped like touchmove. The new test (a watcher's touch gets passive
touchmove, touchend and touchcancel; a tap's touchend stays cancelable)
fails on the previous head.
…ond route around ./internal/*

Lab's per-module tsconfig paths (core's motion, gestures and
touchPresentation) and the Sandbox/docsite resolver that served them opened
a route around architecture:react-component-runtime INV13's ./internal/*
subpath exports. Nothing in Lab imports those paths here; they're dropped,
and the import guard now fails on any Lab import core doesn't export and on
any Lab tsconfig alias for core.

Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…) into the host line

The watchTarget call keeps this PR's blocking test (a kept-press consumer
under a coarse pointer blocks too) beside meta#8589's claiming flag.
 landed) into the experimental touch layer

# Conflicts:
#	packages/core/src/motion/README.md
… detaches

The deferred unmount demotes the detached node, and demote asks for a fresh
will-change read; the detached node now gets none, so no timer outlives the
element (architecture:react-component-runtime INV5).

Co-authored-by: Christopher Chedeau <vjeux@users.noreply.github.com>
…#8522, her Lab route through core's exports only, and her will-change timer fix) into the presented start
…ine opacity

The case that detaches a node mid-dip and attaches it again checked, 25 ms
in, that the dip had written its low opacity inline. With the presented
start, a curve's inline value is the one on screen as it starts and its end
is written when it retires, so 25 ms into the fade out the node holds the
fade's starting opacity while the model is already partway down. That case
now checks what this guarantees: the call owns the opacity inline and the
dip is under way; its assertions after the second attach are unchanged.

A dip that starts from a dimmed opacity still leaves that low value on the
node while the move waits to land, so a new case detaches such a node
mid-dip and attaches it again: the move has landed and the node is
visible. It fails (0.3 instead of 1) when the pending unmount does not land
the waiting move on the node just detached, so that landing stays
covered.
…via meta#8076) into the host line

useGestureRecognizer keeps both fine-pointer options: this PR's
perEventPointer and meta#8309's actionGroup.
…style holds

Whatever the inline style holds while the curve runs (the end, or, once
the start-value write of meta#8340 is in, the start), iOS WebKit's rect and
hit tests stay where the animation started: the note now says only what
holds either way.
# Conflicts:
#	packages/core/src/gestures/GestureHost.ts
…t stream

Ports cixzhang's meta#8072 action-group press (d2c3c78) onto this pull
request's shared input stream, with no change to what it does. Its
per-press listeners were registered straight on the document and the
window with capture constants this layer no longer has, so the merged host
failed to compile, and its window click listener would have doubled the
input stream's click subscriber (two onClick calls per click once the host
installs, and a listener its guard never removed).

The press now subscribes its pointer's moves and lift and the window's own
blur through subscribeInput for as long as it is held. It subscribes to
click only while the host is not installed, drops that subscription after
the lift's task, and an installing host takes it over, so a click is heard
once. Her two host tests read the press's listeners on the window, where
the stream registers them; a new case counts onClick per click before and
after the host installs mid-press, and fails against each of three mutants
(no takeover, no drop, no subscription).
…uard

At this layer the host has no click listener of its own: the click guard
(utils/clickGuard.ts) hears every click through one listener per window.
cixzhang's meta#8072 action-group press, ported onto the input stream below,
still disarmed the host's swallower and subscribed the host's onClick, so
the merged host failed to compile. The press now disarms the click guard
when it begins and subscribes no click: what it does is unchanged.

The click-count case becomes: a group press adds no click listener of its
own, before or after the host installs mid-press. GestureHost.test.ts
resets the click guard after each case, so a guard one case armed no
longer keeps its pointerdown subscription into the next (it hid the coarse
host's pointerdown listener from a later case).
… that finger until it lifts (spec:AST-094 FR14)

A recognition that claimed its touches and was then cancelled gave them
back to the browser unless another recognition owned them, so a finger
resting there could pinch-zoom the page. A gesture that cancels itself on
a finger's move, a long press dragged past its tolerance after it began,
gave back a finger that was dragging: the host stopped cancelling its moves
and the page scrolled the rest of the drag, a hand change FR14 forbids and
UIKit never makes (its scroll view never takes a touch another recognizer
had). A cancel during a finger's move now keeps that finger claimed until
it lifts; a resting finger is given back as before, and a waiting
recognition that fails still gives its claim back.

The new arena case fails on the previous code: the dragging finger reads
unclaimed right after the cancel.
… scroller under it is at the edge

spec:AST-094 FR19 and DEC-18 (proposed in meta#8628): a scrollEdge pan may
cancel its touch's moves from the first one, only while every scroller
under the finger sits at its edge and the finger moves away from it, and
only the moves the browser would rubber-band; it still begins at FR17's
10 px. The first case reads the first 2 px move cancelled at the edge and
fails on this head; the others keep what must not change: room to scroll
leaves every move to the browser, a first move toward the content too, a
pan without scrollEdge cancels nothing before it begins, and a touch the
browser took for a scroll stays the browser's when the list reaches its
edge.
vjeux added 7 commits October 29, 2026 10:16
…core/internal/touch

Core ships the internal entry @astryxdesign/core/internal/touch, which
re-exports the gesture primitives for Astryx's own packages, and the
Lab-import guard fails a Lab import of core outside core's exports
(architecture:react-component-runtime INV13). The README and the barrel's
header named internal workspace imports and said nothing here is
exported; they now name that entry.
…ill needs the element, and the listeners that cancel

The release note, the stream's header, the host's comment and the README
said the stream is one passive listener per event type and that a
document-capture stopPropagation no longer starves the recognizers. Such a
stop never hid a listener on the same node, and some subscriptions cancel.
They now say what changed: an app's window-capture stopPropagation, or a
document-capture stopImmediatePropagation registered before core, no
longer hides a touch; a recognizer that must claim still needs the touch to
reach its element; the gesture host's touchend, click and contextmenu
cancel while a claimer is attached and the page hold's touchmove while it
holds; and the stream stays ahead of an app's window-capture listener only
until a type's listener is re-added. The README's list of blocking
touchmove listeners gains the claimed touch's target and the document
while a claiming element moves.
…default it moves

The {stylexOptions} form of astryxStylex now passes StyleX
treeshakeCompensation: true unless the caller sets it. spec:AST-017 FR3
asks the release note to name the old and new default and the option
that restores the old one; it now does.
…er and for the two stops the input stream fixes

- A still finger with sub-pixel jitter over a scroll view keeps its tap
  and scrolls nothing (spec:AST-094 FR18–FR22).
- A tap is still recognized under an app's window-capture
  stopPropagation, and under a document-capture
  stopImmediatePropagation registered before core's: the recognizers hear
  touches on the window's capture phase.
Chromium and WebKit pass.
…s once

Where nothing that claims is attached, the host's touchend listener is
passive and cannot cancel the engine's own click after a retargeted tap
clicks what is drawn. The shared click guard swallows that late click
whatever is attached; this case pins it, as asked on meta#8293.
meta#8360 now carries the review fixes of the foundation it holds. The two
conflicts are this pull request's own files:
- GestureHost.ts: meta#8360 counts the attachments that ask for an action
  group; here the fine-pointer group listener and the per-event recognizer
  set are gone (every pointer is served, FR43), so the merge keeps the
  count and drops those two.
- GestureHost.test.ts: both sides added the same assertion to the
  second-finger case; the merge keeps this side's comment. meta#8360's
  action-group lifetime case counts a group's own pointerdown under a fine
  pointer, which this pull request replaces with one passive pointerdown
  on any element that uses a gesture (its own idle-listener case), so the
  merge leaves it out.
… attached, in this host's terms

meta#8360's action-group lifetime case pins that an element stays a group
while any attachment asks and stops with the last. Here every element
that uses a gesture is served as a group under a fine pointer, so the
same invariant reads as the element's one passive pointerdown: with two
gestures attached, detaching either keeps it, and detaching the last
removes it. It passes on this head.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request is on hold for a spec decision: the rule that a claimed mouse or pen drag clears and refuses text selection changes spec:AST-094 FR41, which today keeps a mouse's released behavior, and the FR41 sentence added for it is not yet approved at this head. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner), or FR41 and its verification row are amended on main, and the decision should also say whether the rule may clear a selection made before the drag; the description should report the ## Writing rubric results for that edit, which approval reads it against. Independent of that decision, these need changes:

  • Land #8360 first (or rebase onto main once it lands) and move the click-guard test-setup fix there, so this diff carries only its own change, today 28,168 authored lines with 2,482 of them its own.
  • Under a fine pointer, an armed press installs no cancelable touchend or contextmenu listener (GestureHost.ts:1299), a kept press never keeps its finger (GestureHost.ts:1950-1953), and a page hidden mid-press keeps the page listeners (GestureHost.ts:2767-2771).
  • Several comments, README passages and one test still describe the coarse-only host, for example GestureHost.ts:84-86, gestures/README.md:316-320 and GestureHost.test.ts:2599.

[Automated review]

}
this.isArmedPress = true;
this.syncKeptPresses();
this.install();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under a fine pointer this installs the window listeners without syncClaimingListeners(), which only syncListeners() runs after install() (:1106-1110). For the whole press the window's touchend stays passive and no contextmenu canceller is subscribed, so a claimed finger's lift is not cancelled (spec:AST-094 FR21: no click or compatibility mouse event reaches the page) and a claimed long press can also open the browser's menu. Calling this.syncClaimingListeners() right after this.install(), with a fine-pointer test of a claimed finger's touchend and a mid-claim contextmenu, would close it.

Comment on lines +1952 to +1953
(this.isCoarse &&
this.arena.consumerOf(id)?.keepsTouch?.(id) === true) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A fine pointer's kept press gets its blocking touchmove during the armed press (:930, :1305-1311), but this coarse-only condition means its moves are never cancelled, so a finger that moves on a held stepper on a touchscreen laptop is not kept (spec:AST-094 FR11, DEC-21) while the listener still delays scrolls that start there. Dropping this.isCoarse && here and at :1783, or not registering the listener under a fine pointer, would make it consistent; the README says both things today (README.md:249-251, :338-340).

Comment on lines +2767 to +2771
private readonly onVisibilityChange = (): void => {
if (this.doc.visibilityState === 'hidden') {
this.cancelEverything(this.arena.now());
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A hidden page cancels the press here but leaves isArmedPress set, so shouldListen() stays true and the page's listeners remain until the next lift anywhere (spec:AST-094 FR24; architecture:react-component-runtime INV13). onBlur calls releaseArmedPress() for the same case; this branch could too, with a listener-count test.

expect(slider.style.getPropertyValue('user-select')).toBe('');
});

it("keeps selection for every contact where the primary pointer is fine: a touchscreen laptop's finger, a desktop pen and a mouse (spec:AST-094 FR19, spec:AST-017 FR59)", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This title states the superseded rule: locksSelectionFor now locks a finger and a pen under any primary pointer (utils/selectionLock.ts:60-62, selectionLock.test.ts:97). The test passes only because it reads the iOS-only user-select write after a touchstart with no arming pointerdown; it could assert the lock a fine-pointer finger and pen now take.

selection.removeAllRanges();
}
};
view?.getSelection()?.removeAllRanges();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes whatever selection the page holds when a recognizer claims the drag, including one made before the drag that the drag did not replace. Whether FR41's proposed "for that drag only" covers that is part of the spec decision this pull request waits on.

vjeux added 8 commits October 29, 2026 12:16
…esture host's subscriptions

The gesture host subscribes no click: the one cancelling click subscriber
is the click guard's, added at a window's first arm and kept. The release
note, the stream's header and the gestures README listed click with the
host's touchend and contextmenu; they now name the guard's own.
meta#8360's input-stream notes no longer list click among the gesture host's
subscriptions. The merge is clean.
… lift and the browser's menu, keeps a kept press's finger, and goes when the page hides

From the review of this pull request, three cases fail on its head:
- an armed press installed the page listeners without the claiming
  ones, so a claimed finger's touchend stayed passive and the browser's
  menu opened during a claimed long press (spec:AST-094 FR21);
- a kept press got its blocking touchmove during an armed press, but its
  moves were cancelled only where the primary pointer is coarse, so a
  held stepper on a touchscreen laptop lost its finger (FR11, DEC-21);
- a page hidden mid-press cancelled the press but kept the page's
  listeners until the next lift anywhere (FR24).
…lift and the browser's menu, keeps a kept press's finger, and goes when the page hides

- A finger's or a pen's armed press now brings the claiming listeners
  (the cancelable touchend and the contextmenu canceller) with the page
  listeners, as wherever the host installs, so a claimed lift clicks
  nothing and a claimed hold opens no browser menu (spec:AST-094 FR21). A
  mouse's press needs neither and adds neither.
- A kept press keeps its finger under any pointer: its target blocks and
  its moves are cancelled once its press is delivered, not only where the
  primary pointer is coarse (FR11, DEC-21).
- A page hidden mid-press releases the armed press as a blur does, so the
  page's listeners go with the cancelled press (FR24).
…'s selection

An action group's press is a long press with no minimum duration, so it
claims at the press's pointerdown, before any movement. The drag
selection hold then cleared the page's selection, so a plain click on a
group's button left its Copy or Cut with nothing to act on. The case
fails on this head: the selection is gone after the pointerdown.
…age's selection

The drag selection hold cleared the page's selection when any mouse or
pen contact was claimed, but an action group's press claims at its
pointerdown, before it has moved or selected anything, so a plain click
on a group's button left its Copy or Cut with nothing to act on. A claim
before any movement now keeps the page's selection: the hold still
refuses a new one, and puts the page's back over a selection that
appears while it runs. A claim after movement clears what its drag
selected, as before (spec:AST-094 FR41).

The case before the new one now waits for its hold to end a task after
the lift, so no hold outlives that test.
…t every pointer does

- GestureHost's header, the gestures README and coarsePointer.ts no
  longer describe the coarse-only host: a finger or a pen takes the
  touch-down lock under any primary pointer, a kept press's finger is
  kept during a fine pointer's press too, and recognizers attach for
  every pointer while the page listeners last a press (spec:AST-094 FR19,
  FR24, FR41, FR43).
- The fine-pointer selection case asserted the replaced rule and passed
  only because it read iOS WebKit's inline lock without iOS WebKit: it
  now asserts the lock a fine pointer's finger and pen take, and that a
  mouse takes none.
- FR41's sentence and its verification row say that a press claimed at
  its own pointerdown, before it moves, keeps the page's selection, while
  a drag that moved before its claim loses what it selected.
…ion, in the present tense

The sentence said a drag that moved before its claim loses the selection
it started, but the hold clears the page's selection then, whatever made
it; and an unclaimed mouse drag kept "today's" behavior, a phrase that
ages. It now says a claim after movement clears the page's selection, a
press claimed at its own pointerdown keeps it, and an unclaimed mouse
drag keeps its released behavior.
@vjeux

vjeux commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in this push:

  • an armed press under a fine pointer now brings the cancelable touchend and the contextmenu canceller for a finger or a pen, keeps a kept press's finger, and ends when the page hides: bf1a58b → 1585de0;
  • a press claimed at its own pointerdown keeps the page's selection, so a group's Copy or Cut still has it: f46d28f → bce55d5;
  • the coarse-only notes, the coarsePointer.ts header and the fine-pointer selection test now describe every pointer, and FR41's sentence and Verification row say what a claimed drag and a claimed press do with a selection: 4de6442.

Whether FR41 may suppress selection for a claimed drag stays the owner's decision.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request is on hold for a spec decision: the rule that a claimed mouse or pen drag clears the page's text selection and refuses a new one changes spec:AST-094 FR41, which today keeps a mouse's released behavior, and the FR41 amendment written for it is not yet approved at this head. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request (the author may, as an engineering owner), or asks for the amendment to change; the description should report the ## Writing rubric results for that edit, which approval reads it against. Independent of that decision, these need changes:

  • Land #8360 first (or rebase onto main once it lands) and move the click-guard test-setup fix there, so this diff carries only its own change: today 28,406 authored lines, 2,788 of them its own.
  • Several comments and README passages still say mouse selection is unchanged or that content under a pan stays selectable, for example GestureRecognizer.ts:71-73, gestures/README.md:316-320 and PressTracker.ts:90-94.

[Automated review]

Comment on lines +71 to +73
* hold does not bundle it. Content under a sheet's or a pager's pan stays
* selectable, so there is none by default; a mouse keeps desktop selection
* either way. A long press brings its own when it has a hold (a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A claimed mouse pan now clears the page's selection and refuses a new one until after the lift (GestureHost.ts:1659-1690, utils/selectionLock.ts:147-213), so "stays selectable" and "a mouse keeps desktop selection either way" no longer hold for a mouse. Describe what a claimed mouse or pen drag does, or whatever the FR41 decision settles.

Comment on lines +317 to +320
finger or a pen takes the lock under any primary pointer, a touchscreen
laptop and a desktop pen tablet included; a mouse never does, so mouse
selection is unchanged (`spec:AST-017` FR59); content under a sheet's or a
pager's pan stays selectable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A mouse never takes the hold lock, but a claimed mouse pan does change mouse selection (this README's own claimed-drag paragraph at :265-270), so "mouse selection is unchanged" and "content under a sheet's or a pager's pan stays selectable" contradict it for a mouse. The checklist's "content drags keep selection" (:574) has the same problem.

Comment on lines +90 to +94
* For a control whose own record defines a hold action (a stepper's
* hold-repeat): pass `lockSelection` (gestures/selectionLock.ts) to turn
* text selection and the callout off for each press, where the primary
* pointer is coarse (`spec:AST-094` FR41). A control whose hold does
* nothing passes none. @default undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

locksSelectionFor now locks a finger or a pen under any primary pointer (utils/selectionLock.ts:60-62), so "where the primary pointer is coarse" no longer describes when this lock applies.

*/
retain(): () => void {
this.retainers += 1;
// A watch is not an attach: no warning where the pointer is fine.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change deleted the fine-pointer warning, so this comment explains the absence of something that no longer exists; it can go.

Comment on lines +7 to +8
* @position Internal util, the one primary-click rule: the gesture host
* starts a press only for one, and the press controller paints only one;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utils/pressFeedback.ts never imports isPrimaryClick: its mouse path checks event.button === 0 itself (pressFeedback.ts:492), so "the press controller paints only one" describes a rule the controller does not share.

This branch was successfully deployed

1 active deployment
Preview — c6c02fcf Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants