The new renderer-based unified window (V3) replaces the old Mithril-based injected sidebar. While the new UI uses better semantic HTML (<button>, <textarea>, <label>), it regressed in three areas compared to the old UI:
- i18n — ~15 hardcoded English strings that the old UI localized via
Localization.getLocalizedString() - Accessibility — Lost ARIA state attributes, keyboard navigation, focus outlines, and aria-live regions
- Contrast — Error text color
#ff6b6bfails WCAG AA; region button border fails non-text contrast
The old UI's patterns are the blueprint — most string keys already exist in strings.json, and the ARIA patterns are well-documented in the old components.
Strings are fetched from https://www.onenote.com/strings?ids=WebClipper.&locale={locale} at startup by extensionBase.ts, stored in localStorage.locStrings. The renderer reads them via loc(key, fallback). The strings.json file in the repo is the English fallback only — actual translations for 60 locales come from the server. New keys added to strings.json will only have English until the server is updated, so reuse existing keys wherever possible.
extensionBase.ts line 133: navigator.language || navigator.userLanguage → stored in localStorage.locale. A user override is also supported via localStorage.displayLocaleOverride. The old UI only set <html lang="en"> statically — never dynamically. RTL was handled by loading separate CSS files (clipper-rtl.css), not via lang/dir attributes.
| File | Changes |
|---|---|
src/renderer.html |
lang attr, ARIA roles/attributes, for associations, aria-live region |
src/scripts/renderer.ts |
Wire loc() to all remaining strings, dynamic lang attr, ARIA state management, keyboard nav, focus management, aria-live announcements |
src/styles/renderer.less |
Focus outlines, error color fix, region button contrast, sr-only class, high-contrast media query |
Sign-in panel HTML strings (signin-description, signin-msa-btn, signin-orgid-btn, signin-progress) were never replaced by JS. Now wired to existing keys:
WebClipper.Label.SignInDescription→ sign-in descriptionWebClipper.Action.SigninMsa→ MSA buttonWebClipper.Action.SigninOrgId→ OrgId button- "Signing in..." kept as hardcoded English (no existing key, brief transient state)
- Note label →
WebClipper.Label.Annotation("Note") - Save to label →
WebClipper.Label.ClipLocation("Location") - Source label → kept as hardcoded "Source" (no old UI equivalent, new element)
- Title label → kept as hardcoded "Title" (old UI had no visible label, used placeholder only)
| String | Key | Key status |
|---|---|---|
"Capture complete" |
— | REMOVED — dead code, never referenced |
"No notebooks available" |
WebClipper.SectionPicker.NoNotebooksFound |
EXISTS (60 locales) |
"Error loading notebooks" |
WebClipper.SectionPicker.NotebookLoadFailureMessage |
EXISTS (60 locales) |
"Loading article..." |
WebClipper.Preview.LoadingMessage |
EXISTS (60 locales) |
"Article content not available..." |
WebClipper.Preview.NoContentFound |
EXISTS (60 locales) |
"Unknown error" |
— | kept as-is (technical fallback) |
"Sign-in failed..." |
WebClipper.Error.SignInUnsuccessful |
EXISTS (60 locales) |
All meaningful strings wired to existing server-translated keys. Four strings remain English-only as acceptable fallbacks (Title, Source, Signing in..., Copy diagnostics aria-label).
.signin-error color: #ff6b6b → #ff9999 (~5.3:1 on #56197c, passes WCAG AA)
Border: #bbb → #999 (~3.4:1 on #f3f2f1, passes SC 1.4.11)
Text: #666 → #555 (~5.9:1, improvement)
#sidebarinteractive elements:outline: solid 1px #f8f8f8 !important; outline-offset: 1px- Sign-in buttons:
outline-offset: 2px - High contrast mode:
outline: solid 2px Highlight !important; outline-offset: 2px !important
Static lang="en" in HTML + dynamic override in JS reading localStorage.locale (or displayLocaleOverride). Converts _ to - for BCP 47 (e.g., zh_CN → zh-CN).
- Container:
role="toolbar"with localizedaria-label - Buttons: standard
<button>elements witharia-pressed(toggle button pattern) - Reverted from
role="radio"/ radiogroup — buttons are more natural for mode selection
Arrow Up/Down/Left/Right + Home/End navigation between mode buttons. Mirrors old enableAriaInvoke() from componentBase.ts.
- Trigger:
role="combobox",aria-haspopup="listbox",aria-expanded - List items:
role="option",aria-selected - Escape to close, arrow keys to navigate
<label for="title-field">and<label for="note-field">aria-labelledbyon source-url and section-selected (non-input elements)
<div id="aria-status" class="sr-only" aria-live="polite" aria-atomic="true">announceToScreenReader()helper for: capture start/complete, mode change, save start/success/error, sign-in error
Focus first sign-in button on overlay show; focus first mode button on overlay hide.
aria-label="Copy diagnostic information" (hardcoded English — technical label).
Replace pointer-events: none + opacity with aria-disabled="true" + tabindex="-1" (accessible to keyboard/screen readers).
How old UI handled RTL:
localeSpecificTasks.tscallsRtl.isRtl(locale)(checksar, fa, he, sd, ug, ur)- Loads
clipper-rtl.css/sectionPicker-rtl.cssinstead of LTR versions styledFrameFactory.tsflips iframe position (left: 0instead ofright: 0)
Locale override: No UI exists for switching locale. localStorage.displayLocaleOverride is a developer/testing mechanism only (set via console).
What RTL would need for the renderer (estimated ~50-80 LESS lines + testing):
- Detect RTL locale and set
dir="rtl"on<html> - Convert
renderer.lessto CSS logical properties (margin-inline-start/end, etc.) - Flip: sidebar position, section picker arrow, user-info alignment, feedback icon margin
- Test with at least one RTL locale (ar or he)
Why defer: RTL affects layout fundamentals. Best done as a dedicated pass.
- Phase 2a — Error contrast fix (LESS)
- Phase 1a–1c — i18n wiring (renderer.ts only, reuse existing keys)
- Phase 2b–2c — Remaining contrast + focus outlines (LESS)
- Phase 3a —
<html lang>+ dynamic locale (HTML + TS) - Phase 3e — Label
forassociations (HTML) - Phase 3b–3c — Mode button ARIA + arrow keys (TS)
- Phase 3d — Section picker ARIA (HTML + TS)
- Phase 3f — aria-live regions (HTML + LESS + TS)
- Phase 3g–3i — Focus management, copy button label, signout disabled state (TS)
- Build:
npm run build— check for TS compilation errors - Edge target: Verify
renderer.jsandrenderer.cssin target output - Manual testing in Edge (verified):
- Sign-in panel: localized text appears
- Mode buttons: arrow key navigation,
aria-checkedupdates in devtools - Section picker:
aria-expandedtoggles, Escape closes, arrow keys navigate items - Save error:
#ff9999error text readable - Tab order: mode buttons → title → note → section → Clip → Cancel → feedback → sign out (wraps)
- Focus outlines:
2px solid #f8f8f8visible on all sidebar controls - Focus management: Cancel focused during capture → Full Page button after capture → sign-in button on overlay
- No functional regressions: Capture, save, region, article, bookmark modes all work
- Screen reader testing (verified with NVDA + Edge):
- ARIA roles, states, and aria-live announcements are implemented and working
- Accessibility tree verified correct via
edge://accessibility— all nodes present with proper roles - NVDA reads sidebar controls, mode buttons, section picker, and aria-live announcements
- Blur handler was removed to avoid conflicting with screen reader focus management
- Keydown handler allows navigation keys (arrows, Tab, Escape, Home/End, PageUp/PageDown) and modifier combos to pass through for screen reader compatibility
- Note: Previous testing on a stale devbox showed Edge not activating accessibility API flags for extension popup windows. A devbox reboot resolved this — NVDA works correctly with the renderer window