Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4d60463
Client-side migration: replace server APIs with local alternatives
wikirby Mar 6, 2026
ba9c05e
Inline external CSS at capture time for full page screenshot renderer
wikirby Mar 6, 2026
fe8d38a
Iframe scroll blocking for full page screenshot renderer
wikirby Mar 7, 2026
ac0c6a8
Fix scroll stall causing repeated screenshots, add content height cro…
wikirby Mar 7, 2026
5a20adc
Harden renderer interaction blocking and fix cleanup race condition
wikirby Mar 7, 2026
7a986b3
Replace per-event interaction blocking with transparent overlay
wikirby Mar 7, 2026
16b8df5
Move image stitching from helper into renderer for reduced storage usage
wikirby Mar 7, 2026
4644c6c
Update client-side migration docs for renderer-side stitching
wikirby Mar 7, 2026
79cce20
Harden renderer: pointer-events blocking, resize lock, disconnect han…
wikirby Mar 7, 2026
6d7ea31
Fix offscreen communicator context bug, strip script preloads, re-foc…
wikirby Mar 10, 2026
a4aaa0d
Unified window with sidebar: progress panel, sidebar cropping, i18n, …
wikirby Mar 10, 2026
8450a87
V2 unified window: mode selection, article/bookmark, section picker, …
wikirby Mar 10, 2026
e11b5b7
V2 unified window: sign-out, section refresh, region capture, post-si…
wikirby Mar 11, 2026
61506de
Tech debt cleanup: session storage, promise leak, Readability, region…
wikirby Mar 11, 2026
b86fc69
Self-contained sign-in: renderer handles auth directly, no clipperInject
wikirby Mar 11, 2026
c5d9f79
Add telemetry, token refresh before save, update documentation
wikirby Mar 11, 2026
ba106c7
contentCaptureInject: full DomUtils DOM cleaning pipeline, no imports
wikirby Mar 11, 2026
fa6ab8a
Master-compatible DOM extraction, sticky fix, remove stylesheet caching
wikirby Mar 11, 2026
c8e817b
Update docs: resolve sticky duplication, remove stale stylesheet refs
wikirby Mar 13, 2026
8f2f8e4
Article mode ONML cleanup, OneNote page styling, known limitations
wikirby Mar 19, 2026
0063ce5
Preserve body font-size from original page during DOM capture
wikirby Mar 19, 2026
6efd2f6
Add feedback link, session USID, error diagnostics copy button
wikirby Mar 20, 2026
c30de2f
i18n, accessibility, contrast fixes for renderer UI
wikirby Mar 24, 2026
1f1ca8e
Add region overlay instruction bar, mode button tooltips, notebook re…
wikirby Mar 25, 2026
01e8a2f
Bump to 3.11.0, fix prod build minification, fix capture progress tex…
wikirby Mar 26, 2026
996c142
Add charset utf-8 to renderer.html, update NVDA screen reader docs
wikirby Mar 26, 2026
04e5ccd
Revert mode buttons from radio to toolbar with aria-pressed
wikirby Mar 26, 2026
994946f
Article preview header: highlighter, font toggle, size controls, save…
wikirby Mar 26, 2026
0e2d2fe
Add custom highlight cursor on highlighter toggle
wikirby Mar 26, 2026
db621ae
Separate success banner from Clip button, add View in OneNote button
wikirby Mar 26, 2026
74683e3
Hierarchical section picker, success banner, client-side save timeout…
wikirby Mar 26, 2026
9539dd9
Fix lint errors, add SW keepalive and 5-minute inactivity auto-close
wikirby Mar 26, 2026
ad533ff
Keyboard region selection, forced-colors high contrast, bookmark fixes
wikirby Apr 6, 2026
2958f98
Telemetry parity with legacy clipper, version consolidation, nav-away…
wikirby Apr 7, 2026
d1c67ef
PDF support parity: detection, preview, save, telemetry in unified re…
wikirby Apr 7, 2026
bd339bf
Local PDF handling, PDFJS error fix, worker inject guard
wikirby Apr 7, 2026
e70cdb8
Chunked port streaming for save, bookmark fixes, PDF mode button enable
wikirby Apr 7, 2026
24fb43e
PDF region mode: show Region button, mode switching, preview restoration
wikirby Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions THIRD-PARTY-NOTICES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,21 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

-------------------------------------------

@mozilla/readability

Copyright (c) 2010 Arc90 Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
254 changes: 254 additions & 0 deletions docs/client-side-migration.md

Large diffs are not rendered by default.

