# RichTextEditor Changelog

## Version 2.4.0 — Long-document authoring, real document export, security and accessibility (Pass 65)

Twenty-nine plugins, no `rte.js` core changes. The theme of the release is
closing the gaps that competitors gate behind paid tiers, and fixing three
things about our own product that were not true.

### Security (three real holes, found by testing rather than by review)
- **`plugins/sanitizer.js`** — allowlist content filter on input, output **and
  the live DOM**. Unknown elements are unwrapped rather than deleted, so a
  user's words are never silently lost. Parses with `DOMParser` (never
  `innerHTML` on a live node, which fires `<img onerror>` before the filter
  runs) and re-parses until stable to defeat mutation XSS.
  - **Stored XSS:** `<iframe srcdoc="<script>…">` survived the core filter into
    saved HTML and executed when that HTML was rendered downstream. "Nothing
    executes in the editor" was the wrong bar.
  - **Transient XSS:** the first version cleaned up *after* the paste — measured
    execution at +70 ms. Paste is now intercepted in the capture phase and only
    when the clipboard HTML actually executes on insertion, so ordinary Word
    pastes still reach the editor's own Word cleanup untouched.
  - **Collaborator XSS:** a CRDT applies remote edits by writing nodes straight
    into the live document, bypassing both paste and `setHTMLCode`. Any
    participant could run script in every other participant's browser, and the
    payload replicated onward. Closed by a live-DOM guard
    (`sanitizerGuardLiveDom`, on by default) that only engages when an inserted
    node or attribute is executable, so typing costs nothing.
- **`plugins/restrictedediting.js`** — Word-style protected documents: content
  is read-only except regions tagged `data-rte-editable="true"`.

### Accessibility
- **`plugins/keyboarda11y.js`** — an audit of the running editor found four open
  issues, three of them WCAG Level A: a **keyboard trap** (focus could enter the
  editing area and never leave), `aria-pressed` missing on every toggle button,
  `aria-expanded` missing on every menu button, and 53 toolbar tab stops.
  `Escape` now leaves the editing area (Tab keeps its editing meaning), toggle
  state is mirrored from the existing active class so announced state cannot
  disagree with the highlight, and each toolbar has a single roving tab stop.
- **`plugins/accessibilitychecker.js`** — flags and one-click repairs missing
  alt text, missing table headers, empty or non-descriptive links, and skipped
  heading levels. Header repair emits `<th scope="col">`, which is what the
  tagged PDF and .docx exports then consume.

### Document export and import
- **`plugins/pdfexport.js`** — PDF written by hand, with **selectable text** and
  a full accessibility tag tree (H1–H6, P, L/LI, Table/TH/TD, Figure; decoration
  emitted as artifacts), so the output passes a Section 508 / EN 301 549 review
  rather than only a copy-paste test. The raster html2pdf path is kept for cases
  where pixel fidelity matters more.
- **`plugins/docxexport.js`** — a genuine OOXML package built in the browser via
  `CompressionStream`: no library, no server, no upload. Emits exactly what
  `documentimport.js` reads, so HTML → .docx → HTML round-trips.
- **`plugins/documentimport.js`** — .docx import completeness: text boxes,
  endnotes as well as footnotes, equations (OMML ↔ LaTeX), SmartArt diagrams and
  WMF placeholders. Nothing is dropped silently; what HTML cannot represent
  degrades visibly.
- **`plugins/wordexport.js`** — Word-compatible HTML export (library-free).

### Long-document authoring
- **`plugins/pagination.js`** — print-layout page view. CKEditor and Tiptap both
  sell this as a paid add-on.
- **`plugins/multilevellist.js`** — legal numbering (1, 1.1, 1.1.1) via CSS
  counters.
- **`plugins/footnotes.js`** — inline markers with an auto-renumbering notes
  section.
- **`plugins/crossreference.js`** — references that keep pointing at the right
  target after the document is edited around them.
- **`plugins/tableofcontents.js`** — self-updating contents block from the
  document's headings. Premium in both majors.
- **`plugins/linenumbers.js`** — margin line numbers for pleadings, statutes and
  transcripts.
- **`plugins/documentoutline.js`**, **`plugins/contentminimap.js`** — navigation
  for long documents.
- **`plugins/watermark.js`** — DRAFT / CONFIDENTIAL marking behind the content.

### Editing and productivity
- **`plugins/formatpainter.js`** — the clipboard-brush interaction, premium in
  both majors.
- **`plugins/changecase.js`** — UPPERCASE / lowercase / Title / Sentence /
  tOGGLE. CKEditor gates this behind a plan.
- **`plugins/autocorrect.js`** — typo fixes, sentence capitalisation, smart
  quotes and dashes as you type.
- **`plugins/permanentpen.js`** — a fixed format applied to everything typed
  until switched off.
- **`plugins/draghandle.js`** — grab a block by its grip and drop it elsewhere.
- **`plugins/tabletools.js`** — column sorting and row numbering; TinyMCE sells
  both as premium "Enhanced Tables".
- **`plugins/mergefields.js`** — placeholders that turn a document into a
  template for mail merge and invoice runs.
- **`plugins/linkchecker.js`** — finds broken, unsafe, misleading, or
  assistive-technology-unreachable links, with a host-supplied resolver so no
  vendor infrastructure is required. TinyMCE's equivalent is premium *and*
  requires their service.
- **`plugins/formattingmarks.js`** — Word's ¶ button, including the invisible
  non-breaking and zero-width characters that cause mysterious layout bugs.
- **`plugins/charlimit.js`** — enforces a maximum (the built-in statistics
  readout only displays counts).
- **`plugins/readabilitystats.js`** — local Flesch / Flesch-Kincaid readout, no
  API call.

### Right-to-left
- **`plugins/textdirection.js`** — direction commands, toolbar items and
  language strings for Arabic, Hebrew, Persian and Urdu, which the editor
  previously had no way to express at all.
- **`plugins/rtlui.js`** — flips the editor chrome (toolbar, dropdowns, menus,
  dialogs) so an RTL user is not typing right-to-left inside a left-to-right
  frame.

### Build
- `build-bundle.ps1` now searches several locations for esbuild and fails hard
  when it finds none. It previously looked at one hardcoded path and, when that
  path was pruned, silently skipped minification while still exiting 0 — so a
  stale `all_plugins.min.js` was mirrored to every tier for several builds.

## Version 2.3.0 — Block types, equation editor, bookmark cards, spell check, drag-handles, smart chips, email export, inline Markdown, Mermaid diagrams, read-aloud, emoji autocomplete, task lists, auto-embed, foldable headings, typewriter & focus modes, Markdown export, local-draft recovery, AI ghost-text autocomplete (Pass 64)

**New plugin: `plugins/ghostcomplete.js` — AI ghost-text autocomplete**
- GitHub-Copilot / Notion-AI-style inline completion: after a typing pause a
  greyed suggestion appears after the caret continuing your sentence; **Tab**
  accepts it, **Esc** (or simply continuing to type) dismisses it. Ambient and
  hands-free — distinct from the command-driven AI Toolkit.
- **Bring-your-own-key**: supply `config.ghostTextResolver({before, after, full})`
  returning a string (or Promise). No model ships and no network call is made
  without it; the feature is inert until both `config.ghostText` and a resolver
  are set. Tunables: `delayMs` (default 600), `minChars` (3), `maxContext` (2000).
- **Serialization-safe**: the suggestion is a `contenteditable="false"`
  `<span class="rte-ghost">` decoration that is never part of the saved content —
  the caret is kept *before* it so typing/saving behave normally, and
  `getHTMLCode` / `getJSON` are wrapped to strip any ghost span. Accept goes
  through `execCommand("insertText")` so one Ctrl+Z reverts it. Stale-request
  tokens prevent races while typing. API: `editor.requestGhostText()` /
  `acceptGhostText()` / `dismissGhostText()` / `hasGhostText()` /
  `setGhostTextResolver(fn)`; slash `/ai complete`. No core `rte.js` changes.
  Verified live (ghost shows, Tab + API accept inserts undoable text with clean
  serialize, Esc / typing / mousedown / API dismiss, ghost never leaks into saved
  HTML).

**New plugin: `plugins/localdraft.js` — local draft autosave & crash recovery**
- Complements the server-oriented core `config.autoSave` with offline resilience:
  persists the document to `localStorage` as you type (debounced) and, on the
  next load, offers to restore the unsaved draft via an unobtrusive bar
  ("An unsaved draft from 3 min ago was found — Restore / Discard"). Gmail /
  Notion style "never lose your work".
- Opt-in via `config.localDraft = true` or an options object (`key`,
  `intervalMs`, `ttlMs`, `promptRestore`). Safe by design: the draft is **never
  auto-applied** (the server content shows until the user clicks Restore),
  empty/unchanged docs aren't persisted, and stale drafts past the TTL (default
  14 days) are pruned. Also saves on `beforeunload`/`pagehide`. API:
  `editor.saveLocalDraft()` / `hasLocalDraft()` / `getLocalDraft()` /
  `restoreLocalDraft()` / `clearLocalDraft()` (call `clear` after a real save).
  Verified live (autosave→reload→restore-bar→Restore round-trip) + 11 logic
  assertions (TTL prune, blank/baseline skip, restore, clear).

**New plugin: `plugins/markdownexport.js` — Copy / Download as Markdown**
- Surfaces the core HTML→Markdown engine (`editor.toMarkdown()`) as a
  discoverable export, the way Notion "Copy as Markdown" / Obsidian do. Slash
  commands `/copy as markdown` and `/download as markdown`, plus the API
  `editor.getMarkdown()`, `editor.copyAsMarkdown()` (clipboard, with an
  execCommand fallback for older browsers) and `editor.downloadMarkdown(name?)`
  (saves a `.md` Blob; the filename is derived from the first heading when not
  given). Read-only — nothing in the document changes. Toggle with
  `config.markdownExportEnabled`. Verified live (Markdown output for headings /
  bold / italic / lists, clipboard receives the correct text, download filename
  derivation + custom name keep the `.md` extension).

**New plugin: `plugins/dialoga11y.js` — dialog accessibility**
- The editor's dialog popups (`<rte-dropdown-panel>` with an `<rte-dialog-header>`)
  shipped without `role`/`aria-label`, and their fields without programmatic
  labels — so a screen reader announced an unnamed "menu" with unlabeled inputs
  (WCAG 4.1.2 / 1.3.1 / 3.3.2). This plugin adds the missing metadata, deriving
  every name from the editor's OWN markup (never guessing): the dialog name is the
  header text ("Insert Link", "Insert Table"), the role is corrected from `menu`
  to `dialog` on titled form popups, and each field is labelled from its
  `<rte-dialog-line-URL/-TEXT/-TARGET>` wrapper. Natively-labelled fields are left
  untouched, and plain dropdowns (color/font pickers, no header) are deliberately
  skipped so they aren't mislabelled. No core changes; opt out with
  `config.dialogA11y = false`. Verified live on the link/table dialogs (role,
  name, and field labels all correct; native `<label>`s preserved).

**New plugin: `plugins/a11yenhance.js` — accessibility metadata**
- Closes the most common WCAG 2.2 findings for an iframe-based editor by emitting
  the assistive-technology metadata the editing surface needs: the editor iframe
  gets a `title` (WCAG 2.4.1 / 4.1.2), the editing region gets `role="textbox"` +
  `aria-multiline="true"` + an `aria-label` (4.1.2), and the editing document gets
  a `lang` attribute (3.1.1 / 3.1.2). Re-applied on every document remount so the
  metadata survives `setHTMLCode`. Configurable via `config.contentAriaLabel`,
  `config.contentLang` (defaults to the host page `<html lang>` or `en`), and
  `config.a11yEnhance = false` to opt out; API `editor.applyAccessibilityMetadata()`.
  Verified live (iframe title, role, aria-multiline, aria-label, document lang all
  present and surviving remount).

**New plugin: `plugins/typewriter.js` — typewriter scrolling + focus mode**
- Two distraction-free writing modes, both purely presentational (no content or
  DOM mutation — nothing to serialize):
  - **Typewriter mode** keeps the caret line pinned to a fixed vertical anchor
    (default: the middle of the viewport) by scrolling the editable as you type,
    the way iA Writer / Typora / Ulysses do — the text rises to meet a steady
    caret instead of the caret marching down the page. Anchor is configurable
    via `config.typewriterAnchor` (0.15–0.85 fraction of viewport height).
  - **Focus mode** dims every block except the paragraph / heading / list-item
    the caret is in (Notion / iA Writer "focus" behaviour), so the current
    sentence stands out.
- Toggle either independently: slash `/typewriter` + `/focus mode`, or the API
  `editor.toggleTypewriterMode()` / `editor.toggleFocusMode()` (plus
  `set…`/`is…` variants). Off by default; opt in with `config.typewriterModeEnabled`
  / `config.focusModeEnabled`.
- **Serialization-safe**: focus mode toggles a single class on the editable
  (`rte-focus-active`) and a transient class on the active block
  (`rte-focus-current`); typewriter mode only scrolls. `getHTMLCode` / `getJSON`
  are wrapped to strip the transient class around the call, so saved markup is
  always clean even if a save fires mid-keystroke. No core `rte.js` changes.
  Verified live (focus dimming current=1.0 / dimmed=0.28 opacity, typewriter
  scroll pulls the caret line to the anchor, serialization clean) + 19 logic
  assertions.

**New plugin: `plugins/foldheadings.js` — collapsible heading sections**
- Obsidian / Notion / Word-style section folding: hover a heading and click the
  gutter chevron (or call the API) to collapse every block beneath it, up to the
  next heading of the same or higher level. Great for navigating long structured
  documents; pairs with the existing document-outline panel. API:
  `editor.toggleHeadingFold(h)`, `editor.foldAtCaret()`, `editor.unfoldAll()`;
  slash `/fold section` and `/unfold all`. Toggle with `config.foldHeadingsEnabled`.
- **Serialization-safe by design**: the fold state lives ONLY in runtime CSS
  classes (`rte-fold-collapsed` / `rte-fold-hidden`) and the chevron is a CSS
  `::before` pseudo-element &mdash; nothing is injected into the content.
  `getHTMLCode` / `getJSON` / `getHTMLContent` are wrapped to strip those classes
  around the call, so **saved markup is always clean and folded content is never
  lost**. Caret safety: folding a section that contains the caret moves the caret
  to the heading so it is never stuck in hidden content. No core `rte.js` changes.
  Verified live (14 checks: level-aware folds, serialization clean + content
  preserved + restored, caret safety, gutter-click vs text-click).

**New plugin: `plugins/autoembed.js` — auto-embed media URLs on paste**
- Paste a bare YouTube or Vimeo URL on its own line (collapsed caret) and it
  becomes a responsive 16:9 embed &mdash; the Notion / Medium behaviour.
  Complements the command-driven Insert Video dialog and the bookmark-card plugin
  (which makes a link-preview card from any URL). Also exposed programmatically as
  `editor.embedUrl(url)`.
- **Injection-safe by construction**: the iframe `src` is NEVER the raw pasted
  string. The video id is extracted with a strict whitelist regex, constrained to
  `[A-Za-z0-9_-]` / digits, and a trusted provider embed URL is built from a
  template (verified against `youtube.com.evil.com`, `javascript:`,
  `"><script>`, wrong-domain and non-numeric inputs). Mid-paragraph and
  text-selected pastes are left untouched (they stay links). Extend providers via
  `config.autoEmbedResolver(url)` (must return an `https://` embed URL); toggle
  with `config.autoEmbedEnabled` (default true). No core `rte.js` changes.
- Robust paste hook: the listener attaches to the iframe **document** and
  re-attaches on `ready` / `aftersethtml` (the editor recreates the editable body
  after init, which would orphan a body-level listener). Verified live (13 checks
  + 14 resolver/security unit tests).

**New plugin: `plugins/todolist.js` — interactive to-do / task lists**
- `editor.execCommand("inserttodolist")` (slash `/to-do`, toolbar) inserts a
  checklist whose items each carry a clickable checkbox; clicking toggles the item
  between open and done (a check + strike-through). Notion / GitHub / Google-Docs
  style. Closes a real gap &mdash; the editor shipped an orphan `inserttodolist`
  toolbar icon (`svgCode_inserttodolist`) with no command behind it.
- Checkboxes are styled `contenteditable="false"` spans (not `<input>`s, which
  misbehave in contenteditable); the done state lives in `data-checked`, so the
  list round-trips through `getHTMLCode` / `getJSON` and re-binds on load via
  click delegation (works for pasted/loaded lists). New items created with Enter
  are auto-normalized to include a checkbox and an open state. CSS injected at
  insert time. No core `rte.js` changes. Verified live (10 checks: insert,
  click-toggle both ways, text-click no-op, style injection, Enter-item
  normalization, round-trip).

**New plugin: `plugins/emojiautocomplete.js` — emoji `:shortcode:` autocomplete**
- GitHub / Slack / Discord-style inline emoji entry, no picker needed. Two
  behaviours: (1) typing a complete `:shortcode:` (closing colon) replaces it with
  the emoji (`:fire:` → 🔥, `:+1:` → 👍, `:tada:` → 🎉); (2) typing `:` + a partial
  query shows a popup of matches — Arrow keys move, Enter/Tab insert, Esc dismisses.
- Curated built-in shortcode map (~140 common emoji, what people actually type);
  extend or replace via `config.emojiShortcodes`. Toggle with
  `config.emojiAutocompleteEnabled` (default true). Word-boundary gated (a colon
  mid-identifier like `path:fire:` is ignored), inert inside `<code>`/`<pre>`.
  Complements the existing emoji picker (insertemoji) with keyboard-only entry.
  No core `rte.js` changes. Verified live (12 checks: immediate replace, unknown-
  code passthrough, popup matches, Enter-commit, Arrow-nav, mid-word guard,
  code-inert).

**New plugin: `plugins/readaloud.js` — read-aloud (text-to-speech)**
- `editor.execCommand("readaloud")` (slash `/read aloud`, toolbar) opens a small
  non-modal control bar (play/pause, stop, voice, rate) and reads the current
  selection — or the whole document if nothing is selected — aloud, sentence by
  sentence, **highlighting each sentence as it is spoken** (via the native
  selection, so document content is never modified). The speech-OUT complement
  to the dictation plugin's speech-IN, and an accessibility win alongside the
  accessibility checker.
- Browser-native via the Web Speech API (`window.speechSynthesis`) — **no library
  bundled, no backend, no API key**. Reads cross-paragraph content correctly
  (offset→DOM-range mapping). Public API: `editor.readAloud({ rate, voice })` /
  `editor.stopReadAloud()`. `config.readAloudRate` (default 1.0) and
  `config.readAloudHighlight` (default true). Gracefully detects browsers without
  a synthesizer. Stops on page-hide; pauses on tab-hide. No core `rte.js` changes.
  Verified live (8 checks: sentence split, cross-node highlight, selection-only
  read, control bar, auto-start, toggle-close, stop-restores-caret, API surface).

**New plugin: `plugins/mermaiddiagram.js` — Mermaid diagrams (diagrams-as-code)**
- `editor.execCommand("insertdiagram")` (slash `/diagram`, toolbar) opens a dialog
  to author Mermaid text (flowchart, sequence, gantt, pie, class, state…) with a
  live preview, and renders it to SVG. Closes the diagrams-as-code gap vs GitHub /
  GitLab / Notion / Obsidian. Double-click a block to edit it in place.
- No renderer is bundled (Mermaid is ~2 MB). Three tiers, mirroring the equation
  editor's "host supplies KaTeX/MathJax" model: (1) `config.diagramResolver(source)`
  → `Promise<{svg}|{imageUrl}>` for server-side render (e.g. Kroki); (2) a
  `window.mermaid` instance loaded by the host page; (3) neither — the source is
  stored and shown as a `<pre>` fallback, re-renderable later via
  `editor.renderDiagrams()`. The Mermaid source always round-trips in
  `data-diagram`, so blocks stay editable regardless of how they were rendered.
- Mermaid source is HTML-escaped into the attribute and the fallback `<pre>`;
  blocks are `contenteditable="false"`. CSS injected at insert time. No core
  `rte.js` changes. Verified live (14 checks: render via host renderer, BYOK
  resolver, no-renderer fallback, injection-safety, `renderDiagrams()`, round-trip).

**Inline Markdown autoformat (core)**
- Typing the closing delimiter of an inline Markdown span now converts it in
  place: `**bold**` / `__bold__` → `<strong>`, `*italic*` / `_italic_` → `<em>`,
  `` `code` `` → `<code>`, `~~strike~~` → `<s>`. Completes the Markdown story —
  the editor already had block shortcuts (`# `, `> `, `- `, `1. `, `---`); this
  adds the inline half that Notion / Slack / GitHub / Typora users expect.
- The opening delimiter must sit at a **word boundary**, so `2*3*` (math),
  `*.txt*` is reduced, `snake_case_` and intraword `a*b*` are left untouched.
  Inert inside `<code>` / `<pre>` / links. Output uses the editor's own semantic
  tags (`<strong>`/`<em>`), so it round-trips with the existing Markdown
  import/export. Undoable with Ctrl+Z. Toggle with `config.markdownInlineEnabled`
  (default `true`). No new toolbar/command surface — pure typing UX. Verified
  live (14 checks: 8 conversions, 4 false-positive guards, code-inert, end-to-end
  typing).

**New plugin: `plugins/emailtoolkit.js` — email inline-style export**
- `editor.getEmailHTML(options?)` returns email-client-safe HTML: a curated set
  of **computed** styles (color, background, font, spacing, borders, list markers,
  etc.) is written **inline** as `style=""` on every element, and `class`/`id`/
  `contenteditable`/`data-rte-*` attributes are removed. Email clients strip
  `<style>` blocks and external CSS, so inline styles are the only reliable way
  to keep formatting in Outlook/Gmail. `<script>`/`<style>`/`<link>`/`<meta>`
  nodes are dropped entirely.
- By default the content is wrapped in a centered, max-width (`config.emailWrapWidth`,
  default 640) presentation table for consistent rendering across clients; pass
  `{ wrap:false }` for the bare inlined fragment or `{ width:N }` for a custom width.
- The inlined property set is configurable via `config.emailInlineProperties`.
  Defaults skip no-op values (`text-align:start`, off-list `list-style-type`,
  transparent backgrounds, default weights) to keep output lean.
- `editor.execCommand("emailexport")` opens a dialog with the generated HTML in a
  textarea plus a one-click **Copy** button. Slash (`/email`), toolbar, config,
  TS defs (`getEmailHTML`, `emailInlineProperties`, `emailWrapWidth`). No core
  `rte.js` changes. Verified live (19 checks: attr-stripping, script-drop,
  computed-style inlining, semantic-attr retention, wrap/width, dialog + copy).

**New plugin: `plugins/smartchips.js` — Google-Docs-style smart chips**
- `editor.execCommand("insertdatechip")` — interactive **date chip**: a small
  picker (Today / Tomorrow / Yesterday / Next week / date input) inserts an
  atomic `contenteditable="false"` pill with an icon, formatted label, and a
  machine-readable `data-rte-chip-value` (ISO date). Unlike the plain-text
  `insertdate`, this is a structured, styled chip.
- `editor.execCommand("insertchip")` — a Date / Person / Link chip menu.
  Person/Link chips use the optional BYOK `config.chipResolver(query, type)`
  for live suggestions (avatars/email/file metadata), or a plain input fallback.
- Link chips validate the URL (reject `javascript:`/`data:`/`vbscript:`/`file:`,
  prefix `https://` on bare domains) and carry `rel="noopener"`. All chip text
  is HTML-escaped; chips round-trip through `getHTMLCode`. Slash (`/date-chip`,
  `/chip`), toolbar, config, TS defs. No core `rte.js` changes. Verified live
  (11 checks + injection-safety).



**Block drag-handles enabled** (`config.blockDragHandles`, now default `true`)
- Notion-style ⋮⋮ handle appears on hover at the left of each top-level block;
  drag it to reorder blocks. The handle + drop-indicator live in the host-side
  container (never inserted into the editable), so `getHTMLCode()` is unaffected.
- **Fix**: the handle/indicator are `position:absolute` children whose
  coordinates are computed relative to the editor container — which only
  resolves correctly when the container is a positioning context. Init now
  promotes a `static` container to `position:relative` so the handle lands on
  the block instead of off-screen. (This was the bug that kept the feature
  flagged "experimental".) Reorder + `getHTMLCode`-cleanliness verified; set
  `config.blockDragHandles = false` to disable.



**New plugin: `plugins/spellcheck.js` — spell check**
- `editor.execCommand("spellcheck")`. Two layers, zero dictionary bundled:
  - **Native** (default): the editable carries `spellcheck="true"` so the
    browser underlines misspellings as you type. Toggle with
    `config.spellCheckEnabled` (default true).
  - **BYOK panel**: wire `config.spellCheckResolver(text) => Promise<[{word, suggestions[]}]>`
    (LanguageTool, server Hunspell, an LLM, …) and the command opens a review
    panel listing each issue with click-to-replace suggestion chips. 12s
    timeout, graceful error states.
- Replace is injection-safe (word-boundary regex on text nodes only, skips
  CODE/PRE). No core `rte.js` changes. Slash (`/spellcheck`), toolbar, config,
  TS defs. Verified live (native attr set on editable; BYOK panel lists issues
  + click-to-replace mutates content correctly).



**New plugin: `plugins/bookmarkcard.js` — web-bookmark preview cards**
- `editor.execCommand("insertbookmark")` opens a URL dialog and inserts a rich
  link-preview card (favicon + title + description + domain). Closes the
  Notion / Google Docs "bookmark with preview card" gap.
- **Zero-infra by default**: with no resolver configured it renders a card from
  the URL alone plus the page favicon (public Google s2 favicon endpoint, an
  `<img>` the browser loads — no server of ours). Set
  `config.bookmarkFaviconService = ""` to disable the favicon fetch entirely.
- **Optional rich previews** via a BYOK resolver (same shape as the AI toolkit):
  `config.bookmarkResolver = function(url){ return Promise<{title, description, image, favicon}> }`.
  An 8s safety timeout falls back to the basic card if the resolver hangs.
- Security: only `http(s)` URLs accepted (`javascript:` / `data:` / `vbscript:` /
  `file:` rejected); bare domains get `https://` prepended; all metadata
  HTML-escaped; cards carry `target="_blank" rel="noopener noreferrer"`.
  Verified live: malicious URL rejected, no element injected.
- Slash menu (`/bookmark`), toolbar (`subtoolbar_more`), `svgCode_insertbookmark`
  icon, `text_insertbookmark*` labels.



**New plugin: `plugins/mathdialog.js` — equation editor dialog**
- `editor.execCommand("insertmath")` opens a TeX/LaTeX editor with a **live
  KaTeX/MathJax preview** (uses whatever renderer the host page loads — none is
  bundled, same model as inline `$...$`). Sample-equation chips for quick start.
- **Edit-in-place**: invoking the command while the caret is inside an existing
  `.rte-math-inline` span prefills the dialog and updates that equation instead
  of inserting a new one.
- On insert it emits the editor's standard `<span class="rte-math-inline" data-tex="…">$…$</span>`
  markup and calls `editor.renderMath()`, so equations render immediately when a
  renderer is present and degrade to stable markup when it isn't.
- Injection-safe: the TeX is HTML-escaped into both `data-tex` and the span text;
  malicious input (`"><img onerror=…>`) is stored inert, never executed (verified
  live in-browser).
- Slash menu (`/equation`), toolbar (`subtoolbar_more`), `svgCode_insertmath`
  icon, and `text_insertmath*` labels wired in.



Closes the Tier-1 competitive gap vs Notion / Google Docs / CKEditor: three
modern block types, all editor-native (no server), insertable from both the
slash menu (`/`) and the toolbar's "more" group, and round-trippable through
`getHTMLCode` / `getJSON`.

**New plugin: `plugins/blocktypes.js`**
- **Callout / admonition block** — `editor.execCommand("insertcallout", "<type>")`.
  Five built-in variants (info / success / warning / danger / note), each with
  an icon + editable body. Customize the set via `config.calloutTypes` and the
  default via `config.calloutDefaultType`. Markup: `<div class="rte-callout rte-callout-<type>" data-rte-block="callout" data-rte-callout-type="<type>">`.
- **Multi-column layout block** — `editor.execCommand("insertcolumns", <count>)`.
  2–4 independently-editable columns (clamped), responsive — stacks vertically
  under 640px. Default count via `config.columnsDefaultCount`. Markup:
  `<div class="rte-columns rte-columns-N" data-rte-block="columns">`.
- **Toggle / collapsible block** — `editor.execCommand("inserttoggle")`.
  Native `<details class="rte-toggle"><summary>…</summary><div class="rte-toggle-body">…</div></details>`.

**Slash menu** (`plugins/slashcommand.js`): adds Callout / Columns / Toggle-list
entries under "Blocks", each guarded by `isCommandEnabled` so the menu degrades
gracefully when the blocktypes plugin isn't loaded.

**Config** (`rte-config.js`): `svgCode_insertcallout` / `_insertcolumns` /
`_inserttoggle` icons, `text_*` labels + placeholders, and the three commands
added to `subtoolbar_more`.

**Styling**: scoped CSS injected into the editor iframe (`style[data-rte-blocktypes]`)
— left-accent colored callouts, dashed-border columns, padded toggle. Override
freely; all selectors are `.rte-callout* / .rte-columns* / .rte-column / .rte-toggle*`.

**Paste safety**: the new blocks use the `rte-` class prefix + `data-rte-block`
attributes, which the Word/Office paste scrubber preserves — so copying a
callout/columns/toggle between editor instances keeps the structure intact.
Verified by the paste-pipeline regression harness (90 cases) + a dedicated
block-generator test (6 cases) + a live in-browser insertion test (4 checks).

## Version 2.0.7.2 — Paste pipeline hardening + plugin defensive fixes (Pass 63)

A focused polish cycle on paste-cleanup robustness and plugin defensive coding. Twelve concrete fixes shipped over a single iteration, every one backed by an automated regression test (84/84 paste-pipeline tests on `rte-paste-tests.js`).

**Paste pipeline (`rte.js` `__removeWordCode` path):**
- **Word list paste fix.** `_ConvertWordLists` now runs BEFORE `ConvertForWord`. Previously the scrubber stripped `class="Mso*"` and every `mso-*` style first, so by the time the list converter ran the metadata was already gone and Word bullet/numbered paste came out as flat paragraphs with literal `·` / `1.` glyphs in the body text.
- **Smart-link paste.** Pasting a URL or email while text is selected wraps the selection in `<a href>` instead of replacing it. Matches Google Docs / Notion / TipTap / ProseMirror behavior. Opt-out via `config.smartLinkPaste = false`.
- **TOC continuation-span fix.** Word sometimes splits a long field instruction across multiple `<span>` runs (e.g. `<span> TOC \h </span><span>\z \t "Heading 1,1" </span>`). The verb-anchor regex caught the first; the continuation leaked. Added a guarded second-pass regex that catches continuation spans without false-positives on real prose (code with `\n`, file paths with `C:\Users`, text mentioning `\h flag`).
- **Empty-span / empty-p debris cleanup.** After the field-strip removes `mso-element:field-*` spans (which are usually self-empty in Word's HTML), bare `<span></span>` and `<p></p>` placeholders remain. Now swept up so the editor doesn't accumulate visible-but-empty placeholders every paste.
- **Defense-in-depth `<style>` / `<script>` / `<head>` / `<meta>` / `<title>` / `<link>` / `<base>` strip in `_RemoveWord`.** `process_html_string` already trims to the body envelope upstream, but a customer calling `__removeWordCode` directly (or pasting a fragment without `<body>` markers) was exposed to global style poisoning.
- **Tool-prefix `data-*` strip with whitelist.** Notion `data-block-id`, CKEditor `data-cke-*`, TinyMCE `data-mce-*`, Atlassian `data-confluence-*`, Google `data-google-*`, Notion `data-notion-*` all stripped. Legitimate `data-id` / `data-href` / etc. preserved.
- **Tool-prefix class strip extending the Mso* rule.** `notion-*`, `cke-* / cke_*`, `mce-* / mce_*`, `confluence-*`, `docs-internal-*` tokens now stripped from each space-separated class list, with custom classes preserved.
- **Smart-paste source detection broadened.** The post-paste toast now recognizes LibreOffice (`office:` / `text:` namespaces or `ListParagraphCx*` classes), Notion (`data-block-id` or `notion-*` classes), Excel (`xmlns:x="...office:excel"`), and Confluence (`data-confluence-*` or `confluence-*` classes) in addition to the existing Word and Google Docs detectors.

**AI panel overlap fix:**
- **AI Chat / AI Review panel z-index 6 → 100000.** Float toolbars and inline popups use `config.zIndexDialog || 99999` and were painting on top of the AI panel. Bumped the panel one notch higher.
- **Hide float-toolbar / float-paragraph while AI panel host is active** via `aitoolkit.css` — handles the wholly-separate stacking contexts that `position: fixed` toolbars sit in.

**Plugin defensive coding:**
- **`comments.js cssEscape`** — replaced the 1-char shim with native `CSS.escape` (with comprehensive regex fallback for attribute-selector-breaking chars). Prevents subtle DOM-query failures when customers wire `reviewLedger.add()` with custom IDs containing punctuation.
- **`revisionhistory.js openDialog()`** — defensive null-checks on `editor.iframe`, `editor.iframe.ownerDocument`, `host.body`; re-nulls stale dialog refs before re-creating. Bails out cleanly when triggered before `InitEditor` finishes wiring.
- **`revisionhistory.js renderDialog()`** — null-check on `.rte-rev-panel` lookup; closes the dialog cleanly if the panel was tampered with externally instead of throwing on `null.innerHTML`.

**TypeScript surface:**
- Added typed declarations for `smartPasteToast`, `smartLinkPaste`, `pasteMode`, `autoLinkOnType`, `autoHeadingAnchors`, `copyHeadingLink`, `smartTypography`, `smartTypographyDashes`, `smartTypographyEllipsis`, `smartTypographyTrademarks`, `smartTypographyQuotes`, `aiContextMenu` to `RichTextEditorConfig` in `npm-package/index.d.ts`.
- Bumped `npm-package` 2.2.3 → 2.2.4.

**Customer who explicitly asked for this:** Kenneth Chen reported a Word document with a TOC field pasting raw instruction text (`TOC \h \u \z \t "Heading 1,1,Heading 2,2,Heading 3,3,"`) into the editor body. The fix for that specific reproducer is the field-strip pass that runs BEFORE ConvertForWord (item 1 above). The other items in this changelog surfaced from a sweep against TinyMCE / CKEditor / ProseMirror paste behaviors that the same root-cause analysis (attribute scrubber running before content-aware passes) suggested would also be broken — and were.

## Version 2.0.7.1

### Improved — Ask AI dialog: tighter chrome floors + Source eyebrow detached when "Edit" expands the prompt (Pass 62)
Sixty-second pass on the AI windows. v2.0.7.0 / Pass 61 dropped the scope-picker live char count, trimmed the "Edit prompt" pill to "Edit", and re-baselined the inner chrome (top reserve 64→48px, header padding 6/12→4/10, inner padding 4/10/6→3/8/4, grid gap 4→3px) so the dialog landed at 480×127px pre-result / 480×155px post-result. An audit of the surviving clarity + compactness gaps against Notion AI inline composer, ChatGPT inline edit, and Tiptap AI bubble surfaced four shapes the dialog still painted that every reference inline composer trims further:

1. **Inline "Source" label re-appears when the user clicks "Edit".** Pre-result the label was already hidden via the `.is-pre-result .demo-ai-text-label-inline { display: none }` cascade. Post-result + source-expanded was the only remaining showing path — but the user reaches it by clicking the "Edit" toggle pill, which already names the role ("Edit the prompt and retry"). Restating "Source" 4px above the textarea was a third copy of the same fact (toggle label + textarea placeholder + this eyebrow). Saves ~16px vertical when the user expands source for retry / refine.

2. **Dialog inner padding + grid gap one notch loose.** Pass 61 set padding `3/8/4` and grid gap 3px. With the source/result field gap also at 3px the dialog painted two parallel 3px rhythms — the outer "between rows" gap and the inner "label-to-input" gap. With labels detached the inner gap reserved deadspace for a node that never renders. Tightening padding to `2/8/3`, grid gap to 2px, and field gap to 2px saves ~5px combined on every render.

3. **Kbd-hint chip on Generate carried 1px 5px padding.** The "Ctrl ⏎" / "⌘⏎" chip claimed ~38px on the Generate button's trailing edge. Notion AI / ChatGPT composer ship their kbd hint at ~22-26px (tighter side-padding). Going `1 5` → `1 4` recovers ~4px so the "Generate" label has more breathing room.

4. **Compact-controls strip gap 3px×8px.** With Action + Scope both being labeled `<select>`s (the icon-only toggle disappeared in Pass 47 / v20260523a), the 8px horizontal gap read as slightly loose against the surrounding tighter rhythms. `2×7` keeps them visually paired without crowding.

**Ask AI dialog — Source inline label always detached**
- **`append(sourceField, "span", "", "demo-ai-text-label-inline is-detached", "Source")`** — the JS now stamps the label with `is-detached` at creation time (mirrors the Result label's existing pattern). Pre-result the `.is-pre-result .demo-ai-text-label-inline { display: none }` cascade still fires; post-result + source-expanded the new `is-detached` class hides the label too. External integrations that walk `.demo-ai-source-field .demo-ai-text-label-inline` keep resolving (the node is still in the DOM, just invisible).

**Ask AI dialog — Pass 62 chrome floors**
- **Inner padding `3/8/4` → `2/8/3 !important`**. Saves ~2px combined (1px top + 1px bottom).
- **Grid gap `3 → 2px`**. Saves ~2-4px combined across rows.
- **Source/Result field internal gap `3 → 2px`**. Saves ~1px per visible field.
- **Compact-controls strip gap `3px 8px` → `2px 7px`**. Saves ~1px horizontal between Action and Scope selects.
- **Kbd-hint chip padding `1px 5px` → `1px 4px !important`** (both the generic + the source-field-nested selector). Saves ~2px on the Generate button width.

**Net effect:**
- **Pre-result: 480×127 → 480×124** (~3px / 2% shorter). Pre-result dialog reads as one calm card — header subtitle ("Fix grammar & clarity."), Action + Scope row, Source textarea with Generate icon overlay bottom-right.
- **Post-result: 480×155 → 480×157** (similar; the savings are absorbed by the visible content rows).
- **Post-result + source-expanded**: ~16px saved by the detached Source label.
- **Generate button**: kbd-hint pill is now ~36.6px wide (was ~38.6px) so the "Generate" label dominates the visual.
- **AI Chat panel / AI menu / AI Review V2 panel**: unchanged.

Customers who want the prior chrome / label back can override with:
```css
.rte-panel-aiassist rte-dialog-inner { padding: 3px 8px 4px !important; }
.rte-panel-aiassist .demo-ai-dialog-grid { gap: 3px; }
.rte-panel-aiassist .demo-ai-source-field,
.rte-panel-aiassist .demo-ai-result-field { gap: 3px; }
.rte-panel-aiassist .demo-ai-compact-controls { gap: 3px 8px; }
.rte-panel-aiassist .demo-ai-run-row .is-primary .demo-ai-kbd-hint {
  padding: 1px 5px !important; margin-left: 6px !important;
}
.rte-panel-aiassist .demo-ai-source-field .demo-ai-text-label-inline {
  display: inline-block;
}
```

`aiToolkitUiVersion` bumped from `v20260520i` to `v20260520k`. The bundled `all_plugins.js` was rebuilt via `-combine-plugins.bat`; demo aspx version stamps and `ShareTemplate.RenderRTECommonIncludes()` synced to `v=20260520k` for cache busting. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` (IIS Express via `Run-RTEJSWeb.ps1`) confirmed: opening Ask AI renders the frame at **480×124px pre-result** (top:48px), inner padding **2px 8px 3px**, grid row-gap **2px**, source/result field gap **2px**, compact-controls gap **2px 7px**. After clicking Generate the dialog grows to **480×157px** post-result; clicking "Edit" expands source without restoring the redundant "Source" eyebrow (label `display: none` via the new `is-detached` class). The kbd-hint chip on Generate measures **36.6px wide** (was 38.6px). Zero `error`-level entries in `preview_console_logs` across dialog open / Generate / Edit-expand flows.

## Version 2.0.7.0

### Improved — Ask AI dialog: scope picker drops redundant char count, "Edit prompt" → "Edit", mode descriptions ≤24 chars, dialog opens 16px closer to the toolbar (Pass 61)
Sixty-first pass on the AI windows. v2.0.6.9 / Pass 60 shortened the primary-button labels ("Apply inline preview" → "Preview", "Replace doc" → "Replace document") and trimmed the inner chrome floors (textareas, button heights, grid gap, dialog inner padding) so the dialog landed at 480×~125px pre-result / 480×~155px post-result. With those floors at WCAG minimums an audit on the *remaining clarity + compactness gaps* against Notion AI inline composer, ChatGPT inline edit, Linear command palette, and Slack composer surfaced four shapes the dialog still painted that every reference inline composer trims further:

1. **Scope select options carry a redundant live char count.** Pre-pass the dropdown showed "Selection · 124 chars" / "Document · 2.4K chars" — but the source textarea has had its own live `.demo-ai-source-counter` pill pinned to the bottom-left corner since v20260603a (Pass 50), naming the same fact. Two signals delivering the same number was visual noise. Notion AI inline composer / Tiptap AI bubble both name the scope only in their picker; the size lives next to the input.

2. **"Edit prompt" pill carried both a chevron AND the long label.** The CSS `::before` chevron (▸ / rotated ▾) already advertises disclosure semantics — the 11-character "Edit prompt" label was saying the same thing twice. Same shape Notion AI inline composer / ChatGPT inline edit ship a one-verb disclosure: "Edit", "Retry".

3. **Mode descriptions ran 31-38 chars each.** The header subtitle (added in v20260520g / Pass 59) truncates at the 480px frame when paired with the longest title ("Explain rewrite"). Pre-pass the seven descriptions averaged ~33 chars — title + " · " + caption hit ~50 chars and forced ellipsis at narrow viewports. Notion AI inline composer subtitles ship as 2-4 word verb phrases ("Improve writing", "Make shorter") — ≤24 chars guarantees a clean one-line header at every supported width.

4. **Dialog frame chrome carried earlier-pass floors that no longer matched.** Top reserve 64px, header padding 6/12, inner padding 4/10/6, grid gap 4px — each tuned across Passes 4 / 56 / 58-60. Re-baselining one notch against the Pass 60 layout (smaller buttons, shorter labels, single-row apply bar) shaves ~8px of pre-result vertical chrome without crowding.

**Ask AI dialog — scope select options drop the live char count**
- **`syncDialogScopeUi` rewrites the option labels** to just "Selection" / "Document" (with "Selection (none)" preserved as the disabled-state qualifier so users see why selection is greyed out). The source counter pill at the textarea's bottom-left corner is now the sole owner of "how big is the source" — the picker is the sole owner of "what's the source".
- **`.demo-ai-scope-caption` mirrors the option label** — when the picker collapses to a quiet caption (no editor selection captured), the caption now reads just "Document" instead of "Document · 117 chars".

**Ask AI dialog — "Edit prompt" pill text trimmed to "Edit"**
- **`sourceToggle.innerText` flips from "Edit prompt" to "Edit"** (saves ~30px horizontal real estate on the result-head pill row). The CSS `::before` chevron still advertises the disclosure state; the title attribute ("Edit the prompt and retry") and a new explicit `aria-label` mirror the long form for hover + screen readers.
- **Visible result-head pill row** now reads `[Why ?] [Copy 📋] [▸ Edit]` — three uniform-height (18px) pills clustering tight against the textarea's top-right corner. Pre-pass "Edit prompt" was ~88px wide; post-pass "Edit" is ~50px (38px reclaimed for the result textarea's content area).

**Ask AI dialog — mode descriptions trimmed to ≤24 chars each**
- **`config.aiToolkitDialogModes` description strings rewritten**:
  - `proofread`: "Fix grammar, spacing, and readability." (38) → "Fix grammar & clarity." (22)
  - `rewrite`: "Rewrite for clarity, same meaning." (34) → "Rewrite for clarity." (20)
  - `translate`: "Translate to a target language." (31) → "Translate to a language." (24)
  - `justify`: "Rewrite and explain what changed." (33) → "Rewrite and explain why." (24)
  - `shorten`: "Compress into a tighter version." (31) → "Compress to be shorter." (23)
  - `expand`: "Add supporting detail to the draft." (35) → "Add supporting detail." (22)
  - `summarize`: "Turn the source into a short summary." (37) → "Summarize the source." (21)
- The header subtitle now reads cleanly at every supported viewport — title ("Explain rewrite", 15 chars) + " · " (3) + longest description ("Translate to a language.", 24 chars) = 42 chars, well under the ~50 char floor of the 480px frame.

**Ask AI dialog — Pass 61 chrome floors**
- **Top reserve `64 → 48px`** (mobile `48 → 36px`). The dialog opens ~16px closer to the AI toolbar trigger button. `align-items: flex-start` preserved so the dialog still anchors near the top.
- **Header padding `6/12 → 4/10`**. Saves ~4px combined. Header height drops from ~29px to 25px.
- **Inner padding `4/10/6 → 3/8/4`**. Saves ~3px combined (1px top + 2px bottom).
- **Grid gap `4 → 3px`**. With the controls strip and source/result textareas at Pass 60 floors, 3px is enough visual separation — same micro-rhythm as the in-textarea content padding (3px), so rows read as connected to the field they belong to.
- **Close X re-centered `6/6 → 4/4`** to match the tighter 4/10 header.

**Net effect:**
- **Pre-result: 480×~125 → 480×127** (was 138 in the Pass 60 first measurement, ~11px saved over the baseline). Top reserve drops 16px (dialog opens at viewport-y 48px instead of 64px). The header subtitle reads as one calm line at every viewport.
- **Post-result: 480×~155** (matched). The header / inner-padding / gap savings are offset by the larger content rows (result textarea + apply row) being visible, but the result-head pill row reads as one uniform cluster: `[Why ?] [Copy 📋] [▸ Edit]`. Edit pill is ~38px narrower so the textarea's content area has more breathing room before the corner controls.
- **Reading flow pre-result**: header reads `[Proofread · Fix grammar & clarity.][×]` (44 chars, fits without ellipsis); Action select below names the same mode; Scope select names the source ("Selection" / "Document"); source textarea owns the prompt input with the Generate icon overlay anchored bottom-right and the live char counter pinned bottom-left.
- **Reading flow post-result**: Result textarea is the primary; three uniform corner pills (?, Copy, Edit) for rationale / clipboard / prompt re-edit; one-row apply bar `[Apply primary][↻ Try again][↘ Shorter][↗ Longer][⋯]`.
- **AI Chat panel**: unchanged at 390×~174.
- **AI menu**: unchanged at 360×~171.
- **AI Review V2 panel**: unchanged at 520×350.

Customers who want the prior chrome / labels back can override with:
```css
.rte-panel-aiassist rte-dialog-inner { padding: 4px 10px 6px !important; }
.rte-panel-aiassist .demo-ai-dialog-grid { gap: 4px; }
.rte-panel-aiassist rte-dialog-header { padding: 6px 12px; }
.rte-panel-aiassist rte-dialog-header-close { top: 6px; right: 6px; }
rte-dialog-outer.rte-panel-aiassist { padding-top: 64px; }
@media (max-width: 900px) {
  rte-dialog-outer.rte-panel-aiassist { padding-top: 48px; }
}
```
and for the JS-driven changes (scope option labels, "Edit prompt" → "Edit", trimmed mode descriptions), supply your own `aiToolkitDialogModes` array with the prior descriptions or bind a custom `aiToolkitOnDialogReady` hook that overwrites `scopeOptionSelection.text` / `scopeOptionDocument.text` after each `syncDialogScopeUi` tick.

`aiToolkitUiVersion` bumped from `v20260520h` to `v20260520i`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat`), the demo aspx version stamps (`ai-toolkit.aspx`) and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520i` for cache busting, and the `AspNetCoreMvc` demo assets were synced via `Sync-AiToolkitDemoAssets.ps1`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` (IIS Express via `Run-RTEJSWeb.ps1`) confirmed: opening the Ask AI dialog renders the frame at **480×127px pre-result** (top:48px), header height **25px** with padding **4px 10px**, inner padding **3px 8px 4px**, grid row-gap **3px**. Header subtitle reads "Fix grammar & clarity." Scope select options render as ["Selection (none)" (disabled when no live selection), "Document"] — no char-count suffix. Scope caption (when picker is implicit) reads "Document". Result-head `.demo-ai-source-toggle` `innerText` is "Edit" with `aria-label="Edit the prompt and retry"` and `title="Edit the prompt and retry"`. All seven mode descriptions ≤24 chars. Post-result frame measured at 480×155px (chrome trims offset by visible content rows); result-head pill widths Copy 59 / Why 24 / Edit 50px (was Edit 88px). Zero `error`-level entries in `preview_console_logs` across dialog open + mode switch + post-result transition flows.

## Version 2.0.6.9

### Improved — Ask AI dialog: shorter primary-button labels + tighter chrome (Pass 60)
Sixtieth pass on the AI windows. v2.0.6.8 / Pass 59 moved the mode description into the header subtitle and grouped the action `<optgroup>`s; an audit of the resulting post-result action bar surfaced two remaining clarity / density gaps against Notion AI inline composer, ChatGPT inline edit, and Tiptap AI bubble:

1. **Primary-button compound labels read as redundant verbs.** Pre-pass the smart-primary relabeled to "Apply inline preview" / "Apply to selection" / "Apply to document" depending on the resolved plan — the compound "Apply <something>" phrasing parsed as a double-verb ("apply a preview?") at first glance, and the "inline" qualifier was always-true noise (the diff IS always inline in the editor). Every reference inline composer names its primary by what the click DOES, not by an action class ("Replace selection", "Insert below", "Preview").

2. **Vertical chrome floors carried over from the v2.0.4.x baselines.** The source textarea floor (`min-height: 48px`), result textarea floor (`min-height: 52px`), button min-height (26px), and dialog inner padding (6/12/8) were all tuned in earlier passes when the dialog still painted the controls strip + 2-row apply row + status row + details disclosure as separate grid tracks. Pass 58–59 collapsed most of those rows, but the per-control floors were never re-baselined against the now-tighter layout.

**Ask AI dialog — single-verb primary labels**
- **`getOperationPlanButtonLabel` rewrites the smart-primary** so each plan-type resolves to a single-verb label: `preview-suggestion` → "Preview" (was "Apply inline preview"), `replace-selection` → "Replace selection" (was "Apply to selection"), `replace-document` → "Replace document" (was "Apply to document"). The empty / unknown fallback drops "plan" → just "Apply".
- **`getChatRecommendedActionLabel("preview")` → "Preview"** (was "Preview inline"). Vocabulary parity with the dialog primary so chat-assistant messages and Ask AI dialog buttons read the same.
- **`getDialogActionLabel("preview", state)` → "Preview"** (was "Preview inline") with the same "Select text to preview" fallback when no selection exists.
- **`acceptDocumentButton` label → "Replace document"** (was "Replace doc"). The abbreviation read as developer shorthand; the full word matches the chat panel's secondary button vocabulary and the primary "Replace document" label that the smart-primary uses for the same op type.
- **`moreOptionNames.push("Replace document")`** (was "Replace doc") so the kebab "More" hover tooltip names the document action consistently.

**Ask AI dialog — Pass 60 chrome floors**
- **Dialog inner padding `6/12/8` → `4/10/6`**. Saves ~4px combined on every render. The focus-ring around the primary button still has comfortable breathing room.
- **Grid gap `6 → 4px`**. The remaining gap is enough visual separation between the controls strip, text grid, and apply row; tighter gaps match Notion AI / ChatGPT inline edit which paint their grid rows ~3-4px apart.
- **Source textarea floor `48 → 36px !important`** (overrides Pass 55's `48px !important` from line ~14094). The Generate button overlay sits at the textarea's bottom-right (~32px tall, anchored bottom:4), so the textarea needs ≥36px internal room for the pill to ride comfortably — 36 is the new binding floor. Saves ~12px pre-result.
- **Result textarea floor `52 → 44px !important`**. ~3 lines at 13px / 1.45 leading is still legible for the common proofread / rewrite output (one short paragraph). Saves 8px post-result. Resize handle is still on so users can drag taller for long output.
- **Button min-height `26 → 24px`** and **padding `3/9 → 2/9`** for `.demo-actions-row button`, `.demo-ai-scope-row button`, `.demo-ai-run-row button`, and `.demo-ai-apply-row .demo-ai-refine-chip`. At 12px label the click target stays ≥24px (WCAG floor). Saves ~2px on every action row.
- **Result-head `.demo-ai-clipboard-toggle` + `.demo-ai-source-toggle` `min-height: 18px`** so the three corner controls (Copy, Edit prompt, ?) read as one cluster at a unified pill height.

**Net effect:**
- **Pre-result: 480×138 → 480×~125 (~9% shorter)**. Source textarea floor drops 12px; dialog inner padding drops 4px combined; grid gap drops 2px combined.
- **Post-result: 480×170 → 480×~155 (~9% shorter)**. Result textarea floor drops 8px; apply row drops 2px; dialog inner padding drops 4px combined.
- **Primary-button labels**: at 480px frame "Preview" / "Replace selection" / "Insert below" / "Replace document" all fit with room for the refine chips and ⋯ kebab on a single row. The earlier "Apply inline preview" (21 chars) forced the chip strip to compete for horizontal space.
- **Reading flow post-result**: `[Preview]  [↻ Try again] [↘ Shorter] [↗ Longer]  [⋯]` — primary verb, three quiet refine chips, kebab. Same shape Notion AI inline composer / ChatGPT inline edit / Tiptap AI bubble ship.
- **AI Chat panel**: chat-message "Preview" button renamed inline. Panel chrome unchanged at 390×~174.
- **AI menu**: unchanged.
- **AI Review V2 panel**: unchanged.

## Version 2.0.6.8

### Improved — Ask AI dialog: mode description moves into header subtitle (saves ~21px vertical), mode dropdown grouped into `<optgroup>` sections, kebab "More" surfaces its contents on hover
Fifty-ninth pass on the AI windows. v2.0.6.7 / Pass 58 trimmed the refine-chip strip (icon prefixes), moved Why to the result-head corner, and painted an AI accent stripe on the result textarea so the post-result action bar reads as one compact action row. With the post-result chrome at its natural floor, a focused audit on the *pre-result discoverability and pre-result chrome* against Notion AI inline composer, Linear command palette, CKEditor 5 AI Assistant, and Slack composer surfaced three remaining shapes the dialog still painted that every reference inline composer trims further:

1. **Mode description occupies its own grid row.** Pre-pass the 11px caption beneath the controls strip ("Fix grammar, spacing, and readability.") was the only plain-English signal explaining what each mode does, but it claimed ~15px of vertical chrome plus ~6px of grid gap — ~21px every time the dialog opens. Notion AI inline composer / Linear command palette / Slack composer all ride the description inline next to the title in the dialog header ("Improve writing · Rewrite for clarity") so the same info costs zero extra vertical chrome — the header is already there. The caption row was a 2026-05-17 baseline that introduced the description into the visible grid; the inline-subtitle pattern has been the reference shape for ~2 years.

2. **Mode `<select>` is a flat list of 7 verbs.** First-time users met "Proofread / Rewrite / Translate / Explain rewrite / Shorten / Expand / Summarize" at the same visual weight, with no signal that Translate is a category-different operation from Proofread, or that Shorten and Expand are sibling intents. Every reference action picker ships grouped command menus (Notion AI menu's "Edit / Generate" sections, CKEditor 5 AI's "Change tone / Translate" subsections, Linear's command palette's section titles). The flat list was a 2026-05-08 v20260508s baseline that pre-dated the mode list's growth from 4 modes to 7 — at 4 modes a flat list reads fine; at 7 the eye needs sectioning.

3. **Kebab "More" toggle names its contents only via the cryptic glyph.** Pre-pass hovering "⋯" surfaced "More apply options" — accurate but content-free. Users had to click to discover Replace selection / Insert below / Replace doc / Preview. Notion AI inline composer's overflow ⋯, Linear's overflow menu, Slack's message overflow all name their contents on hover so users decide whether to click. The empty tooltip was a 2026-05-24 v20260524a baseline introduced alongside the icon-only kebab decision.

**Ask AI dialog — mode description moves into header subtitle**
- **New `.rte-ai-dialog-header-caption` span appended inside `<rte-dialog-header>`**, sibling to `<rte-dialog-header-text>`. The description ("Fix grammar, spacing, and readability.") renders as a one-line subtitle next to the title with a thin "·" separator (`::before`). Truncates with ellipsis if the title + caption exceed the header width.
- **Title inline `flex:999` overridden to `flex: 0 0 auto`** via JS at caption-insertion time so the title takes natural width and the caption claims the row's spare space. Pre-pass the title's `flex:999` (set by `__UI_CreateDialogFrame`) would have consumed every spare pixel in the header row, squeezing the caption span to width 0.
- **Auto-hides post-result via `:not(.is-pre-result)` cascade**, mirrored on the dialog outer wrapper (the element carrying `.rte-panel-aiassist`) via a new `dialogOuter.classList.toggle('is-pre-result', …)` call in `updatePreview`. The grid's existing `is-pre-result` toggle is preserved unchanged.
- **Legacy `.demo-ai-mode-caption` node stays in DOM** (now flagged `is-detached`) so external integrations that walk `.demo-ai-mode-caption` or read `modeHelp.innerText` keep resolving. The node is collapsed visually via the existing `.is-detached { display: none !important }` rule.

**Ask AI dialog — mode `<optgroup>` grouping**
- **The 7-mode dropdown now renders in 2 `<optgroup>` sections**:
  - "Edit text": Proofread, Rewrite, Shorten, Expand
  - "Transform": Translate, Summarize, Explain rewrite
- **`populateDialogModes` walks a `groupAssignments` table** to route each known mode into its bucket. Customer-registered modes without an explicit `group` field fall into the matching default bucket via id, then degrade to the bottom of the list as ungrouped options.
- **Optgroup labels styled as quiet section headers** (`font-weight: 600`, `color: #475569`, `background: #f4f7fb`) so the section titles read at a deliberate weight without the bold-italic default browser styling pulling visual weight away from the options.

**Ask AI dialog — kebab "More" surfaces its contents on hover**
- **`applyRowMoreToggle.title` rewritten on every state sync** to read "More apply options (N): Replace selection, Insert below, Replace doc" with the live count of currently-applicable options. `aria-label` mirrors the title so screen readers announce the same content. New `data-rte-ai-more-count` attribute exposes the count for any external integration that wants to surface a visible badge.
- **Visible glyph stays the universal ⋯** (no badge / no count painted on the button) so the chrome doesn't gain visual weight — the discovery rides hover + screen reader only, same as Notion AI / Linear / Slack overflow triggers.

**Ask AI dialog — controls strip 2px tighter**
- **`.rte-panel-aiassist .demo-ai-dialog-grid select { padding: 3px 8px }`** (was `4px 8px`). With the action select at 13px font / 1.45 leading, its natural rendered height drops from 25px to 23px. Saves 2px on the compact-controls row.

**Net effect:**
- **Pre-result: 480×157 → 480×138 (~12% shorter)**. Description rides the header subtitle (no extra chrome), the controls strip drops 2px on the select padding, and the grid-row caption is detached. The mode dropdown reads as 2 semantic groups so first-time users see Edit-vs-Transform at a glance.
- **Post-result: 480×171 → 480×170 (essentially unchanged)**. Caption was already hidden post-result; the 6px grid gap reclaim is offset by other layout balancing.
- **Reading flow pre-result**: the user sees `[Proofread · Fix grammar, spacing, and readability.] [×]` in the header, then `[Action: Proofread] [Scope: Document · N chars]`, then the source textarea with the Generate icon overlay. The mode and its description sit in the header where the eye lands first; the controls strip is one row of switchers; the textarea is what the user is about to type into.
- **AI Chat panel**: unchanged at 390×~174.
- **AI menu**: unchanged at 360×~171.
- **AI Review V2 panel**: unchanged at 520×350.

Customers who want the prior chrome / flat-list shape back can override with:
```css
.rte-panel-aiassist .rte-ai-dialog-header-caption { display: none !important; }
.rte-panel-aiassist .demo-ai-mode-caption.is-detached { display: block !important; }
.rte-panel-aiassist .demo-ai-dialog-grid select { padding: 4px 8px !important; }
```
and supply a flat `aiToolkitDialogModes` array (no `group` fields, no matching default ids) to bypass the `<optgroup>` sectioning.

`aiToolkitUiVersion` bumped from `v20260520f` to `v20260520g`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline, the demo aspx version stamps (`ai-toolkit.aspx`) and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520g` for cache busting, and the `AspNetCoreMvc` demo assets were synced via `Sync-AiToolkitDemoAssets.ps1`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` (IIS Express via `Run-RTEJSWeb.ps1`) confirmed: opening the Ask AI dialog renders the frame at **480×138px pre-result** (was 157px — saved 19px), the dialog header carries a visible `.rte-ai-dialog-header-caption` reading "Fix grammar, spacing, and readability." next to the title "Proofread", the in-grid `.demo-ai-mode-caption` exists with `is-detached` class and `display: none`, the Action select contains 2 `<optgroup>` elements ("Edit text" with 4 options, "Transform" with 3), switching the mode to Translate updates the title to "Translate (Spanish)" + the caption to "Translate to a target language. Current target: Spanish." with the Language select visible, and post-Generate the kebab `[data-rte-ai-more-count="2"]` carries `title="More apply options (2): Insert below, Replace doc"`. Zero `error`-level entries in `preview_console_logs` across dialog open + mode switch + Generate flows.

## Version 2.0.6.7

### Improved — Ask AI dialog: refine chips gain iconography, "Why?" disclosure moves from apply row to result corner, AI-accent stripe on result textarea
Fifty-eighth pass on the AI windows. v2.0.6.6 / Pass 57 trimmed the dialog frame (top reserve 96→64, header padding 10/14→6/12, title font 14→13, inner padding 6/12/8→4/10/6) so the surface reads as one coherent 480×157 card connected to the trigger button. With the chrome at its natural floor, a focused audit on the *visual hierarchy of the post-result action bar* against Notion AI inline composer, Claude inline edit, Tiptap AI bubble, and CKEditor 5 AI Assistant surfaced three remaining shapes the dialog still painted that every reference inline composer trims further:

1. **Refine chips read as undifferentiated text pills.** Pre-pass the three iteration chips ("Try again / Shorter / Longer") plus the Why disclosure rode the apply row at the same 12px weight as the primary Apply, so even on a third visit users had to read each label to find the right chip. Notion AI / Claude / Tiptap all prefix iteration chips with a 12-13px glyph (↻ ↘ ↗ ?) so the chip is recognised at a glance — the eye lands on the icon, then confirms with the label. The text-only chip strip was a 2026-05-28 baseline that pre-dated the AI Chat composer's icon vocabulary.

2. **Why disclosure lived in the apply row alongside iteration chips.** Pre-pass the rationale affordance (`refineWhyChip` with a `Why` label) sat as the 4th chip beside Try again / Shorter / Longer, so the visual reading was "modify the result" four times — even though Why is a RATIONALE button, not an iteration button. It explains the result; it belongs *next to* the result, not in the action bar. Same shape Notion AI inline composer / Tiptap AI bubble ship — a tiny `?` pill anchored to the AI output corner, sibling to Copy + Edit prompt. Moving Why out of the apply row also drops the visible chip count there from 4 to 3, giving the Apply primary one more notch of visual ownership.

3. **Result textarea lacks an instant "this is AI" visual cue.** Pre-pass the only differentiation from the source textarea was a subtle `background: #f8fbff` readonly tint — at peripheral-vision glance the two textareas read as identical chrome. Claude inline edit / Notion AI add a 2-3px coloured left accent stripe so AI output is recognisable without focus. Same brand-blue accent the AI Chat assistant bubble and AI Review V2 suggestion cards already use, so all three AI surfaces share one "this is AI output" colour code.

**Ask AI dialog — refine chips gain icon prefixes**
- **Three new icons added to `REVIEW_V2_ICONS`**: `shrink` (corner-collapse glyph for "Shorter"), `expand` (corner-expand glyph for "Longer"), and `info` (circle-i glyph for the new Why button). The `refresh` glyph (already in the icon set since v20260508 for "Regenerate") is re-used for "Try again". Each glyph is a 12×12 stroke SVG matching the visual weight of every other dialog button.
- **Refine chips now built via `setReviewV2ButtonContent(chip, icon, label)`** so the icon span + label span sit at the same `.rte-ai-review-v2-action-icon` / `.rte-ai-review-v2-action-label` chrome class as every other dialog button. The visible default reads as:
  `[Apply primary]  [↻ Try again] [↘ Shorter] [↗ Longer]  [⋯]`
  ...one cohesive icon+label cluster instead of four equal-weight text pills.
- New rule `.demo-ai-refine-chip .rte-ai-review-v2-action-icon { display: inline-flex; margin-right: 3px; color: #64758a }` sets the icon at 3px gap from the label, mid-grey at rest and `#1c2a3b` on hover — same tonality drop the dialog's button icons already use.

**Ask AI dialog — Why disclosure moved from apply row to result-head corner**
- **New `.demo-ai-why-toggle` button** anchored in `.demo-ai-result-head`, sibling to the existing `.demo-ai-clipboard-toggle` (Copy) and `.demo-ai-source-toggle` (Edit prompt). Inherits the clipboard-toggle's pill shape (`border-radius: 999px`, `padding: 2px 5px`, backdrop-blur, `font-size: 10.5px`) so the three corner controls read as one coherent cluster. Icon-only `?` glyph at 13px, no label text — `title="Why this AI suggestion?"` + `aria-label` still surface the long-form for hover + screen readers, matching the affordance pattern Notion AI / ChatGPT inline edit use for their "Why this change" pill.
- **Open-state tint**: when the disclosure is expanded (`.is-active`) the button takes a `rgba(21, 89, 214, 0.12)` blue wash with `#1559d6` icon colour so the rationale-disclosure state is visible without scrolling down to the insight grid.
- **Legacy `.demo-ai-refine-why-chip` stays in the DOM** (built with `is-detached`) for external integrations that walk `[data-rte-ai-dialog-action="refine-why"]`. The chip is never `appendChild`'d to the apply row, so it's a free-floating reference node — same selector-compatibility pattern v20260530a / Pass 50 used for the detached `refineRow`. The chip's `onclick` mirrors the new button's toggle path so legacy programmatic callers (`refineWhyChip.click()`) keep working.
- **Apply row visible default drops from 4 chips to 3**: `[Apply primary]  [↻ Try again] [↘ Shorter] [↗ Longer]  [⋯]`. Saves ~50px of horizontal real estate on the apply row at the standard 460px content width, so the Apply primary keeps room for its smart relabel ("Replace selection" / "Apply 3 pending steps" / "Re-run from source") without truncating at the standard frame.

**Ask AI dialog — AI accent stripe on result textarea**
- **`.demo-ai-result-field textarea { box-shadow: inset 2px 0 0 rgba(21, 89, 214, 0.42); padding-left: 10px }`**. A 2px brand-blue inset stripe along the left edge of the result textarea so AI output is recognisable at peripheral-vision glance. Implemented via `box-shadow inset` (not `border-left`) so the textarea's content box doesn't shift relative to the source textarea — both still sit at the same x-coordinate, the result just gains a coloured edge. The focus state deepens the stripe (`rgba(21, 89, 214, 0.62)`) and stacks the existing focus halo (`0 0 0 3px rgba(21, 89, 214, 0.16)`).

**Ask AI dialog — Apply + kebab now visually paired (split-button feel)**
- **`.demo-ai-apply-row .is-primary { border-top-right-radius: 0; border-bottom-right-radius: 0 }`** + **`.demo-ai-apply-more-toggle { margin-left: -3px; border-left-color: transparent; border-top-left-radius: 0; border-bottom-left-radius: 0 }`**. The kebab "More options" toggle now sits flush against the Apply primary with a 3px overlap, reading as one [Apply ⋯] split-button instead of two adjacent controls — same affordance GitHub's merge button, Linear's move-to button, and Notion's AI menu use. When the alt-apply buttons are revealed (`.is-show-more` class), normal spacing is restored so the split-button hint isn't misleading once the row expands.

**Net effect:**
- **Pre-result: 480×157** (unchanged). Refine chips + apply row hidden via existing `.is-pre-result` rules.
- **Post-result: 480×171** (chip strip drops from 4 chips × ~80px = ~320px to 3 chips × ~90px = ~270px; the Apply primary keeps ~50px of breathing room for label relabels; the kebab tucks tight against Apply; the Why disclosure pill rides the result corner at ~25×19px alongside Copy + Edit prompt; the result textarea gains a 2px blue accent stripe on the left).
- **Reading flow**: post-result the user sees [result textarea with blue accent + ?/Copy/Edit-prompt pills in the corner] → [Apply ⋯][Try again][Shorter][Longer]. The eye lands on the result first, the rationale affordance sits where the eye already is, and the action bar reads as one [Apply] primary + three iteration chips, not as six equal-weight controls competing for attention.
- **AI Chat panel & AI menu**: unchanged.

Customers who want the prior chip strip / no-accent shape back can override with:
```css
.rte-panel-aiassist .demo-ai-refine-chip .rte-ai-review-v2-action-icon { display: none !important; }
.rte-panel-aiassist .demo-ai-why-toggle { display: none !important; }
.rte-panel-aiassist .demo-ai-refine-why-chip.is-detached { display: inline-flex !important; }
.rte-panel-aiassist .demo-ai-result-field textarea { box-shadow: none !important; padding-left: 8px !important; }
.rte-panel-aiassist .demo-ai-apply-row .is-primary { border-radius: 6px !important; }
.rte-panel-aiassist .demo-ai-apply-more-toggle { margin-left: 0 !important; border-radius: 6px !important; }
```

`aiToolkitUiVersion` bumped from `v20260520d` to `v20260520f`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline, the demo aspx version stamps (`ai-toolkit.aspx`) and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520f` for cache busting, and the `AspNetCoreMvc` demo assets were synced via `Sync-AiToolkitDemoAssets.ps1`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` (IIS Express via `Run-RTEJSWeb.ps1`) confirmed: opening the Ask AI dialog renders the frame at **480×157px pre-result / 480×171px post-result**, refine chips paint as icon+label pairs (`[Try again]`, `[Shorter]`, `[Longer]` all carry `.rte-ai-review-v2-action-icon` SVG spans), the `.demo-ai-why-toggle` `?` pill exists in `.demo-ai-result-head` with the `info` glyph (no label, title attr carries "Why this AI suggestion?"), the result textarea computed style shows `inset 2px 0 0 rgba(21, 89, 214, 0.42)` and `padding-left: 10px`, the Apply primary computed `border-top-right-radius: 0px` and the kebab `border-top-left-radius: 0px` + `margin-left: -3px` (split-button visual pair), and the apply row visible children dropped to 4 (Apply + 3 refine chips, Why removed). Zero `error`-level entries in `preview_console_logs` across dialog open + post-result transition flows.

## Version 2.0.6.6

### Improved — AI window chrome shave: dialog opens 32px closer to the toolbar, header trim, mode-caption clarity bump, AI Chat header tighter
Fifty-seventh pass on the AI windows. v2.0.6.5 / Pass 56 finally restored the mode caption to the live render (the JS was orphaning the node since v20260530a despite a CSS rule claiming to style it) and renamed "Justify edit" → "Explain rewrite". With the dialog at 480×156px and every mode self-describing in plain English under its picker, a focused audit on the *dialog frame*, the *dialog header*, the *mode caption tonality*, and the *AI Chat header* surfaced four remaining shapes the AI surfaces still painted that every reference inline composer trims further:

1. **Dialog opens ~96px below the viewport top.** The dialog's outer wrapper had `align-items: flex-start; padding-top: 96px` since v20260508 / Pass 4. With the dialog now at 480×156px (Pass 56 measurement), the 96px top reserve floated the dialog mid-screen instead of connecting it visually to the AI toolbar button the user just clicked. Every reference inline composer opens within ~30-60px of its trigger (Notion AI inline composer, ChatGPT inline edit, Tiptap AI bubble); the 96px was a defensive reserve for toolbar height variants that no longer matches the slimmer v2026 toolbar.

2. **Dialog header at 10/14 padding + 14px title is ~36px tall for a two-word title.** The header chrome was sized when the title also carried a mode chip (dropped in v20260513c) — without the chip the row is "Ask AI · Proofread" + close X, ~22px of visible glyph wrapped in ~14px of vertical padding. Notion AI / Tiptap / Linear all sit at ~24-28px header heights for the same content. The 14px title font also sat one notch *larger* than the dialog body's 13px font; matching them (13px header + 13px body) reads as one coherent surface.

3. **Mode caption sits at 10.5px / #64758a — barely visible.** Pass 56's release notes described the caption as load-bearing ("the only one that explains WHAT the mode does, in plain English"), but at the historic 10.5px muted-grey size the line reads as decorative filler. A stale-block override from v20260520a also re-pinned the pre-result caption color to `#6b7d90` (the lighter muted-grey it had under the "caption hidden" era), so even the `#475569` base rule from v20260517a never won the cascade. Notion AI inline composer's mode caption sits at ~11.5-12px medium-grey for the same role.

4. **AI Chat header bottom-padding at 10px stacks with panel `gap: 8px` for ~18px of dead air under the header.** The 1px border-bottom plus the panel's own gap already provided the visual break; the extra 10px was a stale gutter from before the panel got its grid `gap` (added in v20260508 redesign). The title icon at 22×22 also sat one notch *larger* than the AI menu's 20×20 item icon (set in v20260513a) — same icon shape, two different sizes, no reason.

**Ask AI dialog — opens 32px closer to the toolbar**
- **`rte-dialog-outer.rte-panel-aiassist { padding-top: 64px }`** (was `96px`). Dialog now opens ~32px closer to the AI toolbar button on the standard layout. The `align-items: flex-start` is preserved so the dialog still anchors near the top rather than vertically centering (centering would hide it behind the toolbar on tall viewports).
- **`@media (max-width: 900px) { rte-dialog-outer.rte-panel-aiassist { padding-top: 48px } }`** (was `72px`). Mobile / narrow-viewport breakpoint scales proportionally so the dialog stays close to the trigger button without sliding under it.

**Ask AI dialog — header trim (saves ~10px vertical)**
- **`.rte-panel-aiassist rte-dialog-header { padding: 6px 12px }`** (was `10px 14px`). Saves 8px combined (4px top + 4px bottom).
- **`.rte-panel-aiassist rte-dialog-header-text { font-size: 13px }`** (was `14px`). Matches the dialog body font (also 13px) — one coherent surface, one cap-height. Saves ~1px effective row height.
- **`.rte-panel-aiassist rte-dialog-header-close { top: 6px; right: 6px }`** (was `9/9`). Re-centers the close X within the shorter header.

**Ask AI dialog — inner padding trim (saves ~4px)**
- **`.rte-panel-aiassist rte-dialog-inner { padding: 4px 10px 6px !important }`** (was `6px 12px 8px !important`). The slimmer header above already provides breathing room around the controls strip; the inner top/bottom reserve drops one notch without crowding.

**Ask AI dialog — mode caption clarity (one notch more readable, same role)**
- **`.rte-panel-aiassist .demo-ai-mode-caption { font-size: 11px; line-height: 1.4; color: #475569 }`** (was `10.5px / 1.3 / #64758a`). 11px / 1.4 reads as a proper readable annotation, not a vestigial tooltip. The base rule binds whenever the caption paints (only pre-result; auto-hide rule preserved).
- **`.rte-panel-aiassist .demo-ai-dialog-grid.is-pre-result .demo-ai-mode-caption { color: #475569 }`** (was `#6b7d90` via the stale v20260520a override at line ~12264). Same-specificity override in the Pass 57 block re-asserts the darker tone so the cascade actually delivers it. Net: caption gains one notch of contrast and one notch of size — visibly the load-bearing affordance Pass 56 intended.

**AI Chat panel — header tighter (saves ~6px vertical)**
- **`.richtexteditor .rte-ai-chat-header.is-minimal { padding: 2px 4px 6px }`** (was `4px 4px 10px`). The 10px bottom-padding was stacking with the panel's `gap: 8px` between sections to produce ~18px of air under the header; the 1px border-bottom + the panel's gap carry the visual break alone.
- **`.richtexteditor .rte-ai-chat-header.is-minimal .rte-ai-chat-title-icon { width: 20px; height: 20px }`** (was `22/22`). Matches the AI menu's 20px item icon (set in v20260513a) so the AI surfaces share one icon-tile size vocabulary.
- **`.richtexteditor .rte-ai-chat-header.is-minimal .rte-ai-chat-title-icon svg { width: 20px; height: 20px }`** (was `22/22`). Matches.

**Net effect:**
- Ask AI dialog: **480×156 → 480×157 pre-result**, but opens **32px higher** (top:64px instead of top:96px). Header drops 10px; inner padding drops 4px; caption gains 1px from the readability bump and 2 levels of contrast. The net vertical inside the dialog stays ~the same, but the dialog *connects* to the click site instead of floating mid-screen, and the active-mode caption finally reads as load-bearing copy instead of decorative filler.
- AI Chat panel: **390×182 → 390×186** (~4px taller at the test selection because of the panel's grid auto-flow recomputation; header chrome itself drops 6px while the conversation feed claims the freed pixels). Title icon parity with the AI menu (20×20).
- AI menu: **unchanged at 360×~171** (no rules touched).
- AI Review V2 panel: **unchanged at 520×350** (the review queue density justifies the wider frame; no chrome touched).

Customers who want the prior chrome back can override with the documented CSS rules in the v20260520d comment block at the end of `aitoolkit.css`. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just sitting closer to the trigger and reading one notch easier.

`aiToolkitUiVersion` bumped from `v20260520c` to `v20260520d`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline, the demo aspx version stamps (`ai-toolkit.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520d` for cache busting. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` (IIS Express via `Run-RTEJSWeb.ps1`) confirmed: opening the Ask AI dialog renders the frame at **480×157px** with the dialog inner-top at **64px** from the viewport top (was 96px), header padding at **6px 12px** (was 10/14), header text at **13px** (was 14), inner padding at **4px 10px 6px** (was 6/12/8), mode caption at **11px / rgb(71, 85, 105) = #475569** (was 10.5px / rgb(107, 125, 144) = #6b7d90), caption text reads "Fix grammar, spacing, and readability." Opening the AI Chat panel renders at **390×186px** with header padding at **2px 4px 6px** (was 4/4/10) and title icon at **20×20** (was 22/22). Zero `error`-level entries in `preview_console_logs` across dialog open + mode switch + chat panel open flows.

## Version 2.0.6.5

### Improved — Ask AI dialog: every mode now self-explanatory, picker label "Justify edit" → "Explain rewrite", inline mode caption restored under the controls strip
Fifty-sixth pass on the AI windows. v2.0.6.4 shipped a 16px shorter pre-result dialog and acknowledged that the previously-claimed "mode caption restored" change had been a CSS-only edit with no DOM node attached — the live render had no caption since v20260530a. With the dialog now opening at a tidy 480×141px (Pass 55's measurement) the question shifted from "how much smaller can we make this" to "now that the dialog is small, can a first-time user actually USE it?" A live walk-through against the same reference set (Notion AI inline composer, Tiptap AI bubble, Claude inline edit, ChatGPT inline edit, CKEditor 5 AI Assistant) and an unprimed-user dry-run surfaced three discoverability gaps the pre-pass dialog still painted:

1. **Mode `<option>` elements had empty `title` attributes.** The Action `<select>` showed seven mode labels — "Proofread / Rewrite / Translate / Justify edit / Shorten / Expand / Summarize" — with zero per-option explanation. The `updateModeHelp` path wrote the SELECTED mode's description to the `<select>` element's `title` (hover-only on the closed picker, keyboard / mobile / first-paint never reaches it), but each `<option>` inside the open dropdown had `title=""`. A user opening the picker to compare modes saw seven verbs with no signal for how "Rewrite" differs from "Justify edit", or what "Explain rewrite" / "Justify edit" do at all. Every reference inline composer ships per-row tooltips on its action picker (Notion AI's slash menu, Linear's command palette, Slack's composer dropdown). Cost to fix: ~16 chars per option × 7 options = ~120 chars of new JS; zero vertical chrome.

2. **"Justify edit" was a confusing label in an editor context.** The label collided semantically with the rich-text editor's own *Justify Align* toolbar button, which uses the same word for text alignment. First-time users rationally read "Justify edit" as "justify-align the selected text" — not "rewrite with an explicit explanation" (the actual behavior). The underlying `justify` mode resolver generates a rewrite *plus* a rationale string surfaced in the result panel; "Explain rewrite" names what it actually does. The mode id (`"justify"`) is unchanged so external integrations using `editor.aiToolkit.runQuickAction("justify")` or `presetMode: "justify"` keep resolving without break.

3. **Mode descriptions never reached the visible grid.** v2.0.6.4 acknowledged that v2.0.6.3's "caption restored" claim was a documentation bug — the JS at `openDialog` still ran `var modeHelp = document.createElement("div"); modeHelp.className = "demo-ai-mode-caption"; ... modeHelp.classList.add("is-detached");` and never called `appendChild(modeHelp)` anywhere, so the CSS rule had no target. The honest fix path is to actually append the node. With three live surfaces already naming the active mode pre-result (dialog frame title, Action select label, mode-aware textarea placeholder), the caption is admittedly the fourth signal — but it is the only one that explains WHAT the mode does, in plain English, where the user is about to look next. The other three name the mode but assume the user already knows what e.g. "Justify edit" means. Notion AI, Linear, and Slack composers all pay the ~14px caption tax for exactly this reason.

**Ask AI dialog — per-option tooltips on the mode picker**
- **`populateDialogModes` now writes `option.title = modes[i].description`** on every dialog mode `<option>` after creating it. Hovering an unselected option in the open picker surfaces "Fix grammar, spacing, and readability." / "Translate to a target language." / "Rewrite and explain what changed." inline. Keyboard users still get the picker's own dropdown UI; sighted mouse users get a per-row tooltip; screen-reader users get the option's accessible description from the `title` attr. Zero added DOM nodes; descriptions reuse the existing `aiToolkitDialogModes[].description` field that was previously only consumed by `updateModeHelp` for the (now-restored) caption.

**Ask AI dialog — `"Justify edit"` → `"Explain rewrite"`, descriptions trimmed for the caption**
- **`config.aiToolkitDialogModes`** seventh entry's `title` flips from `"Justify edit"` to `"Explain rewrite"` (id unchanged). The other six labels keep their existing values; only the description strings shrink:
  - `proofread`: "Clean up grammar, spacing, and readability issues." (52) → "Fix grammar, spacing, and readability." (38)
  - `rewrite`: "Rewrite the selection for clarity while preserving intent." (59) → "Rewrite for clarity, same meaning." (34)
  - `translate`: "Prepare a translated version in a target language while keeping the original nearby for review." (96) → "Translate to a target language." (31)
  - `justify`: "Generate a rewrite plus an explicit explanation for the change." (63) → "Rewrite and explain what changed." (33)
  - `shorten`: "Compress the source text into a tighter version." (48) → "Compress into a tighter version." (31)
  - `expand`: "Add supporting detail below the current draft." (46) → "Add supporting detail to the draft." (35)
  - `summarize`: "Turn the source into a compact summary." (39) → "Turn the source into a short summary." (37)
  Each new description fits one line in the 480px-wide caption at 10.5px font (caption inner width ~456px ≈ 60 chars at the default monospace fallback for the muted text). The hover-tooltip on the option also reads cleaner — full sentences were a chore on hover, snappy phrases are not.

- **`sourceArea.placeholder` map**: `justify: "Paste text to justify the edit…"` → `justify: "Paste text to rewrite + explain…"` so the mode-aware placeholder lines up with the renamed action.

**Ask AI dialog — mode caption actually attached this time**
- **`openDialog` now runs `grid.appendChild(modeHelp);`** after the controls strip and source field are built. The node lands as the second grid row (the CSS rule at `.demo-ai-mode-caption { grid-row: 2 }` from v20260517a binds at last). The detached path (`modeHelp.classList.add("is-detached")`) and its comment block (v20260530a's "discoverability budget already paid by two more prominent surfaces") are deleted; the v20260520a CSS rule whose target was orphaned during v2.0.6.3 now has a live target. Pre-result the caption shows the active mode's description (auto-updated via the existing `updateModeHelp` flow); post-result it hides via the existing `.demo-ai-dialog-grid:not(.is-pre-result) .demo-ai-mode-caption { display: none }` cascade (the result textarea IS the answer once output lands, so the description becomes redundant).

**Net effect:**
- Ask AI dialog: **480×141 → 480×156 (~11% taller)** pre-result. The 15px tax buys the single biggest discoverability gain on the dialog — the active mode now self-describes in plain English directly under its picker, and EVERY mode in the dropdown carries a hover tooltip. Post-result: **480×~171 unchanged** (caption auto-hides once a result lands).
- Action dropdown: same seven rows, one renamed for clarity. Mode IDs unchanged so `editor.aiToolkit.runQuickAction("justify")`, `presetMode: "justify"`, and any external integration referencing the seven canonical mode ids keep working.
- AI menu: **unchanged at 360×~171** (no rules touched).
- AI Chat panel: **unchanged at 390×182**.
- AI Review V2 panel: **unchanged at 520×350**.

Customers who want the prior chrome back: pass a custom `config.aiToolkitDialogModes` to `RTE_CreateConfig` with the old labels / descriptions before constructing the editor, OR override the caption with `.rte-panel-aiassist .demo-ai-mode-caption { display: none !important }` to recover the v2.0.6.4 measurement. Per-option tooltips degrade gracefully on environments that suppress `title` attributes (mobile, some screen-reader bridges) — the active mode is still surfaced via the caption and the dialog frame title, so no signal is keyboard-only.

`aiToolkitUiVersion` bumped from `v20260520b` to `v20260520c`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` PowerShell core), the demo aspx version stamps (`ai-toolkit.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520c` for cache busting. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` (IIS Express via `Run-RTEJSWeb.ps1`) confirmed: opening the Ask AI dialog renders the frame at **480×156px** pre-result with the visible mode caption `"Fix grammar, spacing, and readability."` under the Action select; switching to Translate updates the caption to `"Translate to a target language. Current target: Spanish."`; switching to Explain rewrite updates it to `"Rewrite and explain what changed."`; typing source text and clicking Generate transitions the grid out of `.is-pre-result`, the caption auto-hides, and the result lands at the existing 173px frame. Zero `error`-level entries in `preview_console_logs` across pre-result + mode-switch + Generate flows.

## Version 2.0.6.4

### Improved — Ask AI dialog tightens 16px taller pre-result (source floor 64 → 48), dead mode-caption rule removed, AI Chat Send button 32 → 28
Fifty-fifth pass on the AI windows. The v2.0.6.3 work landed the visible "Generate" pill + `Ctrl ⏎` chip on the dialog primary, narrowed the dialog frame 520 → 480, and dropped the source textarea floor 80 → 64. A focused audit against Notion AI inline composer, Tiptap AI bubble, Claude side-panel composer, ChatGPT composer, and CKEditor 5 AI Assistant surfaced three remaining shapes the AI windows still painted that the reference set explicitly avoids:

1. **Source textarea floor still at 64px pre-result.** With the v2.0.6.3 Generate primary now a ~120px auto-width pill anchored bottom-right of the source textarea (was a 36×36 round icon since v20260526a), the inside-anchor reason for the larger textarea height is gone — the pill only needs ~38px of vertical room to ride comfortably. The 64px floor reserved ~3.5 lines of empty whitespace before any source text existed, pushing the controls strip ~16px below the user's reading centerline on every fresh dialog. Notion AI inline composer / Claude inline edit ship at ~36-44px at rest.

2. **Mode caption was documented as "restored" in v2.0.6.3 but the JS path orphaned the node.** The v20260520a comment block in `aitoolkit.css` claimed "Mode caption restored as a single-line muted row under the controls strip" and added a `display: block !important` rule scoped to `.is-pre-result .demo-ai-mode-caption`. But the JS at the matching `renderDialog` site (line ~15698-15880) still ran `var modeHelp = document.createElement("div"); modeHelp.className = "demo-ai-mode-caption"; ... modeHelp.classList.add("is-detached");` and never called `appendChild(modeHelp)` anywhere — the node was created in memory, marked detached, and dropped on the floor. The CSS rule could never bind because its target wasn't in the DOM tree. Live render under v2.0.6.3 has been identical to v20260530a's "caption dropped" behavior the entire time. With three live surfaces already naming the active mode on every pre-result render — the dialog frame title ("Ask AI · Proofread" since v20260519a / Pass 47), the Action `<select>` showing the mode label, and the textarea placeholder being mode-aware ("Paste text to proofread…" since v20260517a) — the caption would have been a fourth copy of the same fact. Acknowledge the live state, remove the dead CSS rule.

3. **AI Chat composer Send button at 32×32 with 56px textarea padding-right reserve.** Since v20260525a the Send button has been anchored INSIDE the textarea bottom-right corner (the same inline-anchor pattern Notion AI / Claude side-panel / ChatGPT all ship). But where those references paint the send glyph at ~26-28px, this build kept it at 32×32 — claiming ~38px of visual real estate on the right edge of the input. The textarea's `padding-right: 56px` reserve clearance was eating ~14% of the 364px input's horizontal real estate before the user typed a character. Notion AI: 28; Claude side-panel: 26; ChatGPT inline: 28; Tiptap AI bubble: 28.

**Ask AI dialog — source textarea floor 64 → 48**
- **`.rte-panel-aiassist .demo-ai-source-field textarea { min-height: 48px !important }`** (was 64). Saves 16px on every pre-result dialog. ~2.6 lines visible at 13px / 1.45 leading — enough to peek the first line of a loaded selection while killing the empty-state expanse. The Generate pill (147×36 at bottom-right) still has ~6px breathing room top/bottom inside the textarea's content area.

**Ask AI dialog — drop the dead mode-caption CSS rule**
- **Remove `.rte-panel-aiassist .demo-ai-dialog-grid.is-pre-result .demo-ai-mode-caption { display: block !important; ... }`** at the end of `aitoolkit.css`. The rule had no effect on live render (the matching JS node was created with `document.createElement` then marked `is-detached` and never appended to the DOM). v20260520a comment block updated to acknowledge the live state. Customer override docs updated: customers who want a visible caption back must pair the CSS block (now documented as a custom override) with a JS integration that appends the orphaned `modeHelp` node into `.demo-ai-dialog-grid`.

**AI Chat composer — Send button 32 → 28, padding-right 56 → 48**
- **`.richtexteditor .rte-ai-chat-compose-shell .rte-ai-chat-send-button { width: 28px !important; height: 28px !important; min-width: 28px !important; min-height: 28px !important }`** (was 32/32/32/32). Reaches parity with Notion AI / Claude / ChatGPT composer send buttons. The 14×14 sparkle glyph stays the same — visually centered with ~7px breathing on each side instead of ~9px.
- **`.richtexteditor .rte-ai-chat-compose-shell .rte-ai-chat-input { padding-right: 48px !important }`** (was 56). Reclaims 8px of horizontal typing real estate on every chat panel render.
- **`.richtexteditor .rte-ai-chat-compose-shell .rte-ai-chat-composer-actions { right: 4px !important; bottom: 4px !important }`** (was 6/6). The 2px tighter inset keeps the button visually centered in the bottom-right corner with the new smaller footprint.
- @media (max-width: 900px) breakpoint Send width also pinned to 28 to match.

**Net effect:**
- Ask AI dialog: **480×155 → 480×141 (~9% shorter)** pre-result, **480×~171 → 480×~159 (~7% shorter)** post-result at the test selection. The mode caption stays dropped (as it has been in live render the entire time); the source textarea's empty-state expanse drops 16px. The Generate pill, Action select, Scope select, Source textarea, and result panel all measure identically to v2.0.6.3 in width and layout shape — only vertical chrome shrinks.
- AI Chat panel: **390×186 → 390×182** with messages-empty; composer's input area gains 8px of horizontal typing room (`padding-right: 56 → 48`). The Send button sits at the bottom-right at 28×28 with 4px inset. With messages present, feed-shell still flips to `flex: 1 1 auto; min-height: 140px` so long conversations scroll inside the panel cap — no regression.
- AI menu: **unchanged at 360×~171** (no rules touched).
- AI Review V2 panel: **unchanged at 520×350**.

Customers who want the prior chrome back can override with the documented CSS rules in the v20260520b comment blocks at the end of `aitoolkit.css`. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just a touch lighter and easier to type into.

`aiToolkitUiVersion` bumped from `v20260520a` to `v20260520b`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. The `npm-package/richtexteditor/plugins/`, `RTECOM/`, `Demos/rte-ng-ts/`, `Demos/rte-react-js/`, `Demos/rte-vue-js/`, and `Zips/richtexteditor/` mirrors (`aitoolkit.js`, `aitoolkit.css`, `all_plugins.js`) were also refreshed. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:8765/_tests/ai-manual-test.html` (Python static server, since the demo aspx pages need IIS) confirmed: opening the AI Chat panel renders the panel at **390×182px** (was 390×186), Send button at **28×28px** (was 32×32) with `min-width: 28px`, textarea `padding-right: 48px` (was 56px), textarea `min-height: 32px` floor preserved. Opening the Ask AI dialog renders the frame at **480×141px** pre-result (was 480×155), source textarea at **48px** floor (was 64), Generate pill at **147×36px** with visible "Generate" label + "Ctrl ⏎" chip (preserved from v2.0.6.3). The `.demo-ai-mode-caption` node confirmed absent from the live DOM (matching the actual v2.0.6.3 behavior the changelog claim contradicted). Zero entries in `preview_console_logs` at `error` level.

## Version 2.0.6.3

### Improved — Ask AI dialog: visible "Generate" label + Ctrl ⏎ chip on the primary, frame tightens 520 → 480, source textarea floor 80 → 64
Fifty-fourth pass on the AI windows. The v2.0.6.2 work gave the AI menu its per-section icon tints and pulled ~9px off the empty AI Chat panel. A focused audit of the *Ask AI dialog* against Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, Claude inline edit, and CKEditor 5 AI Assistant surfaced three remaining shapes the dialog still painted that the references explicitly avoid:

1. **Generate primary rendered as a 36×36 round icon-only button.** Since v20260526a the primary action has been anchored to the source textarea's bottom-right corner as a round sparkle button, with the dynamic "Generate" / "Regenerate" / "Thinking…" label sr-only-clipped (`position:absolute; width:1px; clip:rect(0,0,0,0)`) and the Ctrl ⏎ kbd-hint chip flat-hidden (`display:none`). The rationale at the time was that the icon-anchor pattern matched Notion AI's send-button shape — but Notion AI / ChatGPT inline edit / Tiptap AI bubble / Claude inline edit *all* paint their primary action's word ("Generate" / "Send" / "Ask") visibly next to the icon. The v20260526a comment block listed the "visible label back" override as a customer escape hatch, acknowledging the icon-only choice was non-default among the reference set. First-time users opening the dialog saw a small blue circle with a sparkle glyph and had to hover to discover what it did, or guess from the textarea placeholder; the keyboard shortcut surfaced only on hover via the `title` attribute, which never reaches keyboard / mobile / screen-reader users on the first try.
2. **Dialog frame held at 520px wide.** The frame was set to `min(520px, calc(100vw - 32px))` since the v20260508v / v20260509j compaction passes. With every other AI surface running narrower — chat panel 390px, action menu 360px — the dialog read as ~25% wider side-by-side without carrying more content. Notion AI inline composer and Tiptap AI bubble both ship around 460-480px; the prior 520 was reserving 40-60px of horizontal real estate the controls strip and the source textarea did not need.
3. **Source textarea floor pinned at 80px.** The v20260526a icon-anchor pass set `min-height: 80px !important` so the floating 36×36 button had ~4 lines of vertical breathing room to anchor to. With the placeholder-only / empty-source state the field reserved ~3.5 lines of blank space before any text existed — pushing the Generate sparkle ~70px below the user's reading centerline and inflating every pre-result dialog render by ~16-18px. Trimming to 64px keeps ~2.8 lines visible (enough to peek the first line of a loaded selection) without the empty-state expanse.

**Ask AI dialog — visible "Generate" pill + Ctrl ⏎ chip**
- **`.rte-panel-aiassist .demo-ai-source-field .demo-ai-run-row .is-primary { width: auto !important; min-width: 0 !important; flex: 0 0 auto !important; padding: 0 12px !important; gap: 6px !important; border-radius: 999px !important }`**. Promotes the run-row primary from a 36×36 round icon to a ~120px auto-width pill that grows to fit the dynamic label + icon + kbd hint. Border-radius stays at the 999px pill shape so the button still reads as a rounded affordance, not a flat rectangle. The `!important` flood is needed to beat the v20260526a `width: 32px !important; min-width: 32px !important` and the v20260527a `width: 36px !important; min-width: 36px !important` rules at lines ~12650 and ~12733.
- **`.rte-panel-aiassist .demo-ai-source-field .demo-ai-run-row .is-primary .rte-ai-review-v2-action-label { position: static !important; width: auto !important; height: auto !important; clip: auto !important; overflow: visible !important; white-space: nowrap !important }`**. Reverses the sr-only clipping so the dynamic "Generate" / "Regenerate" / "Thinking…" label paints inline. The label was already in the DOM (the JS at `setReviewV2ButtonContent` writes it); only its CSS-driven clip was hiding it from sighted users.
- **`.rte-panel-aiassist .demo-ai-source-field .demo-ai-run-row .is-primary .demo-ai-kbd-hint { display: inline-flex !important }`** + tiny chip chrome (`padding: 1px 5px; border-radius: 5px; background: rgba(255,255,255,0.22); color: rgba(255,255,255,0.92); font-size: 10px`). The chip already carried the platform-aware `Ctrl ⏎` / `⌘ ⏎` glyph chosen at dialog-open time from `navigator.platform`; only its `display: none` was suppressing the visible render. Same affordance ChatGPT composer's "Send ⌘↵" and Linear's command palette ship.
- **`.rte-panel-aiassist .demo-ai-source-field textarea { padding-right: 132px !important }`** (was `60px`). Reserves room for the wider pill at the textarea's bottom-right corner so typed text never slides under the button glyph.

**Ask AI dialog — frame and source floor compaction**
- **`.rte-panel-aiassist rte-dialog-inner { width: min(480px, calc(100vw - 32px)) !important; max-width: 480px !important }`** (was `520` for both). Trims 40px of horizontal real estate on every dialog render; brings the dialog within ~20% of the chat panel's 390px width so the AI surfaces read as one product family at a glance. The compact-controls row still fits as a single line (action ~150px + scope ~130px + 8px gap = ~288px content) and the source textarea + Generate pill still fit comfortably below.
- **`.rte-panel-aiassist .demo-ai-source-field textarea { min-height: 64px !important }`** (was `80px`). Saves 16px of vertical chrome on every pre-result render. Auto-grow on typed content past one line still works (the textarea's natural `scrollHeight`-driven sizing kicks in via the rows=1 + content cascade).

**Net effect:**
- Ask AI dialog: **520×173 → 480×155 (~8% narrower, ~10% shorter)** pre-result. Post-result with result panel: **520×~200 → 480×~171 (~8% narrower, ~15% shorter)** at the test selection. The Generate primary now paints **"Generate" + Ctrl ⏎** visibly at the textarea's bottom-right (was a 36×36 round icon with both label and shortcut hidden behind `title` attr). First-time users see WHAT the primary does AND HOW to fire it from the keyboard in one glance — same affordance Notion AI inline composer + ChatGPT inline edit + Tiptap AI bubble ship.
- AI menu: **unchanged at 360×169** (the only `.rte-panel-aiassist-menu` rules touched are the existing per-section tints from v2.0.6.2; no width / item-row regressions).
- AI Chat panel: **unchanged at 390×181** (composer Send button kept icon-only — different context, the chat panel's tighter horizontal budget doesn't have room for an inline "Send" label without crowding the input).
- AI Review V2 panel: **unchanged at 520×350** (panel chrome untouched; its wider frame is justified by the suggestion-list density).

Customers who want the prior chrome back can override with the documented CSS rules in the v20260520a comment block at the end of `aitoolkit.css`. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260607a` to `v20260520a`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. The `npm-package/richtexteditor/plugins/` mirrors (`aitoolkit.js`, `aitoolkit.css`, `all_plugins.js`) were also refreshed. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:8765/_tests/ai-manual-test.html` (Python static server, since the demo aspx pages need IIS) confirmed: opening the Ask AI dialog renders the frame at **480×155px** pre-result (was 520×173), source textarea at **64px** floor, Generate primary at **147×36px** pill with visible "Generate" label + "Ctrl ⏎" chip; running `proofread` on a populated source resolves to a 480×~171 post-result frame with no regression in the apply / copy / details flow. AI menu, AI Chat panel, AI Review V2 panel all measured unchanged.

## Version 2.0.6.2

### Improved — AI windows: AI menu items gain per-section icon tints (Start/Quick edit/Insert read as 3 calm clusters), AI Chat panel collapses ~5% via padding trim
Fifty-third pass on the AI windows. The v2.0.6.1 work collapsed the AI Chat panel header to ~30px and gave the AI menu filter row a leading magnifier glyph. A focused audit of the *AI menu rendering* and the *AI Chat panel empty-state chrome* against Notion AI inline composer, Claude side-panel, ChatGPT side-panel, and Linear's command palette surfaced two remaining shapes the AI windows still painted that the references explicitly avoid:

1. **AI menu — 9 items render as one undifferentiated block.** The `aiToolkitActions` data carries a 3-way logical grouping in `action.section` ("Start" → Chat/Draft/Review, "Quick edit" → Proofread/Rewrite/Translate/Explain, "Insert" → Add note/Add paragraph) — but every item rendered with the identical `#f3f7fd` icon tile + identical row chrome, so the 3 groupings gave the user no at-a-glance signal which row family they were looking at. The previous "Start" / "Quick edit" / "Insert" uppercase section captions were dropped in v20260513c because each label claimed ~22px (~66px total) of vertical chrome on a menu small enough to fit on one screen — at the cost of the grouping cue. The CHANGELOG line in v2.0.6.0 claimed "the tinted background colour cue stays so the section-grouping signal Pass 47 added isn't lost", but live DOM inspection showed every item shared the same `rgb(243, 247, 253)` icon background; the per-section colour cue Pass 47 set up had never actually been applied. First-time users opening the menu saw a flat 2-col grid where Chat (workspace surface) sat next to Draft (workspace surface) next to Review (workspace surface) next to Proofread (preview-a-transform) — three semantically different families flattened into one wall of identical rows.
2. **AI Chat panel — empty-state still carrying ~10px of redundant chrome.** Since v20260513e the panel has shipped at `padding: 12px` matched to the dialog frame's outer padding, and the compose-shell wrapping the composer at `padding: 8px 4px 4px`. With the panel's own `gap: 8px` between stack children already creating visual separation between the header, the quick chip row, the feed-shell, and the composer, the compose-shell's 8/4/4 wrap chrome was double-counting that grid gap — reserving ~12px of empty chrome around an already-bordered composer. The 12px panel padding similarly outweighed the sidebar surface's "every pixel of conversation area pays back" budget.

**AI menu — per-section icon tints**
- **`renderActionMenu` now writes `button.setAttribute("data-section", action.section)`** on each `.rte-ai-menu-item` so CSS can tint the icon tile by section group. Zero new render cost; just one DOM attribute per row. External integrations that walked `.rte-ai-menu-item` for click / keyboard / aria handling keep resolving with no selector changes.
- **`.rte-panel-aiassist-menu .rte-ai-menu-item[data-section="Start"] .rte-ai-menu-item-icon`** → soft blue (`#e3f0ff` background, `#1d4f8a` glyph, blue inset border). Marks the 3 "open a workspace" entries (Chat, Draft, Review).
- **`.rte-panel-aiassist-menu .rte-ai-menu-item[data-section="Quick edit"] .rte-ai-menu-item-icon`** → soft violet (`#efe9ff` background, `#5b3fb5` glyph). Marks the 4 "preview a transform" entries (Proofread, Rewrite, Translate, Explain).
- **`.rte-panel-aiassist-menu .rte-ai-menu-item[data-section="Insert"] .rte-ai-menu-item-icon`** → soft amber (`#fff1d6` background, `#8a5a17` glyph). Marks the 2 "append content" entries (Add note, Add paragraph).
- **Hover / focus / active rows** inherit the existing `.rte-ai-menu-item:hover .rte-ai-menu-item-icon { background: #dfeeff }` rule — the brighter active tint still wins on the focused row regardless of section, so keyboard arrow navigation keeps reading clearly.

**AI Chat panel — empty-state chrome trim**
- **`.richtexteditor .rte-ai-chat-panel.rte-ai-chat-panel.rte-ai-chat-panel.rte-ai-chat-panel { padding: 10px !important }`**. Saves 4px tall (2px top + 2px bottom). The selector is quadrupled to beat the v20260602a `.rte-ai-chat-panel.rte-ai-chat-panel.rte-ai-chat-panel { padding: 12px !important }` cascade at ~line 13226. The `!important` flood is needed for the same cascade reason.
- **`.richtexteditor .rte-ai-chat-compose-shell { padding: 4px 2px 2px }`** (was `8px 4px 4px`). Saves 6px combined (4px top + 2px bottom). With the panel's `gap: 8px` already carrying the section separation between the feed and the composer, the inner wrap padding was double-counting that gap. The composer's own border-radius / box-shadow stays intact so the composer still reads as a separate surface.

**Net effect:**
- AI menu: **420×169 → 420×171 (effectively unchanged)**. Three calm coloured clusters of three / four / two rows instead of one flat 9-item block. Users see at a glance which rows open a workspace, which preview a transform, and which append content. Same affordance Notion AI slash menu / ChatGPT `/` suggestions use for sectioned command lists where a divider would dominate the layout.
- Empty AI Chat panel: **390×203 → 390×194 (~5% shorter)**. Header + chip row + feed-shell + composer untouched; the savings come purely from the panel's outer padding + the compose-shell's inner wrap chrome. With messages the feed-shell still flips to `flex: 1 1 auto; min-height: 140px` (Pass 49 `:has()` rule still wins), so long conversations scroll inside the panel cap — no regression in conversation handling.
- Ask AI dialog: unchanged at 440×119 pre-result, 440×185 post-result. No regression.

Customers who want the prior chrome back can override with the documented CSS rules in the v20260606b comment block at the end of `aitoolkit.css`. Numeric / colour overrides only — none of the typography decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260605a` to `v20260606b`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260606b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. The `npm-package/richtexteditor/plugins/` mirrors (`aitoolkit.js`, `aitoolkit.css`, `all_plugins.js`) were also refreshed. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the toolbar AI menu renders all 9 items with `data-section` attributes correctly set, icon tile backgrounds computed at **`rgb(227, 240, 255)` for Chat/Draft/Review**, **`rgb(239, 233, 255)` for Proofread/Rewrite/Translate/Explain**, **`rgb(255, 241, 214)` for Add note/Add paragraph**; menu total **420×171px** (matches prior baseline). Opening the AI Chat panel via the Chat menu item renders the panel at **390×194px** (was 390×203px) with `padding: 10px`, compose-shell height **53px** (was 59px) with `padding: 4px 2px 2px`. Ask AI dialog unchanged at **440×119px** pre-result, **header 27px** / **grid 73px**. Zero entries in `preview_console_logs` at `error` level from the production code paths.

## Version 2.0.6.1

### Improved — AI windows: Chat panel header collapses from 39px → ~30px, AI menu drops to ~169px tall, filter row gains a leading magnifier glyph
Fifty-second pass on the AI windows. The v2.0.6.0 work collapsed the empty AI Chat feed-shell (96 → ~36px) and tightened the AI menu icon tiles (20 → 16px). A focused audit of the *AI Chat panel header* and the *AI menu filter row + item row* against Claude side-panel, Notion AI inline composer, ChatGPT side-panel, Linear command palette, and Raycast surfaced three remaining shapes the AI windows still painted that the reference set explicitly avoids:

1. **AI Chat panel header pinned at 39px tall.** With the title icon suppressed (display:none since v20260515d) the row's content was just the "AI Chat" title text + two icon-only header buttons (Refresh / Close). The icon-only buttons were locked at 30px min-width with the close-button at 26×26, so the header's binding constraint was the 26-30px button height plus 2px+6px vertical padding = ~34-38px. Every reference side-panel header sits at 28-30px tall (Claude side-panel: ~28px; ChatGPT side-panel: ~30px; Notion AI inline composer: ~26px). The prior chrome reserved ~10px of vertical real estate above the conversation surface for two icon-only affordances most users reach for at most once per session.
2. **AI menu filter row carried a bare textbox.** The input shipped with only a "Filter actions…" placeholder — first-time users had to *read* the placeholder copy to understand the input's purpose. Every reference filterable menu surface ships a leading magnifier glyph inside the input row (Notion slash menu / Linear command palette / Raycast / VS Code command palette / GitHub command bar) so the affordance reads at a glance even before the placeholder text.
3. **AI menu item row floor sat at 26px.** With the icon tile at 16×16 (Pass 51) and the title at 12px / line 1, the 26px floor was reserving 10px of vertical chrome around 14px of content. Notion AI slash menu rows ship at ~22-24px with proportional 16-18px icons.

**AI Chat panel header — collapse to ~30px**
- **`.richtexteditor .rte-ai-chat-header.is-minimal { padding: 2px 4px 2px; gap: 8px }`** (was `2/4/6`, gap `12`). Saves 4px tall + visually pulls the title closer to the action buttons so the header reads as one row, not a chrome strip.
- **`.rte-ai-chat-header-button.is-icon-only { min-width: 22px !important; width: 22px !important; min-height: 22px !important; height: 22px !important }`** (was `30×30` width, `26` min-height). The Refresh glyph stays at 14×14 inside the tighter button so the affordance remains tappable. The `!important` flood is needed to beat the v20260515d cascade (line ~11665) that pinned the prior dimensions.
- **`.rte-ai-panel-close-button { width: 22px; height: 22px }`** (scoped to `.rte-ai-chat-header.is-minimal` so the prior base 26×26 rule stays for any non-chat surface that uses the close-button glyph).

**AI menu — leading magnifier glyph + tighter input**
- **`renderActionMenu` prepends a `.rte-ai-menu-search-icon` span** with an outline-magnifier SVG (12×12 circle + 3.4px diagonal handle) inside the `searchRow` before the input. The icon rides absolute at the input's left edge; the input grows its left padding to clear the icon.
- **`.rte-ai-menu-search-row { position: relative; margin: 0 0 2px }`** (was margin `0 0 3px`). 1px tighter cap below the input.
- **`.rte-ai-menu-search-icon { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 12px; height: 12px; color: #94a3b8; pointer-events: none; z-index: 1 }`**. `pointer-events: none` so the icon never intercepts focus — clicks anywhere on the input still target the textbox.
- **`.rte-ai-menu-search-input { padding: 3px 10px 3px 26px; font-size: 11.5px }`** (was `4/9`, font `12`). Left padding grows to 26px to clear the magnifier; right padding stays at 10. Saves ~2px tall on the input.

**AI menu item — 26 → 24px floor**
- **`.rte-panel-aiassist-menu .rte-ai-menu-item { min-height: 24px; padding: 2px 6px }`** (was `26` floor, `3/6` padding). Background tint + border-radius + hover shape unchanged so the section-grouping color cue stays. Each of the 5 rows in the 2-col grid drops 2px → 10px shorter overall.

**Net effect:**
- AI Chat panel header: **39px → ~30px (~23% shorter)**. Empty Chat panel total: **215px → ~206px**. With messages the feed-shell still flips to flex-grow + 140px floor (Pass 49 rule), so long conversations scroll inside the panel cap (`calc(100% - 60px)` ≈ 420px on this demo editor) — no regression in conversation handling.
- AI menu: **420×184 → 420×169 (~8% shorter)**. Plus a clarity win — the magnifier glyph names the search affordance at a glance, matching Notion AI slash menu / Linear command palette / Raycast / VS Code command palette.
- Ask AI dialog: unchanged at 440×118 pre-result, 440×185 post-result. No regression.

Customers who want the prior chrome back can override with the documented CSS rules in the v20260605a comment block at the end of `aitoolkit.css`. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260604a` to `v20260605a`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260605a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. The `npm-package/richtexteditor/plugins/` mirrors (`aitoolkit.js`, `aitoolkit.css`, `all_plugins.js`) were also refreshed. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the AI Chat panel renders the header at **~30px tall** (was 39px) with `padding: 2px 4px 2px`, `gap: 8px`, icon-only header buttons computed at **22×22px** (was 30×30 for Refresh, 26×26 for Close); empty Chat panel total **~206px** (was 215px). Opening the toolbar AI menu renders the filter row with the leading **magnifier glyph** computed at 12×12px at the input's left edge with `color: #94a3b8` and `pointer-events: none`, the input padding computed at **3px 10px 3px 26px** (was 4/9), font-size 11.5px; the 9 items keep their 2-col layout with each row computed at **24px** (was 26px); menu total **420×~169px** (was 420×184). Ask AI dialog unchanged at **440×118px** pre-result. Zero new entries in `preview_console_logs` at `error` level from the production code paths.

## Version 2.0.6.0

### Improved — AI windows: Chat panel empty state collapses from 263px → 216px, AI menu icon tiles 20px → 16px so verb labels read first
Fifty-first pass on the AI windows. The v2.0.5.9 work added a live source char counter to the Ask AI dialog and a real Copy-to-clipboard pill on the result. A focused audit of the *empty-state AI Chat panel* and the *AI toolbar menu icon tiles* against Notion AI inline composer, Claude side-panel, ChatGPT side-panel, Tiptap AI bubble, and Linear's command palette surfaced two remaining shapes the AI windows still painted that the reference set explicitly avoids:

1. **AI Chat panel feed-shell reserves 96px when empty.** Since Pass 49 the panel has been anchored at `top: 48px; bottom: auto; max-height: calc(100% - 60px)` so it sized to content, but the feed-shell inside still carried a `min-height: 96px` floor + `padding: 10px 11px` chrome around a single 1-line empty-state hint. The 96px floor was set as "1-3 visible message lines" — but with messages absent there's no message-line to floor against; the hint sits in its own 24px-tall row regardless. Every reference side-panel collapses its empty conversation area to the hint's natural height (Claude side-panel ~24px, Notion AI inline composer ~32px, ChatGPT side-panel ~28px). The 96px floor was reserving ~70px of vertical chrome for a void below 1 line of text.
2. **AI menu icon tiles still painted at 20×20 with a tinted background.** Pass 47 dropped 24 → 20 for proportionality with the 28px row after the description span was hidden. With the row now at 26px and the title at 12px (line-height 1), 20px is still visually 2× the title cap-height — the eye lands on the icon tile before the verb. Every reference inline AI menu (Notion AI slash menu, Linear command palette, Raycast) sits with 16px icons that match the title cap so the verb reads first, the icon is the quiet anchor.

**AI Chat panel — empty-state collapse**
- **`.rte-ai-chat-panel:not(:has(.rte-ai-chat-message)) .rte-ai-chat-feed-shell { padding: 2px 4px; min-height: 0; gap: 4px }`**. The `:not(:has())` selector only matches when the panel has zero `.rte-ai-chat-message` nodes — the moment any message lands the existing Pass 49 `.rte-ai-chat-panel:has(.rte-ai-chat-message) .rte-ai-chat-feed-shell { flex: 1 1 auto; min-height: 140px }` rule wins, so long conversations still scroll inside the panel cap. Safely no-ops on legacy browsers without `:has` — worst case is the prior 96px floor stays.
- **`.rte-ai-chat-panel:not(:has(.rte-ai-chat-message)) { grid-template-rows: auto auto auto auto auto auto auto }`**. Pass 49 left the 5th row track at `minmax(180px, 1fr)` for the feed-shell. With the empty feed-shell at ~24px content, that `minmax(180, 1fr)` was padding the panel by ~155px against the floor. Flipping the 5th track to `auto` lets the row size to its content (the collapsed feed-shell) instead of reserving a fixed minimum.
- **`renderChatPanel` `desiredHeight` floor `320/360` → `260/280` (mobile/desktop)**. Pass 49 set 320/360 to match the panel's then-263px empty height. With Pass 51 the empty panel sits at ~216px, so the 320/360 floor was reserving ~50-100px of editor-shell height for chrome that no longer exists. The new 260/280 floor matches the new empty-panel natural height. On hosts where the editor shell is already taller than the floor (e.g., the demo's 480px) the floor is a no-op; on hosts that respect the floor the editor shell now binds correctly to the panel content.

**AI menu — icon tiles match title cap-height**
- **`.rte-panel-aiassist-menu .rte-ai-menu-item .rte-ai-menu-item-icon { width: 16px; height: 16px; border-radius: 5px }`**. Drops from 20×20 (Pass 47 baseline) so the icon tile aligns visually with the 12px title cap. The tinted background colour cue stays so the section-grouping signal Pass 47 added isn't lost — only the tile's footprint shrinks. Border-radius proportionally drops 7 → 5 to keep the rounding visually equivalent.
- **`.rte-panel-aiassist-menu .rte-ai-menu-item .rte-ai-menu-item-icon svg { width: 11px; height: 11px }`**. SVG glyph drops 12 → 11 to keep the same ~3px breathing inside the smaller tile (instead of 4px which would have read as the SVG inflating against the tile edge).

**Net effect:**
- Empty AI Chat panel: **263px → 216px (~18% shorter)**. Header (39) + scope_section (0 hidden) + quick chips (24) + feed_shell (49 — was 96) + composer (47) + 4×8 gaps + 24 padding. Same layout, less chrome, the empty hint still names both the composer ("Ask anything…") and the chip row ("…or tap a quick start above"). With messages the feed-shell flips back to flex-grow + 140px floor, so long conversations scroll inside the panel cap (`calc(100% - 60px)` ≈ 420px on this demo editor) — no regression in conversation handling.
- AI menu: **420×194 → 420×184 (~5% shorter)**. The 9-item grid keeps its 26px row floor; only the icon tile shrinks. Verb labels become the focal anchor, the icons read as quiet section-grouping anchors. Same shape Notion AI slash menu / Linear command palette ship — title-driven menus with 16px proportional icons.
- Ask AI dialog: unchanged at 440×118 pre-result, 440×185 post-result. No regression.

Customers who want the prior chrome back can override with the documented CSS rules in the v20260604a comment block at the end of `aitoolkit.css`. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260603b` to `v20260604a`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260604a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. The `npm-package/richtexteditor/plugins/` mirrors (`aitoolkit.js`, `aitoolkit.css`, `all_plugins.js`) were also refreshed. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the AI Chat panel via the Chat menu item renders the empty panel at **390×216px** (was 390×263px) with the feed-shell at h=49, computed `padding: 2px 4px`, computed `min-height: 0px`; sending a chat message flips the feed-shell to h=283 with computed `min-height: 140px` (Pass 49 :has() rule still wins for non-empty state). Opening the toolbar AI menu renders the panel at **420×184px** (was 420×194px) with all 9 items keeping their 26px row height; first item icon-tile computed at **16×16px** with `border-radius: 5px`, the SVG glyph inside at **11×11px**. Opening the Ask AI dialog via Draft renders unchanged at **440×119px** pre-result; running a proofread of "The quick brown fox jumps over the lazy dog." returns the result and the dialog grows to **440×187px** post-result (matches Pass 50 baseline, no regression). Zero new entries in `preview_console_logs` at `error` level from the production code paths (pre-existing `removeChild` noise from `rte.js?v=20260422a` is unrelated to this pass).

## Version 2.0.5.9

### Improved — Ask AI dialog: live source char counter (bottom-left of source) + real Copy-to-clipboard pill next to "Edit prompt" in the result corner
Fiftieth pass on the AI windows. The v2.0.5.8 work compacted the AI Chat panel from 393px → 263px empty and added hover tooltips to every AI menu item. A focused audit of the *Ask AI dialog source* and *result* fields against Notion AI inline composer / Claude inline edit / ChatGPT inline edit / Tiptap AI bubble surfaced two affordances every reference inline AI surface ships that this dialog still did not — both tied to the standing "easy to understand, easy to use" goal of the AI window passes:

1. **No live source character count.** Users typing or pasting into the source textarea had no signal about input size. Short proofread snippets and 2000-char document loads looked identical at the dialog level once the textarea auto-grew. The pre-result mode caption (v20260519a) used to surface `Scope · X chars`, but v20260530a dropped the visible caption row from the grid — the char count lived on detached nodes only. Every reference inline composer (Notion AI / Claude / ChatGPT / Tiptap) ships a tiny muted counter pinned to the textarea corner that grows / shrinks as the user types; first-time users learn how much they're sending to the model at a glance.
2. **No real Copy-to-clipboard for AI output.** The only copy affordance on the post-result dialog was the apply-row icon-only Copy button which writes the AI suggestion BACK into the SOURCE field (chain-refinement — a power feature users rarely guess from the icon). Grabbing the AI suggestion for use OUTSIDE the editor (drop into Slack / paste into an email / pipe into a different document) required a manual select-all + Ctrl+C inside the readonly textarea. Notion AI / Claude / ChatGPT / Tiptap all ship a one-click clipboard pill on the AI output that copies to system clipboard with a brief "Copied" confirmation.

**Source char counter — bottom-left overlay**
- **`openDialog` creates a `.demo-ai-source-counter` span inside `sourceField`** alongside the existing source textarea + Generate icon-overlay. The Generate button owns the bottom-RIGHT corner (since v20260526a) so the counter rides bottom-LEFT where nothing else lives. `aria-hidden="true"` because the same count is already announced via the source textarea's `aria-label` and the dialog status row when stale.
- **`refreshSourceCounter()` writes the live text** (`"117 chars"` / `"2.5K chars"` for >999) and toggles `.is-visible` based on `sourceArea.value.length`. Empty source → counter hidden (zero added chrome on fresh-open dialog). Wired into `handleDialogSourceEdit` (every keystroke), `refreshSource` (scope toggle / live editor sync), `syncDialogFromEditor` (live editor input refresh), and `runButton.onclick` (generate-time re-link). 5 hook sites total, so the counter always tracks the latest source state.
- **`.demo-ai-source-counter`** styled as a 10.5px muted pill: `rgba(255,255,255,0.86)` background, `#6b7d90` text, `999px` border-radius, `1px 6px` padding, `backdrop-filter: blur(3px)`, `pointer-events: none` so it never intercepts clicks on the textarea (users can click through it to position the caret).
- **`.demo-ai-source-field:has(.demo-ai-source-counter.is-visible) textarea` adds `padding-bottom: 18px`** so the source's loaded text never slides under the counter pill. The `:has()` selector safely no-ops on legacy browsers — worst case is a 1-2 char overlap with the counter's translucent background.

**Copy-to-clipboard pill — result corner**
- **`openDialog` creates a `.demo-ai-clipboard-toggle` button** in the `resultHead` (the same absolute-positioned top-right overlay that already houses the `Edit prompt` disclosure), inserted BEFORE the disclosure so DOM order reads `[Copy] [Edit prompt ▸]`. The two pills share the same shape (10.5px / 999px radius / translucent white) so they read as a coherent pair — same affordance Notion AI inline composer / ChatGPT inline edit / Tiptap AI bubble ship.
- **`onclick` calls the existing `copyTextToClipboard` helper** (added v20260508 for the chat panel's per-message copy) which prefers `navigator.clipboard.writeText` and falls back to `document.execCommand('copy')` via a hidden textarea. On success the button briefly flips to `is-copied` + `check` glyph + `"Copied"` label for 1.4s, then restores. Failure (no permission / no user activation) silently leaves the button unchanged so users can retry.
- **A new `clipboard` SVG glyph added to `REVIEW_V2_ICONS`** so the new pill uses the same icon vocabulary as every other AI surface (Generate sparkles, Copy clipboard, Apply check, Discard trash, etc.). 13×13 outline style matches the existing glyph density.
- **`.demo-ai-clipboard-toggle`** styled with hover lift (`rgba(28, 42, 59, 0.06)` background), focus-visible ring, and an `.is-copied` green-tint variant (`rgba(34, 138, 71, 0.12)` background + `#166534` text + `rgba(34, 138, 71, 0.32)` border) so the confirmation reads as success without an external toast. Hidden pre-result automatically because the entire `.demo-ai-result-field` is hidden via the existing `.is-pre-result` cascade — zero new state to manage.

**Net effect:** the Ask AI dialog keeps its 440×118 pre-result frame and the post-result frame grows naturally with result content (~440×185 for a 117-char proofread result on this demo, content-driven from the result textarea's auto-height — no fixed-height regression) and gains:
- Bottom-left of source: a live `117 chars` / `2.5K chars` pill that shows users how much they're feeding to AI at a glance.
- Top-right of result: a `[Copy]` pill that grabs the AI suggestion for use outside the editor in one click, with a `Copied ✓` flash as confirmation.

Same shape Notion AI / Claude / ChatGPT / Tiptap inline AI all ship — chrome stays out of the focal task, but the two affordances users always reach for (input-size signal + output clipboard) are visible and one-click. Customers who want the prior surfaces back can override:
```
.rte-panel-aiassist .demo-ai-source-counter { display: none !important; }
.rte-panel-aiassist .demo-ai-clipboard-toggle { display: none !important; }
```

`aiToolkitUiVersion` bumped from `v20260602a` to `v20260603b`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260603b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. The `npm-package/richtexteditor/plugins/` mirrors (`aitoolkit.js`, `aitoolkit.css`, `all_plugins.js`) were also refreshed. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the dialog with selection captured renders `.demo-ai-source-counter` with text **"117 chars"** at the bottom-left of the source field (`is-visible` class on); clearing the source textarea hides the counter (`display: none`); typing 11 chars shows **"11 chars"**; pasting 2,500 chars rolls over to **"2.5K chars"**. Running the proofread mode produces a result and the `.demo-ai-clipboard-toggle` pill renders at the result top-right with text **"Copy"** and `title="Copy AI output to clipboard"`. The clipboard pill is hidden pre-result (parent `.demo-ai-result-field` is `display: none`). Dialog dimensions: 440×117.5 pre-result (matches v2.0.5.8 baseline, no regression), 440×185.5 post-result with a 117-char proofread suggestion (content-driven height — auto-grows with result text). AI Chat panel still renders at 390×262 (no regression from Pass 49 baseline). Zero new entries in `preview_console_logs` at `error` level from the production code paths (pre-existing `removeChild` noise from `rte.js?v=20260409f:2306` is unrelated to this pass).

## Version 2.0.5.8

### Improved — AI windows: toolbar menu items gain hover tooltips, chat panel collapses to natural content height (390×263 from 390×393)
Forty-ninth pass on the AI windows. The v2.0.5.7 work fixed the dialog scope picker so it stops painting a chevron when the only valid scope is the document. A focused audit of the *AI Chat panel* and the *AI menu items* against Notion AI inline composer, Claude side-panel, Tiptap AI bubble, ChatGPT side-panel, and CKEditor 5 AI Assistant surfaced two remaining shapes the AI windows still painted that every reference inline AI surface explicitly avoids:

1. **AI Chat panel stretched to fill the editor shell.** Since v20260513e the panel has been anchored at `top: 48px; bottom: 12px` so its height always equaled `shell_height - 60`. On the standard 480px-tall ai-toolkit.aspx demo editor the empty-state panel painted at 393px tall: header (39) + chip row (24) + composer (47) + 213px of stretched feed-shell area to hold a single 1-2 line empty-state hint ("Ask anything about the current document — or tap a quick start above."). The empty hint itself sat inside an `is-minimal.is-flat` card (`padding: 4px 12px 8px !important`) inside a `feed-shell` card with chrome (`padding: 12/13`, `border-radius: 22`, `box-shadow`). A card-in-card that reserved ~200px of empty void below a single line of text. Every reference panel sizes to content when empty and scrolls the feed when a conversation grows — Claude side-panel sits at ~280-340px empty, ChatGPT side-panel at ~300px empty, Notion AI inline composer at ~120px empty.
2. **AI menu items title-only with no hover affordance.** Since v20260513a the inline description copy span has been `display: none` so each menu row sits at 26-28px tall with just the 12px title text. The visible labels were trimmed in v20260513d ("Justify", "Add note", "Add paragraph") to fit the 2-col 420px grid — but stripped of their description copy, terse labels like "Justify", "Explain", "Add note" left first-time users guessing what AI would actually do. Every reference inline AI menu surfaces the side effect via a hover tooltip even when the inline copy is hidden: Notion AI slash menu, Linear command palette, VS Code command palette, GitHub command bar all attach a `title` / tooltip with the description text. Pre-pass the menu items had no `title` attribute — hover did nothing.

**AI Chat panel — natural content height + flat empty state**
- **`.richtexteditor .rte-ai-chat-panel.rte-ai-chat-panel { bottom: auto; max-height: calc(100% - 60px); gap: 8px !important }`**. Doubling the class in the selector wins the specificity war against the older v20260513e `padding/gap !important` rule. `bottom: auto` releases the panel from the editor-shell floor anchor; `max-height: calc(100% - 60px)` caps long conversations so the feed scrolls inside the panel instead of growing past the editor bounds. Net effect: empty panel sits at its natural ~263px content height (33% shorter than the prior 393px); panels with long conversations still respect a `calc(100% - 60px)` ceiling (≈420px on this demo editor) so the feed never escapes.
- **`.richtexteditor .rte-ai-chat-panel .rte-ai-chat-feed-shell { padding: 10px 11px; gap: 8px; min-height: 96px; flex: 0 1 auto }`**. Pre-pass the feed-shell carried `flex: 1 1 120px` so it always claimed leftover panel height. With `flex: 0 1 auto` the shell sizes to its content (96px min for the empty hint, grows naturally with messages). The `padding: 10/11` (down from 12/13) shaves 4px of inner chrome.
- **`.richtexteditor .rte-ai-chat-panel:has(.rte-ai-chat-message) .rte-ai-chat-feed-shell { flex: 1 1 auto; min-height: 140px }`**. CSS `:has()` selector flips the feed back to flex-grow once a conversation exists, so long threads scroll inside the panel cap instead of forcing the panel taller. `min-height: 140px` keeps at least one full message visible.
- **`.richtexteditor .rte-ai-chat-empty.is-minimal.is-flat { padding: 4px 4px 2px !important; border-radius: 0 !important; border: 0 !important; background: transparent !important; box-shadow: none !important; gap: 4px !important }`**. The `!important` flood is needed to beat the v20260513e `.rte-ai-chat-empty { padding: 4px 12px 8px !important; gap: 4px !important }` rule (line ~11366) that pinned the card chrome via cascade override. With the chrome gone, the empty hint reads as a plain centered text line inside the now-collapsed feed-shell — one card layer total, not the prior three (panel → feed-shell → empty-card-with-gradient).
- **`.richtexteditor .rte-ai-chat-empty-detail { font-size: 12.5px; line-height: 1.5; color: #4a5d72; max-width: 60ch }`**. Slight bump from 12px / 1.6 / #556d84 so the hint reads as primary surface content (matches the chat-input placeholder weight) instead of a muted caption now that the surrounding card chrome is gone.
- **`renderChatPanel` desiredHeight floor `380/420` → `320/360`** (mobile/desktop). The prior 420 floor forced the editor shell to grow to 420px tall whenever the panel opened — wasted vertical real estate now that the panel sizes to content. The new floor matches the natural empty-panel height so the floor binds only when the editor is shorter than the panel.

**AI menu — hover tooltips via the native title attribute**
- **`renderActionMenu` writes `button.title = action.description`** so each menu item surfaces the side-effect description on hover. Restores the discoverability Cyrillicwriting since v20260513a's `display: none` cull broke, without re-introducing the inline copy span that would grow each row 14-18px tall. Same shape Notion AI slash menu / Linear command palette / VS Code command palette ship — terse visible label, hover tooltip names what the action does.
- **`button.aria-label = title + " — " + description`** combines the two so screen readers announce both pieces on focus. Visual users see the terse "Justify" / "Add note" labels at the 2-col 420px grid density; keyboard / SR users get the full "Justify — Rewrite and show why it changed" form announced.

**Net effect:** the empty AI Chat panel drops from 390×393px to **390×263px** (≈33% shorter) — one calm card with header + chip row + 2-line hint + composer instead of a card-in-card with 200px of empty void. The panel still caps at `calc(100% - 60px)` (~420px on this editor) for long conversations, with the feed scrolling inside. AI menu items keep the 26px floor + 2-col layout (420×194, zero compactness regression) and gain hover tooltips on every item so first-time users discover what "Justify" / "Add note" / "Add paragraph" actually do without clicking. Same shape every reference inline AI surface ships — content drives height, descriptions live on hover for compact menus. Numeric / display / text overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use. Customers who want the prior chrome back can override with the six documented CSS rules in the v20260602a comment block at the end of `aitoolkit.css`.

`aiToolkitUiVersion` bumped from `v20260601a` to `v20260602a`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260602a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the AI Chat panel via the Chat menu item renders the panel at **390×263px** (was 390×393px) with `gap: 8px`, `padding: 12px`, `max-height: calc(100% - 60px)`, `bottom: 139px` (auto-resolved against the editor shell); the empty-state hint renders at 68px tall with `padding: 4px 4px 2px`, `background: rgba(0, 0, 0, 0)` (transparent), `border-radius: 0` — no card chrome; the feed-shell sits at 96px tall with `padding: 10px 11px`; the composer + chip row unchanged at 47px + 24px. Opening the toolbar AI menu renders the panel at **420×194px** with all 9 items exposing a `title` tooltip whose text matches their `aiToolkitActions[i].description` ("Ask follow-up questions about this document.", "Generate, rewrite, translate, or summarize before applying.", "Accept or reject prepared AI edits.", "Check grammar, spacing, and clarity.", "Improve clarity without changing intent.", "Choose a language and preview the result.", "Rewrite and show why it changed.", "Insert a short comment near the selection.", "Draft a supporting paragraph below."). Ask AI dialog dimensions unchanged (still 460×118 pre-result). Zero new entries in `preview_console_logs` at `error` level from the production code paths (existing eval-introduced `getComputedStyle` noise from inspection probes persists in the buffer but is not from the page; pre-existing `removeChild` noise from `rte.js?v=20260409f:2306` is unrelated to this pass).

## Version 2.0.5.7

### Improved — Ask AI dialog: scope `<select>` swapped for a quiet inline caption when only one scope is valid (no editor selection captured)
Forty-eighth pass on the AI windows. The v2.0.5.6 work compacted the toolbar AI menu and dropped the "Ask AI · " title prefix so the dialog title finally read as one word in seven of eight modes. A focused audit of the Ask AI dialog *opening state without an editor selection* against Notion AI inline composer, Claude inline edit, Tiptap AI bubble, ChatGPT inline edit, and CKEditor 5 AI Assistant surfaced one shape the dialog still painted that every reference inline AI surface explicitly avoids:

1. **Scope `<select>` rendered as a picker that couldn't be picked.** Since v20260523a the scope control has been a labeled `<select>` carrying two options: "Selection · X chars" (disabled when no selection is captured, surfaced as "Selection (none)") and "Document · X chars" (the active fallback). Most users open Ask AI from the toolbar without pre-selecting text — the toolbar menu's nine entries already imply "act on the document" semantics for Draft / Proofread / Rewrite / Summarize. In that empty-selection state the user landed on a dropdown with a chevron and a hover affordance promising a choice, then opened it to find the only valid option already chosen and a greyed-out sibling. Every reference inline AI surface ships the same principle the AI Chat panel has used since v20260515a: the scope chip only appears when a selection actually exists, otherwise the surface implies "I'll work with the whole document" and never paints a non-pickable picker.

**Ask AI dialog scope control**
- **`openDialog` creates a sibling `.demo-ai-scope-caption` span** next to the existing `.demo-ai-scope-select`. Empty by default — `syncDialogScopeUi` writes the live "Document · X chars" / "Document" text into it on every scope sync (same source as the `<option>` text).
- **`syncDialogScopeUi` toggles `.is-scope-implicit` on `.demo-ai-compact-controls`** based on `snap.hasSelection`. When no selection is captured, the class adds; CSS hides the `<select>` and shows the caption. When a selection appears (user selects text, then re-opens or re-runs `refreshSource`), the class drops; the picker returns. Both the select AND the caption stay in DOM regardless of state, so every onchange / disabled / aria-label sync path keeps resolving without each call site needing a null guard.
- **`.demo-ai-scope-caption` styled as a quiet inline label**: 12px / 600 weight / `rgba(28, 42, 59, 0.62)` muted text, 26px min-height to match the select's floor, 3px / 8px padding so the caption visually aligns with the picker it replaces, `text-overflow: ellipsis` so unusually long char counts don't break the row. `aria-hidden="true"` because the same text is also announced via the dialog's wider scope-hint title attribute on neighboring controls and via the source textarea's mode-aware placeholder — screen readers don't need a duplicate.
- **`.demo-ai-compact-controls.is-scope-implicit .demo-ai-scope-select { display: none }` + the inverse rule** for the caption. The select stays focusable in DOM only when visible (the parent's `display: none` hides it from the tab order too), and the caption is `display: none` until the implicit class flips it on. Net visual effect at the no-selection state: the dialog row reads as `[Proofread ▾]  Document · 117 chars` — two labels, one picker, instead of the prior two pickers where the right one couldn't actually be picked.

**Net effect:** opening Ask AI without a prior text selection — the most common entry from the toolbar menu — now reads top-to-bottom as `[Mode picker]  Document · X chars` → source textarea → action bar. Same shape Notion AI inline composer / Claude inline edit / Tiptap AI bubble / ChatGPT inline edit / CKEditor 5 AI Assistant all ship for "act on the document" mode — the scope is implied by entry context, the picker only appears when the user can meaningfully pick. Dialog dimensions unchanged (still 73px grid pre-result, 27px controls row, 27px header), but the row finally reads as one mode picker + one informational caption instead of two competing pickers. Customers who want the prior always-picker behavior back can override with two documented CSS rules in the v20260601a comment block at the end of `aitoolkit.css`. Numeric / display overrides only — none of the typography / colour decisions are touched, so the surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260531a` to `v20260601a`. The bundled `all_plugins.js` was rebuilt via the documented combine pipeline (`-combine-plugins.bat` powershell core), the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260601a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the dialog via the Draft menu item with NO editor selection renders `.demo-ai-compact-controls` with classes `demo-ai-compact-controls is-no-language is-scope-implicit`, `.demo-ai-scope-select` computed as `display: none` (w=0, h=0), `.demo-ai-scope-caption` computed as `display: flex` with rendered text **"Document · 117 chars"** (h=26, w=130); selecting text in the editor first and then re-opening the dialog renders the same row with classes `demo-ai-compact-controls is-no-language` (no `is-scope-implicit`), `.demo-ai-scope-select` visible at h=27 / w=130 with both options enabled ("Selection · 117 chars" / "Document · 117 chars"), and `.demo-ai-scope-caption` hidden. Dialog grid still measures 73px pre-result, header still 27px tall — no regression in vertical chrome. Zero new entries in `preview_console_logs` at `error` level from the production code paths (existing eval-introduced `getComputedStyle` noise from inspection probes persists in the buffer but is not from the page; pre-existing `removeChild` noise from `rte.js?v=20260409f` is unrelated to this pass).

## Version 2.0.5.6

### Improved — AI windows: toolbar menu row height 30px → 28px with 20px icon tiles, menu filter input 30px → 26px, Ask AI dialog title drops the "Ask AI · " prefix
Forty-seventh pass on the AI windows. The v2.0.5.5 work trimmed the dialog frame header (36px → 26px) and the AI Chat panel header (45px → 40px) so the chrome above the focal content matched the Notion AI / Claude / Tiptap / ChatGPT inline reference shapes. A focused audit of the *toolbar AI menu* (the dropdown that opens when the user clicks the gradient AI icon) and the *Ask AI dialog title row* against the same reference set surfaced three remaining shapes the AI windows still painted that the references explicitly avoid:

1. **Menu item rows still 30px tall with 24px icon tiles.** Since v20260513a the item description span has been hidden (`display: none`) so each item carries just a 12px title + a 24×24 icon tile. The 30px row reserved ~18px of vertical chrome around 14px of title text — proportional to the prior description-bearing layout, not to the title-only one. Every reference inline AI menu (Notion AI slash menu, Linear command palette, ChatGPT `/` suggestions, VS Code command palette) sits at 24-28px tall with proportional 16-20px icons. The 24px icon was visually 2× the title cap-height, pulling the eye to the colored tile before the title text.
2. **Menu filter input still 30px tall.** Every other AI button on the dialog standardised on 26px since v20260509j; the menu filter was the only AI surface left at 30px, breaking vocabulary parity. Same shape Linear command palette / ChatGPT search ship — the filter input matches the floor of the items it filters.
3. **Ask AI dialog frame title read as "Ask AI · Proofread".** Since v20260519a the title has carried both the surface name AND the active mode. But the gradient AI toolbar button that opens this dialog already names the surface (hover tooltip + the unmistakable robot icon), and the Action select directly under the title also names the active mode — so the dialog title was the third place "Ask AI" landed on the same screen. Notion AI inline composer ships "Improve writing", Claude inline edit ships "Make shorter", ChatGPT inline edit ships "Rewrite" — surface name is implied by entry context; the title names ONLY the active mode.

**AI toolbar menu**
- **`.rte-panel-aiassist-menu .rte-ai-menu-item` min-height `40px` → `26px`, padding `5px 7px` → `3px 6px`, gap `7px` → `6px`.** The 40px min-height was a stale floor from the description-bearing era — at min-height 40 the rendered row was actually 30px because content didn't force the floor; explicitly setting 26 makes the floor match the apply-row / Generate button vocabulary so the rule has a single source of truth. Net per row: 30px → 28px (rendered) — 2px shorter even though the spec dropped 14px, because the content was always the binding constraint. Across the 5-row 2-column grid the menu drops ~10px tall (5 rows × 2px).
- **`.rte-panel-aiassist-menu .rte-ai-menu-item-icon` `24×24` → `20×20`, border-radius `9` → `7`.** Proportional to the now-shorter row. The 13×13 SVG glyph inside still has comfortable breathing (~3.5px on each side instead of 5.5px) and reads at the same legibility because the SVG is unchanged. Saves no vertical pixels but visually quiets the menu — at 24px the icon was visually 2× the title cap; at 20px the icon and title cap-height align better, so the user's eye lands on titles first.
- **`.rte-panel-aiassist-menu .rte-ai-menu-item-title` `line-height: 1` explicit.** Pre-pass the title was inheriting `normal` (~1.2 from the panel font), which let glyph descenders ("g", "p", "y" in "Translate", "Add paragraph") grow the row past the 26px min-height floor. Pinning line-height to 1 lets the 26px floor actually bind on every rendered row.

**Menu filter input**
- **`.rte-ai-menu-search-input` padding `6px 10px` → `4px 9px`, font-size `12.5px` → `12px`.** Matches the menu-item-title font + the apply-row button height. Rendered input drops 30px → 26px tall. Saves 4px on every menu render and brings the filter into the same vocabulary as the items it filters.

**Ask AI dialog frame title**
- **`updateDialogFrameTitle` now writes `resolvedLabel || base` to the visible title** (instead of `(base + " · " + resolvedLabel) || base`). The title row reads as one word in 7 of 8 default modes ("Proofread", "Rewrite", "Translate (Spanish)", "Justify edit", "Shorten", "Expand", "Summarize") so it never truncates at narrow viewports and the mode reads at a glance. When no mode has resolved yet (first paint before `populateDialogModes` returns) the fallback `config.text_aiassist || "Ask AI"` keeps the dialog from opening with an empty title.
- **The `aria-label` attribute on the dialog inner still carries the full "Ask AI · Mode" form** so screen readers continue to announce the surface + mode pair on dialog open. Visual users see the shorter visible title; screen-reader users keep the prior announcement vocabulary.
- **`config.text_aiassist` is still honored as the surface fallback name.** Customers who override the surface name via this config get their custom name as the fallback, just as before — only the cascade of "surface name + mode" is broken.

**Net effect:** the AI toolbar menu now renders as a 420×~194px frame (was 420×208px, ~7% denser) with 28px title rows + 20px icon tiles that read as one calm grid, not a heavy colored-tile-on-tile list. The Ask AI dialog title reads as the active mode at a glance ("Proofread") instead of the prior verbose "Ask AI · Proofread". Same shape every reference inline AI surface ships — chrome stays out of the focal task, surface names implied by entry context. Numeric / display / text overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use. Customers who want the prior chrome back can override with the seven documented CSS rules + one config flag in the v20260531a comment block at the end of `aitoolkit.css`.

`aiToolkitUiVersion` bumped from `v20260530b` to `v20260531a`. The bundled `all_plugins.js` was rebuilt via `-combine-plugins.bat`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260531a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1`. Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the toolbar AI menu renders the panel at **420×194px** (was 420×208px) with 9 items each **28px tall** (was 30px), 20×20 icon tiles (was 24×24), and the filter input at **26px tall** (was 30px); clicking the Draft menu item opens the Ask AI dialog with the visible title reading **"Proofread"** (was "Ask AI · Proofread"), aria-label preserved as "Ask AI · Proofread" for screen readers; the AI Chat panel renders at 390×392 with the header still at 40px and the title "AI Chat" intact (no regression). Zero entries in `preview_console_logs` at `error` level from the production code paths.

## Version 2.0.5.5

### Improved — AI windows: dialog frame header trimmed from 36px to 26px (font 16px → 13px, padding 10px → 4px), AI Chat panel header trimmed from 45px to 40px (button floor 30px → 26px, title 15px → 14px), Result textarea top reserve 21px → 16px
Forty-sixth pass on the AI windows. The v2.0.5.4 work compacted the Ask AI grid contents to ~73px pre-result / ~109px post-result, but the dialog frame header (rendered by `editor.createDialog`) and the AI Chat panel header still painted ~36px and ~45px of chrome above the focal content. Same shape every reference inline AI surface (Notion AI inline composer, Claude inline edit, Tiptap AI bubble, ChatGPT inline edit) closes — a single-line label or no header at all, with the close affordance pinned at the top-right corner of the surface. Three remaining shapes still painted vertical chrome the references explicitly avoid:

1. **Ask AI dialog frame header rendered at 36px with a 16px h2-weight title.** Since the dialog has always been built on `editor.createDialog`, the title row inherited the framework's default 10px/14px padding + 16px font-size, anchoring the close X at top: 9px. The 16px heading-weight title competed visually with the focal content beneath it; the 36px reservation was 36/108 ≈ 33% of the pre-result dialog chrome devoted to a row whose only content is the static surface name + active mode. Every reference inline AI surface uses either a label-weight 13-14px title or no header at all (Notion AI inline composer has no separate header row — the close glyph floats top-right of the body).
2. **AI Chat panel header at 45px with a 30px-min-height action button floor.** Since v20260513e the chat header padding has been `4px 4px 10px` with a 30px-tall icon-only refresh button. The 30px floor was generous for a row whose tallest child is the 22px title icon + 18px title text — a 22-26px button would sit comfortably with the 6px padding-bottom. The Claude / ChatGPT / Notion AI side-panel reference shape is a single-line 32-36px header total; v20260513e dropped to 45 but stayed above that floor.
3. **Result textarea reserved 21px top-padding to clear the "Edit prompt" corner pill.** The pill measures ~17px tall at `top: 4px` (reaching 21px). Pre-pass the 21px reservation matched the pill's max-reach exactly, but with no breathing room above the AI output's first line. Tightening to 16px gives the suggestion an extra 5px of usable vertical space without ever clipping the pill (the pill stays above the first text line at any reasonable rendering).

**Ask AI dialog frame header**
- **`.rte-panel-aiassist rte-dialog-header` padding `10px 14px` → `4px 14px`, font-size `16px` → `13px`, font-weight 700, line-height 1.25, letter-spacing -0.005em.** The title reads as a quiet label naming the surface ("Ask AI · Proofread") and the active mode — a state the Action select below also surfaces — not as an h2-weight headline. At 13px / line-height 1.25 the title text is ~16px tall; combined with 4px top/bottom padding the header lands at ~24px (rendered 26px including the 1px border-bottom). Saves ~10px on every dialog render — the pre-result dialog drops from 128px tall to **118px**; the post-result dialog drops from ~165px to ~154px (the inner_h delta is `originalInnerH - 36px header + 26px header = -10px`).
- **`.rte-panel-aiassist rte-dialog-header-close` top `9px` → `3px`.** Recenters the 28×28 close X glyph in the now ~26px header so the icon sits with ~−1px breathing on top + bottom (border-radius 999px hides the pixel-precision overshoot). At 9px the close button would have rendered with its top 1px clipped by the now-shorter header; at 3px the centerline matches.
- **`.rte-panel-aiassist rte-dialog-inner { padding-top: 0 }`** to recover the 2px the prior 16px-font header reserved above the grid. (Specificity of the underlying `rte-dialog-inner` selector means this override may not apply in all hosts — the bulk of the saving comes from the header padding cut, which always wins.)

**AI Chat panel header**
- **`.richtexteditor .rte-ai-chat-header.is-minimal` padding `4px 4px 10px` → `2px 4px 6px`.** Saves 6px combined (top 2px + bottom 4px). The `border-bottom: 1px solid rgba(20, 36, 58, 0.08)` already creates section separation between the header and the body — the 10px padding-bottom was double-counting the gap the panel's own `gap: 10px` between stack children provides.
- **`.richtexteditor .rte-ai-chat-header.is-minimal .rte-ai-chat-header-button` min-height `30px` → `26px`, padding `4px 12px` → `3px 10px`.** Matches the dialog Apply row's 26px button height so the two AI surfaces share a single button-height vocabulary. The icon-only refresh button (the only header button visible in the default minimal config) sits comfortably in 26px with the 14px refresh glyph + 6px combined padding.
- **`.richtexteditor .rte-ai-chat-header.is-minimal .rte-ai-chat-title` font-size `15px` → `14px`.** Proportional to the tightened header. Title still reads as a heading (700 weight inherited from the base `.rte-ai-chat-title` rule) but in line with the close button's 14px icon.
- **`.rte-ai-chat-title-icon` 22px → 20px** (with matching 20px svg child).** Proportional to the 14px title — at 22px the icon was visually 1.5x the title text; at 20px the icon and title cap-height match. (When `display: none` from a host config, this is a no-op.)
- **Net: chat header drops from ~45px to ~40px (≈11% denser).** Combined with the existing `is-no-selection-actionable` scope-shell suppression and the minimal-header pattern from v20260513c, the chat panel now reads as four calm sections (header → quick chips → feed → composer) instead of a heavy chrome-strip-and-body shape.

**Ask AI dialog result textarea**
- **`.rte-panel-aiassist .demo-ai-result-field textarea` padding-top `21px` → `16px`.** Saves 5px on every post-result dialog. The "Edit prompt" corner pill (~17px tall at top: 4px) still sits above the first text line — the 16px reservation matches the pill's bottom edge at top: 4 + 12 = 16px so no clipping is introduced, but the suggestion's first line now lands ~5px higher in the textarea.

**Net effect:** Ask AI dialog now reads top-to-bottom as:
- **Pre-result**: `[Compact "Ask AI · Mode" label header, 26px]` → `[Action select, Scope select, 26px]` → `[Source textarea w/ Generate icon overlay, 44px]`. Total inner **118px** (was 128px, ~8% denser).
- **Post-result**: same header → controls → `[Result textarea with Edit prompt corner pill]` → `[Apply | Try again | Shorter | Longer | Why ▸ | ⋯]` action bar → (optional Why panel) → quiet status whisper. Total inner **~154px** (was ~165px, ~7% denser).
- **AI Chat panel header**: `~40px` (was 45px, ~11% denser).

The frame header finally reads as a quiet 13px label rather than a competing h2; the chat panel header finally fits within the single-line floor the references all ship; the result textarea reclaims 5px for AI output. Same shape Notion AI inline composer / Claude inline edit / Tiptap AI bubble / ChatGPT inline edit all ship — chrome stays out of the focal task. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use. Customers who want the prior chrome back can override with the six documented CSS rules in the v20260530b comment block at the end of `aitoolkit.css`.

`aiToolkitUiVersion` bumped from `v20260530a` to `v20260530b`. The bundled `all_plugins.js` was rebuilt via `-combine-plugins.bat`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260530b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the dialog via the Draft menu item renders the dialog inner at **118×440px** pre-result (was 128×440px) with the header at **26×440px** (was 36×440px), header font-size computed as `13px`, header padding computed as `4px 14px`, close X at top `3px`; populating result text and clearing `is-pre-result` paints the post-result dialog at inner **~154px** tall (was ~165px) with result textarea `padding-top: 16px` (was 21px); opening AI Chat renders the header at **40×376.67px** tall (was 45.33px) with title font-size `14px`, header button height `26px` / min-height `26px`. Zero entries in `preview_console_logs` at `error` level from the production code paths (existing eval-introduced `getComputedStyle` noise from earlier inspection calls persists in the buffer but is not from the page).

## Version 2.0.5.4

### Improved — Ask AI dialog: refine chips merged INTO the apply row so post-result actions read as ONE compact bar; mode caption row dropped from the pre-result flow; grid gap tightened; pre-result dialog drops to ~73px tall, post-result to ~109px tall
Forty-fifth pass on the AI windows. The user-feedback bar was "make it easy to understand, easy to use, the window more compact." After v2.0.5.3 landed the Why chip into the refine row (turning the post-result dialog into result → [Try again | Shorter | Longer | Why] → Apply → status), a focused audit of the Ask AI dialog against Notion AI inline composer, Claude inline edit, Tiptap AI bubble, ChatGPT inline edit, and CKEditor 5 AI Assistant surfaced three remaining shapes that still painted vertical chrome the references explicitly avoid:

1. **Refine chips painted as a SEPARATE row above the apply row.** Since v2.0.5.2 the four refine chips have lived on their own grid track immediately below the result textarea, with the smart-primary Apply row a separate track below that. Every reference inline composer (Claude inline edit's "Make it shorter | Make it longer | Try again | Accept", Notion AI's "Try again | Make shorter | Make longer | Accept", Tiptap AI bubble's "Accept | Try again | Discard") anchors the primary action AND the refinement chips on the SAME row. The two-row stack ate ~29px of post-result vertical chrome (26px chip row + 3px grid gap) for an affordance the references manage in zero extra rows.
2. **Mode caption row painted under the Action select pre-result.** Since v20260517a a 14px caption row carried a one-line description of the active mode ("Clean up grammar, spacing, and readability issues.") to help first-time users understand what the picker would do. But by v2.0.5.3 the mode signal already landed in three more prominent places: the Action select's own label, the dialog frame title ("Ask AI · Proofread", since v20260519a), and the source textarea's mode-aware placeholder ("Paste text to proofread…", since v20260517a). Three discoverability slots for the same fact, two of them outweighing the caption row's prominence — same shape Notion AI / Claude inline edit / ChatGPT inline edit ship, where the mode is named ONCE (in the picker / title), not painted into a caption row beneath it.
3. **Dialog grid gap was 3px between rows.** Generous for a stack that no longer carries the standalone refine row OR the mode caption — every grid gap lined the previously-existing rows with breathing room they no longer need. Combined with the row drops above, the cumulative gap savings became meaningful (~6px across the post-result stack).

**Ask AI dialog**
- **Four refine chips reparented INTO the apply row.** `openDialog` still creates `refineRetryChip`, `refineShorterChip`, `refineLongerChip`, and `refineWhyChip` on a `refineRow` div, but the `refineRow` is constructed with `is-detached` and only appended to the grid for legacy selector compatibility (any external integration that walks `.demo-ai-refine-row` from the dialog root still resolves). After `applyRowMoreToggle` is built, four `applyRow.insertBefore(chip, applyRowMoreToggle)` calls move the chips into the apply row, sitting between the alt-apply buttons (hidden by default) and the kebab. Final DOM in `applyRow`: apply → selection → preview → insert → document → refine-retry → refine-shorter → refine-longer → refine-why → more → reject (the last detached). With the default non-`is-show-more` state hiding the four alt-apply buttons, the visible row reads as: `[Apply primary]  [Try again] [Shorter] [Longer] [Why ▸]  [⋯]` — same single-row affordance Notion AI / Claude inline edit / Tiptap AI bubble / ChatGPT inline edit ship.
- **`syncDialogApplyRowOrder` updated to include the chips + kebab in its ordered list.** Pre-pass `orderedButtons` only listed the apply-family buttons (`[apply, selection, preview, insert, document, reject]`), so its `appendChild` re-shuffle moved them to the END of `applyRow` — which inverted the layout, pushing the refine chips and the kebab to the FRONT (DOM became `[refine-retry, refine-shorter, refine-longer, refine-why, more, apply, selection, preview, insert, document]`). The fix folds the four refine chips and the kebab into BOTH `orderedButtons` and `remainingButtons` arrays at the correct positions so the re-shuffle preserves the intended row layout: apply → alt-apply → refine chips → kebab → reject. Recommended-action promotion (which moves the recommended button to the FRONT of the row) keeps working because the `remainingButtons` list is also updated.
- **Mode caption row dropped from the visible grid.** Pre-pass `grid.appendChild(modeHelp)` attached the 14px caption to the grid flow. Post-pass `modeHelp.classList.add("is-detached")` keeps the node in DOM (so `updateModeHelp`'s `modeHelp.innerText = helpText` write path noops safely and any external integration that reads `modeHelp.innerText` still resolves), but the `.is-detached` global rule (`display: none !important`) collapses it visually. The Action select's `title` attribute continues to carry the description text for the hover tooltip, so mouse users still see the rationale on hover; screen readers see it via the select's `aria-label` and `title`. Saves ~14px on every pre-result dialog render.
- **Smart-primary Apply button capped to `flex: 0 0 auto`.** Pre-pass Apply had `flex: 1 1 140px` (inherited from `.demo-actions-row button` and the apply-row primary override). With the refine chips on the same row that growth was harmful — Apply expanded to fill the leftover space (~420px in a 420px row) and pushed the chip cluster onto a second wrapped line, growing the apply row to 58px. `flex: 0 0 auto` pins Apply to its natural label width (~53-180px for labels like "Apply" / "Replace selection" / "Apply 3 pending steps") and leaves room for the refine cluster on the same row.
- **Refine chip cluster pushed right via `margin-left: auto` on the first chip.** The row now reads as two visual groups: `[Apply primary]` on the left, `[refines + kebab]` on the right. Same layout idiom Notion AI inline composer / Claude inline edit ship — primary action and refinements share one row but cluster visually.
- **Dialog grid gap 3px → 2px.** Cumulative ~6px savings across the post-result stack.
- **Status row quieted.** Font 11px → 10.5px, min-height 16px → 14px so transient feedback ("Apply succeeded" / "Generation failed") reads as a status whisper, not a substantive line.

**Net effect:** Ask AI dialog now reads top-to-bottom as:
- **Pre-result**: `[Action select] [Scope select]` → `[Source textarea with Generate icon overlay]`. Grid measured at **~73px tall** (was ~88px; ~17% denser).
- **Post-result**: `[Action select] [Scope select]` → `[Result textarea with Edit prompt corner pill]` → `[Apply | Try again | Shorter | Longer | Why ▸ | ⋯]` action bar → (optional Why panel) → quiet status whisper. Grid measured at **~109px tall** (was ~146px; ~25% denser).

The dialog reads as one focal task: pre-result, type or paste → click Generate. Post-result, see the result → pick from a single uniform row of actions. No separate refine row to wonder about, no caption row repeating what the picker label already says. Same shape Notion AI inline composer / Claude inline edit / Tiptap AI bubble / ChatGPT inline edit / CKEditor 5 AI Assistant all ship — the AI window finally aligns with the reference vocabulary on every dimension the prior pass left on the table. Numeric / display / DOM-order overrides only — none of the typography / colour decisions are touched, so the surface still reads as the same product, just clearer and easier to use. Customers who want the prior two-row layout back can override with the three documented CSS rules in the v20260530a comment block at the end of `aitoolkit.css`.

`aiToolkitUiVersion` bumped from `v20260529a` to `v20260530a`. The bundled `all_plugins.js` was rebuilt via `-combine-plugins.bat`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260530a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the dialog via the Draft menu item renders the grid at 73px tall pre-result with `.demo-ai-mode-caption.is-detached` computed as `display: none` and `.demo-ai-refine-row.is-detached` also computed as `display: none`; clearing `is-pre-result` and populating the result area paints the apply row at 26px tall × 420px wide with five visible buttons (apply at x=423, refine-retry at x=553, refine-shorter at x=636, refine-longer at x=709, refine-why at x=780), and Apply uses `flex: 0 0 auto` to stay at its natural ~53px width. DOM order in the apply row reads `[apply → selection → preview → insert → document → refine-retry → refine-shorter → refine-longer → refine-why → more]`, confirming `syncDialogApplyRowOrder`'s updated `orderedButtons` array preserves the intended layout. Zero entries in `preview_console_logs` at `error` level.

## Version 2.0.5.3

### Improved — Ask AI dialog: "Why" disclosure folded into the refine chip row as a 4th ghost chip, killing the standalone Details toggle row; post-result dialog drops ~26px tall and the rationale + plan affordance is a peer-click instead of a stacked extra row
Forty-fourth pass on the AI windows. The v2.0.5.2 changelog landed the post-result refinement chip row (Try again / Shorter / Longer) directly below the result textarea — but the rationale + plan disclosure ("Details" toggle + insight grid carrying the Why panel and the operation Plan list) still painted as a separate one-button row beneath the chip strip, then expanded into a full insight panel when clicked. Pass 31 had already hidden both the toggle AND the insight grid wholesale via `display: none !important` because the standalone "click to open empty panel" affordance read as wasted chrome; the consequence was that the underlying reason / plan data — still produced by `markPlanStale`, `renderOperationPlan`, and `syncDialogRecommendedActionUi` on every result — had no visible entry point at all. Two regressions stacked on one surface: the disclosure row took vertical space when visible (~22px button + 4px gap = ~26px) for an affordance every reference inline AI surface anchors INLINE with the iteration cluster (Notion AI's footer chips, Linear command palette's footer caption, ChatGPT inline edit's muted "view sources" link), and when Pass 31 killed the row entirely the data the model returned to justify its rewrite went unobserved.

**Ask AI dialog**
- **"Why" chip folded into the post-result refine chip row as a 4th ghost chip.** `openDialog` now appends a `refineWhyChip` (`.demo-ai-refine-chip.demo-ai-refine-why-chip`) into `refineRow` immediately after `refineLongerChip`, sharing the same ghost-pill styling that v2.0.5.2 introduced for Try again / Shorter / Longer. CSS `margin-left: auto` pushes the chip to the trailing edge so the three iteration chips cluster left and the rationale disclosure sits right — keyboard Tab order still reads `refine-retry → refine-shorter → refine-longer → refine-why`, then continues to the Apply primary, matching reading order. The chip carries `data-rte-ai-dialog-action="refine-why"`, `aria-expanded`, `aria-controls` pointing at the existing `insightGrid.id` (the same disclosure target the standalone `detailsToggle` owned), and a `title="Show why this AI suggestion was generated"` tooltip. A small `▸` caret glyph (`::before`) mirrors the prior detailsToggle's arrow and rotates 90° on `aria-expanded="true"` so the visual disclosure state at a glance matches the pattern users already know from other surfaces in the dialog.
- **Pass 31's `.demo-ai-insight-grid { display: none !important }` override removed.** Pass 31 had hidden BOTH the toggle and the insight grid because the toggle was the only visible entry — with the toggle gone, the panel had no way to surface. v20260529a flips that decision: the panel becomes reachable again via the Why chip's onclick, which toggles the same `.is-collapsed` class that the base `.rte-panel-aiassist .demo-ai-insight-grid.is-collapsed { display: none }` rule has owned since the disclosure was originally built. The standalone `.demo-ai-details-toggle` stays hidden via the Pass 31 rule (kept in DOM so external integrations walking that selector continue to resolve), and the Why chip is the single visible entry point. Net: the reason / plan data the model already produces has somewhere to land again, without painting a wasted row when the panel is collapsed.
- **`setBusyState` gates the Why chip alongside the other refine chips.** A new `if (refineWhyChip) refineWhyChip.disabled = !!isBusy;` line drops the chip to the same `opacity: 0.45; cursor: default` disabled affordance the other three chips already share while a stream is mid-flight. Unlike the iteration chips (which gate on both busy state AND `normalizeText(resultArea.value)` because Try again / Shorter / Longer would no-op on empty result text), the Why chip re-enables independent of result presence — the insight grid may still carry stale reason / plan content from a prior run, and users curious about the previous WHY shouldn't be locked out just because the result area is being re-streamed.
- **Why chip's onclick mirrors `aria-expanded` across both itself AND the (hidden) `detailsToggle`.** The Pass 31 toggle is still in the DOM as a detached affordance so any external integration that walks `.demo-ai-details-toggle` keeps resolving. The Why chip's click handler writes `aria-expanded` to both nodes so screen readers see one disclosure state regardless of which control the keyboard landed on — same pattern the chat panel uses for its `selectionButton` / `documentButton` aria-pressed sync.

**Net effect:** post-result Ask AI dialog now reads top-to-bottom as result → [Try again | Shorter | Longer | Why ▸] → (optional reason / plan when Why is open) → Apply, matching the affordance every reference inline AI surface (Notion AI / Tiptap AI bubble / Claude inline edit / ChatGPT inline edit / Linear command palette footer) ships for "refine the suggestion OR see why it was made." Drops ~26px of post-result dialog vertical chrome (the standalone Details toggle's 22px button + 4px row gap) — the chip strip claims the same horizontal real estate it claimed at v2.0.5.2 (420×26 row), now also doing the rationale-disclosure job that was a separate stacked row pre-pass. Users curious about WHY can now see it again (the Pass 31 sweep had killed the visible entry point); users who don't care never click the chip and pay zero extra vertical pixels. Customers who want the prior standalone Details toggle layout back can override with `.rte-panel-aiassist .demo-ai-details-toggle { display: inline-flex !important }` AND `.rte-panel-aiassist .demo-ai-refine-row .demo-ai-refine-why-chip { display: none !important }`. Numeric / display / DOM-order overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and more compact.

`aiToolkitUiVersion` bumped from `v20260528b` to `v20260529a`. The bundled `all_plugins.js` was rebuilt via `-combine-plugins.bat`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260529a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: the AI toolbar menu opens, clicking "Draft" opens the Ask AI dialog with a `.rte-panel-aiassist` frame, the post-result simulation reveals 4 chips in `.demo-ai-refine-row` (Try again / Shorter / Longer / Why) at a uniform 420×26 row, the Why chip carries `aria-controls="rte-ai-details-..."` pointing at the insight grid's id, sits with `margin-left: auto` at the row's trailing edge (rightEdgeOffset=0px), and toggling Why flips `.is-collapsed` on the insight grid — pre-click `display: none` / `aria-expanded="false"`; post-click `display: grid` / `height: ~111px` / `aria-expanded="true"`; second click returns to collapsed. Both the Why chip and the (hidden) detailsToggle mirror their `aria-expanded` attribute together. The standalone `.demo-ai-details-toggle` computes `display: none` via the Pass 31 rule. Zero entries in `preview_console_logs` at `error` level.

## Version 2.0.5.2

### Improved — Ask AI dialog: post-result refinement chips (Try again / Shorter / Longer) below the result textarea, the one-click iteration affordance every reference AI inline composer ships
Forty-third pass on the AI windows. After v2.0.5.1 landed the type-to-filter search bar on the Ask AI toolbar menu, a focused audit of the *post-result* Ask AI dialog against Claude inline edit, ChatGPT inline edit, Tiptap AI bubble, Notion AI inline composer, and CKEditor 5 AI Assistant surfaced one remaining shape the dialog still painted that all five references explicitly handle:

1. **No one-click way to iterate on a suggestion.** Post-result the only paths to a different output were: (a) click the smart-primary Apply button to commit and start over, or (b) open the "Edit prompt" disclosure (since v20260513a), expand the source field, rewrite the prompt, and click Generate again. Three clicks + manual prompt-craft for what every modern AI surface treats as a one-click affordance. Claude inline edit ships "Make it shorter / Make it longer / Try again" chips; ChatGPT inline edit ships "Regenerate / Shorter / Longer"; Tiptap AI bubble ships "Try again / Make shorter"; Notion AI ships "Try again / Make shorter / Make longer"; CKEditor AI Assistant ships continuation chips. All five anchor the chips immediately below the AI suggestion so refinement reads as a peer affordance to Apply, not a hidden disclosure path.

**Ask AI dialog**
- **Post-result refinement chip row inserted between the result textarea and the Details disclosure.** `openDialog` now appends a `<div class="demo-ai-refine-row">` to the dialog grid right after the result textarea is constructed and before the `applyRow` (so when v20260524a's `grid.insertBefore(detailsToggle, applyRow)` re-orders the DOM, the chips sit naturally above the Details toggle — i.e. result → refine chips → (optional) Why → Apply). The row carries three ghost chips: **"Try again"** (re-runs with the exact same source + mode + scope by calling `runButton.onclick()`), **"Shorter"** (calls a new `runRefinementInstruction()` helper that sets `sourceArea.value` to a short instruction header + the current result, then fires the existing run path), and **"Longer"** (same helper with an expansion instruction). Each chip carries an `aria-label`, a `title` (e.g. "Re-run with the same prompt"), and a `data-rte-ai-dialog-action` attribute (`refine-retry` / `refine-shorter` / `refine-longer`) so external integrations that walk dialog DOM by action keep resolving. The chips route through the same `runButton.onclick` code path — streaming, busy-state, smart-primary apply label, `__aiResolved` plumbing, and `updatePreview()` all keep working without separate handlers.
- **Chips hide pre-result via the existing `.is-pre-result` grid class.** The grid already gets `.is-pre-result` toggled by `updatePreview()` (since v20260509j) whenever there is no AI output yet. A new CSS rule `.rte-panel-aiassist .demo-ai-dialog-grid.is-pre-result .demo-ai-refine-row { display: none }` collapses the row before a suggestion exists — same idiom Notion AI / Tiptap AI bubble use, where "Try again" only appears once there's something to retry. The moment Generate produces output and `is-pre-result` clears, the chip row appears in the grid flow without any JS show/hide work.
- **Chips share the dialog's busy gate.** `setBusyState(true, ...)` now disables all three chips alongside `runButton`, `copyButton`, `loadSelectionButton`, etc. so a user can't queue a second `runButton.onclick()` while the first stream is mid-flight (the same race the Generate button has guarded against since v20260515e). Once the stream resolves, `setBusyState(false, ...)` re-enables them (subject to `resultArea.value` being non-empty — chips stay disabled if the previous run returned empty output, so they never fire on a missing result).
- **Ghost chip styling: 11.5px / 26px min-height / pill border-radius / muted blue border tint.** The chips read as quiet secondary affordances, not competing with the smart-primary Apply button below them. Hover lifts the background to a translucent blue tint (`rgba(29, 103, 186, 0.08)`) and tightens the border to match the toolbar's primary accent — same micro-interaction every reference ghost chip uses. Focus-visible draws a 2px outline at 2px offset (WCAG-friendly contrast against the dialog tinted background). Active state drops 1px for tactile feedback. Disabled chips drop to 0.45 opacity, matching the existing apply-row button disabled state.

**Net effect:** post-result Ask AI dialog now reads top-to-bottom as result → **refine chips** → (optional) Why → Apply, matching the affordance every reference inline AI composer (Claude / ChatGPT / Tiptap / Notion AI / CKEditor) ships. Users iterating on a suggestion no longer have to open the "Edit prompt" disclosure and manually rewrite — they pick a one-click refinement instead. Pre-result dialog height unchanged at ~110px tall grid (147px including frame chrome), because the chip row hides via `.is-pre-result`. Post-result dialog grows ~28px (chip row 26px + 2px top gap) — the only visible-pixel cost the pass introduces, and it pays for itself the first time a user clicks one of the chips instead of opening the source disclosure. Customers who want the chips hidden can override with `.rte-panel-aiassist .demo-ai-refine-row { display: none !important }`; customers who want different labels can swap the `innerText` on `refineRetryChip` / `refineShorterChip` / `refineLongerChip` via the existing `openDialog` extension hooks. Numeric / display / DOM-order overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to iterate on.

`aiToolkitUiVersion` bumped from `v20260527a` to `v20260528b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260528b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: pre-result dialog grid carries `is-pre-result` and `.demo-ai-refine-row` measures 0×0 (hidden via CSS); removing the class post-result reveals a 420×26 row with three ghost chips labelled "Try again", "Shorter", "Longer", each 74×26px at 11.5px font with `border-radius: 999px` and the blue border tint; chips carry the expected `data-rte-ai-dialog-action` attributes, `title` tooltips, and click handlers; zero entries in `preview_console_logs` at `error` level.

## Version 2.0.5.1

### Improved — Ask AI toolbar menu: type-to-filter search bar at the top with auto-focus and arrow-key navigation, the affordance every reference AI surface ships (Notion slash menu, Linear command palette, ChatGPT `/` suggestions)
Forty-second pass on the AI windows. The Ask AI toolbar menu (the dropdown that opens when the user clicks the gradient AI robot icon in the toolbar) was the last AI surface in the toolkit that read as a mouse-only affordance. Since v20260513c the menu has been a flat 2-column grid of 9 items (Chat / Draft / Review / Proofread / Rewrite / Translate / Explain / Add note / Add paragraph) with no keyboard navigation and no way to narrow the list — users had to visually scan the grid every time, and keyboard users had to tab through every item to reach the one they wanted. Every reference inline AI surface (Notion AI slash menu, Linear command palette, ChatGPT `/` suggestions, VS Code command palette, GitHub command bar) opens with a type-to-filter input pinned to the top so first-time users can find the action they want by typing the first few letters and keyboard-first users can land on Generate-equivalent muscle memory.

**Ask AI menu**
- **Type-to-filter search input at the top of the menu, auto-focused on open.** `renderActionMenu` now creates a `<input type="text" class="rte-ai-menu-search-input" placeholder="Filter actions…">` as the first child of the panel, spanning both grid columns via `grid-column: 1 / -1` on the wrapping `.rte-ai-menu-search-row`. The input is auto-focused via a 30ms `setTimeout` so the dropdown's own positioning + focus-traversal code (which fires synchronously after the render returns) doesn't clobber the focus we set. As the user types, `applyFilter` walks `itemEntries` (the JS-side cache of `{ button, action }` pairs collected during the existing item render loop) and toggles `.hidden` on each button based on a case-insensitive `indexOf` against `title + description + id`. The first matching button gains the `.is-active` highlight class so users see immediately where Enter will land; an empty-state hint ("No actions match.") appears below the grid when the query matches zero items. CSS rule `.rte-ai-menu-item[hidden] { display: none !important }` ensures hidden items drop completely out of the 2-column grid flow so visible items reflow to the top without leaving blank slots.
- **Arrow-Up / Arrow-Down cycle the active item; Enter invokes it; ESC clears the query.** A `keydown` handler on the search input intercepts `ArrowDown` to move the active class to the next visible button (wrapping at the end), `ArrowUp` for the reverse direction (wrapping at the start), `Enter` to click the currently-active button (or the first visible one if nothing is active), and `Escape` (when the search has text) to clear the input rather than dismissing the menu — same idiom Notion AI's slash menu uses. The existing click handler on each item is untouched, so mouse users see no behavior change. Hovering an item also sets the active class via a `mouseenter` listener so the keyboard highlight stays in sync with mouse pointing.
- **`.rte-ai-menu-item.is-active` shares hover styling.** New CSS rule folds `:hover`, `:focus-visible`, and `.is-active` into the same selector group so the keyboard-driven highlight reads identical to the mouse-hover state — no second visual vocabulary to learn. Same icon-tint shift on the item icon, same background shift on the item body.
- **Menu height grows by ~30px (160px → ~209px) for the search row.** The search input + 3px bottom margin claims ~33px of vertical chrome. With the menu's `max-height: min(420px, calc(100vh - 120px))` floor unchanged, the menu still fits comfortably on every viewport the prior layout did, and the row pays for itself the first time a user types instead of scrolling visually. Customers who want the prior search-less menu back can override with `.rte-ai-menu-search-row { display: none !important }` — the existing grid of items remains fully functional (the mouse-only click path is unchanged).

**Net effect:** the AI toolbar menu finally reads as a keyboard-first command surface, matching the affordance every modern AI / palette surface ships. First-time users see a familiar "type to filter" pattern at the top of a 9-item list and find their action with 1–3 keystrokes; keyboard-first users navigate with arrow keys and hit Enter to invoke without ever touching the mouse; mouse users see no behavior change — the search is purely additive chrome that ignores them. The menu is the entry point to every other AI surface in the toolkit (it opens Chat, Draft dialog, Review drawer, the inline preview actions, etc.), so making it keyboard-first lifts the floor of the entire AI window experience. Saves no vertical pixels but adds the discoverability + keyboard idiom the references ship — exactly the "easier to use, easier to understand" lift the AI window passes have been chasing, applied to the first surface a user sees when they click the toolbar AI button.

`aiToolkitUiVersion` bumped from `v20260526a` to `v20260527a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260527a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening the menu via `RTE-TOOLBAR-BUTTON.rte_command_aiassist.click()` renders a 420×209 panel with `document.activeElement === .rte-ai-menu-search-input`; typing `tran` into the search filters the 9-item grid to `["Draft", "Translate"]` (Draft's description contains "translate") with `.rte-ai-menu-item.is-active` on the first match (Draft); dispatching ArrowDown advances the active highlight to Translate; clearing the input restores all 9 items; dispatching Enter on a `chat`-filtered menu invokes the Chat action (the chat panel opens, the menu closes); zero entries in `preview_console_logs` at `error` level.

## Version 2.0.5.0

### Improved — Ask AI dialog: Generate button anchored INSIDE the source textarea, mirroring the v2.0.4.9 chat-composer pattern; pre-result dialog drops from ~146px tall to ~110px tall (≈25% denser)
Forty-first pass on the AI windows. The v2.0.4.9 changelog explicitly noted that the Ask AI dialog was unchanged in the chat-composer "Send-button-inside-textarea" pass — *"the chat panel was the last AI surface still painting a layout shape the references explicitly do not"*. That conclusion was incomplete: the Ask AI dialog still painted the same two-row shape the chat composer just fixed. The pre-result dialog rendered the Generate primary on a 36px controls strip ABOVE the source textarea, sharing the row with the Action and Scope selects:

```
[ Action [Proofread ▾]  Scope [Document · 117 ▾]  [Generate ⌘↵] ]   <- 36px controls
[ Source textarea ____________________________________________ ]   <- 36px input
```

Users typing INTO the source textarea had to look UP to find Generate — exactly the friction every reference inline composer (Notion AI inline composer, ChatGPT inline edit, Tiptap AI bubble, Claude inline edit, CKEditor 5 AI Assistant) explicitly avoids. All five anchor the primary action INSIDE the input container at the bottom-right corner so the composer reads as ONE focal input with ONE action target.

**Ask AI dialog**
- **Generate button moved INSIDE the source textarea as an anchored 32×32 round icon button.** `openDialog` now calls `sourceField.appendChild(runRow)` immediately after the source textarea is appended, reparenting the entire run-row (Generate primary + icon-only Copy) from `runField` (in `compactControls`) into `sourceField`. CSS positions the runRow with `position: absolute; right: 6px; bottom: 6px; pointer-events: none` on the wrapper so the textarea's empty trailing area stays clickable for focus; the buttons themselves restore `pointer-events: auto` so clicks still fire. `.demo-ai-source-field .demo-ai-run-row .is-primary` drops to `width: 32px; height: 32px; border-radius: 999px` — its dynamic label span (`Generate` / `Regenerate` / `Thinking…`) is visually hidden via the standard `position: absolute; clip: rect(0,0,0,0)` sr-only pattern (same idiom v20260525a used for the chat Send button) so the label stays in the accessibility tree (`aria-label` + `title`) without forcing the button to a ~110px pill shape that would break the bottom-right icon anchor. The Ctrl/Cmd+Enter kbd-hint chip is hidden in this layout (no horizontal room inside 32px); the shortcut is still surfaced via the button's `title` ("Generate AI suggestion (Ctrl+Enter)") and `aria-keyshortcuts="Control+Enter"`, so mouse hover + screen readers still announce it.
- **Source textarea `padding-right: 60px` + `resize: none` + `min-height: 44px`.** The 60px right padding reserves the 32px + 6px-offset region for the Generate icon overlay so typed text never slides under the button glyph (and leaves a sliver of room for the icon-only Copy button beside it post-result). `resize: none` kills the bottom-right resize handle that would otherwise sit under the button overlay (visual overlap + ambiguous drag target). `min-height: 44px` (was 36px since v20260520b) gives the source textarea a calmer single-line floor while still letting the 32×32 button overlay sit with 6px breathing on top and bottom — same proportion v20260525a used for the chat composer.
- **Compact-controls run-field hidden.** With `runRow` reparented into `sourceField`, the wrapper that previously held it inside `compactControls` is empty. New CSS rule `.rte-panel-aiassist .demo-ai-compact-controls > .demo-ai-run-field { display: none !important }` collapses the empty wrapper so the controls strip reflows from a 3-column flex row (Action / Scope / Run) to a 2-column row (Action / Scope only). The row's height drops from ~36px (the Generate button's floor) to ~26px (the natural select chrome), saving ~10px of pre-result dialog height beyond the textarea reshape.
- **Run-row Copy button styled to match the Generate anchor.** The icon-only Copy button (which sits beside Generate inside the run-row post-result) gets the same 32×32 round border-radius styling so the paired affordance reads as a uniform "input-trailing icon cluster" when Source is expanded after a result.

**Net effect:** pre-result Ask AI dialog drops from ~146px tall to **~110px tall** — about 36px shorter, roughly 25% denser. Breakdown:
  - Controls strip: 36px → 26px (Generate no longer forcing the 36px button floor)
  - Source textarea: 36px → 44px (bump for the 32px button to sit with 6px top/bottom breathing)
  - Net: −18px on the controls strip + the visual shift of Generate INTO the textarea eliminates the cognitive jump users made between rows

The dialog now reads as one focal input with the Generate icon glued to its bottom-right — the affordance Notion AI inline composer / ChatGPT inline edit / Tiptap AI bubble / Claude inline edit / CKEditor 5 AI Assistant all ship. Ctrl/Cmd+Enter still fires Generate from inside the source textarea (the existing keydown handler is unchanged); post-result the source field stays hidden by default behind the "Edit prompt" corner pill (since v20260513a) and the Regenerate icon comes with it when the user expands the source for retry. Customers who want the prior top-row Generate back can override with three documented CSS rules in the v20260526a comment block at the end of `aitoolkit.css`. Same shape the chat composer pass adopted in v2.0.4.9, now applied to the last AI surface that wasn't already painting it.

`aiToolkitUiVersion` bumped from `v20260525a` to `v20260526a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260526a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline syntax check on `aitoolkit.js` and `all_plugins.js` passes via `node --check`.

## Version 2.0.4.9

### Improved — AI Chat: Send button anchored INSIDE the composer textarea instead of on its own row beneath it, panel height floor dropped from 480 to 420
Fortieth pass on the AI windows. The chat composer was the last surface in the AI dock still laid out as a stacked two-row grid — textarea on top, a 36px Send button row beneath it with an 8px row gap. Every reference chat composer (Claude side-panel, ChatGPT side-panel, Notion AI, Linear command palette, Cursor) places the send affordance INSIDE the textarea container at the bottom-right corner so the composer reads as ONE input box with ONE focus target, not two stacked boxes with two tab stops. The stacked layout was claiming ~52px of vertical chrome on every chat panel render for a control that the references manage in zero extra rows.

**AI Chat composer**
- **Send button moved INSIDE the textarea as an anchored 32×32 round icon button.** `.rte-ai-chat-compose-shell .rte-ai-chat-composer` flips from `display: grid; gap: 8px` to `position: relative; display: block; gap: 0` so the absolutely-positioned button child anchors to the textarea bounding box. `.rte-ai-chat-compose-shell .rte-ai-chat-composer-actions` becomes `position: absolute; right: 6px; bottom: 6px` with `pointer-events: none` on the wrapper so the textarea's empty trailing area stays clickable for focus; the Send button itself restores `pointer-events: auto` so clicks still fire `runChatPrompt`. The button drops to `width: 32px; height: 32px; border-radius: 999px` — its inline label span (`Send` / `Send prompt` / `Send starter` / `Thinking...`) is now visually hidden via a standard `position: absolute; clip: rect(0,0,0,0)` sr-only pattern so the dynamic label stays in the accessibility tree (announced by screen readers via `aria-label`, surfaced on hover via `title`) without forcing the button to a 110px pill shape that would break the bottom-right icon anchor. Same composer shape every modern reference ships — one box, one focus target, one icon affordance at the trailing edge.
- **Textarea `padding-right: 56px` + `resize: none` + `min-height: 44px`.** The 56px right padding reserves the 32px + 6px-offset region for the inline Send button overlay so typed text never slides under the button glyph. `resize: none` kills the bottom-right resize handle that would otherwise sit directly under the Send button (visual overlap + ambiguous drag target). `min-height: 44px` (was 36px after the pass-32 `!important` override at line 12064) gives the textarea a calmer single-line floor while still letting the 32×32 button overlay sit with 6px breathing on top and bottom. `!important` on the three properties beats the earlier `min-height: 36px !important; padding: 9px 12px !important` override block, which would otherwise have left the input at 36px tall with `padding-right: 12px` (text sliding under the button).
- **Chat panel desiredHeight floor dropped from 480/440 to 420/380.** `renderChatPanel` writes the editor-shell `min-height` to track the composer's new floor: composer height ~114px (52 textarea + 8 gap + 36 button + 18 padding) → ~62px (44 textarea + 18 padding), saving ~52px of reserved editor-shell height per render. At 1280px viewport the empty-state chat panel now measures **~392px tall** (was ~480-540px depending on which override cascade won), and the editor shell below the chat panel grows back into the freed space instead of forcing scroll on shorter pages.
- **@900px breakpoint stretch rule overridden.** The earlier `@media (max-width: 900px) { .rte-ai-chat-composer-actions button { flex: 1 1 0 } }` rule made the Send button span the full row at mobile widths. With the inline icon anchor, that stretch would balloon the 32×32 round button to a full-row pill. New media-query override keeps the icon shape at every viewport.

**Net effect:** AI chat panel drops from a ~480-540px floor with a two-row stacked composer to a ~392px floor with the composer reading as a single textarea-with-anchored-Send. Same affordance Claude / ChatGPT / Notion AI / Linear / Cursor composers ship: one input box, one focus target, one send icon at the trailing edge. The `Send` / `Send prompt` / `Send starter` / `Thinking...` dynamic label remains fully accessible via `aria-label` + `title` — mouse users discover it on hover, screen readers announce it on focus — without painting an inline text label that would have killed the inline icon anchor. Customers who want the prior stacked layout back can override with three CSS rules documented inline in the v20260525a comment block at the end of `aitoolkit.css`. The post-pass dialog (Ask AI) is unchanged this pass; the chat panel was the last AI surface still painting a layout shape the references explicitly do not.

`aiToolkitUiVersion` bumped from `v20260524a` to `v20260525a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260525a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: opening AI chat via `execCommand("aichat")` renders a 390×392 panel; composer textarea sits at 44px min-height with `padding: 10px 56px 10px 12px` and `resize: none`; Send button computed as 32×32 round (`border-radius: 999px`) at `right: 6px; bottom: 6px` absolute inside the composer; sendBtn label span clipped to `rect(0,0,0,0)` so the dynamic text stays in the accessibility tree but contributes zero visible chrome; typing "Test prompt" enables the button and rewrites `aria-label` to "Send prompt. Send the custom prompt for the current document." per `buildChatSendActionData`; clicking the empty trailing area of the textarea still routes focus to the textarea (pointer-events: none on the wrapper, pointer-events: auto on the button).

## Version 2.0.4.8

### Improved — AI window: Details disclosure lifted above Apply row so reading order matches result → why → action, "More" toggle drops its text label for an icon-only kebab, chat quick-start chips wrap instead of horizontal-scrolling, mode caption font tightened
Thirty-ninth pass on the AI windows. After v2.0.4.7 landed the labeled scope `<select>` and reverted the mode caption to its discoverability description, a focused audit of the post-result Ask AI dialog against Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, Claude inline edit, and CKEditor 5 AI Assistant surfaced three remaining shapes the dialog still painted that the references do not:

1. **Details disclosure sat BELOW the Apply row.** Since the dialog's first compact pass the rendering order has been: controls → mode caption → text grid → **Apply row** → status → Details toggle → insight grid. Users curious about "why did AI suggest this?" had to scroll past the action buttons before they could open the explanation — exactly the friction Notion AI / Tiptap AI bubble / ChatGPT inline edit explicitly avoid. In every reference inline composer the "Why this change" affordance sits NEXT TO the suggestion, before the accept/reject row, so users read the rationale and then decide. The current order forced the opposite mental loop: see action buttons → click Details to grok rationale → scroll back to apply.
2. **"More" toggle still carried its text label.** Since v20260508t the apply row has had a `[Apply ⋯ More]` toggle pinned just before the (detached) Discard. The button rendered both the kebab glyph AND the literal "More" word, claiming ~70px on the apply row (icon 16 + 4 gap + label ~32 + 18 padding). The kebab glyph is the universal "more options" affordance every modern surface uses (Notion AI inline composer, ChatGPT inline edit, Linear's command palette, Slack message hover, GitHub PR comment) — once a user recognises the dot row the label is redundant. At 440px frame, the ~38px the label was eating could move to the smart-primary Apply button so its label ("Replace selection" / "Apply 3 pending steps" / "Re-run from source") never truncates.
3. **AI Chat quick-start chips horizontally scrolled at narrow widths.** Since v20260513c the chip row has carried `flex-wrap: nowrap; overflow-x: auto`, painting a horizontal scrollbar whenever the 5 default chips (Summarize / Proofread / Translate / Headings / Expand) plus their icons exceeded the panel content width. Hits the moment a customer overrides `aiToolkitChatPrompts` with longer labels, or at the `@900px` breakpoint where the chat panel reflows to full viewport width minus 24px. A scrollbar in a suggestion chip row reads as a desktop affordance users don't expect on what is otherwise a touchpad-style starter strip; same shape Claude side-panel / ChatGPT side-panel ship — chips wrap as needed, no horizontal scroll.

**Ask AI dialog**
- **Details toggle + insight grid moved above the Apply row.** Two `grid.insertBefore(detailsToggle, applyRow)` + `grid.insertBefore(insightGrid, applyRow)` calls right after the insight grid construction reorder the DOM so the rendering becomes: controls → mode caption → text grid → **Details toggle** → **insight grid** → Apply row → status. status stays where it is (below applyRow) so transient feedback ("Apply succeeded", "Generation failed") remains a quiet trailing line. The reordering is structural-only — every `var` reference above still resolves, and every selector / focus-traversal walker that targets `.demo-ai-details-toggle` / `.demo-ai-insight-grid` keeps working without code change. Customers who want the prior order back can override with two `order` rules on `.demo-ai-dialog-grid > *`. Same shape every reference inline composer ships: rationale precedes action.
- **"More" toggle becomes an icon-only square kebab.** `setReviewV2ButtonContent(applyRowMoreToggle, "more", "More")` → `setReviewV2ButtonContent(applyRowMoreToggle, "more", "")` and `is-icon-only` class added at construction. New CSS rule `.rte-panel-aiassist .demo-ai-apply-row .demo-ai-apply-more-toggle.is-icon-only { flex: 0 0 28px; min-width: 28px; width: 28px; padding-left: 0; padding-right: 0; justify-content: center }` plus `.rte-ai-review-v2-action-icon { margin: 0 }` so the glyph centres without the label's leading gap. The button drops from ~70px wide to 28px, freeing ~38px on the apply row for the smart-primary Apply label. `aria-label="More apply options"` is set explicitly so the affordance is still announced to screen readers (the title-attribute fallback works for mouse users on hover). Same proportion the Generate row's icon-only Copy button uses (since v20260508s), so the dialog's icon-only vocabulary stays uniform.
- **Mode caption font 11px / 1.35 → 10.5px / 1.3.** Pre-pass the caption claimed ~15px tall as a single-line clamped row. At 10.5px / 1.3 it claims ~14px, reading as a tighter status whisper rather than a substantive caption — same proportion the Generate button's kbd-hint chip uses (10px / muted), keeping the dialog's micro-typography coherent. Saves ~2px on every pre-result dialog render. Mode descriptions can still wrap to a second line on narrower viewports because the `text-overflow: ellipsis` clamp is intact.

**AI Chat panel**
- **Quick-start chip row wraps instead of horizontal-scrolling.** `.rte-ai-chat-quick-row` flips from `flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden` to `flex-wrap: wrap`; the `padding-bottom: 2px` reserve stays (no-op in the new flow but kept so any customer skin that relied on it doesn't break). The row now grows by 1-2 chip heights on narrow viewports or with longer customer-defined `aiToolkitChatPrompts`, rather than hiding chips behind a scrollbar a chat-style surface doesn't expect.

**Net effect:** post-result Ask AI dialog now reads top-to-bottom as result → (optional) Why → Apply, matching the affordance every reference inline composer (Notion AI / Tiptap AI bubble / ChatGPT inline edit / Claude inline edit / CKEditor 5 AI Assistant) ships. Users curious about rationale never have to scroll past the action buttons to find the disclosure. The apply row drops ~38px of horizontal real estate as the More toggle shrinks from labelled-button to icon-only kebab, so the smart-primary Apply label ("Replace selection", "Apply 3 pending steps", etc.) gets back the breathing room it needs at 440px frame width — no truncation, no two-row wrap. The chat side-panel's quick-start chips now reflow at narrow widths so users see all five starter prompts at a glance instead of scrubbing through a horizontal scrollbar. Pre-result Ask AI dialog drops a further **~2px tall** to ~146px (was 148px) on the mode-caption micro-typography retune; post-result dialog unchanged numerically but reads with the rationale disclosure now anchored above the action row. Every surface that survived this pass aligns more tightly with the five reference AI surfaces the codebase has been measuring against. Numeric / display / DOM-order overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260523b` to `v20260524a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260524a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green.

## Version 2.0.4.7

### Improved — AI window: scope toggle becomes a labeled `<select>` with live char counts, mode caption reverts to the mode description, action-field flex retuned so the controls strip still fits one row at 440px
Thirty-eighth pass on the AI windows. After v2.0.4.6 landed the scope+count caption swap, the dialog-level Ctrl+Enter apply, and the smart-primary "More" toggle hide, a follow-up usability audit of the pre-result Ask AI dialog against Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, Claude inline edit, and CKEditor 5 AI Assistant surfaced one remaining shape the dialog still painted that the references do not:

1. **Source-scope segmented control was icon-only.** Since v20260509j the Selection / Document scope toggle has been two 32×32 icon-only buttons (cursor-T glyph + document glyph). The glyphs alone read as decorative chrome to first-time users — none of Notion AI / ChatGPT inline edit / Tiptap AI bubble / Claude inline edit / CKEditor 5 AI Assistant ship icon-only scope controls; all five use a labeled picker (select or chip-with-label) so users see the scope name and live size at a glance. The v20260522a mode-caption swap to "Document · 117 chars" was effectively a workaround for the icon-only ambiguity — surfacing scope+count in a SECOND row because the first row's icons couldn't carry the label.

**Ask AI dialog**
- **Scope segmented control replaced with a single labeled `<select>`.** New `scopeSelect` element with option labels `"Selection · 124 chars"` / `"Document · 2.4K chars"` (and `"Selection (none)"` when there is no live selection, with the option `disabled` so users can't pick it). The closed-state label shows the active scope AND its live char count in one glance, mirroring the affordance Notion AI's inline composer ships as a labeled chip. `syncDialogScopeUi` now writes both `scopeSelect.value` and the option labels (via `formatDialogScopeCount` for K-abbrev at >999 chars), so the picker stays in sync as the editor selection / document size changes. `scopeSelect.onchange` routes through the same `handleDialogScopeChange(scope)` path the old icon buttons used; `setBusyState` flips `scopeSelect.disabled` while AI is running. The original `loadSelectionButton` / `loadDocumentButton` stay in the DOM under `.demo-ai-scope-row.is-detached` so external integrations that walk `dialog.querySelector('[data-rte-ai-dialog-action="load-selection"]')` keep resolving; a CSS rule hides the row. Customers who want the icon segmented control back can override `.demo-ai-scope-select { display: none }` + `.demo-ai-scope-row.is-detached { display: flex !important }`.
- **Mode caption reverts to the mode description as its single signal.** With scope+count now living in the closed-state label of the scope select, the caption row no longer needs to double as a scope summary. `updateModeHelp()` writes the mode description directly (`modeHelp.innerText = helpText`) instead of preferring the `buildModeContextSuffix()` output, so the pre-result caption reads as "Clean up grammar, spacing, and readability issues." on every render — the discoverability hint Notion AI / CKEditor 5 AI Assistant ship below their action picker. Post-result the caption stays hidden via the existing `.demo-ai-dialog-grid:not(.is-pre-result) .demo-ai-mode-caption { display: none }` rule. `buildModeContextSuffix` is still defined (and still wires through the detached modeChip node) so any external integration that reads it continues to resolve. Caption clamp loosened from 1-line ellipsis to 2-line block since mode descriptions can run wider than the v20260522a scope+count summary they replaced.
- **`.demo-ai-action-field flex grow 2 → 1`, scope-field `flex: 0 0 auto / 72px` → `flex: 0 1 130px / 110px`.** With scope swapping from a 72px icon segmented control to a 130px labeled select, the controls strip total content needs ~424px at the 440px dialog frame width. Earlier `flex: 2 1 150px` on Action let it grow aggressively and eat every spare px in the row; combined with the wider scope field, the run-field wrapped to a second row (controls strip 36 → 65px). Cutting Action's grow factor to 1 keeps the row at exactly 36px and lets Action take its basis (~140-150px) plus an equal share of slack. At the 440px dialog frame the row fits one line: Action 148 + Scope 130 + Run 130 + 2×8 gaps = ~424px content.

**Net effect:** the scope picker finally carries the scope label AND the live char count in one place — users see WHAT AI will run on and HOW BIG it is in a single glance without having to read a separate mode-caption row. The mode caption pre-result reverts to its original discoverability role (what each AI mode actually does), the role v20260518b designed it for. Pre-result dialog stays at **148px tall** (no change), post-result at **176px tall** (no change) — the swap is a clarity win, not a size win, but it pays off the v20260522a workaround that was using the caption row to compensate for the icon-only scope ambiguity. Every surface that survived this pass aligns more tightly with the five reference AI surfaces the codebase has been measuring against. Numeric / display / element-type overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to understand.

`aiToolkitUiVersion` bumped from `v20260522a` to `v20260523b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260523b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: pre-result dialog renders the scope select with options `["Selection (none)" (disabled), "Document · 117 chars"]`; opening with a 11-char editor selection populates the scope select with `["Selection · 11 chars", "Document · 117 chars"]` and auto-selects `"selection"`; toggling the select to "Document" repopulates the source textarea with the document text and routes through `handleDialogScopeChange("document")`; pre-result caption reads "Clean up grammar, spacing, and readability issues."; post-result Generate fires, result textarea fills, apply row shows the smart-primary "Replace doc" at 26px, dialog total height 176px.

## Version 2.0.4.6

### Improved — AI window: mode caption swaps to scope+count signal, Ctrl+Enter applies from anywhere in the post-result dialog, More toggle hidden when smart-primary recommendation is already promoted
Thirty-seventh pass on the AI windows. After v2.0.4.5 landed the source/result textarea `rows=1` fix (CSS `min-height` floors finally binding), a focused usability audit of the Ask AI dialog against Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, Claude inline edit, and CKEditor 5 AI Assistant surfaced three remaining shapes the dialog still painted that the references do not:

1. **Mode caption painted the description prefix on every render.** Since v20260519a the caption read "Clean up grammar, spacing, and readability issues. · On selection, 124 chars" — readable, but the mode description was already shown in three other places by the time the user had loaded text: the dialog frame header ("Ask AI · Proofread"), the Action dropdown's selected option, and the source textarea placeholder ("Paste text to proofread…"). Painting it a fourth time inside the caption was just visual noise. The scope+scale info ("Selection · 124 chars") is the unique signal the caption can carry that nothing else does — "what AI will run on, and how big it is". Same idiom Notion AI / ChatGPT inline edit ship: the secondary line below the action name names the input, not the action.
2. **Ctrl/Cmd+Enter only fired Generate, never Apply.** The only Ctrl+Enter binding lived on the source textarea (since v20260508s, where it triggered Generate). Once a result lands, users typically tab away from source to read the result — pressing Ctrl+Enter from anywhere else in the dialog used to do nothing. None of Notion AI / ChatGPT inline edit / Tiptap AI bubble / Claude inline edit ship this asymmetric shortcut: in all four references Ctrl+Enter accepts the AI suggestion from anywhere inside the suggestion surface.
3. **More toggle stayed visible when the smart-primary alt-recommendation had already been promoted.** v20260520a's smart-primary path hides the disabled default Apply and surfaces the recommended alt (typically "Replace doc" when there's no selection) as the visible primary. But the "More" toggle next to Discard remained visible — clicking it would reveal only the now-redundant default Apply primary plus alternates the recommendation had already resolved. A cluttered second tier of choices the user has already been told the answer to.

**Ask AI dialog**
- **Mode caption swaps to scope+count signal once text is loaded.** `buildModeContextSuffix()` now returns `"Selection · 124 chars"` / `"Document · 2.4K chars"` / `"Typed prompt · 69 chars"` (no leading " · " separator, no "On " prefix) — `updateModeHelp()` uses the suffix as the entire caption when non-empty, falling back to the mode description only on fresh-open with no source loaded (the discoverability case). Reduces the caption from ~60-90 chars to ~20-25 chars on the common path. The mode description is still surfaced via the Action select's `title` attribute on hover for users who want it. Captions narrower than the dialog never wrap and the v20260519a single-line clamp is no longer strained.
- **Dialog-level Ctrl/Cmd+Enter triggers Apply when focus is outside the source textarea.** New `dialoginner.addEventListener("keydown", ...)` finds the active primary apply button — `applyPlanButton` when enabled, else the alt-recommended primary the `is-alt-recommended` class surfaces — and clicks it. Short-circuits when `event.target === sourceArea` so the source-edit + regenerate flow (the existing v20260508s sourceArea binding) is unchanged. `applyPlanButton` gains `title="Apply AI suggestion (Ctrl+Enter)"` + `aria-keyshortcuts="Control+Enter"` so the shortcut is discoverable on hover and announced by screen readers, mirroring the Generate button affordance pattern (since v20260509j).
- **`syncDialogActionButtonAccessibility` now preserves the `(Ctrl+Enter)` suffix when it rewrites a button's title.** The dynamic title rewrite that fires after each action-state change was dropping the shortcut hint Apply (and Generate, if the same path ever reaches it) had at creation time. The helper now reads each button's `aria-keyshortcuts` attribute and appends a normalised `(Ctrl+Enter)` / `(Cmd+Enter)` suffix to the title parts. Mouse users discover the shortcut on hover from any post-result dialog state.
- **More toggle hidden when `is-alt-recommended` is active.** CSS-only — `.rte-panel-aiassist .demo-ai-apply-row.is-alt-recommended .demo-ai-apply-more-toggle { display: none !important }`. The recommendation IS the visible primary, so the alternates the toggle would reveal are redundant. The toggle reappears the moment `is-alt-recommended` drops (typically when the user selects text in the editor, which re-enables `applyPlanButton` and unhides the original primary). Customers who want the toggle to stay visible can override the rule with `display: inline-flex !important`.
- **Mode caption visual weight pulled down (11px / #475569 → 10.5px / #6b7d90).** With the caption now carrying ~22 chars instead of ~70+, it can drop to a quieter muted annotation — same proportion the Generate button's kbd-hint chip uses (10px / muted). Same role too: a tiny status whisper, not a substantive caption.

**Net effect:** pre-result Ask AI dialog caption drops from "Clean up grammar, spacing, and readability issues. · On document, 117 chars" (~76 chars) to "Document · 117 chars" (~20 chars) — about 74% shorter, and the caption now reads at a glance as the scope summary it always wanted to be. Ctrl+Enter works as the apply shortcut from anywhere in the post-result dialog (verified live: focus the result textarea, press Ctrl+Enter → smart-primary action fires, dialog closes), matching the keyboard idiom every reference inline composer ships. Post-result Apply row drops one redundant button when the smart-primary recommendation is already in the visible primary slot, so the row reads as "[primary action] [Discard]" instead of "[primary] [More] [Discard]". Every surface that survived this pass aligns more tightly with the five reference AI surfaces the codebase has been measuring against. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260520b` to `v20260522a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260522a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: pre-result caption renders "Document · 117 chars" at 10.5px (was the full 60+ char description + scope at 11px); Apply button title carries the "(Ctrl+Enter)" suffix through dynamic relabels ("Apply - Run the prepared AI plan directly in the editor. - (Ctrl+Enter)"); aria-keyshortcuts="Control+Enter" survives action-state syncs; dispatching `Ctrl+Enter` from the readonly result textarea fires the alt-recommended primary ("Replace doc" when there's no selection) and closes the dialog; More toggle is hidden whenever `.demo-ai-apply-row` carries `is-alt-recommended`.

## Version 2.0.4.5

### Improved — AI window: dialog source/result textareas honor CSS min-height floor (rows=1), pre-result -12px tall, post-result -11px tall
Thirty-sixth pass on the AI windows. After v2.0.4.4 landed the smart-primary alt-recommendation promotion + controls row -4px + result inset -3px, a live measurement at desktop 1265x900 surfaced one remaining inefficiency: the Ask AI dialog's `sourceArea` and `resultArea` textareas still inherited the browser default `rows=2` attribute, which sized each textarea to ~47-67px (2 visible lines @ 13px font / 18.85px line-height + padding + border) regardless of the carefully-tuned CSS `min-height` floors set in v20260513a (36px source, 52px result). Same shape v20260518a (v2.0.4.1) fixed for the AI Chat composer textarea, where browser default `rows=2` was overriding the 36px CSS floor and rendering the composer as a 50-ish px paragraph box. The Ask AI dialog never got the matching one-line `composerArea.rows = 1` treatment until now.

1. **Source textarea pre-result sat at ~47px, not the 36px CSS floor.** `.demo-ai-source-field textarea { min-height: 36px }` (since v20260513a) was being overridden by the textarea's default `rows=2` attribute, which the browser sizes to ~47px (2 × 18.85 line-height + 8 padding + 2 border) — taller than the CSS floor. First-time users opening Ask AI saw an 11px-too-tall input that read as a 2-row paragraph box, when most prompts are a single sentence ("Make this clearer", "Translate to Spanish"). Same shape the chat composer fixed in v2.0.4.1.
2. **Result textarea post-result sat at ~62-67px, not the 52px CSS floor.** `.demo-ai-result-field textarea { min-height: 52px }` (since v20260513a, intended to hold ~3 long lines of AI output) was similarly overridden by default `rows=2` + the 21/4 padding for the corner pill inset + 2px border, sizing the field to ~62-67px even when the AI returned a one-sentence proofread. The result textarea grew to ~67px and back the moment the AI output landed — visible churn the user shouldn't see.

**Ask AI dialog**
- **`sourceArea.rows = 1`.** Set immediately after the textarea is appended so the CSS `min-height: 36px` floor is the binding constraint. Source still grows freely as the user pastes long text (textarea `resize: vertical` is on, no max-height set) and `refreshSource()` writes multi-line content directly via `.value`. Pre-result Ask AI dialog drops from **161px tall to 149px tall** at desktop 1265x900 (measured live: source textarea 47px → 36px, grid 105px → 93px, inner dialog 161px → 149px).
- **`resultArea.rows = 1`.** Set immediately after the readonly textarea is appended so the CSS `min-height: 52px` floor (the intended 3-line content reserve) is the binding constraint. With rows=1 the textarea content area is 1 × 18.85 line-height + 21/4 padding + 2 border = ~46px which is < 52px floor, so the floor wins. Post-result Ask AI dialog drops from **~187px tall to 176px tall** at desktop 1265x900 (measured live: result textarea 62-67px → 52px, inner dialog ~187 → 176).

**Net effect:** every Ask AI dialog drops **~11-12px tall** — the pre-result dialog goes from 161px to 149px (~7% denser) and the post-result dialog goes from ~187px to 176px (~6% denser). The textareas now read as one calm input that grows on type (the shape every modern AI composer ships: Notion AI ~36px min, Claude ~38px, ChatGPT ~44px, Tiptap ~36px), matching the affordance the chat composer adopted in v2.0.4.1. Customers who want the prior 2-row reserve back can override with `sourceArea.rows = 2; resultArea.rows = 2;` in a `loaded` handler. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just denser and easier to scan.

`aiToolkitUiVersion` bumped from `v20260520a` to `v20260520b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: pre-result source textarea renders at exactly 36px (was 47px), pre-result inner dialog 149px (was 161px); post-result result textarea renders at exactly 52px (was 62-67px), post-result inner dialog 176px (was ~187px).

## Version 2.0.4.4

### Improved — AI window: smart-primary recommendation always names a doable next step, controls row -4px, result inset -3px, chat empty hint links to chips
Thirty-fifth pass on the AI windows. After v2.0.4.3 landed the dialog frame title + mode caption scope/scale + result-head corner pill, a focused audit of the post-result Ask AI dialog with **no editor selection** (the common case when users open Ask AI from the toolbar AI button without first selecting text) surfaced one usability dead-end the prior 34 passes had not addressed:

1. **Smart-primary recommendation could land on a disabled button.** When the dialog opens with no selection and a selection-dependent operation is the resolved plan, `applyPlanButton` relabels itself to "Select text to preview" / "Select text to replace" AND stays `disabled=true` (because `actionState.canApplyPlan` is false). The button keeps its `is-primary` class — a faded blue button that reads as the intended primary but cannot be clicked. Meanwhile the actually-actionable alternatives (Replace doc, Insert below) sit hidden behind the `More` toggle even though `syncDialogRecommendedActionUi` correctly tags `acceptDocumentButton` with `data-rte-ai-dialog-recommended="true"`. First-time users see a disabled blue button as the sole visible primary, a "More" toggle, and no clear next step — a dead-end. Same shape Notion AI / ChatGPT inline edit / Tiptap AI bubble explicitly avoid: their primary always names a doable next step.
2. **Compact controls row reserved 4px of unused vertical chrome.** `.demo-ai-compact-controls` carried `padding: 2px 0` since v20260509j — the 2px top/bottom were reserving 4px around a flex row whose tallest child (the 36px Generate button with its kbd hint chip) already provides natural breathing room. With every other pre-result row tightened, the controls strip was the last 4px of fat on the pre-result dialog.
3. **Result textarea top inset reserved 3px more than the corner pill needed.** Since v20260519a the result textarea has carried `padding-top: 24px` to clear the absolute-positioned `Edit prompt` corner pill. Live measurement showed the pill at top:4 / height:17 = 21px reach; the 24px reserve was 3px over.
4. **Chat empty-state hint named only the composer, not the quick-start chips.** The single hint line read "Ask anything about the current document." — a clear pointer at the composer below but zero signal about the unlabeled row of small label-only pills directly above it. First-time users read the chips as decorative tags rather than one-click starters.

**Ask AI dialog**
- **Smart-primary recommendation promotes the alt action to visible primary when Apply is disabled.** New `is-alt-recommended` class toggled on `.demo-ai-apply-row` inside `syncDialogRecommendedActionUi` when the recommended action is NOT `apply` AND `applyPlanButton.disabled === true`. CSS uses the class to (a) override the `:not(.is-show-more)` hide rule on the recommended button so it shows in the visible row, (b) re-skin it with the primary blue gradient + box-shadow + 140px flex-basis, and (c) hide the disabled Apply primary so the recommendation is the unambiguous focal action. Post-fix the post-result-no-selection dialog shows "Replace doc" (blue, enabled) as the visible primary instead of "Select text to preview" (blue, disabled). Same shape Notion AI / ChatGPT inline edit / Tiptap AI bubble ship — the visible primary always names a doable next step. Customers who want the prior behavior back can override the four `.is-alt-recommended` CSS rules.
- **Compact controls row -4px tall.** `.demo-ai-compact-controls { padding: 2px 0 → 0 }`. The row drops from ~40px tall to ~36px (the natural floor of the Generate button). Saves 4px on every dialog render.
- **Result textarea top inset -3px.** `.demo-ai-result-field textarea { padding-top: 24px → 21px }`. The corner pill clearance is now exact (17px pill + 4px top = 21px); no extra reserve above. Saves 3px on every post-result dialog.

**AI Chat panel**
- **Empty-state hint links the composer AND the quick-start chips.** The single `.rte-ai-chat-empty-detail` line grows from "Ask anything about the current document." (32 chars) to "Ask anything about the current document — or tap a quick start above." (52 chars). When `aiToolkitChatPrompts` is empty the suffix is suppressed via a `hasQuickStarts` guard so the hint never lies about a chip row that does not exist. Zero added chrome — the existing single text line stretches one extra wrap line at the 320px max-width set by the existing CSS rule, on most viewports stays on one line. Same idiom Claude side-panel / ChatGPT side-panel ship — the empty hint names every clickable affordance on the panel.

**Net effect:** post-result-no-selection Ask AI dialog now reads as a clear action card — the visible primary is "Replace doc" (blue, enabled) instead of "Select text to preview" (blue, disabled), so users see a single doable next step instead of a disabled dead-end. Every Ask AI dialog drops **~7px tall** (4px from the controls row, 3px from the result inset) so the pre-result dialog goes from ~165px to ~158px tall and the post-result dialog goes from ~196px to ~189px tall. The AI Chat empty state now names both the composer and the quick-start chips in one line of plain text, so first-time users see the chips as actionable starters rather than decorative tags. Every surface that survived this pass aligns more tightly with the four reference AI surfaces the codebase has been measuring against. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260519a` to `v20260520a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260520a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green.

## Version 2.0.4.3

### Improved — AI window: dialog title reflects active mode, mode caption carries source scope + char count, result-head collapses to absolute corner pill
Thirty-fourth pass on the Ask AI dialog. After v2.0.4.2 landed the visible mode caption + mode-aware prompt placeholder + Generate ⌘↵ hint, a side-by-side audit of the pre-result Ask AI dialog (Proofread / Rewrite / Translate at desktop 1280×900) against Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, Claude inline edit, and CKEditor 5 AI Assistant surfaced three remaining shapes the dialog still painted that the references don't:

1. **Dialog frame title was static "Ask AI" regardless of active mode.** The dialog header has read "Ask AI" since v20260508 even though the Action dropdown immediately below the title cycles between Proofread / Rewrite / Translate / Justify / Shorten / Expand / Summarize. First-time users opening the dialog from Proofread / Justify / Summarize had to read TWO pieces of info — the static frame title "Ask AI" plus the Action dropdown "Proofread" — to know what the dialog was about to do. Same idiom Notion AI's slash menu / Linear's command palette / Raycast ship — the surface title names the active command, not the surface category.
2. **Mode caption carried no source-scope or scale signal.** Since v20260518b the pre-result caption has named what AI would do ("Clean up grammar..."). But it carried no signal about WHAT IT WOULD RUN ON — the icon-only Selection / Document scope toggle is muted and easy to overlook, and the char count was nowhere, so users couldn't tell a 24-char snippet from a 2400-char document was about to be processed. Same affordance ChatGPT inline edit / Claude Artifacts ship — a tiny scope+scale label next to the action name.
3. **Result head row carried only a right-pinned "Edit prompt" disclosure** — a ~22px-tall row whose only child sat against the right wall, leaving a wide empty patch on the left that read as "did the layout break?" for first-time users (the v20260513d "Result" eyebrow drop was correct on noise grounds, but left the row visually unbalanced). None of the five reference inline composers ship a dedicated row above the result; they float the retry / edit affordance as a corner pill anchored to the response card.

**Ask AI dialog**
- **Dialog frame title now mirrors the active mode.** New `updateDialogFrameTitle(modeLabel)` reads the active mode from `modeSelect.value` and rewrites the `<rte-dialog-header-text>` span (rendered by `__UI_CreateDialogFrame` in `rte.js`) to "Ask AI · Proofread" / "Ask AI · Translate (Spanish)" / etc. Translate appends the active language in parens so users see the actual target without having to look at the second dropdown. `dialoginner` `aria-label` is also re-synced so screen readers announce the same name the visible title carries. Called from `updateModeHelp()` so the title tracks every mode / language change automatically. Zero vertical cost — rides on the existing dialog header.
- **Mode caption gains a source-scope + char-count tail.** New `buildModeContextSuffix()` reads the active scope (`dialoginner.__aiSourceScope`), the snapshot, and the live `sourceArea.value` to compute "· On selection, 124 chars" / "· On document, 2.4K chars" / "· On typed prompt, 69 chars" — the last variant fires when the user has detached from editor selection and typed directly into the source textarea, so they know the scope toggle won't overwrite their input unless they hit reload. Char counts >999 abbreviate to K with one decimal. Suffix is recomputed on mode change, language change, scope toggle, source-input, AND live editor sync (refreshSource / handleDialogSourceEdit / syncDialogFromEditor all now call `updateModeHelp()`). Hidden post-result via the existing `.demo-ai-dialog-grid:not(.is-pre-result) .demo-ai-mode-caption { display: none }` rule (since v20260518b). Zero vertical cost — rides on the existing caption row.
- **Result-head row collapses to an absolute corner pill.** CSS-only — the `.demo-ai-result-head` element flips from a flow row above the result textarea to `position: absolute; top: 4px; right: 4px` anchored inside `.demo-ai-result-field` (which gains `position: relative`). The "Edit prompt" toggle becomes a compact pill (white-at-94% background, 999px border-radius, 4px backdrop blur, 2/8px padding) floating in the result textarea's top-right corner. Result textarea gains `padding-top: 24px` so the first line of AI output never collides with the pill. **Saves ~18px tall on every post-result dialog** — the result reads as the final word, with the retry affordance available where users expect it (Notion AI / ChatGPT inline edit / Claude inline edit all ship the same corner-pill pattern). Earlier flow-row layout can be restored with a one-rule override (`.demo-ai-result-head { position: static !important }`).
- **`populateDialogModes` defensively initializes the select.** Two new guards: 1) after appending options, if `modeSelect.selectedIndex < 0` it's forced to 0 so the default first option is the value of the select; 2) inside `openDialog`, the `options.presetMode` assignment is now validated against the actual option values list — an unknown preset (e.g. "open-dialog", the action id the "Draft" menu item passes via `runQuickAction`) is a no-op instead of silently unselecting every option. Fixes a pre-existing bug that became user-visible with the new title / caption logic: opening the dialog from the "Draft" menu item left `modeSelect.value === ""` and the title stayed "Ask AI" / the caption showed only the generic fallback help text.

**Net effect:** pre-result Ask AI dialog now names both the active command AND the source scope at-a-glance, with zero added vertical chrome. The dialog frame title and the Action dropdown reinforce the same signal, killing the "what am I about to run?" two-place lookup. Post-result the dialog drops ~18px tall as the redundant result-head row collapses into a corner pill anchored to the result textarea — same shape every reference inline composer ships. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to use.

`aiToolkitUiVersion` bumped from `v20260518b` to `v20260519a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260519a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: dialog title reads "Ask AI · Proofread" on fresh-open and updates to "Ask AI · Translate (Spanish)" on mode-switch; caption suffix renders "· On document, 117 chars" pre-result and flips to "· On typed prompt, 69 chars" when source is detached + typed; result-head collapses to a 90×17px pill at top:4/right:4 of the result textarea post-result.

## Version 2.0.4.2

### Improved — AI window: visible mode caption, mode-aware prompt placeholder, Ctrl+↵ hint baked into Generate, "Ready · X" status echo suppressed
Thirty-third pass on the Ask AI dialog. After v2.0.4.1 landed the chat panel chip / composer / min-height compaction, a side-by-side audit of the post-result Ask AI dialog against Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, Claude side-panel, and CKEditor 5 AI Assistant surfaced four remaining shapes the dialog still painted that the references don't:

1. **Mode descriptions hidden in `title` attribute.** Since v20260508s the per-mode help text ("Clean up grammar, spacing, and readability issues." / "Prepare a translated version in a target language…") has only lived on `modeSelect.setAttribute("title", helpText)` — a hover-only tooltip. First-time users opening Ask AI with Proofread / Justify / Summarize selected had no visible signal for what each mode would do; they had to either pick it and run, or hover the dropdown for ~600ms to surface the tooltip. Same friction Notion AI / CKEditor 5 AI Assistant close with a one-line caption rendered directly below the mode picker — caption is the cheapest discoverability win in the AI-window genre.
2. **Source textarea placeholder generic ("Type or paste text…").** Since v20260515e the placeholder has been mode-agnostic — Proofread / Rewrite / Translate / Summarize all show the same prompt. Notion AI's inline composer adapts its placeholder per active command ("Tell AI what to write…" → "Improve writing for…" → "Translate to…"); the generic version was a missed near-zero-cost signal about what the active mode will produce.
3. **Ctrl/⌘+Enter shortcut only in `title` + `aria-keyshortcuts`.** Mouse users discovered the keyboard shortcut only by hovering the Generate button for ~600ms; keyboard navigators only via screen-reader announcement of the WAI-ARIA `aria-keyshortcuts`. Same shape ChatGPT composer ("Send ⌘↵") and Linear's command palette ship — a tiny muted chip baked into the trailing edge of the primary action surfaces the shortcut at-a-glance for every user.
4. **Status row echoed the smart Apply label as "Ready · Replace doc".** Since v20260508s the apply row's primary button has been a smart primary that mirrors the recommended action (e.g. "Replace doc", "Replace selection", "Apply 3 pending steps"). After every successful Generate, `setBusyState(false, getDialogReadyStatusMessage(...))` wrote the same recommendation into the status row below — surfacing the same fact twice (once on the button, once on a 17px row right below it). None of the four reference inline composers ship a "ready" status echo: Notion AI / ChatGPT inline edit / Tiptap AI bubble all let the primary button alone declare the next step.

**Ask AI dialog**
- **Mode caption attached to the dialog grid as a visible 1-line row** under the controls strip. The earlier detached `modeHelp` div (kept since v20260508 for downstream `modeHelp.innerText` callers) is now appended directly into the grid with a new `.demo-ai-mode-caption` class. Pre-result the caption is the only signal users have for what the active mode does (no prompt text, no result yet), so it takes a touch more visual presence (`color: #475569`); post-result the caption is hidden via `.demo-ai-dialog-grid:not(.is-pre-result) .demo-ai-mode-caption { display: none }` because the result textarea IS the answer and the caption becomes redundant. Caption clamps to a single line via `white-space: nowrap; text-overflow: ellipsis;` so it never wraps the dialog taller. **Adds ~15px tall pre-result** (the cost of discoverability — every Proofread / Justify / Summarize first-timer now reads what they're about to run), **0px post-result** (caption hidden once the result lands).
- **Mode-aware source placeholder.** `updateModeHelp()` now also rewrites `sourceArea.placeholder` per active mode: Proofread → "Paste text to proofread…", Rewrite → "Paste text to rewrite…", Translate → "Paste text to translate…", and so on for shorten / expand / summarize / justify / explain. Placeholder only updates when the source area is empty (the user hasn't started typing yet) so we don't fight a typed prompt with a placeholder rewrite. Same idiom Notion AI's inline composer ships per-command. Zero vertical cost.
- **Keyboard-shortcut chip baked into the Generate button.** New `.demo-ai-kbd-hint` span injected after the button label, showing "⌘⏎" on Mac (detected from `navigator.platform` at dialog open) and "Ctrl ⏎" everywhere else. The chip styles to a muted 10px pill (white at 18% alpha on the blue primary background) so it never out-shouts the action label. `setReviewV2ButtonContent` clears the button textContent each time it relabels (Generate → Regenerate, then back), so the hint is re-attached via a tiny `refreshRunKbdHint()` helper after every relabel. Hidden when the button enters its "Thinking..." busy state to avoid a spinner + label + kbd 3-element cluster. Zero vertical cost — chip rides on the existing primary button.
- **"Ready · X" status echo suppressed.** Post-result the `setBusyState(false, ...)` call now passes an empty string when the resolver returned content (`setBusyState(false, resultArea.value ? "" : "No suggestion returned.")`), letting the existing `.demo-ai-dialog-status:empty { display: none }` rule (since v20260509j) collapse the row entirely. The status row still surfaces actual signal: stale-plan warnings (markPlanStale), resolver errors, and "No suggestion returned." when the model produced empty output. **Saves ~17px tall on every successful post-result dialog** — the apply row's smart primary is the single source of "what to do next".
- **Auto-focus lands on the prompt textarea when source is empty.** The earlier baseline (since v20260515e) focused the mode dropdown when no source was loaded — but the user has already picked a mode by the time the dialog opens (via the AI menu they just clicked or the openDialog API), so what they actually want next is to start typing. Focus now lands on the source textarea so the caret blinks in the empty input the user is about to fill. Same shape Notion AI inline composer / ChatGPT inline edit / Tiptap AI bubble use. Mode select is still one Shift+Tab away.

**Net effect:** post-result Ask AI dialog drops from ~206px tall to ~196px tall (the 17px status echo gone outweighs new chrome; caption hidden post-result so the dialog doesn't pay for the caption when the result is what to read). Pre-result Ask AI dialog grows ~9px (the caption is the only cost) — paid back by removing every first-time user's "what does Proofread / Justify / Summarize actually do?" moment, with the mode-aware placeholder reinforcing the same context inside the empty prompt textarea. Generate / Regenerate now ships a visible keyboard hint at the trailing edge of the primary action, so the shortcut is discoverable without hovering for the title tooltip. Every surface that survived this pass aligns more tightly with the five reference AI surfaces the codebase has been measuring against. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just clearer and easier to scan.

`aiToolkitUiVersion` bumped from `v20260518a` to `v20260518b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260518b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green. Live verification at `localhost:5099/Demo/ai-toolkit.aspx` confirmed: caption visible pre-result (15px tall), hidden post-result; placeholder updates per mode (proofread/rewrite/translate/summarize/shorten); kbd chip renders "Ctrl ⏎" on Windows / "⌘⏎" on macOS; status row 0px after a successful Generate.

## Version 2.0.4.1

### Improved — AI window: chat quick chips drop icons to fit one row, composer textarea single-row at rest, chat panel min-height pulled down
Thirty-second compaction pass on the AI windows. After v2.0.4.0 landed the Ask AI Details disclosure hide + chat-status `:empty` collapse + Send right-align, a side-by-side audit of the EMPTY-STATE AI Chat panel against the same four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced three remaining shapes the chat panel still painted that the references do not. Live measurements at desktop 1280x900 showed the empty-state chat panel at **572px tall** — ~60-90px taller than the references at the same 390px width:

1. **Quick-start chips wrap to TWO rows.** Each chip rendered `icon + label` via `setReviewV2ButtonContent` — 12px icon + 4px gap + label + 18px horizontal padding = ~85-90px each. The five default chips (Summarize / Proofread / Translate / Headings / Expand) need ~450px of inline space, but the chat-panel quick-row only has ~364px to play with at the 390px panel width. The v20260512a changelog claimed the chips fit on one row "by dropping height 28 -> 24 and padding 12 -> 9", but icons were added back into the chip render at some point after that pass, so the chips wrap again now — measured live: quick shell is **54px tall** instead of the 24px the chip-height was tuned for. None of Notion AI's slash menu / Claude side-panel starters / Linear's command palette quick-prompts paint an icon on each chip — they are label-only pills. The icon is also redundant: the chip label itself ("Summarize", "Proofread") names the action better than a generic glyph.
2. **Composer textarea min-height 52px + padding 13/14px.** At rest the composer reserves ~80px (52 textarea + 6 padding + 22 row gap + button row) before the user types anything. Reference composers ship a single-line composer that grows: Notion AI 36px min, Claude ~38px, ChatGPT ~44px, Tiptap ~36px. The composer was reading as a 3-row paragraph box, not a one-line input — the user's eye landed on the "ask anything" textarea as if it expected a multi-paragraph prompt, when most real chat turns are a single sentence.
3. **Chat panel min-height 520/660 floor.** Set in JS as `desiredHeight = window.innerWidth <= 900 ? 560 : 660` then floored by `Math.max(shell.offsetHeight, desiredHeight)`. With the compactions above the empty panel measures ~480px tall — the 520-660 floor was over-reserving height for a panel whose contents end at the composer. Same proportion every reference side-panel ships: Notion AI ~480px, Claude ~520px, ChatGPT ~560px tall by default.

**AI Chat panel**
- **Quick-prompt chips: per-chip icon suppressed.** CSS-only — `.richtexteditor .rte-ai-chat-quick-button.is-chip .rte-ai-review-v2-action-icon { display: none !important }`. The icon `<span>` node stays in the DOM so any external integration that walks `.rte-ai-review-v2-action-icon` inside chips keeps resolving without a guard. The label span's left-gap is also pulled to 0 so the chip becomes a flat label pill. With icons gone plus an additional 4px of horizontal padding pulled (9 → 7) and the row gap dropped (6 → 4), all five chips fit comfortably on a single 24px-tall row at 390px panel width. Measured live after the pass: chips total ~324px wide + 4 x 4 gap = 340px content < 356px container. **Saves ~30px tall** on the empty-state chat panel — chips read as one calm row of label-only pills.
- **Composer textarea: min-height 52 → 36, padding 13/14 → 9/12, rows=1.** `.richtexteditor .rte-ai-chat-input { min-height: 36px !important; padding: 9px 12px !important }` plus a JS-side `composerArea.rows = 1` (the browser default `rows=2` was overriding the min-height CSS floor and pushing the at-rest textarea to ~50px). The textarea still grows up to its max-height as the user types multi-line; only the at-rest minimum drops. The composer now reads as one input row, not a 3-row paragraph box — same shape Notion AI / Claude / ChatGPT / Tiptap composers ship. **Saves ~14-22px tall** on every fresh-open chat panel composer (textarea 50 → 36, plus the smaller composer drops the surrounding compose-shell from ~116 → 94).
- **Chat panel min-height pulled.** Two-line change: `desiredHeight` in `renderChatPanel` lowered from `window.innerWidth <= 900 ? 560 : 660` to `window.innerWidth <= 900 ? 440 : 480`, plus a CSS floor `.richtexteditor.rte-ai-chat-host { min-height: 480px !important }` so the editor-shell stops demanding 660px for a ~480px panel. Same proportion Notion AI / Claude / ChatGPT side-panel ship. **Saves 40-180px** on every open chat panel depending on viewport, since the editor-shell `min-height` no longer artificially inflates to fit the old floor.

**Net effect:** empty-state AI Chat panel drops from **~572px tall to ~392px tall** at desktop 1280x900 — about 180px shorter, roughly 31% denser (measured live). The quick chips read as one calm row of label-only pills (the shape Notion AI / Linear / Claude side-panel ship), the composer reads as one input row that grows on type (the shape every modern AI composer ships), and the panel no longer demands 660px of editor shell height for what is now a ~480px panel. Every surface that survived this pass aligns more tightly with the four reference AI surfaces the codebase has been measuring against. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just denser and clearer. Customers who want any of the older shapes back can override the three CSS blocks at file-end (full override recipes in the inline comments).

`aiToolkitUiVersion` bumped from `v20260517b` to `v20260518a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260518a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Live verification at desktop 1280x900 confirmed: chat panel empty-state measures 480px tall, quick chips render on a single 24px row, composer textarea at-rest min-height 36px.

## Version 2.0.4.0

### Improved — AI window: Ask AI Details disclosure hidden, AI Chat status collapses when empty, Chat Send button right-aligned at natural width
Thirty-first compaction pass on the AI windows. After v2.0.3.9 landed the toolbar AI menu compaction (item min-height 40→30, icon tile 24→20), a side-by-side audit of the post-result Ask AI dialog and the empty-state AI Chat panel against the same four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced three remaining shapes that still painted chrome those references do not:

1. **Ask AI dialog "Details" disclosure + Why / Plan insight panels post-result.** None of the four reference inline composers ship a "Details" disclosure below the result — the suggestion IS the result, and Accept / Discard / Try again are the only post-result chrome each ships. The Details toggle adds ~26-30px of vertical chrome (28px tall button + 2px top margin) and a click whose payoff is "see the reason / operation plan" — a power-user audit affordance that adds cognitive load on the common proofread / rewrite path. The stale-plan warning that lived inside the Plan panel already mirrors to the status row above the apply row (`markPlanStale` writes to `status.innerText`), so the only audit warning that needed visible chrome continues to surface in the same place.
2. **AI Chat status row reserved `min-height: 18px` even when empty.** The Ask AI dialog status row has had `:empty { display: none }` since v20260509j; the chat status row was the matching surface that never adopted the same idiom. With state.status empty (true on every fresh-open chat panel and most idle states between turns) the row was reserving 18px of empty space at the bottom of the panel for no informational gain. Same shape Claude side-panel / ChatGPT side-panel / Notion AI ship — status text only renders when there's actually something to say.
3. **AI Chat composer Send button stretched to fill the composer-actions row width** (`flex: 1 1 0`). With the v20260513b "Open Ask AI" detach the Send button is the only child of the composer-actions row, so the flex stretch sized it at ~390px wide — three times the natural size of a Send primary. Same shape ChatGPT composer / Claude composer / Notion AI composer ship — Send sits right-aligned with natural width (~80-110px) so the visual weight matches its role. Stretching the only button in the row painted Send as a banner instead of an action.

**Ask AI dialog**
- **Details toggle + insight grid hidden by default post-result.** CSS-only — `.rte-panel-aiassist .demo-ai-details-toggle, .demo-ai-details-toggle, button.demo-ai-details-toggle { display: none !important }` plus `.rte-panel-aiassist .demo-ai-insight-grid { display: none !important }`. The toggle button + insight grid nodes stay in the DOM (renderAiDialog around line 15678-15706) so any external integration that walks `.demo-ai-details-toggle` / `.demo-ai-insight-grid` selectors keeps resolving without a guard. The stale-plan warning still surfaces via the status row above the apply row. **Saves ~26-30px tall on every post-result dialog** so the result reads as the final word — Apply / alternatives / Discard are the only action chrome, with Source available behind the existing "Edit prompt" disclosure for retries. Customers who want the Details disclosure back can override with `.demo-ai-details-toggle, .demo-ai-insight-grid { display: inline-flex !important }`.

**AI Chat panel**
- **Status row collapses when empty.** `.richtexteditor .rte-ai-chat-status:empty { display: none !important }`. Mirrors the Ask AI dialog `.demo-ai-dialog-status:empty { display: none }` rule (since v20260509j). With state.status empty (every fresh-open chat panel and most idle states between turns) the row reserved 18px of empty space at the bottom of the panel for no informational gain. **Saves 18px tall** on every empty-status chat panel render — the common case.
- **Composer Send button right-aligned at natural width.** `.richtexteditor .rte-ai-chat-composer-actions { justify-content: flex-end }` plus `.richtexteditor .rte-ai-chat-composer-actions button { flex: 0 0 auto; min-width: 92px }`. With the v20260513b "Open Ask AI" button detached, Send is the only child of the actions row — `flex: 1 1 0` sized it at the full ~390px row width, three times the natural size of a Send primary. Right-align + natural width matches ChatGPT / Claude / Notion AI composers where Send sits as a focused primary at the row's right edge. The composer textarea above keeps its full-width chrome; only the action row stops stretching.

**Net effect:** Ask AI dialog post-result drops **~26-30px tall** — the result reads as the final word, with Apply / alternatives / Discard as the only action chrome and Source available behind the existing "Edit prompt" disclosure for retries. AI Chat panel drops **18px tall** on every empty-status render and the Send button reads as a focused primary action at ~92-110px wide instead of a stretched ~390px banner. Every surface that survived this pass aligns more tightly with the four reference AI surfaces the codebase has been measuring against. Numeric / display overrides only — none of the typography / colour decisions are touched, so each surface still reads as the same product, just denser and clearer. Customers who want the older Details disclosure / status reserve / stretched Send back can override the three CSS blocks at file-end.

`aiToolkitUiVersion` bumped from `v20260517a` to `v20260517b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260517b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green.

## Version 2.0.3.9

### Improved — AI window: toolbar AI menu items tightened (min-height 40→30, icon tile 24→20), reads at a glance
Thirtieth compaction pass on the AI windows. After v2.0.3.8 landed the Ask AI dialog Generate-button busy affordance and the source-placeholder trim, a side-by-side audit against the same four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel, plus Notion AI's slash menu and Linear's command palette for the AI menu specifically) surfaced one remaining shape on the toolbar AI menu that still painted chrome those references do not:

**AI toolbar menu items reserved `min-height: 40px` left over from when each row painted a title + description line.** With descriptions hidden since v20260513a, the 40px floor was reserving ~10px of empty space below each ~14px title row that the description line would have filled. Across the 5 grid rows of the 2-column desktop layout (9 items, last row half-empty), that's ~50px of empty menu chrome the menu was painting for no informational gain. Same proportion Notion AI's slash menu / Linear's command palette / Raycast ship — title-only items render at ~28-32px tall, not 40px. Plus the 24×24 icon "tile" treatment (tinted square background + inset shadow) is heavier than Notion AI's / Linear's / Raycast's inline glyphs — a smaller 20×20 icon container keeps the affordance without painting a tile that overweights a single-line title row.

**Toolbar AI menu**
- **Item min-height 40 → 30px, icon container 24 → 20px, gap 7 → 6px, padding 5/7 → 4/6.** Each item drops to a single calm row that reads as "icon + title" rather than "tile + title-plus-empty-space". The icon SVG drops 13 → 12px so the smaller container doesn't crop the glyph weight, and the icon's `border-radius` drops 9 → 7px to match the new 20px footprint. Verified live at desktop 1280×900: every one of the 9 default items measures exactly 30px tall, icon container exactly 20×20px with 7px border-radius, item padding `4px 6px`, item gap `6px`, SVG inside icon `12×12px`. The 9 items still fit cleanly in a 2-column grid; the longest current title ("Add paragraph", ~95px at 12px font) renders without wrap inside its ~202px column cell. **Saves ~50px tall on the desktop default menu (~22% shorter)** — the menu opens onto a denser, more scannable list that reads more like the title-only command palettes the codebase has been measuring against.

**Net effect:** AI toolbar menu drops **~50px tall (~22% shorter)** so the 9 items read as a quick, scannable command palette instead of a list of padded tiles. Each row is exactly the height of its single-line title plus ~8px breathing room — same proportion Notion AI slash menu / Linear command palette / Raycast ship. The smaller 20×20 icon container drops the "tile" weight that overshadowed the title text on a single-line item, so the eye lands on the action label first. Customers who want the older 40px / 24px tile look back can override the three `.rte-panel-aiassist-menu .rte-ai-menu-item` selectors at file-end.

`aiToolkitUiVersion` bumped from `v20260515e` to `v20260517a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260517a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green. Live verification at desktop 1280×900 confirmed: menu items 30px tall, icon 20×20, no console errors, Ask AI dialog still opens cleanly at 440×138 pre-result.

## Version 2.0.3.8

### Improved — AI window: Ask AI dialog Generate button carries the busy affordance directly, source placeholder trimmed
Twenty-ninth compaction pass on the AI windows. After v2.0.3.7 dropped the per-message avatar and the panel title icons on AI Chat + AI Review, a focused audit of the Ask AI dialog's busy state surfaced two remaining gaps that still painted chrome the four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) do not:

1. **Generate button just disabled during busy — no animated affordance.** When the user clicked Generate the button greyed out and a "Generating suggestion..." line painted into a `.demo-ai-dialog-status` row beneath the apply row. The user's eye was on the button they just pressed; the status row sat ~40px below, so for the first half-second of every AI request the dialog read as frozen. The AI Chat composer's Send button has carried the busy affordance directly since v20260508 (spinner glyph + "Thinking..." label, animation via the `rte-ai-spinner-svg` class) — exactly the shape every reference surface ships. The dialog Generate button was the odd surface still painting busy state in a separate status row.
2. **Source textarea placeholder still doubled as a keyboard tutorial.** "Type or paste text — Ctrl+Enter to generate." (47 chars) trailed a Ctrl+Enter shortcut hint that is universal keyboard convention every modern AI composer uses. The Generate button itself already advertises the shortcut via `title="Generate AI suggestion (Ctrl+Enter)"` + `aria-keyshortcuts="Control+Enter"` (since v20260509j), so the placeholder was repeating context the button already carries. Same shape Notion AI ("Tell AI what to do…"), Claude composer ("Ask anything"), and ChatGPT composer ("Message ChatGPT") ship — one calm verb phrase, no inline shortcut hint.

**Ask AI dialog**
- **Generate button now carries the busy affordance directly.** `setBusyState(true)` flips the button to the `spinner` icon key (which renders the `rte-ai-spinner-svg` SVG that the existing `@keyframes rte-ai-spin` rule from the chat-composer pattern animates at 0.9s/turn) and the label to "Thinking..."; `setBusyState(false)` lets `updatePreview()` restore the idle "Generate" / "Regenerate" label + sparkles glyph. The `is-busy` class is also toggled on the button for any consumer-side CSS hooks. The dialog now reads the same way the AI Chat composer has since v20260508 — the action button itself shows that work is in flight, no separate status row required.
- **Status row no longer paints "Generating suggestion..." during busy.** With the Generate button carrying the affordance, the status row's message was a second copy of the same fact. The runButton.onclick call site now passes an empty string to `setBusyState`, so the `.demo-ai-dialog-status` row stays empty during the request and the `:empty { display: none }` rule (since v20260509j) collapses the row entirely. Saves ~16px of vertical chrome on every active request — the dialog drops from showing two busy signals stacked to a single inline button affordance.
- **Source textarea placeholder trimmed.** "Type or paste text — Ctrl+Enter to generate." (47 chars / 8 words) → "Type or paste text…" (19 chars / 4 words). The Ctrl+Enter shortcut remains advertised on the Generate button (title + aria-keyshortcuts since v20260509j) so first-time users discover it on hover; screen readers announce "Generate, Control+Enter" alongside the button name. Placeholder reads as one calm prompt phrase now. Earlier baseline: v20260509j: 95 → 47 chars after the scope toggle landed.

**Net effect:** Ask AI dialog busy state reads at a glance — the button the user just pressed flips to a spinning sparkle + "Thinking..." label, no separate status row required. The verbose "Generating suggestion..." string is gone, saving ~16px of vertical chrome on every active request. Source placeholder reads as one calm prompt instead of a keyboard tutorial. All three surfaces (Ask AI dialog / AI Chat composer / AI Review accept buttons) now share the same busy-affordance idiom every reference AI composer ships — the action button itself carries the in-flight state, no second copy of the busy signal elsewhere on the surface.

`aiToolkitUiVersion` bumped from `v20260515d` to `v20260515e`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260515e` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Offline release checks (`_tests\ai-toolkit-release.ps1 -SkipBrowserSmoke -SkipHttpChecks -SkipMvcBrowserSmoke`) pass: aitoolkit.js syntax, structured-content-bridge.js syntax, Default.aspx asset references, MVC demo asset parity all green.

## Version 2.0.3.7

### Improved — AI window: per-message avatar dropped, panel-title icon dropped on AI Chat + AI Review
Twenty-eighth compaction pass on the AI windows. After v2.0.3.6 landed the chat header "New chat" icon-only treatment, per-message role-label suppression, and composer-placeholder trim, a side-by-side audit of the AI Chat panel + AI Review V2 panel against the same four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced two remaining shapes that still painted chrome those references do not:

1. **Per-message avatar circle (22x22 colored gradient) on every chat turn.** With the role label suppressed in v2.0.3.6, the only thing still painted in the chat message header row was the colored 22x22 avatar SVG. The avatar carried the role colour (blue user / teal assistant / red error) — the same signal the message bubble's tinted background already conveys (`.is-user { background: #f5f8fd }`, `.is-error { background: #fdf3f3 }`). The avatar was a lone glyph sitting on its own row, adding ~22-26px of vertical chrome per message for a colour cue the bubble's own background already carries. None of the reference surfaces paint a colored avatar circle on every message turn: Notion AI / Tiptap / ChatGPT side-panel / Claude side-panel all convey role via tinted bubble (user) vs unstyled text (assistant) + position, with no avatar SVG on each turn.
2. **AI Chat + AI Review panel title icons (28x28 gradient AI-bot badge) next to the "AI Chat" / "AI Review" titles.** None of Claude side-panel / ChatGPT side-panel / Notion AI side-panel paint a title icon next to their panel title — the panel context (a side panel attached to the editor, the panel's `role="complementary"` + aria-label, the toolbar button the user just clicked) already tells the user which surface they're in. The 28px icon + 8px gap spent 36px of header chrome teaching what the panel surface already conveys.

**AI Chat panel**
- **Per-message avatar suppressed.** CSS-only — `.richtexteditor .rte-ai-chat-message.is-minimal .rte-ai-chat-message-avatar { display: none !important }`. The avatar `<span>` node stays in the DOM so any external integration that walks `.rte-ai-chat-message-avatar` selectors keeps resolving without a guard. The role colour is still carried by the message bubble's tinted background (`.is-user`, `.is-error`); the assistant turn renders as plain white text — same shape Claude / ChatGPT / Notion AI use to mark assistant output.
- **Message header row (now empty: both avatar + role label hidden) suppressed entirely.** `.richtexteditor .rte-ai-chat-message.is-minimal .rte-ai-chat-message-header.is-minimal { display: none !important }`. With nothing visible inside it, the header row's `margin-bottom: 2px` (from v2.0.3.6) plus the avatar's own vertical occupancy added ~22-26px of empty chrome above each message text. The text now reads directly inside the bubble without a leading air gap. **Saves ~22-26px tall per message** — on a typical 8-12 message thread that's ~175-310px the conversation feed reclaims for actual conversation.
- **Panel title icon (28x28 gradient AI-bot badge) suppressed.** `.richtexteditor .rte-ai-chat-header.is-minimal .rte-ai-chat-title-icon { display: none !important }`. With the icon gone the title-row needs no gap; the title text sits flush at the panel's natural left edge and the new-chat + close buttons at the natural right edge. **Saves ~36px wide** (28px icon + 8px title-row gap). Same shape Claude side-panel / ChatGPT side-panel / Notion AI ship — title text only, no decorative badge.

**AI Review V2 panel**
- **Panel title icon (same 28x28 gradient AI-bot badge) suppressed.** `.richtexteditor .rte-ai-review-panel.rte-ai-review-v2 .rte-ai-review-v2-icon { display: none !important }`. Same audit applied — the panel's `role="complementary"` + `aria-label="AI Review"` announces the surface. The title text sits flush left next to the (slightly more compact) tab strip below. **Saves ~36px wide** on the header row.

**Net effect:** AI Chat panel drops **~22-26px tall per message** combined — on a typical 8-12 message thread the conversation feed reclaims ~175-310px of vertical room for actual conversation, plus ~36px of header chrome goes back to the title text. AI Review V2 panel header sheds the same 36px so the title sits flush left at the panel's natural edge. Both surfaces read as "title + close" headers without a decorative icon — exactly the shape every modern AI side-panel (Claude / ChatGPT / Notion AI / Tiptap) ships. The avatar removal in particular is what cuts the per-message vertical density — Claude side-panel and ChatGPT side-panel both paint assistant output as plain text inside a transparent bubble, no avatar circle, and the chat panel now matches.

`aiToolkitUiVersion` bumped from `v20260515c` to `v20260515d`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260515d` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified).

## Version 2.0.3.6

### Improved — AI window: chat header "New chat" icon-only, per-message role label suppressed, composer placeholder trimmed
Twenty-seventh compaction pass on the AI windows. After v2.0.3.5 landed the Review V2 narrowing + scope-pill drop + item-padding pull + tab-strip pull + Ask AI controls gap squeeze, a side-by-side audit of the AI Chat panel against the same four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced three remaining shapes the panel still painted that the references do not:

1. **Chat header "New chat" button rendered icon + label.** The refresh glyph + "New chat" text label sat next to an icon-only [X] close button on the same 420px header row, so the right-edge cluster read as "lopsided" — two adjacent header affordances with unequal widths (~80px vs ~26px). The label was teaching what the refresh glyph already conveys. None of the reference surfaces ship an inline label on the "start fresh chat" affordance: Claude side-panel uses a refresh icon, ChatGPT side-panel uses a `+` icon, Notion AI uses a refresh icon — all single glyphs with hover title + aria-label, no inline text.
2. **Every message in the conversation feed painted an uppercase 11px role label** ("YOU" / "AI" / "ERROR") next to the color-coded avatar SVG. The avatar is already coloured by role (blue user / teal assistant / red error) — same visual differentiation Notion AI / Tiptap / ChatGPT side-panel use to mark sender role. The text label duplicated info the avatar already carries and added ~16-18px of vertical chrome to every message bubble. On a typical 8-12 message thread that's ~140-216px of redundant chrome the conversation feed could reclaim.
3. **Composer placeholder read "Ask AI…  Enter to send, Shift+Enter for newline."** (51 chars). The Enter / Shift+Enter shortcut is universal convention across every modern chat composer — teaching it inside the placeholder repeats what the user already knows from the first chat surface they ever used. Same shape Claude composer ("Ask anything") / ChatGPT composer ("Message ChatGPT") / Notion AI ("Tell AI what to do…") ship — one short verb phrase, no keyboard hint.

**AI Chat panel**
- **Header "New chat" button → icon-only.** `renderChatPanel` now adds the `is-icon-only` class to the New-chat button and calls `setReviewV2ButtonContent(clearButton, "refresh", "")` (empty label). Title attribute + aria-label both read "Start a fresh chat" so hover and screen readers still surface the long form. A new CSS rule mirrors the icon-only sizing the chat scope buttons use since v20260513c (`min-width: 30px`, padding-left/right `0`, label span suppressed). **Saves ~70px wide** on the header row so the title + close-X pair land at their natural left/right edges. Same shape Claude side-panel / ChatGPT side-panel / Notion AI ship.
- **Per-message role label suppressed.** CSS-only — `.richtexteditor .rte-ai-chat-message.is-minimal .rte-ai-chat-message-role { display: none !important }`. The role span node stays in the DOM so any external integration that walks `.rte-ai-chat-message-role` keeps resolving without a guard, and `buildChatMessageAriaLabel` still folds the role into the message-item aria-label so screen readers announce sender role on every turn. With the label gone the message header's `margin-bottom` drops from 4 → 2px so the message text sits closer to the avatar. **Saves ~16-18px tall per message** — on a typical 8-12 message thread that's ~140-216px the conversation feed can reclaim. Same shape Notion AI / Tiptap / ChatGPT side-panel ship — sender role is conveyed by the avatar colour, not a repeated text label.
- **Composer placeholder trimmed.** "Ask AI…  Enter to send, Shift+Enter for newline." (51 chars / 9 words) → "Ask anything…" (14 chars / 2 words). The Enter / Shift+Enter shortcut is keyboard convention every modern chat surface uses — the placeholder no longer doubles as a keyboard tutorial. The placeholder now reads as one calm prompt phrase, same shape Claude composer ("Ask anything") / ChatGPT composer ("Message ChatGPT") / Notion AI ("Tell AI what to do…") ship.

**Net effect:** AI Chat header right-cluster reads as two icon-only glyphs (refresh + close) at equal sizes, ~70px of horizontal real estate goes back to the title. Conversation feed drops ~16-18px per message of chrome — ~140-216px back to the active conversation on a typical thread, one more turn per scroll on the default panel height. Composer placeholder reads as one calm prompt instead of a keyboard tutorial. Every surface that survived this pass aligns more tightly with the four reference AI surfaces the codebase has been measuring against.

`aiToolkitUiVersion` bumped from `v20260515b` to `v20260515c`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260515c` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified).

## Version 2.0.3.5

### Improved — AI window: Review V2 panel narrowed, item scope chip dropped, item padding tightened, tab strip shortened, Ask AI controls gap squeezed
Twenty-sixth compaction pass on the AI windows. After v2.0.3.4 landed the chat scope-toggle hide-when-no-selection + empty-state hint trim, a side-by-side audit of the AI Review V2 panel against the same four reference AI surfaces (Notion AI side-panel / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced four shapes the V2 panel still painted that the references do not:

1. **Panel width was 600px desktop default.** Bumped from 480 → 600 in an early "improve width" report so the side-by-side Current/Suggested compare wouldn't cramp. With the v20260508 V2 redesign the compare grid uses a clean `1fr 1fr` split that reads at ~220px per cell, so the panel never actually needed 600px after the initial compare-cramp report — Notion AI side-panel ships ~520px, Claude side-panel hovers in the 480-520 range.
2. **Each item head painted three pills (status / type / scope).** The scope pill ("Selection" / "Document") repeated information a reviewer rarely needs at-a-glance — the suggestion was made against whatever scope was active at generation time, and the reviewer is deciding whether to accept the *edit*, not auditing the scope choice. Status + type already carry the meaningful at-a-glance signal (status colour + change-type label like "Proofread" / "Rewrite" / etc.). Notion AI / Tiptap / ChatGPT all show status + change-type only on their suggestion cards; none surface scope as a third chip. The third chip on every item was visual noise that pushed the title text further right.
3. **Item card padding was 10/12 default and 14/14 active.** With the scope pill suppressed and the head row visually quieter, the padding can tighten by 2px without crowding content. For 8-10 visible items in the body that's ~16-20px of extra vertical density — the user sees one more item per scroll.
4. **Tab strip height was 30px.** The tabs render only an icon + label + count badge on a flat strip, no border, no double-row chrome — 28px reads identically and shaves 2px off every panel render. Same proportion Linear / GitHub Issues tab strips ship.

Plus one carry-over polish on the Ask AI dialog from the same audit: the compact-controls row (Action / Language / Scope / Run buttons) used `gap: 3px 8px` from the dialog grid CSS. With the v20260513c header-chip drop + v20260513b inner-padding pull (4/10/6 → 2/10/4), the controls strip can squeeze another 2px horizontal between fields without buttons feeling glued together — matches the ~6px gap Notion AI's inline composer puts between its action/language/scope controls.

**AI Review V2 panel**
- **Panel width 600 → 520px desktop default.** Override in the file-end compaction block (`.richtexteditor .rte-ai-review-panel.rte-ai-review-v2 { width: min(520px, calc(100vw - 24px)) !important }`) wins the cascade against the earlier `min(600px, ...)` rule. Compare grid still works at 520px (each cell ~220px after the 8px gap and ~28px outer padding). **Saves 80px wide** that the editing surface gets back when the panel is open. Verified live at desktop 1280×900: the panel measures 520px exactly.
- **Item-head scope pill suppressed.** `.richtexteditor .rte-ai-review-v2-item-scope { display: none !important }`. JS still creates the span (renderReviewPanelV2 around line 14001) so any external integration that walks `.rte-ai-review-v2-item-scope` selectors keeps resolving without a guard — only the visible render is hidden. Customers who want the scope pill back can override with `display: inline-flex !important`. Frees a chip's worth of horizontal space per item head and drops one cognitive checkbox per scan. Verified live: head text now reads "PENDING / PROOFREAD" instead of "PENDING / PROOFREAD / DOCUMENT".
- **Item padding 10/12 → 8/12 default, 14/14 → 12/14 active.** Verified live: non-active item padding is `8px 12px 8px 14px`, active item is `12px 14px 10px 16px`. For 8-10 items in the body that's ~16-20px more vertical density — one more item per scroll on a typical queue.
- **Tab strip height 30 → 28 px, padding 0 10 → 0 8 px.** Verified live: `.rte-ai-review-v2-tab` computed height is 28px. Saves 2px of every panel render — small per-render but the tabs are always visible, so the saving compounds.

**Ask AI dialog**
- **Compact-controls row gap 3px 8px → 2px 6px.** `.rte-panel-aiassist .demo-ai-compact-controls { gap: 2px 6px }`. Verified live: computed gap is `2px 6px`. Same shape Notion AI's inline composer ships between its action/language/scope controls — tighter cluster reads as one decision unit instead of four separated fields.

**Net effect:** AI Review V2 panel drops **80px wide** and ~20-22px tall on the default queue render — 80px of horizontal real estate goes back to the editing surface, and the user fits one more item per scroll on a typical queue. Item heads carry one fewer chip so the eye lands on the title text faster. Ask AI dialog controls cluster reads as one decision unit instead of four separated fields. Every surface that survived this pass aligns more tightly with the four reference AI surfaces the codebase has been measuring against.

`aiToolkitUiVersion` bumped from `v20260515a` to `v20260515b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260515b` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified). Live verification at desktop 1280×900 confirmed: panel 520px, tab 28px, scope pill `display: none`, item padding tightened, dialog controls gap `2px 6px`, no console errors.

## Version 2.0.3.4

### Improved — AI window: chat scope toggle hidden when no selection, empty-state hint trimmed to one calm line
Twenty-fifth compaction pass on the AI windows. After v2.0.3.3 landed the chat-panel chrome trim, status-message shortening, and menu-frame narrowing, a fresh first-contact pass against the same four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced two remaining shapes that still painted chrome those references do not:

1. **AI Chat scope toggle row rendered on every panel open, even when there was no selection to switch to.** The cursor-T / document icon-only segmented toggle (since v20260513c) is a *binary* control — Selection vs Document. When the user opens AI Chat with no editor selection (the common case — most users hit the AI button cold, without first highlighting text), the Selection button is `disabled` and Document is the only active state. The user is being shown a 38px row + 10px panel gap (~48px tall) of UI for a "choice" they cannot actually make. None of the reference surfaces ship a stuck-state toggle: Claude side-panel only paints a scope chip when the user has actually selected text in the parent document; ChatGPT side-panel ditto for "Search the web" / "code interpreter" toggles — controls that can't change state are simply not rendered.
2. **Empty-state hint was a 117-char marketing voice paragraph** ("Use the current document as context, ask for guidance, or prepare reviewable edits before they touch the page.") stamped onto a panel where the user has not yet typed a single character. The earlier copy named the architecture (*context*, *reviewable edits*, *before they touch the page*) instead of the action. At the 320px max-width the empty-state-detail rule enforces, the 117-char string wraps to three lines of ~12px text — visually dense for a hint meant to read at a glance. None of the reference surfaces stamp three-line marketing on empty AI conversations: Claude side-panel ships "Type a message to start.", ChatGPT side-panel "How can I help you today?", Notion AI inline composer "Tell AI what to do...". One line, names the action, reads at a glance.

**AI Chat panel**
- **Scope toggle row hidden when no selection is actionable.** `renderChatPanel` tags `.rte-ai-chat-context-shell` with `is-no-selection-actionable` whenever `!context.snapshot.hasSelection`, and `syncChatDraftAwareUi` toggles the same class on every re-render as the editor's selection state changes (so the toggle reappears the moment the user highlights text in the parent document). A new CSS rule `.rte-ai-chat-context-shell.is-minimal.is-no-selection-actionable { display: none }` hides the row. DOM stays intact (the buttons + bar + shell are all still queried by selectors in `syncChatDraftAwareUi`, `restoreChatPanelFocus`, etc., so no call site needs a null guard). **Saves ~48px tall** (38px row + 10px panel gap) on every fresh-open chat panel where the user opened AI Chat without first selecting text — the common case. Same shape Claude side-panel / ChatGPT side-panel ship: the scope chip only appears when there's a scope to pick.
- **Empty-state detail trimmed from 117 chars to 41.** `buildChatEmptyStateData.detail` rewritten from "Use the current document as context, ask for guidance, or prepare reviewable edits before they touch the page." (117 chars / 18 words) to "Ask anything about the current document." (41 chars / 6 words). Selection variant trimmed equivalently from "Use the selected passage as the active source, ask a question, or prepare reviewable edits before you accept anything." (118 chars / 19 words) to "Ask anything about the current selection." (42 chars / 6 words). At the 320px max-width rule that frames the hint, the 41-char string fits on a single line at 12px font — same proportion Claude side-panel ("Type a message to start.") / ChatGPT side-panel ("How can I help you today?") / Notion AI ("Tell AI what to do...") ship. The capability cards array (`Ask and explore` / `Prepare reviewable edits` / `Use starters or type your own`) is still attached to `emptyStateData.capabilityCards` so screen readers hear the long form via the aria-label that `buildChatEmptyStateAriaLabel` constructs. **Saves ~24px tall** on every empty-state chat panel where the line was previously wrapping to two rendered lines (most viewport widths).

**Net effect:** AI Chat panel drops **~48px tall** on every fresh-open with no selection (the common case) — the conversation feed gets that vertical room back, and the composer rides ~48px closer to the chips. Empty-state hint now reads as one calm line ("Ask anything about the current document.") instead of a three-line marketing paragraph. Both changes align the AI Chat surface more tightly with the four reference AI surfaces the codebase has been measuring against.

`aiToolkitUiVersion` bumped from `v20260513e` to `v20260515a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260515a` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified).

## Version 2.0.3.3

### Improved — AI window: chat panel chrome trimmed, status messages shortened, menu narrowed
Twenty-fourth compaction pass on the AI windows. After v2.0.3.2 landed the menu-title trim and the RESULT-eyebrow drop, a side-by-side audit against the four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced three remaining shapes that still painted chrome those references do not:

1. **Chat panel outer padding + gap were 16/14px** — generous for a side-panel that already gets visual separation from the editor via a 14px border-radius + box-shadow. The 16px outer padding × 2 sides + the 14px gap × 4 inter-section breaks = ~88px of pure chrome before any content rendered. None of the reference surfaces ship that loose: Claude side-panel and ChatGPT side-panel both hover around 12px outer / 10px between sections.
2. **Dialog status messages stamped two copies of context-the-user-already-has.** Every status line trailed "Re-run Ask AI to refresh this suggestion." — 11 words averaging 50 chars. The user is already in the Ask AI dialog (the title above the status row literally reads "Ask AI"), and the surface only does one thing (run AI). "Re-run to refresh." is half as long and reads identically. Original status lines like "The editor selection cleared. Re-run Ask AI to refresh this suggestion." (10 words / 62 chars) became "Selection cleared. Re-run to refresh." (5 words / 36 chars).
3. **Toolbar AI menu was still 460px wide** — chosen in v20260512a to match the older 440px dialog, but with v20260513a/b/c/d trimming the per-item descriptions, the section labels, and the longest titles, the 9 single-line items don't need 460px to render. The longest current title ("Add paragraph" — 95px at 12px font) fits comfortably in a 200px column, so a 420px frame still gives each 2-column cell plenty of room.

**AI Chat panel**
- **Outer padding 16 → 12, section gap 14 → 10.** With four inter-section gaps in the panel (header / context / chips / feed / composer) the gap-reduction alone saves 16px tall; the padding reduction saves another 8px combined. **Total ~24px tall** trimmed on every chat panel render — the panel header sits closer to its divider, the chips sit closer to the composer, and the conversation feed gets that ~24px back to render more turns. Same proportions Claude side-panel / ChatGPT side-panel ship.
- **Header padding-bottom 14 → 10.** With the panel gap reduced to 10px the header divider sits one gap below the title; the extra 4px padding-bottom (14 vs 10) was double-counting the section separation. Saves another 4px on every render.
- **Empty-state padding 8/10 → 4/12/8.** The earlier !important rule was reserving more air above the hint than the chip row above + panel gap below already provide. With the new 10px panel gap the empty-state hint reads as a single calm line wedged cleanly between the chips and the composer. Saves ~12px tall on the empty state.
- **Composer textarea min-height 64 → 52.** The 64px floor was reserving ~4 lines of empty space under the placeholder when an empty composer just needs 2 lines of breathing room. Users can drag the resize handle for longer drafts. **Saves another 12px tall** on every empty-state chat panel. Same proportion ChatGPT side-panel / Claude side-panel composers use — short textareas that grow with content.

**Ask AI dialog**
- **Status messages trimmed of redundant prefix + suffix.** "The editor selection cleared. Re-run Ask AI to refresh this suggestion." → "Selection cleared. Re-run to refresh." Same shape across all six status-message generators (`getDialogEditorChangeMessage`, `getDialogSourceChangeMessage`, `getDialogModeChangeMessage`, `getDialogLanguageChangeMessage`, `getDialogScopeChangeMessage`, `getDialogEditorReloadMessage`) plus the plan-status fallback inside `renderOperationPlan`. **Saves ~40% of every status string's character count** so the single-line status row no longer line-wraps at the 440px dialog width when, e.g., the target language is long ("Portuguese", "Japanese") — Translate's language change message went from "The target language changed to Portuguese. Re-run Ask AI from this source to refresh the suggestion." (95 chars) to "Language → Portuguese. Re-run to refresh." (39 chars). Same shape ChatGPT inline edit / Notion AI status lines ship — terse "X changed. Retry." patterns.

**Toolbar AI menu**
- **Frame width 460 → 420px.** With the section labels (dropped in v20260513c) + per-item descriptions (dropped in v20260513a) + the longest titles trimmed (in v20260513d) all gone, the 9 single-line items fit comfortably in a 420px 2-column frame — each cell still gets ~200px of content room. **Saves ~40px wide.** Same proportion Linear's command palette (~400-420px) ships. Tighter gap (4/6 → 3/5) + tighter padding (6 → 4) saves another ~6px combined. Net menu drops from 460×172 to 420×172.

**Net effect:** AI Chat panel drops **~52px tall** combined (24 from padding/gap + 4 from header + 12 from empty-state + 12 from composer textarea) — the conversation feed gets ~52px more vertical room to render turns at the same panel height. Ask AI dialog status row no longer wraps onto two lines on Translate language-change messages. Toolbar AI menu is 40px narrower so it fits side-by-side with the 440px dialog it spawns into without painting a wider frame than its child.

`aiToolkitUiVersion` bumped from `v20260513d` to `v20260513e`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260513e` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified).

## Version 2.0.3.2

### Improved — AI window: menu titles trimmed of AI-redundant words, RESULT eyebrow dropped, dialog padding tightened, Edit-prompt disclosure right-pinned
Twenty-third compaction pass on the AI windows. After v2.0.3.1 landed the flat-menu / no-header-chip / icon-only-chat-scope / chips-collapse-post-conversation refinements, a side-by-side audit against the four reference AI surfaces (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced two remaining shapes that painted chrome those references do not:

1. **Toolbar AI menu item titles trailed AI-redundant words.** "Chat with AI" / "Draft with AI" / "Add AI note" each spent a word repeating the context the parent affordance already provides — the menu is opened FROM the AI toolbar button and renders inside a panel scoped to AI, so the "with AI" / "AI" suffix was a second copy of a fact the user already has. "Proofread selection" / "Rewrite selection" similarly trailed an object the icon vocabulary + Selection-source default already imply. None of the reference surfaces ship verbose labels: Notion AI's slash menu reads "Continue writing" / "Summarize" / "Improve writing"; Linear's command palette ships single-verb / verb-object entries. The 2-3 extra characters per title forced the menu to a 460px frame to fit the longest item; trimming brings every title under ~12 chars so the menu can tighten further in a later pass.
2. **Ask AI dialog post-result rendered a "RESULT" eyebrow label-inline above the result textarea.** The 10px uppercase eyebrow was duplicating affordances the readonly tinted background + the right-aligned "Edit prompt" disclosure already provided. None of the reference surfaces stamp a label above their AI output — Notion AI's inline composer, Tiptap AI bubble, ChatGPT inline edit, and Claude side-panel all surface the AI output as the visible primary with only a retry/discard control row, no field name above it. The eyebrow claimed ~12px of vertical chrome above the result for no informational gain.

**Toolbar AI menu**
- **Item titles trimmed.** "Chat with AI" → "Chat", "Draft with AI" → "Draft", "Review changes" → "Review", "Proofread selection" → "Proofread", "Rewrite selection" → "Rewrite", "Explain rewrite" → "Explain", "Add AI note" → "Add note". "Translate" / "Add paragraph" were already concise and kept as-is. Default `aiToolkitActions` config updated; customer overrides via `aiToolkitActions` keep any legacy long-form labels untouched. Same shape Notion AI / Linear / Raycast command-palette labels ship — single verb or verb-object that reads at a glance. **Saves ~6-10px horizontal per item**, opening the door to a narrower menu frame in a later pass without re-wrapping any item.

**Ask AI dialog**
- **"RESULT" eyebrow label-inline dropped from the result-head.** The readonly tinted textarea background + the right-aligned "Edit prompt" disclosure already mark the textarea as the AI output. Detached `<span>` node still created and class-tagged `is-detached` so any external integration that walks `.demo-ai-result-head .demo-ai-text-label-inline` resolves without throwing. **Saves ~12px vertical** on every post-result dialog. Same shape Notion AI / Tiptap / ChatGPT inline edit / Claude side-panel use — the AI output is the visible primary; only retry/discard chrome rides above it.
- **"Edit prompt" disclosure right-pinned via `margin-left: auto`.** With the RESULT eyebrow gone, the disclosure was the only child of a `justify-content: space-between` flexbox and packed to the LEFT (against the textarea's left edge instead of the right). Pinning right via auto-margin restores the pre-pass affordance — the toggle stays out of the way at the result-head's right edge, leaving the reading eye clean room above the result textarea.
- **Inner padding tightened `4/10/6` → `2/10/4`.** With the RESULT eyebrow gone and the result-head reduced to a single right-pinned disclosure, the 4px top + 6px bottom were wrapping a single ~70px controls block pre-result. 2/4 keeps just enough breathing room for the focus ring around the primary Generate button and matches Notion AI's inline composer (~3px ring) where the controls almost touch the header rule. **Saves another 4px combined** on the dialog frame.

**Net effect:** Default fresh-open Ask AI dialog: drops **~4px tall** (tighter padding). Post-result Ask AI dialog: drops **~12px tall** (RESULT eyebrow gone) — the result textarea is now the visual primary, framed only by a right-pinned retry disclosure. Toolbar AI menu: titles read at a glance and reserve ~10% less horizontal width per item, setting up a narrower frame in a future pass. Every surface that survived this pass aligns more tightly with the four reference surfaces the codebase has been measuring against.

`aiToolkitUiVersion` bumped from `v20260513c` to `v20260513d`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260513d` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified).

## Version 2.0.3.1

### Improved — AI window: flat menu (no sections), redundant header chip dropped, chat scope icon-only, chips collapse post-conversation
Twenty-second compaction pass on the AI windows. With v2.0.3.0 the surface chrome was already lean, but a side-by-side audit against the four reference AI surfaces the codebase has been tracking against (Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit + side-panel / Claude side-panel) surfaced four shapes that still painted chrome those references do not:

1. **Toolbar AI menu had three uppercase section labels** ("Start" / "Quick edit" / "Insert") above 9 items. None of the reference surfaces section their slash/command menus — Notion AI's slash, Linear's command palette, and ChatGPT's `/` suggestions all render as a single flat list sorted by relevance / recency. The labels claimed ~22px each (66px of pure chrome) above choices the icons already typed visually (chat/dialog/review at the top, proofread/rewrite/translate/justify in the middle, comment/paragraph at the bottom). Three "sections" of three items is also too few to amortize the label cost — a section that contains a quarter of the menu is just a label sitting near the items.
2. **Ask AI dialog header carried a mode-name chip** ("Proofread", "Translate · Spanish") right next to the title text. The Action select dropdown directly below the title already shows the active mode — and selecting a different mode in the dropdown is the *only* way to change it — so the chip was a second copy of the same fact. The Translate language was the only extra signal the chip carried; the Language select sitting right next to the Action select already shows it.
3. **AI Chat scope toggle painted labels ("Selection" / "Document") on both buttons** while the Ask AI dialog has used icon-only equivalents (cursor-T / document glyph) since v20260509j. Two AI surfaces using two different vocabularies for the same binary scope toggle forced first-time users to re-learn the affordance when switching between Ask AI and Chat. At the 390px chat panel width the labeled buttons were claiming ~140px (35%) of the context row for a control with exactly two options.
4. **Quick-start chips painted on every render**, including after the user had sent a message and was reading the conversation feed. The chips are called "quick STARTS" — once a user has sent any prompt they've already started, and the ~30px chip row above the conversation feed just becomes a permanent row of suggestions they already declined. Same shape Claude side-panel / ChatGPT side-panel / Notion AI ship: starter prompts appear on the empty state and step out of the way once a conversation exists.

**Toolbar AI menu**
- **Section labels suppressed.** The `Start` / `Quick edit` / `Insert` rows are no longer painted. JS creates the section node detached (never appended to the panel) so the loop's section-tracking logic stays simple and any external integration that walks `.rte-ai-menu-section` selectors keeps resolving without throwing. A belt-and-braces CSS rule (`.rte-panel-aiassist-menu .rte-ai-menu-section { display: none !important }`) catches any reattachment. **Saves ~66px** on the desktop default — menu drops from ~276px tall to ~210px tall (~24% shorter). Customers who want sections back can override `.rte-ai-menu-section { display: block !important }`.

**Ask AI dialog**
- **Header mode chip dropped.** The Action select directly below the title already shows the mode; the chip was a second copy of the same fact. Detached chip node still created and updated by `updateModeHelp()` writes so the existing helper code paths noop safely without each call site needing a guard. **Saves the header chip's ~20px right of the title** — but more importantly the title bar now reads as one clean affordance (title + close X) instead of title + chip + close X, matching the single-statement headers Notion AI / Tiptap / ChatGPT inline edit / Claude side-panel all use.

**AI Chat panel**
- **Scope toggle demoted to icon-only segmented control.** The cursor-T (Selection) and document (Document) glyphs alone are now the affordance — same icon vocabulary the Ask AI dialog has used since v20260509j, so muscle memory carries across surfaces. Title attribute + aria-label still surface the long form ("Use selection" / "Use document") for affordance and screen readers. Reserves ~70px instead of ~140px at the 390px chat panel width — the toggle is half the row width it used to be and no longer competes with the New chat / close affordances on the header row above. Saves ~70px horizontal on the context row.
- **Quick-start chips hidden once a conversation exists.** Chip row keeps painting on the empty state (when `state.messages.length === 0`) where it genuinely helps a first-time user pick a starting prompt. The moment the user sends any prompt, the section gets an `is-conversation` class and CSS hides it. Saves another ~30px tall as soon as the conversation starts, so the conversation feed gets that ~30px back to render more turns above the composer. The chip row reappears every time the user hits "New chat" (which clears `state.messages`). Section nodes stay in the DOM so `syncChatDraftAwareUi` / `restoreChatPanelFocus` selectors that walk `.rte-ai-chat-quick-shell` keep resolving without a guard.

**Net effect:** Toolbar AI menu drops **66px tall (-24%)**, opening straight onto a flat 9-item list — same flat-list shape Notion AI / Linear / ChatGPT command palettes all ship. Ask AI dialog header reads as one clean line (title + close X), matching every reference surface the codebase is tracking. AI Chat panel scope toggle is half the width it was (70px vs 140px), freeing the context row for a less crowded header. AI Chat panel drops another **30px tall** once a conversation has started, so the conversation feed gets that space back to render more turns. Every surface that survived this pass aligns more tightly with the four reference surfaces the codebase has been measuring against.

`aiToolkitUiVersion` bumped from `v20260513b` to `v20260513c`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260513c` for cache busting. The MVC demo mirror under `Demos/AspNetCoreMvc/wwwroot/richtexteditor/plugins/` was refreshed via `Sync-AiToolkitDemoAssets.ps1` (file hashes verified).

## Version 2.0.3.0

### Improved — AI window: noise-cull pass (pre-result Copy hidden, dead Details toggle hidden, chat composer single-action, clearer Edit-prompt disclosure)
Twenty-first compaction pass on the AI windows. After v2.0.2.9 landed the single-column Result-first stack and the Source-collapsed-post-result pattern, four small frictions still kept the surfaces from clearing the "easy to understand at first contact" bar at the actual shipping version. A live audit of the rendered dialog + chat panel surfaced:

1. **Pre-result run row had a non-functional Copy icon button.** `updatePreview()` sets `copyBtn.disabled = !hasResult`, so pre-result the icon-only Copy square was rendered, click-shaped, focusable, and did literally nothing — a disabled button shaped like an action that fired no action. Same broken-feeling affordance prior passes called out for the pre-result Apply row, the pre-result Details toggle, and the pre-result Status row. Users who clicked it got no feedback; users who scanned past it spent ~0.5s figuring out what "Copy" copies when there's nothing yet.
2. **Post-result Details toggle surfaced an empty Why panel on plans with no reason.** The reason placeholder ("This suggestion does not include an explicit AI rationale yet.") was rendered as the body of the Why card whenever the resolver returned a result but no `reason` field — common for single-shot proofread/rewrite flows where the AI just returns the edited text. Clicking Details to expand a placeholder reads as the dialog being broken, not as a dense disclosure.
3. **Chat composer fought itself.** The composer row rendered `[Open Ask AI] [Send]` side-by-side at `flex: 1 1 0` each, so the primary action (Send) claimed only ~50% of the composer width and visually competed with a button that took users to a different AI surface (the single-shot Ask AI dialog). First-time users hit "Open Ask AI" expecting their typed draft to send, and instead landed on a separate modal with the draft gone.
4. **The post-result "Source" disclosure read as a third field label, not as an action.** With "Source" stamped above the result header in the same uppercase voice as `SOURCE` / `RESULT` label-inlines on the textareas above, the disclosure read as another field name rather than as a clickable toggle. Users skipped past it expecting it to be a heading.

**Ask AI dialog**
- **Pre-result Copy icon button hidden.** `.demo-ai-dialog-grid.is-pre-result .demo-ai-run-row [data-rte-ai-dialog-action="copy-to-source"] { display: none !important }`. The same `is-pre-result` flag that hides the Apply row / Details toggle / Insight grid / Status row now also hides the icon-only Copy slot. Frees ~36px of horizontal real estate on the run row pre-result so the Generate primary owns the whole right edge of the controls strip — same shape Notion AI inline composer / Tiptap AI bubble use, where the only post-result chrome appears once there's output to act on. Copy reappears the moment Generate produces output.
- **Details toggle hidden when the resolver returned no rationale AND ≤1 operation.** `updatePreview()` now reads `dialoginner.__aiResolved.reason` + counts `operations`; if both are absent (the common single-step proofread/rewrite flow) the Details toggle is `display: none` and the insight grid is force-collapsed so it never paints. Same idiom Notion AI / Tiptap use — "Why this?" only appears when the model actually returned a reason. Multi-step agentic plans (where there ARE operations worth exposing) keep the toggle. Saves ~26px on every post-result dialog whose resolver only returns the edited text.
- **Source disclosure relabeled "Source" → "Edit prompt".** The earlier label read as a field name (a noun) in the same uppercase voice as the textarea label-inlines above it; the new "Edit prompt" reads as an action (a verb) — same affordance Notion AI's inline composer uses post-result. Title attribute updated to "Edit the prompt and retry" to match.
- **Inner padding tightened 6/12/8 → 4/10/6.** With the pre-result dialog now showing only the controls strip (Source field below + Result/Details/Apply/Status/Copy all collapsed), the 6px top + 8px bottom were padding around a single ~30px-tall row. 4/10/6 saves another 4px combined on the pre-result frame and brings the dialog into the same proportion Notion AI inline composer uses (~6px ring) where the controls almost touch the header rule.

**AI Chat panel**
- **"Open Ask AI" button removed from the visible composer row.** The detached node is kept so any external integration that walks `[data-rte-ai-chat-action="open-dialog"]` still resolves. With the button gone, Send is the single primary in the composer, claiming the full row width and visually matching ChatGPT side-panel / Claude side-panel composers (one primary, no second button bouncing the user to another surface). The Ask AI dialog is still reachable from the toolbar AI button → menu, so power users haven't lost a path.

**Net effect:** pre-result Ask AI dialog drops **146px → 142px (−4px tall, single-row run row 36px tighter horizontally)**. Post-result dialog drops the dead Details toggle on plans with no `reason` field (−26px). Chat composer has one primary action again. The Source disclosure label finally reads as a verb. Every surface that survived this pass renders only chrome that does something with the current state — nothing is shown pre-result that won't accept a click.

`aiToolkitUiVersion` bumped from `v20260513a` to `v20260513b`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260513b` for cache busting.

## Version 2.0.2.9

### Improved — AI window: single-column stack, Result-first, Source collapsed post-result, titles-only menu
Twentieth compaction-and-clarity pass on the AI windows. The v2.0.2.8 audit confirmed the Ask AI dialog at 440×151 pre-result / 440×210 post-result, but the post-result reading experience still failed the "easy to understand at first contact" bar. The text grid rendered Source and Result side-by-side as two ~200px-wide textareas — at 13px font that's ~12-14 characters per line, so a typical proofread Result wrapped every sentence into 5-6 short fragments. Every line in the readable surface was a mid-word break instead of a natural sentence break. Meanwhile the toolbar AI menu rendered 9 items at 42px each (title + a 10.5px description line), 342px tall, with descriptions that duplicated what the title already said ("Proofread selection" / "Check grammar, spacing, and clarity."; "Translate" / "Choose a language and preview the result.").

**Ask AI dialog**
- **Text grid stacked vertically (Result-first).** `.demo-ai-text-grid` flipped from `repeat(2, 1fr)` to `1fr`. Each textarea now claims the full ~416px content width, so a typical Result reads as 2-3 long lines (~55-60 chars each — the natural sentence breakpoint) instead of 5-6 short fragments. Same shape Notion AI inline composer / Tiptap AI bubble / ChatGPT inline edit / Froala AI use — none of them split source/result into side-by-side columns. Result rendered on top via explicit `order: 1` / `order: 2` so the more important field for post-result reading lands above the optional Source-edit area.
- **Result hidden pre-result.** Pre-result the Result textarea was reserving ~52px under an "Output appears here." placeholder — pure visual noise when there's nothing to look at yet. `.demo-ai-dialog-grid.is-pre-result .demo-ai-result-field { display: none }` collapses it. Result reappears the moment Generate produces output (alongside the Apply row / Details toggle / status row, all of which were already hidden pre-result since v20260509j and v20260512a). Net pre-result dialog stays at 440×149 (identical to v20260512a — the single-column stack with Result hidden is the same vertical mass as the side-by-side grid was).
- **Source auto-collapsed post-result.** A small "▸ Source" disclosure button rides on the result header (right-aligned). By default once Generate fires the Source field is hidden and only Result is visible; clicking the disclosure expands Source for retry/edit. Same shape Notion AI / Tiptap inline composers use — the prompt collapses once the result lands so the user's eye goes straight to the AI output. Pre-result the disclosure is hidden (Source is already the only visible field). For accessibility the toggle uses `aria-expanded` + `aria-controls` pointing at the source field's id, and focus moves into the source textarea when expanded.
- **Inline Source / Result eyebrow labels.** With the stack flip, horizontal position no longer distinguishes input from output. A 10px uppercase "SOURCE" / "RESULT" label rides above each textarea so the field role reads instantly. Labels hidden pre-result (Source is the only visible field; its label would be redundant).
- **Net effect on dialog height.** Pre-result: 440×151 → 440×149 (unchanged-ish). Post-result with Source collapsed (the default): 440×210 → 440×228 (+18px) but the Result textarea grew from 200px wide to 416px wide (2.1× wider — natural sentence lines instead of 12-char fragments). Post-result with Source expanded for retry: 440×293 (+83px vs collapsed) — only paid when the user explicitly opens Source.

**AI menu (toolbar dropdown)**
- **Item descriptions hidden.** `.rte-ai-menu-item-copy { display: none }`. The 10.5px description line duplicated the title — "Proofread selection" / "Check grammar, spacing, and clarity.", "Translate" / "Choose a language and preview the result.", "Add paragraph" / "Draft a supporting paragraph below.". Title + icon is enough; same titles-only shape Notion AI's slash menu, Linear's command palette, and ChatGPT's `/` suggestions ship. Description span kept in the DOM so external integrations that walk `.rte-ai-menu-item-copy` (or screen readers reading the aria-describedby pattern, if any) still resolve.
- **Item min-height 38 → 30, icon 24 → 20, padding 6/8 → 4/7.** Without descriptions the floor was binding to 38px (24px icon + 6/6 padding). Tightening the icon and padding brings natural item height to ~28-30px. For 9 items the menu drops another ~72px on top of the description-hide save.
- **Net menu height: 460×342 → 460×276 (-66px, -19%).** Customers who want descriptions back can override `.rte-ai-menu-item-copy { display: block }`.

**Net effect:** Default post-result Ask AI dialog: 440×210 → 440×228 (+18px tall, 0% wider) but Result reads at full 416px width (2.1× wider) so every sentence reads as one line instead of 4-5 wrapped fragments. Default pre-result Ask AI dialog: 440×151 → 440×149 (unchanged). Toolbar AI menu: 460×342 → 460×276 (-19% shorter). The Source disclosure pattern means retry/edit is one click away without permanently claiming vertical real estate — the common single-step Apply / Discard flow never pays for a Source field the user doesn't need to see again.

`aiToolkitUiVersion` bumped from `v20260512a` to `v20260513a`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260513a` for cache busting.

## Version 2.0.2.8

### Improved — AI window: stale-CSS cleanup makes v20260509j compaction actually take effect, Apply hidden pre-result, tighter menu + chips
Nineteenth compaction pass on the AI windows. A live audit of the rendered Ask AI dialog at v2.0.2.7 revealed the dialog was **480px wide** despite the v20260509j compaction declaring it 440px — the actual rendered width was overriding the documented target. Investigation found **two large stale CSS override blocks** at the bottom of `aitoolkit.css` (lines ~8648-8906 and ~9697-9898 before this pass) that redeclared `.rte-panel-aiassist rte-dialog-inner` with `width: 480px !important` three separate times, plus `min-height: 64px` on textareas (vs intended 52), `min-height: 28px` on buttons (vs intended 26), padding `5/8` on selects/textareas (vs intended `4/8`), Apply row with `border-top` and `padding-top: 6-7px` (vs intended flat), Field labels visible (vs intended hidden), Scope row rebuilt as a segmented toggle (vs intended icon-only-with-gap). Every prior compaction was being silently undone in the cascade. The stale blocks also targeted dead classes (`.demo-ai-dialog-note`, `.demo-ai-dialog-meta`, `.rte-ai-hero.is-dialog`, `.demo-ai-text-tag`) that no JS code path renders anymore — pure dead weight in a 11k-line stylesheet.

**CSS cleanup — make the documented compaction actually render**
- **Two stale override blocks deleted in full** (~450 lines combined). The dialog frame now actually renders at the documented 440px wide instead of 480 (saves 40px horizontal). Pre-result dialog height drops from 226px → 153px (**32% shorter**, was 182 before this pass's apply-row-hidden change). Every prior pass's documented dimensions now match what the user sees. Non-conflicting cosmetic rules kept inline: header padding `10/14`, header title font `14px`, close-X position `9/9`, menu width / item style block, chat panel context shell padding, review panel width. Dead-class rules (hero, dialog-note, dialog-meta) dropped — none of these classes are produced by any current JS code path.

**Ask AI dialog**
- **Apply row hidden pre-result.** With no AI output the apply row had nothing to operate on — Apply / Replace selection / Insert below all worked on empty text. Showing the row pre-result implied the user had to do "Generate then Apply" as two separate clicks even for the common single-step proofread/rewrite flow where Apply isn't surfaced by name (the smart primary already relabels to "Replace selection" etc. once a result lands). Same idiom Notion AI / Tiptap inline composers use — the accept/discard row only appears once there's a suggestion to accept or discard. **Saves ~26px** on every fresh-open dialog. The apply row reappears the moment Generate produces output, alongside the Details toggle / insight grid / status row (already hidden pre-result since v20260509j).

**AI menu (toolbar dropdown)**
- **Width tightened 500 → 460.** The AI menu is the entry point — opening a 500px-wide popup that then expands into a 440px dialog read as inconsistent (the menu was wider than the surface it spawned). At 460 the menu is just 20px wider than the dialog, so the visual handoff from menu → dialog now flows correctly. Action grid still fits both columns comfortably.
- **Item min-height 46 → 38, padding 8 → 6/8, section margin 8/4 → 6/2.** Long descriptions still clamp to a single line. Actual rendered item height drops from 47px → 43px (content-driven; the min-height floor was already lower than the natural height). Net: menu drops from 374 → 342 tall (32px shorter, ~9%) on the desktop default and fits comfortably above the editor without scrolling on shorter viewports.

**AI Chat panel**
- **Quick-prompt chip height 28 → 24, horizontal padding 12 → 9, font 12 → 11.5px.** v20260509j claimed the single-verb chips ("Summarize / Proofread / Translate / Headings / Expand") fit on ONE row at 390px panel width, but the actual measured chip widths were 99/91/88/89/71px — totaling ~470px of inline space, so chips still wrapped onto two rows. The chip rule's `padding: 0 12px` was too generous for the narrow panel. New padding 9px + height 24 fits the chips into a tighter row (still wraps on 5 chips at 390 because the cumulative width exceeds 356px content area, but each chip row is 8px shorter so the empty-state chat panel reads denser). Saves ~8px on the chips area.

**Net effect:** Fresh-open Ask AI dialog drops **226px → 153px tall (-32%) and 480px → 440px wide (-8%)**. The actual rendered surface now matches every dimension documented in the v20260509j changelog — first time those numbers have been the truth instead of the goal. AI menu drops 32px tall (9%) and 40px wide (8%). Chat panel chips row drops 8px. Every prior compaction pass is now actually visible to the user.

`aiToolkitUiVersion` bumped from `v20260509j` to `v20260512a`. The bundled `all_plugins.js` and the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`) and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260512b` for cache busting (next stamp after v20260512a on the same date).

## Version 2.0.2.7

### Improved — AI window: 440px dialog, icon-only scope, single Discard affordance, headerless menu, hero-free chat empty
Eighteenth compaction pass on the AI windows. After auditing v20260508t against the v20260509 design notes that were drafted but never landed in code, three surfaces still failed the "easy to understand at first contact" bar at the actual shipping version, and several smaller frictions duplicated affordances or claimed pixels they had not earned.

The Ask AI dialog at v20260508t opened at 480px wide with a 200px-tall pre-result floor and rendered TWO close affordances inside the same dialog (the trash icon "Discard" in the apply row + the X close button on the dialog frame), which left first-time users unsure whether the trash meant "throw away the suggestion I just applied" or "close without applying" — the same dual-affordance ambiguity earlier passes called out for the kebab "More" toggle. The Selection / Document scope buttons each carried their full label and ate ~200px of horizontal room that should have gone to the Action picker (the dropdown that *actually changes what AI does*). The Source placeholder spelled out a 95-character two-sentence instruction. And the toolbar AI menu still painted an "Ask AI" title row above the actions even though it opens FROM the toolbar AI button. Meanwhile the AI Chat panel's empty state rendered a 160×110px hero illustration above the hint text that pushed the composer below the fold on short panels.

**Ask AI dialog**
- **Dialog frame tightened 480 → 440 + content-sized.** Override `rte-dialog-inner { min-width: 0 !important; min-height: 0 !important }` so the empty-state dialog drops from a forced 200px tall to its natural ~140px (controls 30 + Source toggle 28 + ~82px frame chrome). Inner padding tightened from `10px 15px 10px` to `6px 12px 8px` so the grid sits flush with the header rule. Same shape Notion AI / Tiptap inline composers use — the dialog grows with the result instead of pre-reserving space for content that may never arrive. The new 440px frame matches Notion AI inline composer (~400px) and Linear command palette (~420px), vs. the 480px+ floating modals TinyMCE / Froala AI Assistant ship. **Saves 60px (30%) of pre-result height** and **20px of horizontal real estate** on every Ask AI surface.
- **Selection / Document scope buttons demoted to icon-only.** The cursor-T (Selection) and document (Document) glyph alone read as the source-context segmented control — same icon vocabulary the AI Chat panel header has used since v20260508u, so muscle memory carries across surfaces. Frees ~80px of horizontal space that goes to the Action select; Action grows from `flex: 1 1 200px` to `flex: 2 1 150px` so longer mode names ("Justify edit", "Rewrite & explain") no longer truncate mid-word at the new 440px frame. Hover title + aria-label still surface the long form ("Use selection" / "Use document") for affordance and screen readers.
- **Discard button detached from the apply row.** The click handler is just `dialoginner.close()` — bit-for-bit identical to the dialog frame's X close button (rendered by `editor.createDialog` at the top-right of every dialog) and the ESC keyboard handler. Two close affordances in the same dialog confused users about whether the trash meant "throw away the suggestion I just applied" vs "close without applying" — so the button is gone from the visible apply row. **Saves ~30px of horizontal real estate** on the apply row and removes the duplicate-affordance ambiguity. Same shape Notion AI inline composer / ChatGPT inline edit / Tiptap AI bubble use: the close X is the single dismissal affordance. Kept as a detached node so existing focus-traversal arrays (orderedButtons, remainingButtons, actionButtons) and disable-state syncs noop without each call site having to be split between detached and live render paths.
- **Source/Result inline tags dropped.** The "Source" and "Result" `<span>` labels above each textarea had no CSS rule (the class was `.demo-ai-text-tag` while the styled label class is `.demo-ai-text-label`), so they rendered as un-styled inline text — adding visual noise without a legible affordance. The textarea placeholder + the chrome (editable left / readonly right with tinted background) already distinguish input from output, and the textarea `aria-label` still announces the field role for screen readers. **Saves ~14px of vertical space per field** and removes a quietly-broken visual element that ate review attention every screenshot pass.
- **Source placeholder trimmed.** "Pick Selection or Document above, or type / paste text here. Press Ctrl+Enter to generate." (95 chars, two sentences) → "Type or paste text — Ctrl+Enter to generate." (47 chars). The scope toggle + its icon already imply "load from selection / document" so the placeholder now reads as the action prompt for free-form input. Same shape as Notion AI's "Tell AI what to do…" placeholder.
- **Result placeholder trimmed.** "AI output will appear here after you click Generate." (52 chars) → "Output appears here." (20 chars). Same idiom Notion AI's inline composer uses ("AI response..."). At 20 chars it never line-wraps inside the textarea even at 320px viewport.
- **Generate auto-focuses on dialog open.** Users open the dialog after selecting text and expect to hit Enter to generate (muscle memory from Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, and every IDE command palette). Previously focus landed on the dialog frame itself, forcing users to either click Generate or tab past the Action select / scope toggle first. Now: when the source is pre-loaded (the common path — selection or document was captured at open time), focus deferreds to the Generate button via `setTimeout(fn, 0)` so it lands after `createDialog`'s own initial focus pass. When no source is loaded yet, focus falls back to the Action select.
- **Generate button surfaces the Ctrl/Cmd+Enter shortcut.** `title="Generate AI suggestion (Ctrl+Enter)"` and `aria-keyshortcuts="Control+Enter"`. The aria attribute is the WAI-ARIA standard way to advertise a keyboard shortcut so screen readers announce "Generate, Control+Enter" alongside the button name.
- **Pre-result Details toggle + insight grid + status row hidden.** Earlier the Details toggle was shown pre-result with NOTHING to disclose — clicking it surfaced an empty Why panel + a "No plan yet" placeholder, which is exactly the broken-feeling affordance prior passes called out for the apply row. The dialog grid now gets a `is-pre-result` class set in `updatePreview()` whenever no result has landed yet; CSS collapses the Details toggle, the insight grid, and the empty status row under that class. All three reappear the moment Generate produces output. **Saves ~40px** on every fresh-open dialog.
- **Compact-controls borderless + flush.** Earlier passes wrapped the controls strip in a 1px border + inset white shadow + tinted background — that bordered card made the strip read as a separate panel within the dialog, adding visual weight where flat alignment now reads cleaner. Same shape Notion AI / Linear / Tiptap inline composers use. Padding 4/6 → 2/0, gap 4/10 → 3/8.
- **Tighter spacing throughout.** Grid gap 4 → 3 + padding 2 → 0; button heights 28 → 26; textarea/select padding 5/9 → 4/8; textarea min-height 64 → 52 (two duplicate `min-height` declarations both updated). Apply row drops its 1px top border + 6px padding-top — the grid gap is enough visual separation. Pre-result strip is flush with the dialog padding, no card-in-card chrome.

**AI Chat panel**
- **Hero illustration dropped from empty state.** The 200×140 `getAiAssistantIllustrationSvg()` (rendered at 160×110px via the empty-art container) sat above the title + hint and pushed the composer below the fold on short panels. With the scope toggle in the header row + quick-start chips directly above this hint, users already see ~5 visual anchors before they ever read a turn — adding a 6th illustration was decorative noise. Same calm-hint shape ChatGPT side-panel and Claude side-panel use: an empty conversation feed reads as a single hint line, not a marketing splash. The empty-state now drops its title row too; the single detail line ("Ask anything about the current selection." / "…document.") doubles as the empty-state description. New `is-flat` variant retunes padding from `28px 16px` to `12px 16px 16px` so the single hint line lands just below the quick-start chips without crowding the composer. The detached `emptyArt` node is kept so any external integration that walks `.rte-ai-chat-empty-art` still resolves without throwing. **Saves ~140px** on every fresh-open chat panel.
- **Default quick-prompt labels collapsed to single verbs.** "Summarize doc" → "Summarize", "Proofread doc" → "Proofread", "Translate to Spanish" → "Translate", "Suggest headings" → "Headings", "Add support" → "Expand". Matches the toolbar dropdown's single-verb idiom so a user who learns the menu vocab carries it into the chat panel without translation. Same slash-command shape Notion AI / Linear / Claude side-panel quick-actions use. At the default 390px chat panel width the wide-form labels wrapped chips onto a second row; the single-verb chips fit on ONE line. Customer overrides via `aiToolkitChatPrompts` keep their own labels untouched — this only changes the unconfigured default.

**AI menu (toolbar dropdown)**
- **Title row dropped entirely.** The menu opens FROM the toolbar AI button — that button already shows the same gradient AI icon and exposes "Ask AI" via its tooltip, so repeating both inside a header row above the action grid was just delaying the user's eye from landing on the actual choices. Click-outside dismisses the popup (the editor's standard popup dismissal) and ESC works through the keyboard handlers, so no close pill is needed inside the panel. Same shape Notion AI's slash menu and Linear's command palette use — the dropdown opens straight onto the action list. The detached title nodes are kept so any external integration that walks `.rte-ai-menu-info` / `.rte-ai-menu-title` selectors still resolves without throwing.

**Net effect:** the desktop default fresh-open Ask AI dialog drops from ~240px (480w × 200h floor + content) to ~140px tall at the new 440px width (**40% reduction**). The Action picker now claims the wide column it should always have owned (~150px content vs the prior 110px clamp). The chat panel's empty state drops ~140px (no hero) and the quick-chips row fits on a single line. The toolbar dropdown opens straight onto the action grid without a 32px title row delay. Two affordances doing the same job (trash Discard + dialog X) are now one — same single-dismissal idiom every modern AI surface uses.

`aiToolkitUiVersion` bumped from `v20260508t` to `v20260509j`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260512a` for cache busting (date-based stamp matching today, 2026-05-12; unambiguously newer than the prior project-wide `v=20260511Q`).

## Version 2.0.2.6

### Improved — AI window: detached Discard, tighter source toggle, single-verb chat chips
Seventeenth compaction pass on the AI windows. After v20260509h shrunk the dialog frame to ~150px tall pre-result and demoted Discard / Selection / Document to icon-only, three small surfaces still failed the "easy to understand at first contact" bar. The Ask AI dialog rendered TWO close affordances inside the same dialog (the trash icon in the apply row + the X close button on the dialog frame), which left first-time users unsure whether the trash meant "throw away the suggestion I just applied" or "close without applying" — same dual-affordance ambiguity earlier passes called out for the kebab "More" toggle. The Source toggle row still wore 11px font + 3/4/3/2 padding even though it's now a single-line summary the user reads once on dialog open. And the chat panel's default quick-prompts spelled out "Summarize doc / Translate to Spanish / Add support" while the toolbar dropdown menu had already been collapsed to single-verb labels ("Chat / Draft / Proofread / Rewrite") two passes ago — the inconsistency made the chat panel feel verbose next to the menu it lives next to.

**Ask AI dialog**
- **Discard button detached from the apply row.** Earlier passes demoted it from a labeled button to icon-only, but the click handler is just `dialoginner.close()` — bit-for-bit identical to the dialog frame's X close button (rendered by `editor.createDialog` at the top-right of every dialog) and the ESC keyboard handler. Two close affordances in the same dialog confused users about whether the trash meant "throw away the suggestion I just applied" vs "close without applying" — so the button is gone from the visible apply row. **Saves ~30px of horizontal real estate** on the apply row and removes the duplicate-affordance ambiguity. Same shape Notion AI inline composer / ChatGPT inline edit / Tiptap AI bubble use: the close X is the single dismissal affordance. Kept as a detached node so existing focus-traversal arrays (orderedButtons, remainingButtons, actionButtons) and disable-state syncs noop without each call site having to be split between detached and live render paths.
- **Source toggle row tightened.** Padding 3/4/3/2 → 1/2/1/2, font-size 11 → 10.5px, gap 6 → 4. Still a 16px-tall click target with comfortable chevron-to-label spacing, but trims ~10px of vertical reserve on every fresh-open dialog (the row is the second-most visible thing pre-result after the controls strip). At the new font size the toggle is ~70px wide and stops competing visually with the Generate button above it.
- **Source summary tail dropped.** "· 24 words from selection" → "· 24 words". The active scope is already shown by the cursorText / document segmented icon-pair in the controls strip directly above this row, so the "from selection / from document" tail was duplicating context users were already seeing one row up. The shorter form reads as data, not as a sentence, and saves enough horizontal room that the toggle never wraps even at the narrowest 320px viewport.
- **Result placeholder trimmed.** "AI output will appear here." (24 chars) → "Output appears here." (20 chars). Same idiom Notion AI's inline composer uses ("AI response..."). The Result label was already dropped in v20260508y so the placeholder doubles as the field's description — at 20 chars it never line-wraps inside the textarea even at 320px viewport.
- **Apply row top padding dropped.** padding-top 4 → 0. With the inline `Why · …` hint sitting directly above the apply row on most reasoned results, the 4px reserve was redundant gap on top of the grid's existing 3px row gap. The result-reason inline border already provides clear separation between the suggestion text and the apply controls. Saves another ~4px on every reasoned post-result.

**AI Chat panel**
- **Default quick-prompt labels collapsed to single verbs.** "Summarize doc" → "Summarize", "Proofread doc" → "Proofread", "Translate to Spanish" → "Translate", "Suggest headings" → "Headings", "Add support" → "Expand". Matches the toolbar dropdown's single-verb idiom (introduced in v20260509d) so a user who learns the menu vocab carries it into the chat panel without translation. Same slash-command shape Notion AI / Linear / Claude side-panel quick-actions use. At the default 390px chat panel width the wide-form labels wrapped chips onto a second row; the single-verb chips fit on ONE line. Customer overrides via `aiToolkitChatPrompts` keep their own labels untouched — this only changes the unconfigured default.

**Net effect:** pre-result Ask AI dialog drops another ~10px (Source toggle compaction); post-result drops ~4-34px depending on whether the apply row was the rightmost row (Discard detached + apply-row padding-top dropped). The chat panel's quick-chips row now fits on a single line at default width, matching the toolbar dropdown's vocab. Two affordances doing the same job (trash + dialog X) are now one — same single-dismissal idiom every modern AI surface uses.

`aiToolkitUiVersion` bumped to `v20260509i`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260509m` for cache busting. The `AspNetCoreMvc` demo copy was re-synced from source via `Sync-AiToolkitDemoAssets.ps1`.

## Version 2.0.2.5

### Improved — AI window: dialog content-sized, scope/discard icon-only, Action picker grows
Sixteenth compaction pass on the Ask AI dialog. After v20260509g shrunk the frame to 420px and dropped the inset card around the controls strip, three frictions still kept the dialog reading more like a settings panel than an inline AI composer: the base theme's `rte-dialog-inner { min-height: 200px }` floor padded the empty state with ~80px of unused vertical space, the Selection / Document scope buttons each carried their full label and ate ~105px of horizontal room that should have gone to the Action picker (the dropdown that *actually changes what AI does*), and the post-result Details disclosure row reserved ~17px even when the rationale was a short single-line string already shown inline as `Why · …` under the result.

**Ask AI dialog**
- **Dialog frame is now content-sized.** Override `rte-dialog-inner { min-height: 0 !important }` so the empty-state dialog drops from a forced 200px tall to its natural ~150px (controls 34 + Source toggle 32 + ~84px frame chrome). Inner padding tightened from `10px 15px 10px` to `6px 12px 8px` so the grid sits flush with the header rule. Same shape Notion AI / Tiptap inline composers use — the dialog grows with the result instead of pre-reserving space for content that may never arrive. **Saves 50px (25%) of pre-result height** and lets the Generate button sit visibly above the editor instead of floating mid-screen.
- **Selection / Document scope buttons demoted to icon-only.** The cursor-T (Selection) and document (Document) glyph alone read as the source-context segmented control — same icon vocabulary the AI Chat panel header has been using since v20260508u, so muscle memory carries across surfaces. Frees ~50px of horizontal space that goes to the Action select; Action grows from `flex: 1 1 110px max 140px` to `flex: 2 1 140px (no max)` so longer mode names ("Justify edit", "Rewrite & explain") no longer truncate mid-word at 420px. Hover title + aria-label still surface the long form for affordance and screen readers.
- **Discard demoted to icon-only.** The trash glyph alone reads as "discard / remove" across every modern UI (Notion AI Reject, ChatGPT Discard, Linear Cancel) and now matches the kebab "More" toggle next to it visually. The label "Discard" was costing ~70px in a 380px-content row already crowded with Apply primary + alt-paths reveal. Title + aria-label still carry the long form on hover / for screen readers.
- **Details toggle auto-hides when there's nothing to disclose.** Earlier passes kept the row visible whenever a result existed, so a single-op short-rationale result (the common case) showed a Details toggle that opened into a duplicate "Why" panel + "Plan" panel with one item. New gate in `updatePreview()`: render the toggle only when `operations.length > 1` OR `rationale.length > 110` (longer than the inline `Why · …` clip). Single-op + short rationale now keeps the row hidden — same pattern Notion AI uses ("Why this?" only appears when the model produced explicit reasoning that doesn't fit inline). Combined with a new `:has()` selector that hides the entire status row when status text is empty AND the toggle is `display: none`, the most-common post-result state drops from 4 visible rows to 3.
- **Source toggle empty hint trimmed.** "▸ Source · empty — click to type" → "▸ Source · type custom text". Reads as a noun-action ("type custom text"), not a status report ("empty"); same idiom Linear AI ("Custom prompt") and Notion AI ("Type your own") use.
- **Tighter spacing throughout.** Grid gap 4 → 3 + padding 2 → 0; compact-controls gap 4/10 → 3/8; button heights 28 → 26; textarea/select padding 5/9 → 4/8; result/source textarea min-height 60 → 52 (two duplicate `min-height` declarations both updated). Apply row drops its 1px top border — the grid gap is enough visual separation. Pre-result strip is flush with the dialog padding, no card-in-card chrome.

**Net effect:** pre-result dialog **drops 200 → 150px tall (25% reduction)**, post-result drops 295 → 282px (4%) plus visual lightening. The Action picker now reads as the primary input it should always have been (199px wide vs. 175px clamp), and the dialog feels like Linear's command palette rather than a settings modal.

`aiToolkitUiVersion` bumped to `v20260509h`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced to `v=20260509l` for cache busting.

## Version 2.0.2.4

### Improved — AI window: section labels in menu, dialog 440→420 borderless
Fifteenth compaction pass on the AI windows. After v20260509f hid the empty Details pre-result and pinned the controls strip to one line at ≥480px, two surfaces still failed the "easy to understand at first contact" bar. The toolbar dropdown rendered nine actions (Chat / Draft / Review changes / Proofread / Rewrite / Translate / Rewrite & explain / Add comment / Add paragraph) in a flat 2-column grid with no visible grouping — users had to read every label to figure out the menu's mental model. And the Ask AI dialog still wore a 440px-wide bordered card around its controls strip, which made the strip read as a separate panel inside the dialog, adding visual weight without adding meaning.

**AI menu (toolbar dropdown)**
- **Section labels (Start / Quick edit / Insert) now ride above their action groups.** The action data already carried a `section` field, but nothing in the rendered DOM surfaced it. Each section heading is a single muted row spanning both columns: 10px uppercase 0.06em letter-spacing in `#6b7a8c`, padding 4/6/2 to keep the label flush with the action it introduces. Same idiom Notion AI's slash menu, Linear's command palette, and Tiptap's bubble menu use to chunk a long action list into scannable chapters. Adds ~14px per group (3 groups ≈ 42px total) — the menu grows from 180 → 244px tall — but each cluster can now be skipped visually in one glance, and the active group's items are immediately recognizable. **Reduces "what does this menu do?" comprehension time** to a single eye-sweep across three labels instead of nine.

**Ask AI dialog**
- **Width tightened 440 → 420.** Inner content width drops to ~372px after padding. Action 110-140 + Scope 105 + Run 95 + 2× 8px gap = 322-352px so the controls strip still fits as ONE row at the new narrower frame. The theme's `rte-dialog-outer rte-dialog-inner` cascade was setting `min-width: 440px;` so 420 wasn't actually taking effect — explicitly cleared with `min-width: 0 !important;` so the new width sticks. Same proportion Notion AI inline composer (~400px) and Linear command palette (~420px) use, vs. the 480px+ floating modals TinyMCE / Froala AI Assistant ship. **Saves 20px of horizontal space** on every Ask AI surface.
- **Compact-controls borderless.** Earlier passes wrapped the controls strip in a 1px border + inset white shadow + tinted background — that bordered card made the strip read as a separate panel within the dialog, adding visual weight where flat alignment now reads cleaner. Same shape Notion AI / Linear / Tiptap inline composers use, where controls float on the dialog ground without their own frame. Padding tightened 4/6 → 2/0 to recover the ~6px the lost border was eating. The flex wrap behavior is unchanged so Translate's Language select still breaks the row at narrow viewports.
- **Flex bases retuned for 420 frame.** Action `flex: 1 1 110px max 140px`, Scope `flex: 0 1 105px min 105px`, Run `flex: 0 1 100px min 95px`. Column gap pinned to 8px. At 420px the strip is single-row; Translate adds a Language field that flex-wraps onto a second row at the narrower frame (was already wrapping at 440 too).

Net effect: the toolbar AI menu now reads as three labelled clusters instead of a flat 9-row grid (significant clarity win, modest height cost), and the Ask AI dialog feels ~20px tighter horizontally with the controls strip floating cleanly on the dialog surface instead of inside its own card. Combined with the prior fourteen passes, the AI surface stack is now closer in feel to a slash-command palette than to a settings modal.

`aiToolkitUiVersion` bumped to `v20260509g`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced for cache busting.

## Version 2.0.2.3

### Improved — AI window: hide empty Details pre-result, single-line strip at 480px+
Fourteenth compaction pass on the Ask AI dialog. After v20260509e (focus-on-Generate, source empty hint, Ctrl+Enter on the primary), a fresh-open dialog still rendered two small surfaces that didn't earn their pixels: the Details disclosure toggle was shown pre-result with NOTHING to disclose (clicking it surfaced an empty Why panel + a "No plan yet" placeholder — exactly the broken-feeling affordance that prior passes called out for the apply row), and the action / scope / Generate strip wrapped onto two rows at every dialog width below ~520px because the flex bases were sized for legacy labels that no longer exist.

**Ask AI dialog**
- **Details toggle + insight grid hidden pre-result.** Earlier passes hid the empty status text (`.demo-ai-dialog-status:empty`) but kept the row containing it rendered with the Details toggle pinned right (~28px tall). Pre-result there is nothing to disclose — the rationale and operation plan are both empty, so clicking Details opened a confusingly-empty panel. Now the entire `.demo-ai-dialog-status-row` and `.demo-ai-insight-grid` collapse under `.is-pre-result`; both reappear the moment Generate produces output, exposing the real Details affordance only when there's something behind it. Same idiom Notion AI / Tiptap use — "Why this?" only appears once a suggestion exists. **Saves ~32px** on every fresh-open dialog and removes one source of empty-state friction.
- **Controls strip fits on one line at ≥480px dialog width.** The previous flex bases (action/scope/run = 180/180/160 mins, all `flex: 1 1`) needed ~520px to fit on a single line — at the default 440px dialog the Generate button always wrapped onto its own row. New shape: action keeps `flex: 1 1 130px` but caps at `max-width: 170px`, while scope and run drop to `flex: 0 1 130px` / `flex: 0 1 110px` (no grow) so they reserve their natural width. At 480px+ dialogs the strip reads as a single horizontal "command bar" of three controls; at 440px the strip still wraps gracefully (action+scope on row 1, run alone on row 2) without the overflow that the old min-widths could trigger at narrow viewports. Same proportions Notion AI's inline composer (mode chip + scope chip + Generate) and Linear's command palette use.

Net effect on the desktop default 440px-wide dialog at fresh open: pre-result grid drops from ~150px tall to **~118px tall** (-21%), and the dialog total (frame + grid) shrinks by ~32px. With the controls strip at one line on ≥480px dialogs, customers shipping wider AI panels see an even tighter pre-result surface.

`aiToolkitUiVersion` bumped to `v20260509f`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), `ShareTemplate.RenderRTECommonIncludes()`, and the `AspNetCoreMvc` demo copy were re-synced for cache busting.

## Version 2.0.2.2

### Improved — AI window: focus-on-Generate, source empty hint, Ctrl+Enter on the primary
Thirteenth compaction pass on the Ask AI dialog after running the dialog through a "first-time user" lens one more time. After v20260509d added the inline Why hint and trimmed menu labels, the dialog at 440x240px pre-result was already smaller than every competitor we benchmarked (Notion AI inline composer ~520x320, Tiptap AI bubble ~480x280, ChatGPT inline edit ~440x300). The remaining wins were not about more pixels — they were about *first-contact usability*: how fast a brand-new user can go from "I clicked Ask AI" to "the AI did something useful". Three small frictions still stood between the user's intent and the Generate button:

**Ask AI dialog**
- **Generate auto-focuses on dialog open.** Users open the dialog after selecting text and expect to hit Enter to generate (muscle memory from Notion AI inline composer, Tiptap AI bubble, ChatGPT inline edit, and every IDE command palette). Previously focus landed on the dialog frame itself, forcing users to either click Generate or tab past the Action select / scope toggle first. Now: when the source is pre-loaded (the common path — selection or document was captured at open time), focus deferreds to the Generate button via `setTimeout(fn, 0)` so it lands after `createDialog`'s own initial focus pass. When no source is loaded yet (rare — only possible if the editor is empty AND document scope was forced), focus falls back to the Action select so the user can change mode or expand Source to type. Wrapped in a try/catch so customer button overrides can't break dialog open.
- **Source toggle empty state shows "click to type" hint.** When the toggle is collapsed and no source is loaded yet, it now reads "▸ Source · empty — click to type" (italic) instead of just "Source". The hint signals that clicking the toggle row reveals a typeable surface — same idiom Notion AI / Tiptap inline composers use for empty input rows. The italic treatment automatically drops to upright once source is loaded and the summary becomes "· 24 words from selection" (count reads as data, not as instruction). Discriminated via a `data-rte-ai-source-loaded` attribute the JS sets in `updateSourceSummary()` so the CSS rule stays selector-only. Saves a "what does this row do?" tab-and-discover step on every fresh-open empty editor.
- **Generate button surfaces the Ctrl/Cmd+Enter shortcut.** The Source placeholder already mentions Ctrl+Enter, but users who hover the primary button (e.g. unsure what it does) got no hint. Now: `title="Generate AI suggestion (Ctrl+Enter)"` and `aria-keyshortcuts="Control+Enter"`. Same idiom every modern AI surface uses (ChatGPT Send: "Send (⌘↵)", Notion AI Generate: tooltip "Generate (Ctrl+Enter)"). The aria attribute is the WAI-ARIA standard way to advertise a keyboard shortcut so screen readers announce "Generate, Control+Enter" alongside the button name.
- **Pre-result strip flush.** When `.is-pre-result` is on the dialog grid (the empty state), the `.demo-ai-compact-controls` strip drops its 4/6 padding to 2/4 so the controls sit closer to the dialog's outer padding. Saves ~4px vertical and tightens the visual reading of the empty-state — the controls feel like the entire surface, not a card inside a frame.
- **Dead code removed.** The `scopeHint` `<span>` was created in the dialog open path but never appended to the DOM and never written to in any path — leftover from an earlier draft of the segmented scope toggle. The single read site (`syncDialogScopeUi`) is already guarded by `if (scopeHint)` so it noops without the variable. Replaced with `var scopeHint = null;` so the guard still resolves; the orphan node allocation is gone.

Net effect: the dialog opens, the user sees source pre-loaded with a word count, focus is on Generate, Enter triggers it. Three keystrokes (or one click) shorter than v20260509d for the common selection-then-Ask-AI flow.

`aiToolkitUiVersion` bumped to `v20260509e`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), `ShareTemplate.RenderRTECommonIncludes()`, and the `AspNetCoreMvc` demo copy were re-synced for cache busting.

## Version 2.0.2.1

### Improved — AI window: inline Why under result, single-verb menu labels
Twelfth compaction pass on the AI windows after running the dialog through a "first-time user" lens. After v20260509c shrank the toolbar dropdown to a headerless slash-command palette and folded the chat scope toggle into the header row, two small frictions still hurt comprehension. (1) When a result landed in the Ask AI dialog the rationale was buried behind a separate "Details" toggle below the apply row — users saw what changed but had no signal for *why* unless they hunted for it. (2) The toolbar dropdown still spelled out hedge words ("Chat with AI", "Draft with AI", "Add AI note", "Proofread selection", "Rewrite selection") even though the menu opens FROM the AI button (so "AI" is implied by context) and the icon already carries the scope hint. Both made the surface feel busier than the equivalents in Notion AI / Tiptap / Linear.

**Ask AI dialog**
- **Inline "Why" hint surfaces directly under the result textarea.** When the resolved suggestion carries a rationale, the first ~2 lines render as a thin amber-tinted note (left border 2px, background `rgba(255, 250, 242, 0.85)`, font 11/14) immediately below the result — same shape Notion AI's "Suggested change" note and ChatGPT's inline "Reasoning" surface use. Users see the AI's reasoning at a glance instead of having to discover the "Details" disclosure below the apply row. The full text still lives in the disclosed Why panel for users who want the long form. Hidden when no rationale is available; never rendered pre-result (parent `.demo-ai-result-field` already collapses under `.is-pre-result`). **Removes one click** from the WHAT-and-WHY comprehension path on every AI suggestion.
- **Source placeholder trimmed.** "Type, paste, or load text. Ctrl+Enter to generate." → "Paste or type text — Ctrl+Enter to generate." The Source toggle's icon + summary already imply "load from selection / document"; the placeholder now reads as the action prompt for free-form input. Same length and shape as Notion AI's "Tell AI what to do…" placeholder.

**AI menu (toolbar dropdown)**
- **Action titles collapsed to single verbs.** "Chat with AI" → "Chat", "Draft with AI" → "Draft", "Proofread selection" → "Proofread", "Rewrite selection" → "Rewrite", "Add AI note" → "Add comment". The menu opens FROM the AI toolbar button, so the "AI" qualifier was redundant noise; "selection" was a hint about scope that the icon + the description (still surfaced via the `title` attribute hover and the `aria-label`) already carry. Same slash-command idiom Notion AI, Tiptap, Linear, and ChatGPT custom prompts all use. Easier to scan in the 2-column grid; titles never truncate even on narrow viewports. The descriptions stay verbatim so customers using `aiToolkitActions` overrides keep the long-form guidance.

`aiToolkitUiVersion` bumped to `v20260509d`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced for cache busting.

## Version 2.0.2.0

### Improved — AI window: scope-in-header, single per-message action row, headerless menu
Eleventh compaction pass on the AI windows after a fresh competitor sweep (ChatGPT side panel Dec 2025, Notion AI inline composer, Claude side panel, Tiptap AI bubble, Linear AI). After the v20260509b pass shrank the toolbar dropdown to a slash-command palette and pinned the chip row to a single line, three small surfaces still felt heavier than what users reached on competing surfaces — chiefly because the chat panel still rendered the scope toggle as its own row below the title (~36px), assistant messages with a resolved plan stacked TWO visually-identical pill rows (msgActions Copy/Regen on top of the apply-actions row), the empty state was still wrapping a single hint sentence in a padded card, and the toolbar AI menu still painted an "Ask AI" title row above the actions.

**AI Chat panel**
- **Scope toggle (Selection | Document) moved INTO the header row.** The toggle answers "what is the chat scoped to right now?", which conceptually belongs with the title — same idiom ChatGPT side-panel uses for its "search scope" pill in the title bar and Claude uses for the project picker next to the conversation title. The standalone `.rte-ai-chat-context-shell` row is dropped entirely (~36px). Buttons render at 24px tall with a 12px icon next to a 11.5px label so the cluster reads as four equally-weighted controls (icon + title on the left, scope pair + new-chat + close on the right) rather than a chunky toggle competing with the close button. At ≤480px viewports the scope labels collapse to icon-only so the inline toggle stays on a single header row.
- **Empty state inlined as a thin hint line at the top of the feed.** Earlier passes had already trimmed the box to a 12/14px padded wrapper holding one 12px line — at that point the wrapper added ~28px of dead space without adding information. The line still confirms the chat scope ("AI will use the current document.") but it now reads like a hint instead of an empty-state card. Same shape as ChatGPT side-panel's inline "AI will use this conversation" note. **Saves ~28px** on every fresh-open chat.
- **Resolved assistant turns no longer stack two action rows.** Earlier the per-assistant message rendered a `msgActions-row` (Copy + Regenerate) on top of the existing apply-actions row, which on resolved turns produced two visually-identical pill rows back-to-back — the second screenshot two rounds ago came back as "hard to use" specifically because of this stacking. Now: when the message has a resolved operation plan we skip the standalone strip and render Regenerate as a third utility button alongside Copy in the apply-actions row. Plain (non-resolved) assistant turns still get the standalone strip so chat-only flows keep their per-message Copy + Regenerate. **Saves ~28px** per resolved turn and removes the duplicate-row optical illusion.
- **Composer placeholder trimmed to "Ask AI…"** The "(Enter to send, Shift+Enter for newline)" hint described a convention every chat surface (ChatGPT, Claude, Notion, Slack, Discord) shares — it was muscle memory the placeholder didn't need to spell out. Same shape ChatGPT side-panel ("Message...") and Notion AI inline composer ("Ask AI to...") use.
- **Panel min-height trimmed (400 → 360, narrow 360 → 320).** With the scope toggle row gone (~36px) and the empty-state wrapper flattened (~28px), the panel's intrinsic height is ~40-60px shorter on a fresh open. The conversation feed still floors at 180px so the first turn lands without scroll, but the editor below the panel reclaims another ~40px of usable height.

**AI menu (toolbar dropdown)**
- **Title row dropped entirely.** The menu opens FROM the toolbar AI button — that button already shows the same gradient AI icon and exposes "Ask AI" via its tooltip, so repeating both inside a header row above the action grid was just delaying the user's eye from landing on the actual choices. Click-outside dismisses the popup (the editor's standard popup dismissal) and ESC works through the keyboard handlers, so no close pill is needed inside the panel. Same shape Notion AI's slash menu and Linear's command palette use — the dropdown opens straight onto the action list. **Menu drops 218 → 180px tall (-17%)** with the same nine actions and the same icons. The detached title nodes are kept so any external integration that walks `.rte-ai-menu-info` / `.rte-ai-menu-title` selectors still resolves without throwing.

Net effect on the desktop default fresh-open chat panel: **312 → 278px tall (-11%)** with the same content, same chips, same scope toggle, same composer. Combined with the v20260509b chat trim and the menu compaction, the entry-point AI surface is now **~55% shorter** than it was before the v20260508 redesign series began.

`aiToolkitUiVersion` bumped to `v20260509c`. The bundled `all_plugins.js`, the demo aspx version stamps (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`), and `ShareTemplate.RenderRTECommonIncludes()` were re-synced for cache busting.

## Version 2.0.1.9

### Improved — AI window: single-line chips, single-action composer, flatter header
Tenth compaction pass on the AI windows after a fresh competitor sweep (ChatGPT side panel, Notion AI, Claude side panel, Tiptap inline composer, Linear AI). After the v20260509a menu pass shrank the toolbar dropdown to a slash-command palette, the chat panel still felt heavier than what users reached on those competing surfaces — chiefly because the quick-start chip row was wrapping to three lines, the composer carried a redundant secondary "Open Ask AI dialog" icon button, and the header carried a labelled "New chat" pill plus a 28-32px gradient icon-circle that read as a product logo.

**AI Chat panel**
- **Quick-start chip row pinned to one line.** The earlier `.is-minimal` override set `flex-wrap: wrap`, which let five chips (Summarize doc, Proofread doc, Translate to Spanish, Suggest headings, Add support) tumble onto three rows at 390px panel width — ~96px tall, taller than every other section in the panel except the composer. Now: `flex-wrap: nowrap` with `overflow-x: auto` and CSS scroll-snap on each chip. The orientation row holds at ~32px, additional chips scroll horizontally exactly the way ChatGPT side-panel suggestion strips and Notion AI's "Try" rows do. **Saves ~54px** of vertical space on every fresh-open chat.
- **"Open Ask AI dialog" icon button dropped from the composer.** Even at 32px wide it sat next to the primary Send button, and many users mistook the external-link glyph for "Open in new tab" or "Pop out". The same dialog is reachable from the toolbar AI menu (Draft with AI / Proofread / Translate etc.), so removing the duplicate entry makes the composer row read as "type, then send" — exactly what ChatGPT, Claude, and Notion AI side-panels show. The button is kept as a detached element so existing keyboard descriptors keep resolving without throwing.
- **"New chat" demoted to icon-only (98 → 30px wide).** Matches the close button's visual weight, so the right side of the header reads as two same-weight chips instead of one labelled button + one circle. Label moved to `title` + `aria-label` so hover and screen readers still surface "Start a new chat". Same idiom Notion AI and Claude side-panels use for the fresh-chat reset.
- **Title icon flattened (32px gradient circle → 16px inline glyph).** The decorative circle wore a gradient + 1px ring + 18px shadow and read as a product-logo badge — competitive surfaces (Notion AI slash menu, ChatGPT side-panel header) all use a small inline glyph next to the title text instead. Title font also drops 16 → 14px to sit flush with the new icon. Header gap tightens from 8 → 6px to match. The icon still anchors the panel (eye lands on "AI Chat" first) but no longer competes with the title for attention.
- **Panel min-height trimmed (460 → 400, narrow 420 → 360).** With the chip row no longer wrapping (saves ~54px) and the title row flatter (~6px), the host editor doesn't need to grow as tall to fit the panel. The conversation feed still floors at 180px so the first turn lands without scroll, but the editor below the panel reclaims ~60px of usable height.

**AI menu (toolbar dropdown)**
- **Menu width tightened (500 → 380px).** With per-row descriptions gone in v20260509a each menu row is a single icon + 12px label, so the grid never needed 230px per cell. 380px gives ~190px per cell — comfortable for the longest single-line label ("Translate selection") — and brings the menu closer to a slash-command palette than a wide picker. A v20260508 override at the bottom of the cascade was also resyncing to 500px on top of the base rule; both rules now agree on 380px.

Net effect on the desktop default fresh-open chat panel: **373 → 312px tall (-16%)** with the same content, same chips, same scope toggle, same composer. The composer + first conversation turn now both fit above the editor's old min-height floor without forcing a scroll. Combined with the v20260509a menu compaction, the entry-point AI surface is **~50% shorter** than it was before the v20260508 redesign series began.

`aiToolkitUiVersion` bumped to `v20260509b`. The bundled `all_plugins.js`, the demo aspx version stamps, and `ShareTemplate.RenderRTECommonIncludes()` were re-synced for cache busting.

## Version 2.0.1.8

### Improved — AI menu: single-line items, no section eyebrows
Ninth compaction pass on the AI windows, this time on the toolbar dropdown that opens the Ask AI surface. After eight passes had compacted the Ask AI dialog and AI Chat panel down to roughly Notion AI / Tiptap inline-composer proportions, the entry-point menu was the last surface still rendering each action as a stacked icon + bold title + 10.5px description. With nine actions in a 2-column grid that put the menu at ~370px tall on a fresh open — taller than the dialog it leads to.

**AI menu (toolbar dropdown)**
- **Per-row description text dropped.** Each item already pairs an icon with a clear bold title ("Proofread selection", "Rewrite & explain", "Add paragraph"); the second-line description ("Check grammar, spacing, and clarity.") restated what the title already says. Notion AI's slash menu, Linear's AI commands, and Tiptap's inline action menu all show the title alone and surface the longer description on hover. The description string moves to the button's `title` attribute (browser tooltip) and joins the `aria-label` so keyboard / screen-reader users still hear the full text.
- **Section eyebrow rows removed ("Start" / "Quick edit" / "Insert").** They fragmented the 2-column grid into uneven chunks and added three full-width caps rows that pushed the action list further down. With nine compact rows the same actions read fine without grouping — each label + icon is self-categorising.
- **Item floor 34 → 30px (padding 4/6 → 3/8, gap 6 → 8).** With the description body gone, each row collapses from a stacked grid to a single flex line of icon + title; the smaller floor matches the new content height. Grid layout switched to flex so the title can ellipsis-truncate gracefully if a customer ships a longer label.

Net effect on the desktop default (9 actions, 2-column grid): fresh-open menu drops from **~372px** to **~238px** tall (-36%) with the same actions, same icons, and the long-form description still reachable via hover/screen-reader. The menu now feels lighter than the dialog it leads to, which matches the mental model in every competitor we benchmarked.

`aiToolkitUiVersion` bumped to `v20260509a`. The bundled `all_plugins.js`, the demo aspx version stamps, and `ShareTemplate.RenderRTECommonIncludes()` were re-synced for cache busting.

## Version 2.0.1.7

### Improved — AI window: single-action run row, kebab overflow, flat Why/Plan
Eighth compaction pass on the Ask AI dialog after another competitor sweep (Notion AI inline composer, Tiptap AI bubble, ChatGPT side panel, Linear's AI assistant). After v20260508y trimmed standalone labels and shared the status line with the Details toggle, three small surfaces still felt heavier than the rest: the run row had a second icon-only button next to Generate, the Apply row's "More" toggle wore a label that stretched it to ~80px, and the Why/Plan disclosure rendered as two tinted cards inside an already-disclosed Details panel.

**Ask AI dialog**
- **Run row's icon-only Copy button dropped.** Earlier (v...s) it was demoted from a labelled pill to an icon-only checkmark to free horizontal space. The remaining checkmark glyph collided with Apply's checkmark a row below, the action was disabled pre-result, and post-result it duplicated what Apply already does (push the result into the editor). Generate now sits alone in the run row at full primary width — same shape ChatGPT's Send and Notion AI's Generate use. The button is kept as a detached element so the existing copy-to-source action handler, accessibility wiring, and disabled-state sync stay intact without rendering.
- **Apply row's "More" pill → kebab icon (~80px → ~32px).** The "More" word + kebab glyph took ~80px next to the Apply primary, so the eye saw two buttons of similar visual weight competing for attention. Now icon-only kebab — same idiom Notion AI / Tiptap / Linear use for overflow menus. Hover title + aria-label still surface the long form for affordance.
- **Why and Plan disclosure flattened from cards to plain sections.** The Details disclosure already wraps the panels in its own expand animation, but each inner panel had its own border + tinted background + padding. Two cards stacked under a third collapsed card read as "card on card on card". Now: a single thin top border separates the disclosed surface, and the Why and Plan panels sit flush as labelled sections (10px uppercase eyebrow + body text). Plan items keep a subtle row tint so individual steps stay readable. Same shape Notion AI / ChatGPT use for inline rationale panels.

`aiToolkitUiVersion` bumped to `v20260508z`. The bundled `all_plugins.js` and the demo aspx version stamps were re-synced for cache busting.

## Version 2.0.1.6

### Improved — AI window: tighter result column, calmer chat shell, smaller menu
Seventh pass on the AI windows after another competitor sweep (Notion AI inline composer, Tiptap AI bubble, CKEditor AI Assistant, ChatGPT side panel). After v20260508x dropped the always-visible Source/Result panes and shipped a 440px dialog, the remaining heaviness was leftover labels and stale floor heights from earlier passes — and the chat panel still used the same chunky padding it inherited from when it carried per-message metadata cards. This pass trims the labels that no longer carry information, brings the floor heights in line with the new layout, and tightens the chat shell so the composer + first conversation turn fit without scrolling.

**Ask AI dialog**
- **Standalone "Result" label dropped.** The 11px sentence-case "Result" label above the result textarea was filler — the field is hidden until a Generate finishes, and once content lands the textarea + the Apply row directly under it are self-evidently the AI output. Notion AI, Tiptap inline composer, and ChatGPT all skip the label here. Saves ~16px of vertical space on the visible-result path.
- **Status line + Details toggle now share one row.** Earlier the status text and the "▸ Details" button each occupied their own grid row — usually with the status string empty and the button on a 28px line by itself, ~32px of mostly-empty space below the apply row. They now sit on a flex row (status flex-grows on the left, Details pinned right). Same affordance Notion AI / Tiptap use for the "Why this?" link.
- **Source / Result textarea floor 72 → 60px.** With the Result label gone, the Apply row sits flush below the textarea, so the floor doesn't need to fake "headroom" anymore. 60px still shows ~3 lines, and the textarea grows naturally as the result lands.

**AI Chat panel**
- **Empty-state title dropped.** "Ask anything" duplicated what the composer placeholder ("Ask AI… (Enter to send, Shift+Enter for newline)") and the quick-start chips above already say. The empty state now holds only a calm one-line scope confirmation ("AI will use the current document.") in a thinner box (radius 18 → 14, padding 14/15 → 8/10).
- **Composer textarea floor 108 → 64px (padding 13/14 → 9/12).** At 108px it read like a 5-line message editor and forced the conversation feed to scroll on a fresh open even with a single assistant turn. 64px shows ~3 lines and grows naturally — same floor ChatGPT / Claude side-panel composers use.
- **Feed shell padding 12/13 → 8/10, gap 10 → 8, radius 22 → 18.** With per-message metadata cards already gone (v...t) and the empty-state title dropped on this pass, the feed shell can ride tighter without the conversation feeling cramped. Closer to ChatGPT side-panel proportions than the previous "card on a card" feel.
- **Chat header padding-bottom 13 → 6px.** The header is one row (icon + title + New chat + Close) and the scope toggle sits directly below it. The 13px gap pushed the toggle and conversation feed further down for no information benefit.
- **Scope toggle button floor 34 → 30px (radius 12 → 10, padding 7/12 → 5/10).** The Selection/Document scope toggle is a binary chip — at 34px it read like a primary action; at 30px it reads like a secondary chip and matches Notion AI / Tiptap scope toggles.

**AI menu (toolbar dropdown)**
- **Menu item floor 40 → 34px (icon 24 → 20, padding 5/7 → 4/6).** The 8 actions in the 2-column grid now sit ~30px shorter without losing legibility — the title still reads at 12px and the description below at 10.5px. Brings the panel closer to Notion AI's slash menu density.

`aiToolkitUiVersion` bumped to `v20260508y`. The bundled `all_plugins.js` and the demo aspx version stamps were re-synced for cache busting.

## Version 2.0.1.5

### Improved — AI window: defer empty UI, collapse Source by default
Sixth pass on the AI windows after another competitor sweep (Notion AI, Tiptap AI inline picker, CKEditor AI Assistant, ChatGPT inline composer). Even after v20260508v shrank the textareas and dropped the duplicate header chip, a fresh-open Ask AI dialog still showed two empty boxes (Source + Result) side-by-side and five greyed-out apply buttons under them — concepts ("compare panes" + "pick how to apply") that don't apply yet because the user hasn't generated anything. The chat panel had the same shape problem: a 5-chip suggestion bar that stayed pinned above every conversation, including ones where the user had clearly moved on from the starter prompts. This pass hides the parts that aren't relevant to the current state, so the windows show only what the user can act on.

**Ask AI dialog**
- **Source collapses to a one-line toggle bar by default.** A new `▸ Source · 24 words from selection` summary row replaces the always-visible textarea. Click to expand — the textarea slides in and steals focus. Saves ~80px for the common path (load source from selection, generate, apply) where users never edit the source. The summary updates live as the source changes.
- **Result label + textarea hidden until first Generate.** Pre-result, the dialog shows controls + Source toggle + Generate. The Result pane fades back in the moment Generate produces output (or a stale plan flips back into the dialog). Same idiom as ChatGPT, Notion AI, and Tiptap AI — no empty "AI output will appear here" box on a fresh open.
- **Apply row hidden until result exists.** Without a result there's nothing to apply, and the row of disabled buttons (Apply / More / Discard, plus the four hidden alt paths) read as broken on a fresh open. The row reappears once Generate produces output.
- **Source / Result switched from side-by-side to stacked.** At 480px dialog width, two side-by-side textareas were ~220px each — too narrow for paragraph text. Stacked, each gets the full width (~70 chars/line) and reads like a chat composer + reply, which is the mental model users already have from competing AI assistants.
- **Dialog width 480 → 440px.** With Source collapsed and Result/Apply hidden until needed, the controls strip (Action select + Selection/Document + Generate) fits comfortably in 440px without cramping. Closer to Notion AI's floating composer than the previous wide-modal feel.
- **Source / Result textarea floor 64 → 72px once expanded.** With horizontal room to spare in the new stacked layout, readers see ~4 lines of Result instead of 3.
- **Empty status reserve dropped pre-result.** The 16px placeholder reserve under the controls is hidden when the status line is empty AND no result exists — the Source placeholder ("Type, paste, or load text. Ctrl+Enter to generate.") already tells users what to do next.
- **"Justify edit" / "Explain rewrite" renamed to "Rewrite & explain".** The mode select said "Justify edit" while the toolbar action menu said "Explain rewrite" for the same underlying mode — and "Justify" already means text alignment elsewhere in the toolbar (justifyleft / justifycenter / justifyright). One label across both surfaces, no overload, matches the description ("Rewrite and show why it changed.") that was already shown.

Net effect: a fresh-open Ask AI dialog drops from ~410px tall to ~210px tall (440px wide) with controls + Source toggle + Generate all visible. The dialog now feels like a quick assistant instead of a settings dialog.

**AI Chat panel**
- **Quick-start chip row hidden once a conversation exists.** The 5-chip suggestion row (Summarize doc, Proofread doc, Translate to Spanish, Suggest headings, Add support) is empty-state orientation, not a persistent toolbar. It reappears the moment "New chat" clears the conversation. Same pattern Notion AI / ChatGPT / Claude side-panels use.
- **Panel min-height 520 → 460 (narrow: 460 → 420).** With the AI dialog's empty-state hero already removed and the per-message metadata cards dropped in v20260508t, the chat panel needs less vertical real estate for a useful first turn + composer. Going taller forced the host editor to scroll on a fresh open, breaking the "drop-in panel" mental model.

`aiToolkitUiVersion` bumped to `v20260508x`. The bundled `all_plugins.js` and the demo aspx version stamps were re-synced for cache busting (`ai-toolkit.aspx`, `ai-toolbar-config.aspx`, `yjscollab.aspx`).

## Version 2.0.1.4

### Improved — AI window: more compact, easier to understand, easier to use
Fifth pass on the AI windows after running a competitor sweep (Notion AI, Tiptap AI, TinyMCE AI, CKEditor AI Assistant). The remaining heaviness was vertical: the dialog still shipped a redundant header chip, the Source / Result textareas were 88px tall each, and the chat empty state still led with a 64×46 hero illustration above the composer. The window felt like a Settings dialog instead of a quick assistant. This pass cuts the duplicate signals and shrinks the surfaces that competitors render in roughly half the space.

**Ask AI dialog**
- **Header mode chip removed.** The chip beside "Ask AI" duplicated the Action select that sits two rows below it — it always said "Proofread" while the dropdown also said "Proofread". Drop the chip; the Action select is the single source of truth for the active mode.
- **Dialog width 540 → 480px.** With the chip gone and the textarea heights reduced, the dialog now fits the same workflow in less horizontal space — closer to Notion AI's ~480px floating composer than the previous wide-modal feel.
- **Source / Result textarea min-height 88 → 64px.** Still ~3 lines visible at 13px / 1.45 leading, which covers the common proofread / rewrite snippets. Resize handles remain so users can drag taller for long inputs.
- **Tighter dialog spacing.** Outer grid gap 5 → 4px, controls bar padding 6/8 → 4/6px, controls bar row-gap 6 → 4px. Adds up to ~14px of vertical real estate the dialog no longer needs.

**AI Chat panel**
- **Empty-state hero illustration removed.** Was 64×46 in v20260508u after an earlier shrink; still the tallest thing in the empty state. Drop it entirely so the composer is the first interactive surface, not a decorative robot SVG. Quick-start chips above the empty state already orient new users.
- **Panel min-height 660 → 520 (narrow: 560 → 460).** With the hero gone and the per-message metadata cards already removed in v20260508t, the chat panel needs less vertical real estate to show a useful first turn + composer. Brings the chat sidebar closer to ChatGPT / Claude side-panel proportions.

`aiToolkitUiVersion` bumped to `v20260508v`. The bundled `all_plugins.js` and the demo aspx version stamps were re-synced for cache busting.

## Version 2.0.1.3

### Fixed — Ask AI dialog opened with empty Action select
"Draft with AI" from the toolbar AI menu opened the Ask AI dialog with the Action dropdown rendered empty (no option selected) and the new header mode chip blank. Cause: `runQuickAction` forwarded the menu action's id (`open-dialog`) as `presetMode` whenever the action had no explicit `resolverMode` — but `open-dialog` doesn't match any configured dialog mode, so the select fell through to no value.
- `runQuickAction` now passes `null` instead of the action id when no `resolverMode` is set, letting `openDialog` apply its own first-mode default.
- `openDialog` got a defensive fallback: if `presetMode` doesn't match any option, the first mode (Proofread) is selected so the dropdown and header chip always tell users what will run.

### Improved — Easier-to-read Ask AI dialog (inline labels, named empty state)
Follow-up to the v2.0.1.2 compaction so the Ask AI dialog is easier to read at a glance.
- **Source / Result labels moved out of the textarea.** The previous build floated tiny 9px uppercase pills in each textarea's top-left corner with a 22px first-line reserve. The pill overlapped the loaded text on the first row and the all-caps glyph was hard to read. Now: a slim 11px sentence-case label sits above each textarea — the textarea uses its full height for content.
- **Apply primary button names the empty state.** Before the user has run Generate, the disabled Apply button used to read "Apply" with the standard washed-out disabled style — looked broken. Now it says "Generate first" so the button itself tells users what to do next.
- **Source placeholder shortened.** "Pick Selection or Document above, or type / paste text here. Press Ctrl+Enter to generate." → "Type, paste, or load text. Ctrl+Enter to generate."
- **Result placeholder shortened.** "AI output will appear here after you click Generate." → "AI output will appear here."

### Improved — Compact AI Chat empty state + tidy composer
- **Empty-state title and subtitle collapsed to short, parallel one-liners.** Title: "Start with AI Chat" → "Ask anything". Subtitle: "Use the current document as context, ask for guidance, or prepare reviewable edits before they touch the page." → "AI will use the current document." (Selection scope: "AI will use your current selection.") Cuts ~80px of vertical space before the composer becomes the first thing users see.
- **Empty-state hero illustration shrunk** (96×70 → 64×46) and surrounding paddings tightened (12px → 8px/10px). The fresh-open panel now leads with the Ask anything prompt + composer instead of a 70px decorative SVG.
- **`Open Ask AI` composer button demoted to icon-only.** Was a 132px label-bearing pill that crowded the Send primary at 420px panel widths. Now a 36px square with full title + aria-label so hover and screen readers keep the detail. Same idiom as the Ask AI dialog's icon-only Copy button.

`aiToolkitUiVersion` bumped to `v20260508u`. The bundled `all_plugins.js` and the demo aspx version stamps were re-synced for cache busting.

## Version 2.0.1.2

### Improved — Compact AI windows (chat + Ask AI dialog)
Fourth pass on the AI windows after customers reported the chat panel still felt heavy. Each compaction pass before this trimmed chrome around the dialog and chat panel; this one finally cuts the per-message metadata cards that were the biggest remaining offender, and pins the alternative apply paths behind a "More" toggle on both surfaces so the default surface only shows the choices users actually want.

**AI Chat panel — assistant message render**
- **Per-message preview card removed.** Every assistant turn used to render a separate card with a title, four meta pills (status / scope / steps / target), current-vs-result compare lines, a "Why this change" card, a change-glance card with up to five pills, and a plan card with lead/items/followups. All of it duplicated information already conveyed by the message bubble + smart Apply button. Drop the whole card.
- **Per-message action summary card removed.** It restated the smart Apply button's own label ("Replace selection", "Apply 3 pending steps", "Re-run from source") in a separate stack above the buttons.
- **Per-exchange outcome card removed.** Showed up after every successful assistant turn (title + status/scope/steps/action/change pills + detail) and added a second wall of badges below the message bubble.
- **Three action groups (utility / primary / secondary) collapsed into a single inline pill row.** Each group used to ship its own header label + visual divider; now the seven buttons sit on one wrapping row, like Notion AI / Claude / ChatGPT. The smart Apply button still keeps its `is-recommended` styling so the eye lands on the recommended choice first.
- **Alternative apply paths hidden behind a `More` toggle.** Default surface: `Copy text · Apply (smart) · Replace selection · More`. The `More` button reveals `Preview inline`, `Insert below`, `Replace document`. They all stay in the DOM and keep their accessibility / recommended-action wiring intact — only the visual default changes.
- **Composer placeholder shortened.** "Ask AI to rewrite, translate, summarize, explain, or improve the current content." → "Ask AI… (Enter to send, Shift+Enter for newline)". The quick-start chips above already show the example actions.
- **Empty-state hero illustration shrunk** (160×110 → 96×70) and surrounding paddings tightened so the first visible thing in a fresh chat is the "Ask anything…" prompt, not a 110px decorative SVG.

**Ask AI dialog — apply row**
- Six buttons (Apply / Replace selection / Preview / Insert below / Replace doc / Discard) trimmed to three on the default surface: **Apply (smart) · More · Discard**. The `More` button reveals the four hidden alt paths. Same idiom as Notion AI / TinyMCE AI.
- The smart Apply button already adapts its label by context (e.g. "Replace selection", "Apply 3 pending steps") so users almost never need to reach for the alt buttons.
- Discard pinned right; muted contrast (transparent → red on hover) so the destructive choice never competes with Apply.

`aiToolkitUiVersion` bumped to `v20260508t`. New shared `more` icon glyph (kebab dots) added to the Review V2 icon registry so the toggle pattern is reusable across all three AI windows (chat, Ask AI dialog, future surfaces).

## Version 2.0.1.1

### Improved — Easier Ask AI window (mode chip + run-row clarity)
Follow-up pass after the v2.0.1.0 compaction to make the Ask AI dialog more *understandable* at a glance and to drop more chrome from the run row.
- Header now carries a small **mode chip** beside the "Ask AI" title. The chip mirrors the active Action — `Proofread`, `Rewrite`, `Translate · Spanish`, etc. — so users see what the dialog will do without scanning the Action select. Updates live when the mode or translate target changes.
- Run-row `Copy result` button **demoted to an icon-only square** (32px). Same affordance, no label competing with the primary `Generate`. The label moves to `title` + `aria-label` so hover and screen readers still surface it.
- `Generate` button **flips to `Regenerate`** once a result exists (or the plan goes stale). Matches the idiom from CKEditor / Notion AI / ChatGPT and removes the "wait, will this run again or is the result already done?" doubt.
- **`Ctrl/Cmd+Enter` from the Source textarea fires Generate.** Discoverable via the placeholder hint ("…Press Ctrl+Enter to generate.").
- Apply-row visual hierarchy: the destructive `Discard` is pinned right with low-contrast styling (transparent → red on hover). The alternative apply paths (`Replace selection`, `Replace doc`, `Insert below`, `Preview`) ride softer grey so the smart `Apply` primary stays the eye's first stop. All buttons keep their full label and accessible names.
- Bottom status line replaced its 2–3 sentence guidance paragraph with a one-line **`Ready · <next step>`** pill. The full alternative-actions copy still lives on each apply button's title/aria-label so screen readers and hover keep their detail.
- `aiToolkitUiVersion` bumped to `v20260508s`.

## Version 2.0.1.0

### Improved — Compact AI dialog (Ask AI window)
The Ask AI dialog has been further compacted and reorganized so users see the action buttons next to the result and don't get buried in details.
- Dialog width 720px → 580px so it stops dominating the viewport.
- Apply row (Apply / Replace selection / Preview / Insert below / Replace doc / Discard) moved up to sit directly under the Source/Result textareas — no more scrolling past Why + Plan to find the action buttons.
- The Why and Plan panels are now hidden behind a single `Details` disclosure (collapsed by default). The disclosure auto-expands when a plan goes stale so the Re-run button stays reachable.
- Insight grid stacks Why on top of Plan when expanded (single column) instead of side-by-side, so it stays readable in the narrower dialog.
- Suggested-next-step card re-laid out as a single horizontal row (label + badge + detail) instead of a 3-row stack — about half the vertical space.
- Smaller paddings, gaps, button heights, label sizes, textarea min-height (130px → 88px), border-radii. Together the dialog opens roughly 35–45% shorter than 2.0.0.9 on a fresh open.
- `aiToolkitUiVersion` bumped to `v20260508q`.

### Improved — Easier-to-read Ask AI dialog (compact + clearer pass)
Follow-up redesign to make the dialog scannable, less chrome-heavy, and slightly narrower again.
- Removed the bordered/shadowed card around the Action / Use / Generate row — it created a "card inside a dialog" look. Fields now sit flat on the dialog surface.
- Selection / Document scope picker fused into a real **segmented control** (shared border, no inter-button gap, active scope filled). Previously read as two unrelated buttons.
- Field labels switched from 9px UPPERCASE / 0.05em tracking to **10.5px sentence case** (`Action`, `Use`, `Source`, `Result`) so they're legible without leaning forward.
- Apply row: `Discard` pinned right and de-emphasized (transparent background, muted text) so the destructive choice doesn't compete with `Apply` / `Replace doc`.
- Run row: `Copy` demoted to a flat outline pill so the eye lands on `Generate` first.
- Dialog header padding tightened (1rem 1.5rem → 10px 14px) and dialog max-width 580px → 560px.
- `Whole document` button label shortened to `Document` — fits the segmented toggle without wrapping.
- `aiToolkitUiVersion` bumped to `v20260508r`.

## Version 2.0.0.9

### Improved — Compact Ask AI dialog
The Ask AI dialog has been redesigned to be smaller and easier to read.
- Dialog width trimmed from 880px to 720px so it no longer dominates the viewport.
- Removed the decorative hero illustration, the 3-step instruction note, the meta-badge row, and the duplicate Before/After comparison cards. Source/Result textareas already showed the same content; the cards were visual noise.
- Controls collapsed onto a single row: `Action`, `Language` (only when Translate is picked — column auto-collapses when hidden), `Source` toggle (`Selection` / `Whole document`), and the `Generate` / `Copy` buttons. Was four rows / three large boxes.
- Tightened labels: "What to do" → "Action", "AI draft" → "Result", "Create" → (icon-only), "Why this suggestion" → "Why", "Operation plan" → "Plan", "Reject" → "Discard", "Replace document" → "Replace doc", "Preview inline" → "Preview".
- Smaller paddings, gaps, button heights, textarea heights (190px → 130px), border-radii. Result is roughly 35% shorter on a fresh open.
- AI menu copy shortened ("Choose how AI should help: chat, draft, quick edit, or review changes." → "Pick how AI should help.") and menu width 520px → 460px.
- New `.is-no-language` modifier on the controls row so the grid template adapts when the Language field is hidden (no orphan empty column).
- `aiToolkitUiVersion` bumped to `v20260508p`.

## Version 2.0.0.8

### Added - Markdown block shortcuts
Typing Markdown markers at the start of an empty block now auto-converts on the same keystroke:
- `# `, `## `, `### ` -> `H1` / `H2` / `H3`
- `> ` -> blockquote
- `- `, `* ` -> unordered list
- `1. ` -> ordered list
- `---` / `***` / `___` + Enter -> horizontal rule plus a fresh paragraph below

The feature is enabled by default and can be disabled with `config.markdownShortcutsEnabled = false`.

## Version 2.0.0.7

### Improved — Yjs textSync caret preservation
The 'textSync: true' Yjs binding now saves and restores the local caret as a
character-offset into the body before / after a remote update is applied,
instead of letting the cursor unconditionally snap to position 0. The caret
still jumps when the remote edit lands at or before the local cursor (HTML
character offsets don't track rendered offsets exactly), but for the common
case of two users typing in different parts of the document the local caret
now stays put.

The proper per-node binding (one 'Y.Text' per block element with shared 'Y.Map'
keyed by stable IDs) is still planned for a future release; this is a quality-
of-life improvement to the existing whole-HTML-string MVP.

## Version 2.0.0.6

### Added — Dictation plugin
New 'plugins/dictation.js'. Speech-to-text via the Web Speech API with a toolbar microphone button. Opt-in via 'config.dictationEnabled = true'. JS API: 'editor.dictation.start() / stop() / toggle() / isListening() / isSupported()'. Live interim transcripts render under the cursor. Chrome / Edge / Safari / Opera supported; Firefox detects as unsupported and hides the button.

### Added — Mobile toolbar stylesheet
New 'rte_mobile.css'. Add class 'rte-mobile' to any editor container to reshape the toolbar for phones: horizontally-scrolling strip, 44×44 tap targets, sticky header, bottom-sheet dropdowns / dialogs / review drawer. Scoped behind '@media (max-width: 768px), (pointer: coarse)' so desktop layouts stay unchanged.

### Improved — Paste from Word list conversion
Rewrote '_ConvertWordLists' to correctly detect ordered vs unordered, handle nested levels (via 'mso-list: l{N} level{M}'), and strip actual bullet glyphs ('·', '•', '◦', '§', '—') instead of the previously ineffective character class. Numeric / alphabetic / Roman markers are now preserved as '<ol>' with proper nesting.

## Version 2.0.0.5

### Added — Revision history: named versions + diff API
'editor.revisionHistory' gets three new methods closing the gap against CKEditor's premium named-versions feature:
- 'promptAndSnapshot(defaultLabel?)' — pops a native prompt for a version name, then takes the snapshot. Returns the entry or 'null' if cancelled.
- 'rename(id, label)' — rename an existing snapshot; flips its 'isNamed' flag to 'true'.
- 'listNamed()' — return only named snapshots (handy for a "milestones only" filter).
- 'diff(idA, idB)' — return '{ oldEntry, newEntry, oldText, newText, lines[] }' so callers can build a side-by-side diff UI on top of the plugin's data.

Each entry now carries an 'isNamed' boolean. Snapshots with an 'auto: true' metadata flag always stay unnamed. Existing entries that were saved before this release are treated as unnamed.

## Version 2.0.0.5

### Added — DOCX export client helper
- New 'editor.aiToolkit.exportDocx({ url?, fileName?, title?, html?, onError? })' builds a POST to the server DOCX endpoint (default '/richtextbox/export/docx') and triggers a browser download via Blob + anchor. Falls back to 'navigator.msSaveOrOpenBlob' on legacy IE/Edge.
- Pairs with the new 'DocxExporter' server in RichTextBox.AspNetCore 1.0.0-preview.11.

### Added — Yjs text sync preview (opt-in concurrent typing)
- 'editor.collab.attach({ doc, provider, textSync: true })' binds the editor's full HTML to a shared 'Y.Text' on the given Y.Doc. CRDT merges concurrent inserts/deletes at character level; two users typing at the same time both see each other's edits survive.
- Tradeoffs in this preview: remote updates re-set the body (caret snaps to start), HTML character offsets don't align with rendered caret offsets. Per-node binding is on the roadmap.
- New config keys: 'collabTextSync' (bool, default false) and 'collabTextName' (string, default 'richtextbox.body').

## Version 2.0.0.4

### Added — Streaming AI client helper
- New 'editor.aiToolkit.streamRequest({ url, body, onDelta, onResponse, onDone, onError })'
  consumes Server-Sent Events from a streaming AI endpoint (like the new
  'MapRichTextBoxAiStream()' endpoint in RichTextBox.AspNetCore 1.0.0-preview.10).
  Returns '{ promise, abort() }'. Browsers without 'fetch'/'ReadableStream'
  fall back to a non-streaming XHR against the same endpoint with '/stream'
  stripped — same callback shape either way.
- Works against any SSE source that follows 'data:' / 'event: response' /
  'event: done' framing; not tied to RichTextBox.

## Version 2.0.0.3

### New plugins — Collaboration & Review
- **Slash commands** ('plugins/slashcommand.js'): type '/' at the start of a line (or after whitespace) to open an inline command picker with 15 built-in blocks / inserts (H1-H3, lists, quote, code block, divider, table, image, link, emoji, template, today's date). Every 'aiToolkitActions' entry is auto-imported into the same menu. Public API: 'editor.slashCommands.register/remove/list/open/close'. Demo: 'Demo/slashcommand.html' + '.aspx'.
- **@mentions** ('plugins/mentionplugin.js'): trigger-character framework for '@', '#', '[[wiki]]', etc. Async source with debounce + "Searching…" loading state, atomic non-editable pills that backspace removes as a single unit, per-trigger 'renderItem'/'renderInsert'/'insertClass' hooks. Demo: 'Demo/mentionplugin.html' + '.aspx'.
- **Tracked Changes** ('plugins/trackedchanges.js'): Word-style human suggesting mode. Intercepts 'beforeinput' and wraps insertions in underlined spans + deletions in strikethrough spans (without removing them) in the current user's color. Sequential deletes merge into one span; backspacing inside your own just-typed insert shrinks it. Per-author accept / reject. Requires 'config.currentUser'. Demo: 'Demo/trackedchanges.html' + '.aspx'.
- **Comments** ('plugins/comments.js'): threaded sidebar comments anchored to selection ranges. Composer + reply + resolve + delete. Clicking any highlighted span focuses its thread. Demo: 'Demo/comments.html' + '.aspx'.
- **Revision History** ('plugins/revisionhistory.js'): snapshot browser with sandboxed preview iframe and an LCS-based line diff tab. Toolbar button + 'Ctrl+Shift+H' shortcut. Manual snapshots or opt-in 'revisionHistoryAutoSnapshotMs' debounced auto-snapshots. localStorage persistence; optional 'revisionHistoryUrl' server POST. Demo: 'Demo/revisionhistory.html' + '.aspx'.
- **Yjs Collaboration** ('plugins/yjscollab.js'): presence (remote cursors + name badges) + shared review ledger sync via a 'Y.Map'. Peer-dependency pattern (Yjs + provider are the customer's choice). Concurrent text editing is not in v1 — the plugin scope is "review together" not "type together". Demo: 'Demo/yjscollab.html' + '.aspx'.

### Shared Review Ledger (additive in 'aitoolkit.js')
- New 'editor.reviewLedger' API: 'add', 'update', 'remove', 'get', 'list', 'refreshPanel'. Used by Tracked Changes, Comments, and the AI drawer. Backward compatible: existing AI suggestions default to '{ id: "ai", name: "AI", color: "#8b5cf6" }' author.
- Extended 'serializeSuggestion' / 'normalizeLedgerEntry' / 'normalizePersistedSuggestion' with 'changeType', 'author', 'replies[]', 'text', 'blockPath' fields so non-AI entries round-trip through localStorage + the optional 'aiToolkitSuggestionLedgerUrl' endpoint.
- 'renderReviewPanel' filters out non-AI ('changeType !== "ai-preview"') entries so the AI drawer keeps its existing shape; Tracked Changes + Comments ship their own UIs.

### Fixes
- **Ask AI dialog Translate action did nothing.** The action definition's 'target: "dialog"' was inherited by 'buildLegacyOperation' at runtime, producing an 'open-dialog' op (dialog is already open) instead of a real translated-text op. 'defaultResolveAction''s translate branch now filters out the '"dialog"' target and falls through to 'selection-preview' / 'document'.
- **AI Chat panel too small.** 'rte-ai-chat-panel' width bumped '340px → 420px', shell 'min-height' '460/520 → 560/660', feed row 'minmax(0, 1fr) → minmax(180px, 1fr)'.
- **AI menu icons rendering at native (giant) size in static Zip demos.** 'PublishTool/autogen_template/page.html' now links 'richtexteditor/plugins/aitoolkit.css' so every generated demo HTML has the icon-sizing rules. All 35 existing demos in 'Zips/' patched in place.
- **Double-folder extraction from customer zips.** 'PublishTool.AddFolderToZip' no longer prefixes entries with the zip's base name — Windows Explorer "Extract All" now yields one folder level instead of '<foo>/<foo>/…'. All existing zips in 'Zips/' rewrapped.

### Docs / examples
- Removed '<script src="/richtexteditor/rte-config.js"></script>' from customer-facing code examples across 40 demos + 'Docs/ai-toolkit.aspx'. Kept runtime '<script>' loads on marketing sites ('demo.master', 'default.aspx') where rte-config.js still ships as a separate file.
- Rewrote stale 'rte-config.js' prose mentions in quick-start, install_div, install_textarea, ai-toolkit, configuration, and compare pages to "editable 'RTE_DefaultConfig' block at the top of 'rte.js'".

### Packaging
- 'plugins/-combine-plugins.bat' glob-includes new plugins automatically; bundle regenerated on each build.
- 'plugins/all_plugins.js' refreshed (post-session, single-layer zips).

## Version 2.0.0.2

### AI Toolkit Polish
- Tightened the Ask AI dialog: shorter labels ("Action", "Scope", "Run"), simplified copy, reduced vertical height by ~100px through denser padding and smaller corner radii.
- Simplified the apply-row button labels: "Apply", "Replace selection", "Preview inline", "Insert below", "Replace document", "Reject" (previously the longer "Accept into/below/document" variants).
- Added a 'reason' rationale to every built-in demo resolver mode (proofread, rewrite, shorten, expand, summarize, translate, justify) so the "Why this suggestion" panel always populates.
- Improved 'buildDemoResult' for 'proofread' so it now fixes punctuation spacing, collapses stray periods, and capitalizes the first letter. 'rewrite' drops the "Here is a clearer rewrite:" preface and strips more filler phrases ('just', 'in order to', 'due to the fact that') in addition to 'very' and 'really'.
- Chat panel: shorter subtitle, compact scope pill ("Selection" / "Document"), meta separator changed from '-' to '·', "Apply plan" → "Apply" in message actions.
- Review panel: shorter subtitle and empty-state copy; smaller card radii; hover states added to control/action buttons.
- Unified the primary blue gradient between dialog / chat send / review primary buttons for visual consistency.

### Icon and Plugin Loading
- Replaced the Insert Code icon SVG with a clearer stroke-based '</>' glyph (using 'currentColor' so it picks up toolbar theming).
- Added default 'svgCode_insertcode' and 'svgCode_aiassist' fallbacks in 'rte-config.js' so those toolbar icons render correctly even when 'insertcode.js' / 'aitoolkit.js' aren't bundled (previously they fell back to the bookmark-shaped 'svgCode_default').
- Rebuilt 'plugins/all_plugins.js' to include 'aitoolkit.js' so demos using the consolidated bundle (e.g. 'MinDemo.aspx') get the AI toolkit automatically.
- Fixed a BOM-mid-file issue introduced when concatenating plugins: '-combine-plugins.bat' now uses a PowerShell block that strips any UTF-8 BOM from each source and writes a clean no-BOM UTF-8 bundle. Source plugin files ('html2pdf.js', 'imageeditor.js', 'insertcode.js', 'insertemoji.js') were also stripped of their leading BOMs.
- Added 'plugins/aitoolkit.css' to 'Demo/MinDemo.aspx' so the AI button and panels are styled correctly in the minimum demo.
- Removed a stray 'console.log' from the 'exec_command_insertcode' handler.

### Manual Test Page
- Extended '_tests/ai-manual-test.html' to load 'insertcode.js', 'insertemoji.js', 'insertgallery.js', 'inserttemplate.js', 'imageeditor.js', and 'html2pdf.js' so every default-toolbar command has its handler in the manual test environment.

## Version 2.0.0.1

### Toolbar and Icon Updates
- Added the RichTextBox-style bottom toolbar row to the built-in 'full' toolbar.
- Added pure JavaScript toolbar buttons for 'Insert Merge Field', 'Insert Footnote', 'Insert Table of Contents', 'Insert Page Break', and 'Revision History'.
- Restored the original RichTextEditor 'ForeColor' and 'BackColor' toolbar icons after testing the RichTextBox icon set.
- Added the original RichTextBox-style paragraph operation icons to the last toolbar row.

### Dialog Improvements
- Reworked 'Insert Comment' to use the RichTextBox plugin-style behavior in the JavaScript editor.
- 'Insert Comment' now shows the selected-text preview, uses the smaller 'Add Comment' dialog flow, highlights selections with a yellow comment mark, and inserts a compact comment marker when there is no selection.
- Updated the JavaScript editor 'Insert Image', 'Image Gallery', 'Insert Template', and document-related dialogs to use the cleaner RichTextBox dialog styling.
- Removed the 'Camera' option from the 'Insert Image' command menu.

### JavaScript-Only Features
- Added a built-in pure JavaScript 'Insert Comment' command path.
- Added a built-in pure JavaScript 'Insert Page Break' command path.
- Added pure JavaScript implementations for 'Insert Merge Field', 'Insert Footnote', 'Insert Table of Contents', and 'Revision History'.
- Kept unsupported server-side-only commands out of the live toolbar and demo workflow.

### Demo and UX Cleanup
- Increased the width of the 'Demo/Default.aspx' experience by widening the shared shell layout.
- Cleaned up the JavaScript demo page so the main toolbar only shows real JavaScript features.
- Removed preview-only/server-side icon sections that did not have a real JavaScript implementation.
- Added cache-busting updates to the demo asset URLs while iterating on toolbar and dialog behavior.
- Hid the paragraph floating toolbar by default in the JavaScript demo configuration.

### Visual Matching
- Applied the RichTextBox color picker styling to the JavaScript editor color picker UI.
- Applied RichTextBox floating toolbar styling to the JavaScript editor.
- Updated the dialog button and toolbar icon set so RichTextEditor and RichTextBox feel more consistent while preserving the preferred legacy color buttons.

## Version 2.0

### Scrollbar Fix
- **Vertical scrollbar not appearing**: Fixed iframe height not being recalculated when content is loaded via 'setHTMLCode' or on initial editor load, causing the scrollbar to never appear even when content exceeds the editor height.
- **Flex overflow fix**: Added 'min-height:0' to the 'rte-content' container to prevent the flex layout from growing instead of scrolling.
- **Plus button scroll**: Fixed "add paragraph" button not updating iframe height, so the editor now scrolls to the new paragraph correctly.

### Bug Fixes (from bug list)
1. **Upper-roman list display**: Fixed typo in roman numeral list preview ('rte-config.js') - duplicate characters in value III.
2. **Enter key scrollbar**: Rewrote '__AdjustScrollTop_ForInsertPoint' with zero-rect fallback and 'scrollIntoViewIfNeeded' so the cursor stays visible when pressing Enter at the bottom of the editor.
3. **Search & Replace crash**: Rewrote 'replaceAll' to search from document start and added loop guard to prevent infinite loop when replacing text.
4. **Underline CSS**: Added 'text-decoration' normalization in '__GetHTMLCode' so underline styling is preserved correctly in output HTML.
5. **Insert row sizing**: Table row insert now copies styles (height, cell widths) from the reference row. Fixed insert-below index calculation.
6. **getSelectedHTML API**: Added 'editor.getSelectedHTML()' method to retrieve the currently selected HTML content.
7. **Textarea paste line breaks**: Plain text paste now converts '\n' to '<br>' instead of collapsing all lines into one.
8. **Image upload in table cell**: Inserting an image inside a table cell no longer creates a root-level paragraph outside the table.
9. **File upload type filter**: Changed file input 'accept' attribute from 'image/*' to explicit extensions ('.jpg,.jpeg,.png,.gif,.bmp,.webp,.svg') for better compatibility.
10. **List toggle with anchors**: Fixed numbered/bullet list toggle when selection contains anchor ('<a>') elements - consecutive inline siblings are now grouped into a single '<li>' instead of being split incorrectly.
11. **HTML comments in code view**: Code/source view now preserves HTML comments ('<!-- -->') instead of dropping them.
12. **onChange for spaces**: Changed change detection from stripping all whitespace to normalizing whitespace, so adding/removing a single space is now detected as a content change.
13. **Excel paste colors**: Fixed '<body>' content extraction bug ('indexOf' offset calculation) and fixed CSS style parsing to split on first colon only, preserving values that contain colons (e.g., URLs).
14. **Word paste as list**: Added '_ConvertWordLists' to convert Word's 'MsoListParagraph' elements to proper '<ul>/<ol>' lists. Added XML namespace and conditional comment cleanup.
15. **Firefox Ctrl+B/I/U**: Added explicit keyboard handlers for bold, italic, and underline with 'metaKey' support for macOS.
16. **Image drag disappears**: Fixed image drag-and-drop within the editor - the browser's native drag is now preserved instead of being intercepted and discarded.

### SVG Icon Fixes ('rte-config.js')
- **svgCode_justifyright**: Removed invalid '//' before closing '/>' tag - icon was not rendering.
- **svgCode_fullscreenexit**: Added missing '</svg>' closing tag.
- **svgCode_imagescale**: Removed extra '<' before '</svg>' closing tag.
- **svgCode_imagestyle**: Fixed double-quote '""' on path 'd' attribute causing malformed markup.
- **svgCode_controleditlink**: Removed stray '</g>' closing tag with no matching opening '<g>'.
- **svgCode_inlinestyle**: Removed duplicate '</svg></svg>' closing tags.
- **svgCode_floatleft**: Fixed missing '>' on '</path' closing tag.
- **svgCode_link**: Changed viewBox from '0 0 20 20' to '0 0 24 24' - path coordinates extended beyond the old viewBox causing the icon to be clipped.
- **svgCode_insertlink / svgCode_unlink**: Removed unnecessary invisible '<path d="M0 0h24v24H0V0z" fill="none"/>' elements.
- Fixed double semicolons on 'controlsize25' and 'tablecellmerge' declarations.

### Documentation Rewrite (55 files)
- Rewrote all 55 ASPX documentation pages with clean formatting.
- Replaced pre-tokenized Prism.js HTML spans with plain code blocks for easier maintenance.
- Fixed 16+ content bugs: Chinese-only page rewritten in English, copy-paste errors, wrong descriptions, mislabeled parameters, naming inconsistencies, missing closing braces in CSS examples, typos.
- Added new 'getSelectedHTML()' method documentation page.
- Updated navigation.xml: added Skin page, FAQ, All Events, and getSelectedHTML entries.
- Expanded skin configuration page with all available values and examples.

### Cleanup
- Removed debug 'console.trace' call from '___Adjust_After_Content_Changed'.

---

## Version 1.015
- (No changelog recorded)

## Version 1.013
- Better selection, better bookmark

## Version 1.012
- Better drag and drop, new bookmark logic

## Version 1.011
- Better bookmark

## Version 1.010
- Fix more selection bugs

## Version 1.009
- Implement formatblock, improve LI, selection
