Skip to content

timeslider: respect showAuthorshipColors and padFontFamily settings#7899

Merged
JohnMcLear merged 2 commits into
ether:developfrom
JohnMcLear:fix/timeslider-settings
Jun 5, 2026
Merged

timeslider: respect showAuthorshipColors and padFontFamily settings#7899
JohnMcLear merged 2 commits into
ether:developfrom
JohnMcLear:fix/timeslider-settings

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

Fixes two settings that were not properly applied in the timeslider view: authorship colors visibility and font type selection.

Changes

Authorship colors (broadcast.ts)

  • Gate author background styles on .authorColors class prefix so toggling the setting in the pad editor hides authorship colors in the timeslider (matching ace2_inner.ts behaviour)

Timeslider settings bridge (timeslider.ts + pad_mode.ts)

  • Expose BroadcastSlider.setShowAuthorColors() and BroadcastSlider.setPadFontFamily() for parent-to-iframe dynamic propagation
  • Bridge outer authorship-colours checkboxes and font-family selectors to the timeslider iframe in pad_mode.ts wireSettingsBridges, with proper teardown

Font family (timeslider.ts)

  • Read padFontFamily cookie at init time so preferences set in the pad editor are honoured on first paint
  • Save font-family to cookie on change and restore on reload

Regression tests

  • Playwright tests covering: authorship colors off via cookie, colours on by default, font-family applied on change, and font-family restored from cookie

Testing

cd src
npx playwright test tests/frontend-new/specs/timeslider_author_colors.spec.ts --project=chromium

@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Jun 5, 2026

Review Summary by Qodo

(Agentic_describe updated until commit 5e039a8)

Respect authorship colors, font family, and line numbers in timeslider

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Gate authorship color CSS on .authorColors class to respect visibility setting
• Bridge pad editor settings (colors, font, line numbers) to embedded timeslider iframe
• Read and persist view preferences from cookie on timeslider init and change
• Refactor settings-bridge wiring into unified listener management system
• Add Playwright tests for authorship colors and font persistence
Diagram
flowchart LR
  PadEditor["Pad Editor Settings"]
  Cookie["Preference Cookie"]
  Bridge["Settings Bridge<br/>pad_mode.ts"]
  Timeslider["Timeslider iframe<br/>BroadcastSlider"]
  UI["UI Elements<br/>colors/font/lines"]
  
  PadEditor -->|"setShowAuthorColors<br/>setPadFontFamily<br/>setShowLineNumbers"| Bridge
  Cookie -->|"readPadPrefs on init"| Timeslider
  Bridge -->|"call methods"| Timeslider
  Timeslider -->|"applyShowAuthorColors<br/>applyPadFontFamily<br/>applyShowLineNumbers"| UI
  Timeslider -->|"setPadPref"| Cookie

Loading

Grey Divider

File Changes

1. src/static/js/broadcast.ts 🐞 Bug fix +1/-1

Gate author colors CSS on class prefix

• Gate author background color CSS selector on .authorColors class prefix
• Ensures authorship colors only apply when the setting is enabled

src/static/js/broadcast.ts


2. src/static/js/pad_mode.ts ✨ Enhancement +39/-32

Unify settings bridge wiring and add view-setting propagation

• Replace five ad-hoc listener stores with unified outerControlListeners array
• Introduce bindOuter() helper for consistent listener registration and teardown
• Refactor wireSettingsBridges() to use data-driven bridgeView() helper
• Bridge authorship colors, font family, and line numbers from pad settings to timeslider
• Support both legacy popup ids and new #padsettings-… pane elements

src/static/js/pad_mode.ts


3. src/static/js/timeslider.ts ✨ Enhancement +35/-2

Add view-setting methods and cookie persistence

• Add applyShowAuthorColors() helper to toggle .authorColors class on body elements
• Add applyPadFontFamily() helper to set font-family with proper jQuery 3 reset handling
• Read showAuthorshipColors and padFontFamily from cookie on init for first-paint consistency
• Expose BroadcastSlider.setShowAuthorColors(), setShowLineNumbers(), setPadFontFamily()
 methods for parent iframe calls
• Persist font family and authorship color preferences to cookie on change

src/static/js/timeslider.ts


View more (2)
4. src/static/js/vendors/nice-select.ts 🐞 Bug fix +5/-1

Dispatch native change event for listeners

• Dispatch native change event after jQuery trigger on option selection
• Ensures addEventListener-based bridges in pad_mode.ts receive the event
• Fixes jQuery 3.7.1 behavior where trigger() does not dispatch native DOM events

src/static/js/vendors/nice-select.ts


5. src/tests/frontend-new/specs/timeslider_author_colors.spec.ts 🧪 Tests +84/-0

Add Playwright tests for timeslider settings

