patches: fix all safe pre-submission issues
- Blocker #1: add BOOL emacsMovedCursor = YES in patch 0005 so series compiles standalone; patch 0008 replaces it with !voiceoverSetPoint - Blocker #3: change all Daneel authorship to Martin Sukany - Zoom gate: remove ns_accessibility_enabled guards from Zoom code paths (0005 no longer adds them; 0008 retains the clarifying comment) - eassert: remove redundant BUFFER_LIVE_P eassert with contradictory comment in patch 0008 - macos.texi: integrate orphan 'Block-style cursors' paragraph as @item in the Known Limitations list - cindex: restore @cindex Zoom, cursor tracking (macOS) removed in 0008 - ChangeLog 0002: list only functions actually added in that patch - ChangeLog 0008: accurate description (remove wrong BUF_CHARS_MODIFF claim for ensureTextCache; ns_ax_buffer_text block_input was in 0001) - git apply --check: all 9 patches apply cleanly on fresh base Remaining open issue: BUF_MODIFF regression in patch 0007 (ensureTextCache O(N) rebuild per font-lock pass) requires design decision before submission.
This commit is contained in:
@@ -1,54 +1,50 @@
|
||||
From 1e3d3919fd41e4480a02190fb89bee1ef8107d62 Mon Sep 17 00:00:00 2001
|
||||
From: Daneel <daneel@sukany.cz>
|
||||
From 137cb30bb546a9599983c25a9873d1518ad8edee Mon Sep 17 00:00:00 2001
|
||||
From: Martin Sukany <martin@sukany.cz>
|
||||
Date: Mon, 2 Mar 2026 18:49:13 +0100
|
||||
Subject: [PATCH 8/8] ns: announce child frame completion candidates for
|
||||
Subject: [PATCH 9/9] ns: announce child frame completion candidates for
|
||||
VoiceOver
|
||||
|
||||
Child frame popups (Corfu, Company-mode) render completion candidates in
|
||||
a separate frame whose buffer is not accessible via the minibuffer
|
||||
overlay path. This patch scans child frame buffers for selected
|
||||
candidates and announces them via VoiceOver.
|
||||
Child frame popups (Corfu, Company-mode child frames) render completion
|
||||
candidates in a separate frame whose buffer is not accessible via the
|
||||
minibuffer overlay path. This patch scans child frame buffers for
|
||||
selected candidates and announces them via VoiceOver.
|
||||
|
||||
* src/nsterm.h (EmacsView): Add childFrameLastBuffer, childFrameLastModiff,
|
||||
childFrameLastCandidate, childFrameCompletionActive, lastEchoCharsModiff
|
||||
ivars; remove cachedOverlayModiffForText (unused after BUF_OVERLAY_MODIFF
|
||||
removed from ensureTextCache to prevent hl-line-mode O(N) rebuilds).
|
||||
Initialize voiceoverSetPoint, childFrameLastBuffer in initFrameFromEmacs:.
|
||||
ivars. Initialize childFrameLastBuffer to Qnil in initFrameFromEmacs:.
|
||||
(EmacsAccessibilityBuffer): Add voiceoverSetPoint ivar.
|
||||
* src/nsterm.m (ns_ax_buffer_text): Add block_input protection for
|
||||
Lisp calls; use record_unwind_protect_void to guarantee unblock_input.
|
||||
(ensureTextCache): Remove BUF_OVERLAY_MODIFF tracking; keep only
|
||||
BUF_CHARS_MODIFF. BUF_OVERLAY_MODIFF caused O(buffer-size) rebuilds
|
||||
with hl-line-mode (moves overlay on every post-command-hook).
|
||||
* src/nsterm.m (ns_ax_selected_child_frame_text): New function; scans
|
||||
child frame buffer text for the selected completion candidate.
|
||||
(announceChildFrameCompletion): New method; scans child frame buffers
|
||||
for selected completion candidates. Store childFrameLastBuffer as
|
||||
BVAR(b, name) (buffer name symbol, GC-reachable via obarray) rather
|
||||
than make_lisp_ptr to avoid dangling pointer after buffer kill.
|
||||
than a raw buffer pointer to avoid a dangling pointer after buffer kill.
|
||||
(postEchoAreaAnnouncementIfNeeded): New method; announces echo area
|
||||
changes for commands like C-g.
|
||||
changes (e.g., "Wrote file", "Quit") for commands that produce output
|
||||
while the minibuffer is inactive.
|
||||
(postAccessibilityNotificationsForFrame:): Drive child frame and echo
|
||||
area announcements.
|
||||
* doc/emacs/macos.texi: Fix dangling semicolon in GNUstep paragraph.
|
||||
area announcements. Add voiceoverSetPoint flag and singleLineMove
|
||||
adjacency detection to distinguish VoiceOver-initiated cursor moves
|
||||
from Emacs-initiated moves; sequential adjacent-line moves use
|
||||
next/previous direction, teleports use discontiguous. Add didTextChange
|
||||
guard to suppress overlay completion announcements while the user types.
|
||||
(setAccessibilitySelectedTextRange:): Set voiceoverSetPoint so that the
|
||||
subsequent notification cycle uses sequential direction.
|
||||
* doc/emacs/macos.texi (VoiceOver Accessibility): Update to document
|
||||
echo area announcements and VoiceOver rotor cursor synchronization.
|
||||
Remove Zoom section (covered by patch 0000). Fix dangling paragraph.
|
||||
---
|
||||
doc/emacs/macos.texi | 14 +-
|
||||
etc/NEWS | 25 ++-
|
||||
doc/emacs/macos.texi | 13 +-
|
||||
etc/NEWS | 25 +-
|
||||
src/nsterm.h | 21 ++
|
||||
src/nsterm.m | 514 +++++++++++++++++++++++++++++++++++++++----
|
||||
4 files changed, 510 insertions(+), 64 deletions(-)
|
||||
src/nsterm.m | 561 +++++++++++++++++++++++++++++++++++++------
|
||||
4 files changed, 529 insertions(+), 91 deletions(-)
|
||||
|
||||
diff --git a/doc/emacs/macos.texi b/doc/emacs/macos.texi
|
||||
index 8d4a7825d8..03a657f970 100644
|
||||
index 72ac3a9aa9..cf5ed0ff28 100644
|
||||
--- a/doc/emacs/macos.texi
|
||||
+++ b/doc/emacs/macos.texi
|
||||
@@ -278,7 +278,6 @@ restart Emacs to access newly-available services.
|
||||
@cindex VoiceOver
|
||||
@cindex accessibility (macOS)
|
||||
@cindex screen reader (macOS)
|
||||
-@cindex Zoom, cursor tracking (macOS)
|
||||
|
||||
When built with the Cocoa interface on macOS, Emacs exposes buffer
|
||||
content, cursor position, mode lines, and interactive elements to the
|
||||
@@ -309,10 +308,15 @@ Shift-modified movement announces selected or deselected text.
|
||||
@@ -309,10 +309,15 @@ Shift-modified movement announces selected or deselected text.
|
||||
The @file{*Completions*} buffer announces each completion candidate
|
||||
as you navigate, even while keyboard focus remains in the minibuffer.
|
||||
|
||||
@@ -115,7 +111,7 @@ index 7f917f93b2..bbec21b635 100644
|
||||
interface and eliminate the associated overhead.
|
||||
|
||||
diff --git a/src/nsterm.h b/src/nsterm.h
|
||||
index 21a93bc799..b5c9f84499 100644
|
||||
index a210ceba14..2edd7cd6e0 100644
|
||||
--- a/src/nsterm.h
|
||||
+++ b/src/nsterm.h
|
||||
@@ -504,9 +504,20 @@ typedef struct ns_ax_visible_run
|
||||
@@ -139,7 +135,7 @@ index 21a93bc799..b5c9f84499 100644
|
||||
@property (nonatomic, assign) ptrdiff_t cachedOverlayModiff;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedTextStart;
|
||||
@property (nonatomic, assign) ptrdiff_t cachedModiff;
|
||||
@@ -596,6 +607,14 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
|
||||
@@ -596,6 +607,14 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType)
|
||||
Lisp_Object lastRootWindow;
|
||||
BOOL accessibilityTreeValid;
|
||||
BOOL accessibilityUpdating;
|
||||
@@ -154,7 +150,7 @@ index 21a93bc799..b5c9f84499 100644
|
||||
#endif
|
||||
BOOL font_panel_active;
|
||||
NSFont *font_panel_result;
|
||||
@@ -665,6 +684,8 @@ typedef NS_ENUM (NSInteger, EmacsAXSpanType)
|
||||
@@ -665,6 +684,8 @@ typedef NS_ENUM(NSInteger, EmacsAXSpanType)
|
||||
- (void)rebuildAccessibilityTree;
|
||||
- (void)invalidateAccessibilityTree;
|
||||
- (void)postAccessibilityUpdates;
|
||||
@@ -164,42 +160,22 @@ index 21a93bc799..b5c9f84499 100644
|
||||
@end
|
||||
|
||||
diff --git a/src/nsterm.m b/src/nsterm.m
|
||||
index 8ef344d9fe..1acb64630a 100644
|
||||
index 54cee74401..6ba2229639 100644
|
||||
--- a/src/nsterm.m
|
||||
+++ b/src/nsterm.m
|
||||
@@ -1275,7 +1275,13 @@ If a completion candidate is selected (overlay or child frame),
|
||||
@@ -1275,6 +1275,12 @@ If a completion candidate is selected (overlay or child frame),
|
||||
static void
|
||||
ns_zoom_track_completion (struct frame *f, EmacsView *view)
|
||||
{
|
||||
- if (!ns_accessibility_enabled || !ns_zoom_enabled_p ())
|
||||
+ /* Zoom cursor tracking is controlled exclusively by
|
||||
+ ns_zoom_enabled_p (). We do NOT gate on ns_accessibility_enabled:
|
||||
+ users can run Zoom without VoiceOver, and those users should still
|
||||
+ get completion-candidate tracking. ns_accessibility_enabled is
|
||||
+ only set when a screen reader (VoiceOver or similar) activates the
|
||||
+ AX layer; it has no bearing on the Zoom feature. */
|
||||
+ if (!ns_zoom_enabled_p ())
|
||||
if (!ns_zoom_enabled_p ())
|
||||
return;
|
||||
if (!WINDOWP (f->selected_window))
|
||||
return;
|
||||
@@ -1393,7 +1399,7 @@ so the visual offset is (ov_line + 1) * line_h from
|
||||
(zoomCursorUpdated is NO). */
|
||||
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
||||
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
||||
- if (ns_accessibility_enabled && view && !view->zoomCursorUpdated
|
||||
+ if (view && !view->zoomCursorUpdated
|
||||
&& ns_zoom_enabled_p ()
|
||||
&& !NSIsEmptyRect (view->lastCursorRect))
|
||||
{
|
||||
@@ -3571,7 +3577,7 @@ EmacsView pixels (AppKit, flipped, top-left origin)
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
|
||||
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
||||
- if (ns_accessibility_enabled && ns_zoom_enabled_p ())
|
||||
+ if (ns_zoom_enabled_p ())
|
||||
{
|
||||
NSRect windowRect = [view convertRect:r toView:nil];
|
||||
NSRect screenRect
|
||||
@@ -7407,6 +7413,117 @@ visual line index for Zoom (skip whitespace-only lines
|
||||
|
||||
return nil;
|
||||
@@ -318,6 +294,21 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
/* Build accessibility text for window W, skipping invisible text.
|
||||
Populates *OUT_START with the buffer start charpos.
|
||||
Populates *OUT_RUNS with an array of visible runs and *OUT_NRUNS
|
||||
@@ -7440,9 +7557,13 @@ visual line index for Zoom (skip whitespace-only lines
|
||||
return @"";
|
||||
|
||||
specpdl_ref count = SPECPDL_INDEX ();
|
||||
+ /* block_input must precede record_unwind_protect_void (unblock_input):
|
||||
+ if anything between SPECPDL_INDEX and block_input were to throw,
|
||||
+ the unwind handler would call unblock_input without a matching
|
||||
+ block_input, corrupting the input-blocking reference count. */
|
||||
+ block_input ();
|
||||
record_unwind_current_buffer ();
|
||||
record_unwind_protect_void (unblock_input);
|
||||
- block_input ();
|
||||
if (b != current_buffer)
|
||||
set_buffer_internal_1 (b);
|
||||
|
||||
@@ -8605,6 +8726,11 @@ - (void)setAccessibilitySelectedTextRange:(NSRange)range
|
||||
|
||||
[self ensureTextCache];
|
||||
@@ -392,7 +383,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
+ - C-n/C-p: SelectedTextChanged carries granularity=line, but
|
||||
+ VoiceOver processes those keystrokes specially and may not
|
||||
+ produce speech; the explicit announcement is the reliable path.
|
||||
+ - Discontiguous jumps (]], M-<, xref, imenu, ...): granularity=line
|
||||
+ - Discontiguous jumps (]], M-<, xref, imenu, …): granularity=line
|
||||
+ in the notification is omitted (see above) so VoiceOver will
|
||||
+ not announce automatically; this explicit announcement fills
|
||||
+ the gap.
|
||||
@@ -422,7 +413,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
+ postEchoAreaAnnouncementIfNeeded (called from postAccessibilityUpdates
|
||||
+ before this per-element loop) so that they are never lost to a
|
||||
+ concurrent tree rebuild. For the inactive minibuffer (minibuf_level
|
||||
+ == 0), skip normal cursor and completion processing --- there is no
|
||||
+ == 0), skip normal cursor and completion processing — there is no
|
||||
+ meaningful cursor to track. */
|
||||
+ if (MINI_WINDOW_P (w) && minibuf_level == 0)
|
||||
+ return;
|
||||
@@ -452,12 +443,12 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9453,8 +9625,15 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
@@ -9453,37 +9625,44 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
displayed in the minibuffer. In normal editing buffers,
|
||||
font-lock and other modes change BUF_OVERLAY_MODIFF on
|
||||
every redisplay, triggering O(overlays) work per keystroke.
|
||||
- Restrict the scan to minibuffer windows. */
|
||||
- if (!MINI_WINDOW_P (w))
|
||||
- if (MINI_WINDOW_P (w))
|
||||
+ Restrict the scan to minibuffer windows.
|
||||
+ Skip overlay announcements when the user just typed a character
|
||||
+ (didTextChange). Completion frameworks update their overlay
|
||||
@@ -467,10 +458,66 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
+ characters inaudible. VoiceOver should read the overlay
|
||||
+ candidate only when the user navigates (C-n/C-p), not types. */
|
||||
+ if (!MINI_WINDOW_P (w) || didTextChange)
|
||||
goto skip_overlay_scan;
|
||||
|
||||
int selected_line = -1;
|
||||
@@ -9500,7 +9679,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
+ goto skip_overlay_scan;
|
||||
+
|
||||
+ int selected_line = -1;
|
||||
+ NSString *candidate
|
||||
+ = ns_ax_selected_overlay_text (b, BUF_BEGV (b), BUF_ZV (b),
|
||||
+ &selected_line);
|
||||
+ if (candidate)
|
||||
{
|
||||
- int selected_line = -1;
|
||||
- NSString *candidate
|
||||
- = ns_ax_selected_overlay_text (b, BUF_BEGV (b), BUF_ZV (b),
|
||||
- &selected_line);
|
||||
- if (candidate)
|
||||
+ /* Deduplicate: only announce when the candidate changed. */
|
||||
+ if (![candidate isEqualToString:
|
||||
+ self.cachedCompletionAnnouncement])
|
||||
{
|
||||
- /* Deduplicate: only announce when the candidate changed. */
|
||||
- if (![candidate isEqualToString:
|
||||
- self.cachedCompletionAnnouncement])
|
||||
- {
|
||||
- self.cachedCompletionAnnouncement = candidate;
|
||||
-
|
||||
- /* Announce the candidate text directly via NSApp.
|
||||
- Do NOT post SelectedTextChanged --- that would cause
|
||||
- VoiceOver to read the AX text at the cursor position
|
||||
- (the minibuffer input line), not the overlay candidate.
|
||||
- AnnouncementRequested with High priority interrupts
|
||||
- any current speech and announces our text. */
|
||||
- NSDictionary *annInfo = @{
|
||||
- NSAccessibilityAnnouncementKey: candidate,
|
||||
- NSAccessibilityPriorityKey:
|
||||
- @(NSAccessibilityPriorityHigh)
|
||||
- };
|
||||
- ns_ax_post_notification_with_info (
|
||||
- NSApp,
|
||||
- NSAccessibilityAnnouncementRequestedNotification,
|
||||
- annInfo);
|
||||
- }
|
||||
+ self.cachedCompletionAnnouncement = candidate;
|
||||
+
|
||||
+ /* Announce the candidate text directly via NSApp.
|
||||
+ Do NOT post SelectedTextChanged --- that would cause
|
||||
+ VoiceOver to read the AX text at the cursor position
|
||||
+ (the minibuffer input line), not the overlay candidate.
|
||||
+ AnnouncementRequested with High priority interrupts
|
||||
+ any current speech and announces our text. */
|
||||
+ NSDictionary *annInfo = @{
|
||||
+ NSAccessibilityAnnouncementKey: candidate,
|
||||
+ NSAccessibilityPriorityKey:
|
||||
+ @(NSAccessibilityPriorityHigh)
|
||||
+ };
|
||||
+ ns_ax_post_notification_with_info (
|
||||
+ NSApp,
|
||||
+ NSAccessibilityAnnouncementRequestedNotification,
|
||||
+ annInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9497,7 +9676,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
self.cachedPoint = point;
|
||||
self.cachedMarkActive = markActive;
|
||||
|
||||
@@ -490,7 +537,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
NSInteger direction = ns_ax_text_selection_direction_discontiguous;
|
||||
if (point > oldPoint)
|
||||
direction = ns_ax_text_selection_direction_next;
|
||||
@@ -9512,6 +9702,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
@@ -9509,6 +9699,7 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
|
||||
/* --- Granularity detection --- */
|
||||
NSInteger granularity = ns_ax_text_selection_granularity_unknown;
|
||||
@@ -498,7 +545,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
[self ensureTextCache];
|
||||
if (cachedText && oldPoint > 0)
|
||||
{
|
||||
@@ -9526,7 +9717,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
@@ -9523,7 +9714,18 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
NSRange newLine = [cachedText lineRangeForRange:
|
||||
NSMakeRange (newIdx, 0)];
|
||||
if (oldLine.location != newLine.location)
|
||||
@@ -518,12 +565,16 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
else
|
||||
{
|
||||
NSUInteger dist = (newIdx > oldIdx
|
||||
@@ -9548,34 +9750,23 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
@@ -9545,38 +9747,23 @@ frameworks like Vertico bump BOTH BUF_MODIFF (via text property
|
||||
granularity = ns_ax_text_selection_granularity_line;
|
||||
}
|
||||
|
||||
- /* Treat all moves as Emacs-initiated until voiceoverSetPoint
|
||||
- tracking is introduced (subsequent patch). */
|
||||
- BOOL emacsMovedCursor = YES;
|
||||
-
|
||||
- /* Programmatic jumps that cross a line boundary (]], [[, M-<,
|
||||
- xref, imenu, ...) are discontiguous: the cursor teleported to an
|
||||
- xref, imenu, …) are discontiguous: the cursor teleported to an
|
||||
- arbitrary position, not one sequential step forward/backward.
|
||||
- Reporting AXTextSelectionDirectionDiscontiguous causes VoiceOver
|
||||
- to re-anchor its rotor browse cursor at the new
|
||||
@@ -566,7 +617,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
{
|
||||
NSWindow *win = [self.emacsView window];
|
||||
if (win)
|
||||
@@ -9734,6 +9925,13 @@ - (NSRect)accessibilityFrame
|
||||
@@ -9735,6 +9922,13 @@ - (NSRect)accessibilityFrame
|
||||
if (vis_start >= vis_end)
|
||||
return @[];
|
||||
|
||||
@@ -580,7 +631,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
block_input ();
|
||||
specpdl_ref blk_count = SPECPDL_INDEX ();
|
||||
record_unwind_protect_void (unblock_input);
|
||||
@@ -10040,6 +10238,10 @@ - (void)dealloc
|
||||
@@ -10042,6 +10236,10 @@ - (void)dealloc
|
||||
#endif
|
||||
|
||||
[accessibilityElements release];
|
||||
@@ -591,7 +642,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
[[self menu] release];
|
||||
[super dealloc];
|
||||
}
|
||||
@@ -11489,6 +11691,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
|
||||
@@ -11491,6 +11689,9 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
|
||||
|
||||
windowClosing = NO;
|
||||
processingCompose = NO;
|
||||
@@ -601,7 +652,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
scrollbarsNeedingUpdate = 0;
|
||||
fs_state = FULLSCREEN_NONE;
|
||||
fs_before_fs = next_maximized = -1;
|
||||
@@ -12797,6 +13002,161 @@ - (id)accessibilityFocusedUIElement
|
||||
@@ -12799,6 +13000,156 @@ - (id)accessibilityFocusedUIElement
|
||||
The existing elements carry cached state (modiff, point) from the
|
||||
previous redisplay cycle. Rebuilding first would create fresh
|
||||
elements with current values, making change detection impossible. */
|
||||
@@ -702,11 +753,6 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
+ childFrameLastBuffer = BVAR (b, name);
|
||||
+ childFrameLastModiff = modiff;
|
||||
+
|
||||
+ /* BUFFER_LIVE_P(b) is already checked at entry (line above the
|
||||
+ EQ comparison). No code between that check and here can kill
|
||||
+ the buffer, so this second check is redundant. */
|
||||
+ eassert (BUFFER_LIVE_P (b));
|
||||
+
|
||||
+ /* Skip buffers larger than a typical completion popup.
|
||||
+ This avoids scanning eldoc, which-key, or other child
|
||||
+ frame buffers that are not completion UIs. */
|
||||
@@ -763,7 +809,7 @@ index 8ef344d9fe..1acb64630a 100644
|
||||
- (void)postAccessibilityUpdates
|
||||
{
|
||||
NSTRACE ("[EmacsView postAccessibilityUpdates]");
|
||||
@@ -12807,11 +13167,69 @@ - (void)postAccessibilityUpdates
|
||||
@@ -12809,11 +13160,69 @@ - (void)postAccessibilityUpdates
|
||||
|
||||
/* Re-entrance guard: VoiceOver callbacks during notification posting
|
||||
can trigger redisplay, which calls ns_update_end, which calls us
|
||||
|
||||
Reference in New Issue
Block a user