184 changes: 184 additions & 0 deletions docs/i18n-a11y-contrast-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Renderer UI: i18n, Accessibility & Contrast Fixes

## Context

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:

1. **i18n** — ~15 hardcoded English strings that the old UI localized via `Localization.getLocalizedString()`
2. **Accessibility** — Lost ARIA state attributes, keyboard navigation, focus outlines, and aria-live regions
3. **Contrast** — Error text color `#ff6b6b` fails 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.

### How i18n works in this extension

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**.

### How locale is detected

`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.

## Files Modified

| 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 |

---

## Phase 1: i18n (mirror old `Localization.getLocalizedString()` via existing `loc()`)

### 1a. Wire sign-in panel to `loc()` in renderer.ts

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 description
- `WebClipper.Action.SigninMsa` → MSA button
- `WebClipper.Action.SigninOrgId` → OrgId button
- "Signing in..." kept as hardcoded English (no existing key, brief transient state)

### 1b. Wire field labels to `loc()`

- 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)

### 1c. Wire remaining hardcoded strings to `loc()`

| 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) |

### 1d. New keys in strings.json — ZERO

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).

---

## Phase 2: Contrast Fixes

### 2a. Error text color (CRITICAL — 3.8:1 → 5.3:1)

`.signin-error` color: `#ff6b6b` → `#ff9999` (~5.3:1 on `#56197c`, passes WCAG AA)

### 2b. Region add-button border (1.7:1 → 3.4:1)

Border: `#bbb` → `#999` (~3.4:1 on `#f3f2f1`, passes SC 1.4.11)
Text: `#666` → `#555` (~5.9:1, improvement)

### 2c. Focus outlines (mirroring old `@FocusOnPurpleBackground: #f8f8f8`)

- `#sidebar` interactive 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`

---

## Phase 3: Accessibility — ARIA & Keyboard

### 3a. `<html lang>` attribute (WCAG 3.1.1 Level A)

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`).

### 3b. Mode buttons — ARIA state

- Container: `role="toolbar"` with localized `aria-label`
- Buttons: standard `<button>` elements with `aria-pressed` (toggle button pattern)
- Reverted from `role="radio"` / radiogroup — buttons are more natural for mode selection

### 3c. Mode buttons — arrow key navigation

Arrow Up/Down/Left/Right + Home/End navigation between mode buttons. Mirrors old `enableAriaInvoke()` from `componentBase.ts`.

### 3d. Section picker — ARIA combobox

- Trigger: `role="combobox"`, `aria-haspopup="listbox"`, `aria-expanded`
- List items: `role="option"`, `aria-selected`
- Escape to close, arrow keys to navigate

### 3e. Label `for` associations

- `<label for="title-field">` and `<label for="note-field">`
- `aria-labelledby` on source-url and section-selected (non-input elements)

### 3f. aria-live regions

- `<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

### 3g. Sign-in focus management

Focus first sign-in button on overlay show; focus first mode button on overlay hide.

### 3h. Copy diagnostics button

`aria-label="Copy diagnostic information"` (hardcoded English — technical label).

### 3i. Sign-out disabled state

Replace `pointer-events: none` + opacity with `aria-disabled="true"` + `tabindex="-1"` (accessible to keyboard/screen readers).

---

## RTL Support Assessment (Deferred — Separate Effort)

**How old UI handled RTL:**
- `localeSpecificTasks.ts` calls `Rtl.isRtl(locale)` (checks `ar, fa, he, sd, ug, ur`)
- Loads `clipper-rtl.css` / `sectionPicker-rtl.css` instead of LTR versions
- `styledFrameFactory.ts` flips iframe position (`left: 0` instead of `right: 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):**
1. Detect RTL locale and set `dir="rtl"` on `<html>`
2. Convert `renderer.less` to CSS logical properties (`margin-inline-start/end`, etc.)
3. Flip: sidebar position, section picker arrow, user-info alignment, feedback icon margin
4. Test with at least one RTL locale (ar or he)

**Why defer:** RTL affects layout fundamentals. Best done as a dedicated pass.

---

## Implementation Order

1. **Phase 2a** — Error contrast fix (LESS)
2. **Phase 1a–1c** — i18n wiring (renderer.ts only, reuse existing keys)
3. **Phase 2b–2c** — Remaining contrast + focus outlines (LESS)
4. **Phase 3a** — `<html lang>` + dynamic locale (HTML + TS)
5. **Phase 3e** — Label `for` associations (HTML)
6. **Phase 3b–3c** — Mode button ARIA + arrow keys (TS)
7. **Phase 3d** — Section picker ARIA (HTML + TS)
8. **Phase 3f** — aria-live regions (HTML + LESS + TS)
9. **Phase 3g–3i** — Focus management, copy button label, signout disabled state (TS)