• Test authorship colors respect showAuthorshipColors=false cookie from pad editor
• Test authorship colors enabled by default when cookie unset
• Test font-family selector applies to innerdocbody element
• Test font-family selection persists and restores from cookie

src/tests/frontend-new/specs/timeslider_author_colors.spec.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Jun 5, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0)

Grey Divider


Action required

1. Double change event firing 🐞 Bug ≡ Correctness ⭐ New
Description
The nice-select option click handler triggers both trigger('change') and a native
dispatchEvent('change'), so change handlers can run twice for one user action. This can
duplicate side effects such as pad.setMyViewOption()pad.applyOptionsChange() when selecting
font/language/etc., and can also double-run the new addEventListener-based pad→timeslider bridge
handlers.
Code

src/static/js/vendors/nice-select.ts[R168-172]

+      const $nativeSelect = $dropdown.prev('select');
+      $nativeSelect.val($option.data('value')).trigger('change');
+      // Fire native event for handlers attached via addEventListener (e.g.
+      // the pad_mode.ts settings bridge to the embedded timeslider iframe).
+      $nativeSelect[0]?.dispatchEvent(new Event('change', {bubbles: true}));
Evidence
The PR adds a second change emission (native dispatch) after an existing jQuery trigger. jQuery
attaches .on('change') handlers using addEventListener, so dispatching a native change event
will invoke jQuery handlers as well, making the combination run them twice. This is impactful
because select change handlers (e.g. font menu) call into pad.setMyViewOption() which applies
options changes.

src/static/js/vendors/nice-select.ts[157-173]
src/static/js/vendors/jquery.ts[4944-5000]
src/static/js/vendors/jquery.ts[8626-8632]
src/static/js/pad_editor.ts[82-84]
src/static/js/timeslider.ts[260-264]
src/static/js/pad.ts[610-637]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`nice-select` currently emits **two** change notifications for a single option click:
1) a jQuery `trigger('change')`
2) a native `dispatchEvent(new Event('change'))`

Because jQuery registers its `.on('change')` handlers via a native `addEventListener` (jQuery eventHandle), the native `dispatchEvent()` will also invoke jQuery handlers. The result is that jQuery handlers run once via `trigger()` and again via the native dispatch.

### Issue Context
This was added to support native `addEventListener` handlers (e.g., the new `pad_mode.ts` settings bridge), but keeping both calls causes duplicate handler execution.

### Fix Focus Areas
- src/static/js/vendors/nice-select.ts[157-173]

### Suggested fix
Update the option click handler to emit **only one** change event. The simplest fix is:
- Keep setting the value via `$nativeSelect.val(...)`
- **Remove** `.trigger('change')`
- Keep **only** the native `dispatchEvent(new Event('change', {bubbles: true}))`

This single native event will still trigger:
- native `addEventListener('change', ...)` listeners
- jQuery `.on('change', ...)` listeners (via jQuery’s native eventHandle)
- `onchange` property handlers

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. No initial settings sync 🐞 Bug ≡ Correctness
Description
In PadModeController.wireSettingsBridges(), the new author-colors and font-family bridges only
forward future change events, so the history iframe can initialize from its cookie defaults even
when the outer pad has already applied different effective view options programmatically. This can
leave in-place history showing wrong authorship-color visibility or font until the user toggles the
setting.
Code

src/static/js/pad_mode.ts[R542-562]

+    const bridgeAuthorColors = (cb: HTMLInputElement | null) => {
+      if (!cb) return;
+      const listener = () => {
+        try { inner.BroadcastSlider?.setShowAuthorColors?.(cb.checked); } catch (_e) {}
+      };
+      cb.addEventListener('change', listener);
+      this.authorColorsListeners.push({el: cb, type: 'change', fn: listener});
+    };
+    bridgeAuthorColors(document.getElementById('options-colorscheck') as HTMLInputElement | null);
+    bridgeAuthorColors(document.getElementById('padsettings-options-colorscheck') as HTMLInputElement | null);
+
+    const bridgePadFontFamily = (sel: HTMLSelectElement | null) => {
+      if (!sel) return;
+      const listener = () => {
+        try { inner.BroadcastSlider?.setPadFontFamily?.(sel.value); } catch (_e) {}
+      };
+      sel.addEventListener('change', listener);
+      this.fontFamilyListeners.push({el: sel, type: 'change', fn: listener});
+    };
+    bridgePadFontFamily(document.getElementById('viewfontmenu') as HTMLSelectElement | null);
+    bridgePadFontFamily(document.getElementById('padsettings-viewfontmenu') as HTMLSelectElement | null);
Evidence
The bridge code only registers change handlers and never invokes them to push the initial state.
The outer pad can set font/colors via changeViewOption() and padeditor.setViewOptions() without
triggering change, so the iframe will keep its cookie-derived defaults from timeslider.ts until
the user manually changes the control.

