diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 6e92c8fd..b2c90da1 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -3258,6 +3258,19 @@ app.whenReady().then(async () => { return } + // Force Chromium's renderer accessibility tree on. By default Chromium builds + // it lazily, only once it detects an assistive client through the macOS + // accessibility activation handshake. Tools that read the tree without + // performing that handshake are otherwise left blind: they find the native + // window but never see the CodeMirror contentEditable text. Enabling it + // unconditionally exposes editor content to that whole class of accessibility + // clients (e.g. the Grammarly desktop app and similar proofreaders, which + // would otherwise show their UI but report nothing). macOS-only to avoid the + // small always-on cost where it isn't needed. + if (process.platform === 'darwin') { + app.setAccessibilitySupportEnabled(true) + } + await migrateLegacyRemoteWorkspaceSecrets() protocol.handle(LOCAL_ASSET_SCHEME, async (request) => { diff --git a/packages/app-core/src/components/EditorPane.tsx b/packages/app-core/src/components/EditorPane.tsx index b39aabd3..993e7a74 100644 --- a/packages/app-core/src/components/EditorPane.tsx +++ b/packages/app-core/src/components/EditorPane.tsx @@ -1461,6 +1461,12 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element { doc: initialBody, extensions: [ appMarkdownSnippetExtension(), + // Give the editable surface an accessible name so accessibility + // clients (screen readers, proofreaders such as Grammarly) identify + // it as a text field. + EditorView.contentAttributes.of({ + 'aria-label': 'Note editor' + }), vimCompartment.of(s0.vimMode ? vim() : []), historyCompartment.of(history()), drawSelection(), diff --git a/packages/app-core/src/lib/cm-heading-fold.ts b/packages/app-core/src/lib/cm-heading-fold.ts index 78aa80cd..9547bbd3 100644 --- a/packages/app-core/src/lib/cm-heading-fold.ts +++ b/packages/app-core/src/lib/cm-heading-fold.ts @@ -168,6 +168,14 @@ class HeadingFoldArrow extends WidgetType { el.setAttribute('role', 'button') el.setAttribute('aria-label', this.folded ? 'Expand heading' : 'Collapse heading') el.setAttribute('aria-expanded', String(!this.folded)) + // Keep this decorative affordance out of the accessibility tree. It sits + // (via side:-1) at the very start of the heading line, so as an AX-visible + // `role="button"` it becomes the editable field's leading element. Some + // accessibility clients (e.g. Grammarly) inspect a field's leading content + // and, finding a button, treat the whole field as a non-prose control and + // disengage. Folding stays reachable by mouse, keyboard, and vim, so hiding + // it from the accessibility tree costs nothing. + el.setAttribute('aria-hidden', 'true') el.textContent = this.folded ? '▸' : '▾' // Eat mousedown so CodeMirror's own handler doesn't interpret the // click as a caret position and steal focus before we dispatch diff --git a/packages/app-core/src/lib/cm-wysiwyg-blocks.ts b/packages/app-core/src/lib/cm-wysiwyg-blocks.ts index 0a73317c..0e198528 100644 --- a/packages/app-core/src/lib/cm-wysiwyg-blocks.ts +++ b/packages/app-core/src/lib/cm-wysiwyg-blocks.ts @@ -40,6 +40,10 @@ class BulletWidget extends WidgetType { const span = document.createElement('span') span.className = 'cm-wq-bullet' span.textContent = '•' + // Decorative marker only — keep it out of the accessibility tree so it + // doesn't pollute the field's text value for clients that read it (screen + // readers, proofreaders). The underlying `- ` is still the source text. + span.setAttribute('aria-hidden', 'true') return span } ignoreEvent(): boolean {