---

## Verification

1. **Build**: `npm run build` — check for TS compilation errors
2. **Edge target**: Verify `renderer.js` and `renderer.css` in target output
3. **Manual testing in Edge** (verified):
- Sign-in panel: localized text appears
- Mode buttons: arrow key navigation, `aria-checked` updates in devtools
- Section picker: `aria-expanded` toggles, Escape closes, arrow keys navigate items
- Save error: `#ff9999` error text readable
- Tab order: mode buttons → title → note → section → Clip → Cancel → feedback → sign out (wraps)
- Focus outlines: `2px solid #f8f8f8` visible on all sidebar controls
- Focus management: Cancel focused during capture → Full Page button after capture → sign-in button on overlay
4. **No functional regressions**: Capture, save, region, article, bookmark modes all work
5. **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
125 changes: 125 additions & 0 deletions docs/telemetry-parity-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Telemetry Parity: Implementation Status

## Background

The new unified renderer window communicates telemetry to the worker via port messages (`{ action: "telemetry", data: LogDataPackage }`). The worker routes these to `this.logger` (an `AriaLoggerDecorator` in production builds) via `Log.parseAndLogDataPackage()`.

### Key findings during implementation

- **Build-time swap**: `gulpfile.js` replaces `logManager.ts` (stub) with `logManager_internal.ts` (Aria-enabled) when the internal file exists. The `enable_console_logging` localStorage flag only controls *additional* console output — Aria events always flow in production builds.
- **Aria SDK transport**: v2.8.2's `ajax()` method uses `fetch()` (not XHR), so it works in MV3 service workers.
- **ProductionRequirements gate**: `SessionLogger` queues ALL events until 8 context properties are set: `AppInfoId`, `AppInfoVersion`, `BrowserLanguage`, `ExtensionLifecycleId`, `ClipperType`, `DeviceInfoId`, `FlightInfo`, `InPrivateBrowsing`. Events only flush to Aria once all 8 exist. The new renderer bypassed the legacy i18n path that set `BrowserLanguage`, so events were silently queued forever. Fixed by setting `BrowserLanguage` + fallback `FlightInfo` in `WebExtensionWorker` constructor.
- **Version string**: `ExtensionBase.version` was hardcoded and drifted from manifest. Fixed: `getExtensionVersion()` now reads `chrome.runtime.getManifest().version` with static fallback. Four locations to keep in sync: `extensionBase.ts`, `edge/manifest.json`, `chrome/manifest.json`, `package.json`.

## Telemetry helpers in renderer.ts

```
logFunnel(label) — Funnel events (user journey steps)
logTelemetryEvent(event) — BaseEvent/PromiseEvent (stops timer, serializes)
logFailure(label, type, info, id) — Failure events
logClickEvent(clickId) — Click tracking
setTelemetryContext(key, value) — Context properties (persist across events)
logSessionEnd(trigger) — Session end
logSessionStart() — Session start
```

## Context properties set by renderer

| Property | When set | Value |
|----------|---------|-------|
| `ContentType` | Renderer open (default FullPage) + mode switch | `"FullPage"`, `"Article"`, `"Bookmark"`, `"Region"` |
| `AuthType` | Sign-in button click + already-signed-in path | `"Msa"` or `"OrgId"` |
| `UserInfoId` | After sign-in succeeds + already-signed-in path | User CID string |

On sign-out, `AuthType` is reset to `"None"` and `UserInfoId` to `""` before the new session starts — prevents next sign-in's events from carrying the old user's identity.

## Events implemented

### Funnel events (user journey)
| Event | Trigger | Properties |
|-------|---------|------------|
| `Invoke` | Renderer opens | — |
| `AuthAlreadySignedIn` | User is pre-authenticated | — |
| `AuthAttempted` | Sign-in button click | — |
| `AuthSignInCompleted` | Sign-in succeeds | — |
| `AuthSignInFailed` | Sign-in fails | — |
| `ClipAttempted` | Save button click | — |
| `ViewInWac` | "View in OneNote" button click | — |
| `SignOut` | Sign-out link click | — |