src/static/js/pad_mode.ts[542-563]
src/static/js/pad.ts[741-743]
src/static/js/pad.ts[880-885]
src/static/js/pad_editor.ts[253-274]
src/static/js/timeslider.ts[252-270]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new settings bridges in `pad_mode.ts` attach `change` listeners but never push the *current* outer state into the iframe on entry to history mode. If the outer state was set programmatically (URL/global override) and therefore does not emit a `change` event, the iframe will stay on its own cookie-derived defaults.
### Issue Context
- Outer pad can apply view overrides via `pad.changeViewOption(...)` (no cookie persistence, no DOM `change` event).
- `pad_editor.setViewOptions()` updates checkbox/select values programmatically (`.prop()` / `.val()`), which does not fire `change`.
- Timeslider iframe initializes from `readPadPrefs()` cookies.
### Fix Focus Areas
- src/static/js/pad_mode.ts[542-563]
- src/static/js/timeslider.ts[201-205]
- src/static/js/timeslider.ts[252-275]
### Suggested fix
1. In `wireSettingsBridges()`, after registering each bridge listener, perform an initial sync using the current outer control value.
2. Avoid persisting cookies during this initial sync if you want URL/global overrides to remain non-sticky:
 - Option A: Extend `BroadcastSlider.setShowAuthorColors` / `setPadFontFamily` to accept an optional `{persist: boolean}` (default `true`), and call with `persist:false` for the initial sync.
 - Option B: For the initial sync only, directly toggle the iframe DOM (`#innerdocbody/#sidedivinner` classes and `#innerdocbody` font-family) without calling the cookie-writing setters.
3. If `inner.BroadcastSlider` might not be ready at iframe `load`, retry initial sync (short polling with timeout) until the API becomes available.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 5e039a8

Results up to commit 03251f8


🐞 Bugs (1) 📘 Rule violations (0)


Remediation recommended
1. No initial settings sync 🐞 Bug ≡ Correctness
Description
In PadModeController.wireSettingsBridges(), the new author-colors and font-family bridges only
forward future change events, so the history iframe can initialize from its cookie defaults even
when the outer pad has already applied different effective view options programmatically. This can
leave in-place history showing wrong authorship-color visibility or font until the user toggles the
setting.
Code

src/static/js/pad_mode.ts[R542-562]

+    const bridgeAuthorColors = (cb: HTMLInputElement | null) => {
+      if (!cb) return;
+      const listener = () => {
+        try { inner.BroadcastSlider?.setShowAuthorColors?.(cb.checked); } catch (_e) {}
+      };
+      cb.addEventListener('change', listener);
+      this.authorColorsListeners.push({el: cb, type: 'change', fn: listener});
+    };
+    bridgeAuthorColors(document.getElementById('options-colorscheck') as HTMLInputElement | null);
+    bridgeAuthorColors(document.getElementById('padsettings-options-colorscheck') as HTMLInputElement | null);
+
+    const bridgePadFontFamily = (sel: HTMLSelectElement | null) => {
+      if (!sel) return;
+      const listener = () => {
+        try { inner.BroadcastSlider?.setPadFontFamily?.(sel.value); } catch (_e) {}
+      };
+      sel.addEventListener('change', listener);
+      this.fontFamilyListeners.push({el: sel, type: 'change', fn: listener});
+    };
+    bridgePadFontFamily(document.getElementById('viewfontmenu') as HTMLSelectElement | null);
+    bridgePadFontFamily(document.getElementById('padsettings-viewfontmenu') as HTMLSelectElement | null);
Evidence
The bridge code only registers change handlers and never invokes them to push the initial state.
The outer pad can set font/colors via changeViewOption() and padeditor.setViewOptions() without
triggering change, so the iframe will keep its cookie-derived defaults from timeslider.ts until
the user manually changes the control.

src/static/js/pad_mode.ts[542-563]
src/static/js/pad.ts[741-743]
src/static/js/pad.ts[880-885]
src/static/js/pad_editor.ts[253-274]
src/static/js/timeslider.ts[252-270]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new settings bridges in `pad_mode.ts` attach `change` listeners but never push the *current* outer state into the iframe on entry to history mode. If the outer state was set programmatically (URL/global override) and therefore does not emit a `change` event, the iframe will stay on its own cookie-derived defaults.

### Issue Context
- Outer pad can apply view overrides via `pad.changeViewOption(...)` (no cookie persistence, no DOM `change` event).
- `pad_editor.setViewOptions()` updates checkbox/select values programmatically (`.prop()` / `.val()`), which does not fire `change`.
- Timeslider iframe initializes from `readPadPrefs()` cookies.

