docs: fix 41 unresolved dartdoc references

Convert non-resolving [refs] in doc comments to backtick code-spans across 24
lib/ files (param/field names out of doc scope, method refs on other classes,
non-API strings like regex char-classes and command ids). Verified 0
"unresolved doc reference" warnings via `dart doc --validate-links`. The
dart-doc CI gate (test.yml) never ran before — Gitea Actions was inactive — so
this debt had accumulated unchecked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 22:09:55 +02:00
co-authored by Claude Opus 4.8
parent 8e0f33b79f
commit c08f249b00
24 changed files with 45 additions and 45 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
/// This file is pure (no Flutter): it turns a flat [ConversationItem] list /// This file is pure (no Flutter): it turns a flat [ConversationItem] list
/// into a list of [RenderGroup]s — each either a first-class [StickyItem] or /// into a list of [RenderGroup]s — each either a first-class [StickyItem] or
/// a foldable [FoldedCluster]. The widget layer renders sticky items as /// a foldable [FoldedCluster]. The widget layer renders sticky items as
/// before and clusters as one [activity card]. Kept separate + unit-tested /// before and clusters as one `activity card`. Kept separate + unit-tested
/// because the fold rules are the load-bearing part. /// because the fold rules are the load-bearing part.
library; library;
+2 -2
View File
@@ -53,8 +53,8 @@ String nextSafePermissionMode(String current) {
} }
/// Status-line segments split around the permission-mode badge so the UI can /// Status-line segments split around the permission-mode badge so the UI can
/// render the mode as an interactive control between them (T-226). [leading] /// render the mode as an interactive control between them (T-226). `leading`
/// is the model; [trailing] joins context / cost / rate-limit. Either may be /// is the model; `trailing` joins context / cost / rate-limit. Either may be
/// null when there's nothing to show. /// null when there's nothing to show.
({String? leading, String? trailing}) statusSegmentsAroundMode(SessionStatus s) { ({String? leading, String? trailing}) statusSegmentsAroundMode(SessionStatus s) {
final trailing = [ final trailing = [
@@ -28,8 +28,8 @@ class ConversationController extends ChangeNotifier {
} }
/// Build a controller fed from the kernel [MessageBus] — it consumes /// Build a controller fed from the kernel [MessageBus] — it consumes
/// the [ConversationItem]s a [TranscriptPublisher] writes onto /// the [ConversationItem]s a `TranscriptPublisher` writes onto
/// [publisher]/[channel]. Decouples the view from the reader so several /// `publisher`/[channel]. Decouples the view from the reader so several
/// panels can render the same conversation (team work, T-139/T-140). /// panels can render the same conversation (team work, T-139/T-140).
factory ConversationController.fromBus({required MessageBus messages, String channel = ClaudeConversation.leadChannel, Future<void> Function()? onDispose}) { factory ConversationController.fromBus({required MessageBus messages, String channel = ClaudeConversation.leadChannel, Future<void> Function()? onDispose}) {
final stream = messages final stream = messages
@@ -4,7 +4,7 @@
/// process), so to "watch the same output" we open our OWN read-only follower /// process), so to "watch the same output" we open our OWN read-only follower
/// on the file the command tails. This never spawns a process and never /// on the file the command tails. This never spawns a process and never
/// touches Claude's command — it just reads the file as it grows, like /// touches Claude's command — it just reads the file as it grows, like
/// `tail -f`, and hands new bytes to [onData]. /// `tail -f`, and hands new bytes to `onData`.
/// ///
/// Pure dart:io/dart:async (no Flutter) so it's unit-testable. Polls rather /// Pure dart:io/dart:async (no Flutter) so it's unit-testable. Polls rather
/// than using a watcher so it works uniformly across platforms and survives /// than using a watcher so it works uniformly across platforms and survives
@@ -51,7 +51,7 @@ class AgentRosterRow extends StatefulWidget {
final void Function(String memberName, String text) onInjectSubmit; final void Function(String memberName, String text) onInjectSubmit;
final void Function(String memberName) onClose; final void Function(String memberName) onClose;
/// Called when the badge cycles to a new [mode] string for this member. /// Called when the badge cycles to a new `mode` string for this member.
/// Handles both safe-trio clicks and confirmed bypass. The parent sends /// Handles both safe-trio clicks and confirmed bypass. The parent sends
/// the mode to the session via `StreamJsonSession.setPermissionMode`. /// the mode to the session via `StreamJsonSession.setPermissionMode`.
final void Function(String memberName, String mode) onSetPermissionMode; final void Function(String memberName, String mode) onSetPermissionMode;
+1 -1
View File
@@ -5,7 +5,7 @@
/// Rendered in the composer zone (not inline in the conversation) so /// Rendered in the composer zone (not inline in the conversation) so
/// interaction and conversation widgets don't mix — the pane swaps it in for /// interaction and conversation widgets don't mix — the pane swaps it in for
/// the text input while a prompt is open. The decision is returned via /// the text input while a prompt is open. The decision is returned via
/// [onResolve]; the pane then removes the card. /// `onResolve`; the pane then removes the card.
/// ///
/// Plain [ClideButton]s (Semantics buttons → keyboard/AT reachable), no /// Plain [ClideButton]s (Semantics buttons → keyboard/AT reachable), no
/// hover-revealed chrome that would fight the buttons. /// hover-revealed chrome that would fight the buttons.
@@ -2,8 +2,8 @@
/// ///
/// A session is a `claude` stream-json process clide spawns and renders; a /// A session is a `claude` stream-json process clide spawns and renders; a
/// pane is just a *view* on one. The orchestrator decouples a session's /// pane is just a *view* on one. The orchestrator decouples a session's
/// lifecycle from any pane: [spawn] starts and registers it, [show]/[hide] /// lifecycle from any pane: `spawn` starts and registers it, `show`/`hide`
/// toggle visibility WITHOUT tearing the process down, and [close] kills it. /// toggle visibility WITHOUT tearing the process down, and `close` kills it.
/// This is the one primitive behind teammate / secondary tab / forked branch /// This is the one primitive behind teammate / secondary tab / forked branch
/// (Phase 2): they are all just managed sessions shown as panes. /// (Phase 2): they are all just managed sessions shown as panes.
/// ///
+2 -2
View File
@@ -5,8 +5,8 @@
/// and the full workspace pane read from this one model — they share state, /// and the full workspace pane read from this one model — they share state,
/// they do NOT each hold their own copy. /// they do NOT each hold their own copy.
/// ///
/// [postAsUser] is the user's write path: it routes by @tag (one agent or /// `postAsUser` is the user's write path: it routes by @tag (one agent or
/// broadcast) and, when the interrupt flag is set, calls [interrupt()] on the /// broadcast) and, when the interrupt flag is set, calls `interrupt()` on the
/// target session THEN delivers the message. /// target session THEN delivers the message.
/// ///
/// Flutter-free on purpose: this module (like [TeamBroker]) runs under /// Flutter-free on purpose: this module (like [TeamBroker]) runs under
@@ -2,7 +2,7 @@
/// ///
/// Displays the live broker chat timeline as colour-coded rows and provides a /// Displays the live broker chat timeline as colour-coded rows and provides a
/// quick @-post composer. Tapping the pop-out icon opens the full chat pane /// quick @-post composer. Tapping the pop-out icon opens the full chat pane
/// ([claude.team-chat] workspace tab). /// (`claude.team-chat` workspace tab).
/// ///
/// Both this widget and [TeamChatPane] read from the same [TeamChatModel] — /// Both this widget and [TeamChatPane] read from the same [TeamChatModel] —
/// there is one model, two surfaces. /// there is one model, two surfaces.
@@ -24,7 +24,7 @@
/// ///
/// # Version drift-guard /// # Version drift-guard
/// If the envelope `version` field has an unfamiliar major version the reader /// If the envelope `version` field has an unfamiliar major version the reader
/// warns via [onWarn] (or stderr if omitted) and degrades gracefully — it /// warns via `onWarn` (or stderr if omitted) and degrades gracefully — it
/// parses whatever it can and skips the rest rather than crashing. /// parses whatever it can and skips the rest rather than crashing.
library; library;
@@ -227,7 +227,7 @@ class TranscriptReader {
/// [pollInterval] controls how often the reader polls for new data and /// [pollInterval] controls how often the reader polls for new data and
/// session switches (default 500 ms). /// session switches (default 500 ms).
/// ///
/// [onWarn] receives warning messages from the version drift-guard. /// `onWarn` receives warning messages from the version drift-guard.
/// If omitted, warnings are written to stderr. /// If omitted, warnings are written to stderr.
TranscriptReader( TranscriptReader(
this.workspacePath, { this.workspacePath, {
@@ -419,7 +419,7 @@ class TranscriptReader {
} }
/// Parse a single JSONL line into its items (forwarding any version /// Parse a single JSONL line into its items (forwarding any version
/// warnings to [onWarn]). Public so tests exercise the real parser. /// warnings to `onWarn`). Public so tests exercise the real parser.
List<ConversationItem> parseLine(String line) { List<ConversationItem> parseLine(String line) {
final parsed = parseTranscriptChunk(line); final parsed = parseTranscriptChunk(line);
for (final w in parsed.warnings) { for (final w in parsed.warnings) {
+1 -1
View File
@@ -13,7 +13,7 @@ import 'vim_edit_ops.dart';
/// Tier-2 editor pane. Shows one tab per open buffer via the shared /// Tier-2 editor pane. Shows one tab per open buffer via the shared
/// [MultitabPane] (the same strip the Claude pane uses); the body /// [MultitabPane] (the same strip the Claude pane uses); the body
/// reflects the daemon's active buffer. The daemon ([EditorRegistry]) /// reflects the daemon's active buffer. The daemon (`EditorRegistry`)
/// is the source of truth for which buffers are open and which is /// is the source of truth for which buffers are open and which is
/// active — the local [MultitabController] is reconciled from it, and /// active — the local [MultitabController] is reconciled from it, and
/// tab gestures (select / close) are routed back as `editor.activate` /// tab gestures (select / close) are routed back as `editor.activate`
+2 -2
View File
@@ -9,7 +9,7 @@
/// the `vim.yaml` preset (T-65) guards its bindings with `when: vim.normal` /// the `vim.yaml` preset (T-65) guards its bindings with `when: vim.normal`
/// etc. Nothing reaches into this object across the builtin boundary. /// etc. Nothing reaches into this object across the builtin boundary.
/// ///
/// The whole layer is gated by [enabled], which the Vim extension ties to /// The whole layer is gated by `enabled`, which the Vim extension ties to
/// the active preset: under a non-Vim preset the flags are cleared so they /// the active preset: under a non-Vim preset the flags are cleared so they
/// can never affect another preset's bindings. /// can never affect another preset's bindings.
library; library;
@@ -47,7 +47,7 @@ class VimModeService extends ChangeNotifier {
/// Whether the Vim layer is live. False under non-Vim presets. /// Whether the Vim layer is live. False under non-Vim presets.
bool get enabled => _enabled; bool get enabled => _enabled;
/// The active mode. Meaningful only while [enabled]; defaults to /// The active mode. Meaningful only while `enabled`; defaults to
/// [VimMode.normal] and resets to it whenever the layer is enabled. /// [VimMode.normal] and resets to it whenever the layer is enabled.
VimMode get mode => _mode; VimMode get mode => _mode;
+1 -1
View File
@@ -13,7 +13,7 @@
/// ///
/// Scope context is a `Map<String, bool>` keyed by named flags (e.g. /// Scope context is a `Map<String, bool>` keyed by named flags (e.g.
/// `palette.open`, `editor.focused`). Producing services call /// `palette.open`, `editor.focused`). Producing services call
/// [setScopeFlag] when their state changes; consumers reference the /// `setScopeFlag` when their state changes; consumers reference the
/// flag name in when-clauses. /// flag name in when-clauses.
library; library;
+2 -2
View File
@@ -9,8 +9,8 @@
/// ///
/// Headless and clock-injected: the caller (the root shell's raw-keyboard /// Headless and clock-injected: the caller (the root shell's raw-keyboard
/// handler) passes the event time so it neither reads a clock nor consumes /// handler) passes the event time so it neither reads a clock nor consumes
/// events. Feed every [KeyDownEvent] to [down] and every [KeyUpEvent] to /// events. Feed every [KeyDownEvent] to `down` and every [KeyUpEvent] to
/// [up], passing the event's [KeyModifier] (null for non-modifier keys). /// `up`, passing the event's [KeyModifier] (null for non-modifier keys).
library; library;
import 'key_chord.dart'; import 'key_chord.dart';
+1 -1
View File
@@ -10,7 +10,7 @@
/// While a `vim.normal` scope flag is set and this region holds focus, bare and /// While a `vim.normal` scope flag is set and this region holds focus, bare and
/// shift-only chords (plus the two half-page chords `ctrl+d` / `ctrl+u`) feed /// shift-only chords (plus the two half-page chords `ctrl+d` / `ctrl+u`) feed
/// the matcher against the live keymap; a fired [NavIntent] is handed to /// the matcher against the live keymap; a fired [NavIntent] is handed to
/// [onNav] with its repeat count. Everything else under `vim.normal` is /// `onNav` with its repeat count. Everything else under `vim.normal` is
/// swallowed (vim normal mode is inert for unbound keys), except other-modifier /// swallowed (vim normal mode is inert for unbound keys), except other-modifier
/// chords (palette, quick-open, …) which bubble to the global handler. Under a /// chords (palette, quick-open, …) which bubble to the global handler. Under a
/// non-vim preset or in insert mode the region is transparent — keys pass /// non-vim preset or in insert mode the region is transparent — keys pass
+1 -1
View File
@@ -6,7 +6,7 @@
/// and := unary ('&&' unary)* /// and := unary ('&&' unary)*
/// unary := '!' unary | atom /// unary := '!' unary | atom
/// atom := IDENT | '(' expr ')' /// atom := IDENT | '(' expr ')'
/// IDENT := [a-zA-Z_][a-zA-Z0-9._-]* /// IDENT := `[a-zA-Z_][a-zA-Z0-9._-]*`
/// ///
/// Identifiers resolve against a `Map<String, bool>` context. A missing /// Identifiers resolve against a `Map<String, bool>` context. A missing
/// identifier evaluates to `false` — bindings can assume any required /// identifier evaluates to `false` — bindings can assume any required
+3 -3
View File
@@ -2,9 +2,9 @@
/// output dock (T-54 / D-87) reads on open. /// output dock (T-54 / D-87) reads on open.
/// ///
/// The [Logger] only live-broadcasts to its stream; a panel that opens late /// The [Logger] only live-broadcasts to its stream; a panel that opens late
/// would see nothing. [LogRing] is a sink that keeps the last [capacity] /// would see nothing. [LogRing] is a sink that keeps the last `capacity`
/// records (drop-oldest, same shape as the D-85 event ring) plus enough /// records (drop-oldest, same shape as the D-85 event ring) plus enough
/// bookkeeping to drive the panel's filter dropdown (distinct [sources]) and /// bookkeeping to drive the panel's filter dropdown (distinct `sources`) and
/// the status-bar health badge (level counts). /// the status-bar health badge (level counts).
/// ///
/// Flutter-free (only `dart:async`/`dart:collection` + the [LogRecord] type) /// Flutter-free (only `dart:async`/`dart:collection` + the [LogRecord] type)
@@ -42,7 +42,7 @@ class LogRing {
int get length => _records.length; int get length => _records.length;
bool get isEmpty => _records.isEmpty; bool get isEmpty => _records.isEmpty;
/// Append a record (the [Logger] sink). Drops the oldest past [capacity]. /// Append a record (the [Logger] sink). Drops the oldest past `capacity`.
void add(LogRecord r) { void add(LogRecord r) {
_records.addLast(r); _records.addLast(r);
_sourceCounts.update(r.source, (n) => n + 1, ifAbsent: () => 1); _sourceCounts.update(r.source, (n) => n + 1, ifAbsent: () => 1);
@@ -1,7 +1,7 @@
/// Snapshots the kernel's live tabs into [ViewPane]s so `pane list` reflects /// Snapshots the kernel's live tabs into [ViewPane]s so `pane list` reflects
/// the panes the user actually sees in the GUI (T-219, D-6 parity / D-83). /// the panes the user actually sees in the GUI (T-219, D-6 parity / D-83).
/// ///
/// Read-at-request-time: no state is mirrored into the IPC [PaneRegistry], so /// Read-at-request-time: no state is mirrored into the IPC `PaneRegistry`, so
/// nothing can drift from the live UI. Lives in the kernel (not `lib/src/panes/`) /// nothing can drift from the live UI. Lives in the kernel (not `lib/src/panes/`)
/// because it reads Flutter-coupled kernel state; it produces the Flutter-free /// because it reads Flutter-coupled kernel state; it produces the Flutter-free
/// [ViewPane] the pane command serialises. /// [ViewPane] the pane command serialises.
+5 -5
View File
@@ -5,15 +5,15 @@
/// re-derive the same four things: a [LayerLink] + [CompositedTransformFollower] /// re-derive the same four things: a [LayerLink] + [CompositedTransformFollower]
/// (or a hand-rolled `Positioned`), a full-screen tap-away barrier, the /// (or a hand-rolled `Positioned`), a full-screen tap-away barrier, the
/// `Overlay.insert` / `OverlayEntry` bookkeeping, and post-frame focus capture. /// `Overlay.insert` / `OverlayEntry` bookkeeping, and post-frame focus capture.
/// This widget owns all of it; callers supply the trigger ([anchor]) and the /// This widget owns all of it; callers supply the trigger (`anchor`) and the
/// floating content ([overlayBuilder]). Modal, centred dialogs stay on the /// floating content (`overlayBuilder`). Modal, centred dialogs stay on the
/// kernel `DialogRouter` — this is for anchored, non-modal popovers. /// kernel `DialogRouter` — this is for anchored, non-modal popovers.
library; library;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
/// Primary placement of the floating panel relative to the [anchor]. /// Primary placement of the floating panel relative to the `anchor`.
enum ClideAnchorSide { below, above, left, right } enum ClideAnchorSide { below, above, left, right }
/// Cross-axis alignment of the panel's edge to the anchor's edge. /// Cross-axis alignment of the panel's edge to the anchor's edge.
@@ -41,8 +41,8 @@ class ClideOverlayController extends ChangeNotifier {
void toggle() => _open ? close() : open(); void toggle() => _open ? close() : open();
} }
/// Wraps [anchor] with a [CompositedTransformTarget] and, while [controller] is /// Wraps `anchor` with a [CompositedTransformTarget] and, while [controller] is
/// open, inserts an [OverlayEntry] built from [overlayBuilder], positioned /// open, inserts an [OverlayEntry] built from `overlayBuilder`, positioned
/// relative to the anchor (or centred when [centered]). /// relative to the anchor (or centred when [centered]).
class ClideAnchoredOverlay extends StatefulWidget { class ClideAnchoredOverlay extends StatefulWidget {
const ClideAnchoredOverlay({ const ClideAnchoredOverlay({
+4 -4
View File
@@ -4,9 +4,9 @@
/// list of one: there is no separate single-card path, which keeps the model /// list of one: there is no separate single-card path, which keeps the model
/// uniform and reliable. /// uniform and reliable.
/// ///
/// - **Collapsed** (default): a one-line ticker — the [label], the echoed /// - **Collapsed** (default): a one-line ticker — the `label`, the echoed
/// [collapsedSummary] (the run's latest content line), a fixed-width [counter] /// `collapsedSummary` (the run's latest content line), a fixed-width `counter`
/// ("3 steps"), and the aggregate [status] (spinner / check / cross). The /// ("3 steps"), and the aggregate `status` (spinner / check / cross). The
/// whole row is the toggle. /// whole row is the toggle.
/// - **Expanded**: a framed inner canvas wrapping the item cards; clicking the /// - **Expanded**: a framed inner canvas wrapping the item cards; clicking the
/// frame BACKGROUND (padding, the gaps between items, the gutter — anywhere an /// frame BACKGROUND (padding, the gaps between items, the gutter — anywhere an
@@ -16,7 +16,7 @@
/// ///
/// Chrome is consistent across every collapser: the chevron is hard against the /// Chrome is consistent across every collapser: the chevron is hard against the
/// LEFT edge, the status icon hard against the RIGHT edge, the counter sits in a /// LEFT edge, the status icon hard against the RIGHT edge, the counter sits in a
/// fixed-width slot just inboard of it, and [color] drives the border + the /// fixed-width slot just inboard of it, and `color` drives the border + the
/// chevron/label tint so each instance keeps its visual identity through one /// chevron/label tint so each instance keeps its visual identity through one
/// widget. The inner item cards are content (they keep their OWN per-item status /// widget. The inner item cards are content (they keep their OWN per-item status
/// + stripe); the aggregate status/count/title shown here are computed by the /// + stripe); the aggregate status/count/title shown here are computed by the
+1 -1
View File
@@ -1,5 +1,5 @@
/// Full-screen zoom + pan overlay (T-252 / D-78). A reusable primitive: it /// Full-screen zoom + pan overlay (T-252 / D-78). A reusable primitive: it
/// takes any [child] and shows it over the [DialogRouter]'s dimmed backdrop /// takes any `child` and shows it over the [DialogRouter]'s dimmed backdrop
/// (the host supplies the backdrop + outside-click dismiss). The image card is /// (the host supplies the backdrop + outside-click dismiss). The image card is
/// its first consumer; canvas / graph / diff previews can adopt it later. /// its first consumer; canvas / graph / diff previews can adopt it later.
/// ///
+2 -2
View File
@@ -1,6 +1,6 @@
/// Horizontal marquee (T-150). Shows [child] statically when it fits the /// Horizontal marquee (T-150). Shows `child` statically when it fits the
/// available width; when it's wider, scrolls it leftward in a seamless /// available width; when it's wider, scrolls it leftward in a seamless
/// loop (a second copy follows after [gap]). Clips to its box. Used by /// loop (a second copy follows after `gap`). Clips to its box. Used by
/// the status-bar slot so a long pane status doesn't get truncated. /// the status-bar slot so a long pane status doesn't get truncated.
/// ///
/// Own-the-stack: a `Ticker`-driven `SingleChildScrollView`, no package. /// Own-the-stack: a `Ticker`-driven `SingleChildScrollView`, no package.
+3 -3
View File
@@ -1,12 +1,12 @@
/// Behaviour-only wrapper every pane uses (T-150). No chrome — that's /// Behaviour-only wrapper every pane uses (T-150). No chrome — that's
/// [ClidePaneChrome]'s job. ClidePane handles cross-pane uniformity: /// [ClidePaneChrome]'s job. ClidePane handles cross-pane uniformity:
/// surfacing the pane's [statusWidget] to the bottom status bar while the /// surfacing the pane's `statusWidget` to the bottom status bar while the
/// pane is focused, via [FocusTracker.setStatusWidget]. /// pane is focused, via [FocusTracker.setStatusWidget].
/// ///
/// The widget lives with the pane; ClidePane only conveys it to the /// The widget lives with the pane; ClidePane only conveys it to the
/// shared slot while this pane is the shown one (its contribution is /// shared slot while this pane is the shown one (its contribution is
/// focused and, for multi-pane contributions, it's the [active] sub-tab), /// focused and, for multi-pane contributions, it's the `active` sub-tab),
/// and re-conveys whenever [statusWidget] changes. A backgrounded pane /// and re-conveys whenever `statusWidget` changes. A backgrounded pane
/// keeps its content locally and re-conveys on regaining focus. /// keeps its content locally and re-conveys on regaining focus.
library; library;
+2 -2
View File
@@ -2,11 +2,11 @@
/// ///
/// The host owns the text parsing + completion (where the `@`/`/` token is, how /// The host owns the text parsing + completion (where the `@`/`/` token is, how
/// to filter, how to rewrite the text on select); `ClideTypeahead` owns the /// to filter, how to rewrite the text on select); `ClideTypeahead` owns the
/// anchored overlay + the suggestion list. It is driven by [suggestions] /// anchored overlay + the suggestion list. It is driven by `suggestions`
/// non-empty shows the popover above the field, empty hides it. Unlike a menu, /// non-empty shows the popover above the field, empty hides it. Unlike a menu,
/// it does NOT capture focus or install a tap-away barrier: the text field keeps /// it does NOT capture focus or install a tap-away barrier: the text field keeps
/// focus (you're still typing), and the host closes it on text change / blur / /// focus (you're still typing), and the host closes it on text change / blur /
/// Esc. Pass [navController] to drive the highlight from the field's own key /// Esc. Pass `navController` to drive the highlight from the field's own key
/// handler (the slash typeahead does this while the EditableText keeps focus); /// handler (the slash typeahead does this while the EditableText keeps focus);
/// omit it for a mouse-only list (the @-mention). /// omit it for a mouse-only list (the @-mention).
library; library;