### Lifecycle events
| Event | Type | Trigger | Properties |
|-------|------|---------|------------|
| `HandleSignInEvent` | PromiseEvent | Sign-in result | `CorrelationId`, `UserInformationReturned`, `SignInCancelled`, Status, FailureInfo |
| `UserInfoUpdated` | BaseEvent | After sign-in | `UserUpdateReason`, `LastUpdated` (UTC) |
| `CloseClipper` | BaseEvent | Cancel/close when clipCount===0 | `CurrentPanel`, `CloseReason` |
| `InvokeClipper` | BaseEvent | Extension button click (worker) | `InvokeSource`, `InvokeMode` |
| `HideClipperDueToSpaNavigate` | BaseEvent | Original tab URL changes (worker) | — |

### Clip events
| Event | Type | Trigger | Properties |
|-------|------|---------|------------|
| `ClipToOneNoteAction` | PromiseEvent | Save result (timer from save click) | `CorrelationId`, Status, FailureInfo |
| `ClipCommonOptions` | BaseEvent | Save button click | `ClipMode` (FullPage/Augmentation/Bookmark/Region), `PageTitleModified`, `AnnotationAdded` |
| `ClipRegionOptions` | BaseEvent | Save in region mode | `NumRegions` |

### Region events
| Event | Type | Trigger | Properties |
|-------|------|---------|------------|
| `RegionSelectionCapturing` | BaseEvent | Region capture complete | `Width`, `Height` |
| `RegionSelectionProcessing` | BaseEvent | Region image processed | `Width`, `Height`, `IsHighDpiScreen` |

### Section picker events
| Event | Type | Trigger | Properties |
|-------|------|---------|------------|
| `GetNotebooks` | PromiseEvent | Notebook fetch | `CurrentSectionStillExists`, Status, FailureInfo |
| Click: `sectionPickerLocationContainer` | Click | Dropdown opened | — |
| Click: `sectionComponent` | Click | Section selected | — |

### Mode button clicks
| Click ID | Trigger |
|----------|---------|
| `fullPageButton` | Full Page mode selected |
| `augmentationButton` | Article mode selected |
| `bookmarkButton` | Bookmark mode selected |
| `regionButton` | Region mode selected |

### Error handling
| Event | Trigger | Properties |
|-------|---------|------------|
| `UnhandledExceptionThrown` | `window.onerror` / `unhandledrejection` | `{ error: "msg (file:line:col) at stack" }`, id: `"Renderer"` |
| `OnLaunchOneNoteButton` | View in OneNote URL missing | `{ error: "..." }` |
| `RegionSelectionProcessing` failure | Region capture error | `{ error: "..." }` |

### PromiseEvent timer notes
`ClipToOneNoteAction` and `HandleSignInEvent` are created at action start (save click / sign-in click) and completed on result, so `Duration` reflects actual wait time. `GetNotebooks` is created at fetch start.

## Events NOT migrated (legacy-only)

| Event | Reason |
|-------|--------|
| `ClearNoOpTracker` | No NoOp tracker in new renderer |
| `LocalFilesNotAllowedPanelShown` | PDF not yet in new UI |
| `SetDoNotPromptRatings`, `SetIsRatingsPromptLogicExecutedInEdge`, `ShouldShowRatingsPrompt` | Ratings not in new UI |
| `ClosePageNavTooltip` | PageNav fires independently |
| `OrphanedWebClippersDueToExtensionRefresh` | Different lifecycle |
| `DebugFeedback` | Legacy debug panel |
| `CompressRegionSelection`, `RegionSelectionLoading` | Different flow |
| `ClipPdfOptions`, `PdfByteMetadata`, `ClipSelectionOptions` | PDF/Selection modes not yet in new UI |
| `ClipAugmentationOptions` | Legacy article model — `ClipCommonOptions` covers this |
| `InvokeWhatsNew`, `InvokeTooltip` | Fire through PageNav system independently |

## Navigation-away detection

Worker listens on `tabs.onUpdated` for URL changes on the original tab while the renderer is open. On change:
1. Logs `HideClipperDueToSpaNavigate` event (matches legacy)
2. Sends `{ action: "pageNavigated" }` to renderer via port
3. Renderer closes the window

Listener is cleaned up on port disconnect.

## How to debug telemetry

1. **Console logging**: In any extension page console (e.g., renderer F12), run `localStorage.setItem("enable_console_logging", "true")`, then reload extension. Events appear in service worker console.
2. **Network tab**: Open SW DevTools → Network → filter `aria`. POST requests to `browser.pipe.aria.microsoft.com/Collector/3.0/` contain Bond-binary payloads.
3. **Aria token**: `build/settings.json` has the dev sandbox token. Production token is injected during release build via `--production` flag which merges `src/settings/production.json` + `src/settings_internal/production.json`.
Loading