### Fix Focus Areas
- src/static/js/pad_mode.ts[542-563]
- src/static/js/timeslider.ts[201-205]
- src/static/js/timeslider.ts[252-275]

### Suggested fix
1. In `wireSettingsBridges()`, after registering each bridge listener, perform an initial sync using the current outer control value.
2. Avoid persisting cookies during this initial sync if you want URL/global overrides to remain non-sticky:
  - Option A: Extend `BroadcastSlider.setShowAuthorColors` / `setPadFontFamily` to accept an optional `{persist: boolean}` (default `true`), and call with `persist:false` for the initial sync.
  - Option B: For the initial sync only, directly toggle the iframe DOM (`#innerdocbody/#sidedivinner` classes and `#innerdocbody` font-family) without calling the cookie-writing setters.
3. If `inner.BroadcastSlider` might not be ready at iframe `load`, retry initial sync (short polling with timeout) until the API becomes available.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

@JohnMcLear JohnMcLear marked this pull request as draft June 5, 2026 11:29
JohnMcLear and others added 2 commits June 5, 2026 12:37
- nice-select.ts: dispatch native change event after jQuery trigger so
  addEventListener-based bridges (pad_mode.ts) fire (jQuery 3.7.1
  trigger() does not dispatch native DOM events)
- timeslider.ts: fix font-family reset (jQuery 3 ignores null css value);
  add showAuthorColors + showLineNumbers + padFontFamily support;
  wire cookie read/write for all three settings
- broadcast.ts: gate author color CSS on .authorColors class
- pad_mode.ts: bridge author colors, font family, line numbers checkboxes
  from pad settings into embedded timeslider iframe
- add Playwright test for showAuthorshipColors
Refactor only — no behaviour change.

pad_mode.ts: collapse the five ad-hoc listener stores (playbackChange-
Listener, followChangeListener, authorColorsListeners, fontFamily-
Listeners, lineNumbersListeners) into the single outerControlListeners
list via a shared bindOuter() helper, so every outer-control listener is
registered and torn down through one uniform path. The three view-setting
bridges (author colours, font family, line numbers) become one
data-driven bridgeView() over their element ids instead of three
near-identical closures.

timeslider.ts: hoist applyPadFontFamily to a top-level helper next to
applyShowAuthorColors (keeping the '' reset for jQuery 3), and co-locate
the three BroadcastSlider view-setting methods that were split across the
function.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JohnMcLear JohnMcLear force-pushed the fix/timeslider-settings branch from 03251f8 to 5e039a8 Compare June 5, 2026 11:58
@JohnMcLear JohnMcLear marked this pull request as ready for review June 5, 2026 12:27
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Jun 5, 2026

Code review by qodo was updated up to the latest commit 5e039a8

Comment on lines +168 to +172
const $nativeSelect = $dropdown.prev('select');
$nativeSelect.val($option.data('value')).trigger('change');
// Fire native event for handlers attached via addEventListener (e.g.
// the pad_mode.ts settings bridge to the embedded timeslider iframe).
$nativeSelect[0]?.dispatchEvent(new Event('change', {bubbles: true}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Double change event firing 🐞 Bug ≡ Correctness

The nice-select option click handler triggers both trigger('change') and a native
dispatchEvent('change'), so change handlers can run twice for one user action. This can
duplicate side effects such as pad.setMyViewOption()pad.applyOptionsChange() when selecting
font/language/etc., and can also double-run the new addEventListener-based pad→timeslider bridge
handlers.
Agent Prompt
### Issue description
`nice-select` currently emits **two** change notifications for a single option click:
1) a jQuery `trigger('change')`
2) a native `dispatchEvent(new Event('change'))`

Because jQuery registers its `.on('change')` handlers via a native `addEventListener` (jQuery eventHandle), the native `dispatchEvent()` will also invoke jQuery handlers. The result is that jQuery handlers run once via `trigger()` and again via the native dispatch.

### Issue Context
This was added to support native `addEventListener` handlers (e.g., the new `pad_mode.ts` settings bridge), but keeping both calls causes duplicate handler execution.

### Fix Focus Areas
- src/static/js/vendors/nice-select.ts[157-173]

### Suggested fix
Update the option click handler to emit **only one** change event. The simplest fix is:
- Keep setting the value via `$nativeSelect.val(...)`
- **Remove** `.trigger('change')`
- Keep **only** the native `dispatchEvent(new Event('change', {bubbles: true}))`

This single native event will still trigger:
- native `addEventListener('change', ...)` listeners
- jQuery `.on('change', ...)` listeners (via jQuery’s native eventHandle)
- `onchange` property handlers

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@JohnMcLear JohnMcLear merged commit 9af77e7 into ether:develop Jun 5, 2026
34 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant