codex-overleaf-link 1.9.3 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,248 @@
1
+ (function initCodexOverleafUpdateNotice(root) {
2
+ 'use strict';
3
+
4
+ let notice = null;
5
+ let currentView = null;
6
+ let mountedPanel = null;
7
+ let actionInFlight = false;
8
+ let completionTimer = null;
9
+ let listenerInstalled = false;
10
+ let getLocale = () => '';
11
+
12
+ function mount(panelLike, options = {}) {
13
+ if (typeof options.getLocale === 'function') {
14
+ getLocale = options.getLocale;
15
+ }
16
+ const panel = panelLike?.panelEl || panelLike;
17
+ if (!panel) return;
18
+ if (mountedPanel === panel) {
19
+ render();
20
+ return;
21
+ }
22
+ mountedPanel = panel;
23
+ notice?.remove();
24
+ notice = document.createElement('section');
25
+ notice.className = 'codex-update-notice';
26
+ notice.hidden = true;
27
+ notice.setAttribute('aria-label', tx('Stable update', '稳定版更新'));
28
+ const header = panel.querySelector('[data-panel-header]');
29
+ if (typeof header?.insertAdjacentElement === 'function') {
30
+ header.insertAdjacentElement('afterend', notice);
31
+ } else if (typeof header?.after === 'function') {
32
+ header.after(notice);
33
+ } else if (typeof panel.prepend === 'function') {
34
+ panel.prepend(notice);
35
+ } else {
36
+ panel.append?.(notice);
37
+ }
38
+ notice.addEventListener('click', handleClick);
39
+
40
+ if (!listenerInstalled) {
41
+ listenerInstalled = true;
42
+ chrome.runtime.onMessage.addListener(message => {
43
+ if (message?.type !== 'codex-overleaf/consent-update-state') return;
44
+ currentView = message.view;
45
+ render();
46
+ });
47
+ }
48
+ request('codex-overleaf/consent-update-get-state')
49
+ .then(view => {
50
+ currentView = view;
51
+ render();
52
+ })
53
+ .catch(() => {});
54
+ }
55
+
56
+ async function handleClick(event) {
57
+ const action = event.target?.closest?.('[data-update-notice-action]')?.dataset?.updateNoticeAction;
58
+ if (!action || actionInFlight) return;
59
+ actionInFlight = true;
60
+ render();
61
+ try {
62
+ currentView = await request({
63
+ install: 'codex-overleaf/consent-update-install',
64
+ later: 'codex-overleaf/consent-update-later',
65
+ retry: 'codex-overleaf/consent-update-check'
66
+ }[action]);
67
+ } catch (error) {
68
+ currentView = {
69
+ ...(currentView || {}),
70
+ state: {
71
+ ...(currentView?.state || {}),
72
+ state: 'failed',
73
+ code: error?.code || 'update_failed',
74
+ message: error?.message || tx('Update failed.', '更新失败。')
75
+ },
76
+ progress: { value: 0, determinate: true, phase: 'failed' },
77
+ showPanel: true
78
+ };
79
+ } finally {
80
+ actionInFlight = false;
81
+ render();
82
+ }
83
+ }
84
+
85
+ async function request(type) {
86
+ if (!type) throw new Error('Unknown update action.');
87
+ const response = await chrome.runtime.sendMessage({ type });
88
+ if (!response?.ok) {
89
+ const error = new Error(response?.error?.message || tx('Update action failed.', '更新操作失败。'));
90
+ error.code = response?.error?.code || 'update_failed';
91
+ throw error;
92
+ }
93
+ return response.result;
94
+ }
95
+
96
+ function render() {
97
+ if (!notice || !currentView) return;
98
+ clearTimeout(completionTimer);
99
+ if (!currentView.showPanel) {
100
+ notice.hidden = true;
101
+ notice.replaceChildren();
102
+ return;
103
+ }
104
+
105
+ const focusedAction = document.activeElement?.dataset?.updateNoticeAction || '';
106
+ const state = currentView.state || {};
107
+ const progress = currentView.progress || {};
108
+ const copy = getCopy(state);
109
+ notice.hidden = false;
110
+ notice.dataset.state = state.state || 'idle';
111
+
112
+ const body = document.createElement('div');
113
+ body.className = 'codex-update-notice-body';
114
+ const eyebrow = document.createElement('span');
115
+ eyebrow.className = 'codex-update-notice-eyebrow';
116
+ eyebrow.textContent = copy.eyebrow;
117
+ const title = document.createElement('strong');
118
+ title.textContent = copy.title;
119
+ const detail = document.createElement('span');
120
+ detail.className = 'codex-update-notice-detail';
121
+ detail.textContent = copy.detail;
122
+ body.append(eyebrow, title, detail);
123
+
124
+ if (!['update_available', 'failed', 'rolled_back'].includes(state.state)) {
125
+ const bar = document.createElement('div');
126
+ bar.className = 'codex-update-notice-progress' + (progress.determinate ? '' : ' is-indeterminate');
127
+ bar.setAttribute('role', 'progressbar');
128
+ bar.setAttribute('aria-valuemin', '0');
129
+ bar.setAttribute('aria-valuemax', '100');
130
+ bar.setAttribute('aria-valuetext', copy.detail);
131
+ if (progress.determinate) bar.setAttribute('aria-valuenow', String(progress.value || 0));
132
+ const fill = document.createElement('span');
133
+ fill.style.width = Math.max(0, Math.min(100, Number(progress.value || 0))) + '%';
134
+ bar.append(fill);
135
+ body.append(bar);
136
+ }
137
+
138
+ const actions = document.createElement('div');
139
+ actions.className = 'codex-update-notice-actions';
140
+ for (const action of visibleActions(state.state)) {
141
+ const button = document.createElement('button');
142
+ button.type = 'button';
143
+ button.dataset.updateNoticeAction = action.id;
144
+ button.className = action.primary ? 'is-primary' : '';
145
+ button.textContent = action.label;
146
+ button.disabled = actionInFlight;
147
+ actions.append(button);
148
+ }
149
+ notice.replaceChildren(body, actions);
150
+ if (focusedAction) {
151
+ notice.querySelector('[data-update-notice-action="' + focusedAction + '"]:not(:disabled)')?.focus();
152
+ }
153
+ if (state.state === 'committed') {
154
+ completionTimer = setTimeout(() => {
155
+ if (notice?.dataset.state === 'committed') notice.hidden = true;
156
+ }, 5000);
157
+ }
158
+ }
159
+
160
+ function visibleActions(state) {
161
+ if (actionInFlight) return [];
162
+ if (state === 'update_available') {
163
+ return [
164
+ { id: 'later', label: tx('Later', '稍后'), primary: false },
165
+ { id: 'install', label: tx('Update now', '立即更新'), primary: true }
166
+ ];
167
+ }
168
+ if (['staged', 'waiting_for_idle'].includes(state)) {
169
+ return [{ id: 'later', label: tx('Later', '稍后'), primary: false }];
170
+ }
171
+ if (['failed', 'rolled_back'].includes(state)) {
172
+ return [{ id: 'retry', label: tx('Retry', '重试'), primary: true }];
173
+ }
174
+ return [];
175
+ }
176
+
177
+ function getCopy(state) {
178
+ const target = state.latestVersion ? 'v' + state.latestVersion : '';
179
+ const blocker = blockerCopy(state.blocker);
180
+ return {
181
+ update_available: {
182
+ eyebrow: tx('Update available', '发现新版本'),
183
+ title: target,
184
+ detail: tx('Signed stable update for the extension and Native Host.', '扩展与 Native Host 的签名稳定更新。')
185
+ },
186
+ downloading: {
187
+ eyebrow: tx('Updating Codex Overleaf Link', '正在更新 Codex Overleaf Link'),
188
+ title: target,
189
+ detail: tx('Downloading and verifying the coordinated update.', '正在下载并验证协调更新。')
190
+ },
191
+ staged: {
192
+ eyebrow: tx('Update ready', '更新已就绪'),
193
+ title: target,
194
+ detail: tx('Verified and ready to install at a safe point.', '已完成验证,将在安全时机安装。')
195
+ },
196
+ waiting_for_idle: {
197
+ eyebrow: tx('Waiting for a safe point', '正在等待安全时机'),
198
+ title: target,
199
+ detail: blocker
200
+ },
201
+ applying: {
202
+ eyebrow: tx('Installing update', '正在安装更新'),
203
+ title: target,
204
+ detail: tx('Installing extension and Native Host.', '正在安装扩展与 Native Host。')
205
+ },
206
+ awaiting_health: {
207
+ eyebrow: tx('Checking updated components', '正在检查更新后的组件'),
208
+ title: target,
209
+ detail: tx('Restarting and checking both components.', '正在重启并检查两个组件。')
210
+ },
211
+ committed: {
212
+ eyebrow: tx('Update complete', '更新完成'),
213
+ title: target,
214
+ detail: tx('Both components passed health confirmation.', '两个组件均已通过健康检查。')
215
+ },
216
+ rolled_back: {
217
+ eyebrow: tx('Previous version restored', '已恢复上一版本'),
218
+ title: state.currentVersion ? 'v' + state.currentVersion : '',
219
+ detail: tx('The replacement failed its health check.', '替换版本未通过健康检查。')
220
+ },
221
+ failed: {
222
+ eyebrow: tx('Update could not continue', '更新无法继续'),
223
+ title: target,
224
+ detail: state.message || tx('Review the update details and retry.', '请查看更新详情后重试。')
225
+ }
226
+ }[state.state] || { eyebrow: '', title: '', detail: '' };
227
+ }
228
+
229
+ function blockerCopy(value) {
230
+ return {
231
+ unsaved: tx('Overleaf has not confirmed that this document is saved.', 'Overleaf 尚未确认当前文档已保存。'),
232
+ active_run: tx('A Codex task is still running.', 'Codex 任务仍在运行。'),
233
+ cancelling: tx('Cancellation is still settling.', '取消操作仍在收尾。'),
234
+ storage_write: tx('Extension state is still being saved.', '扩展状态仍在保存。'),
235
+ review_action: tx('Accept or Undo is still running.', '接受或撤销操作仍在进行。'),
236
+ dialog_open: tx('A confirmation dialog is open.', '确认对话框仍处于打开状态。'),
237
+ native_project_locked: tx('Native Host still holds a project lock.', 'Native Host 仍持有项目锁。'),
238
+ native_run_active: tx('Native Host is still processing a task.', 'Native Host 仍在处理任务。')
239
+ }[value] || tx('Waiting until every Overleaf tab is saved and idle.', '正在等待所有 Overleaf 标签页完成保存并进入空闲状态。');
240
+ }
241
+
242
+ function tx(english, chinese) {
243
+ const locale = String(getLocale() || document.documentElement?.lang || 'en');
244
+ return /^zh\b/i.test(locale) ? chinese : english;
245
+ }
246
+
247
+ root.CodexOverleafUpdateNotice = Object.freeze({ mount });
248
+ })(window);
@@ -45,6 +45,7 @@
45
45
  });
46
46
 
47
47
  initPopupState();
48
+ scheduleConsentUpdateUi();
48
49
 
49
50
  async function initPopupState() {
50
51
  await checkNativeHost();
@@ -198,11 +199,11 @@
198
199
  function getStatusIconText(classification) {
199
200
  switch (classification) {
200
201
  case 'compatible':
201
- return 'OK';
202
+ return '';
202
203
  case 'update-available':
203
- return '!';
204
+ return '';
204
205
  default:
205
- return 'X';
206
+ return '';
206
207
  }
207
208
  }
208
209
 
@@ -244,4 +245,268 @@
244
245
  return 'Native host is not compatible. Update the native host, then reload the extension.';
245
246
  }
246
247
  }
248
+
249
+ function scheduleConsentUpdateUi() {
250
+ const start = () => {
251
+ if (typeof document.createElement !== 'function') return;
252
+ injectConsentUpdateStyles();
253
+ const section = ensureUpdateSection();
254
+ let actionInFlight = false;
255
+ let currentView = null;
256
+
257
+ section.addEventListener('click', async event => {
258
+ const action = event.target?.closest?.('[data-update-action]')?.dataset?.updateAction;
259
+ if (!action || actionInFlight) return;
260
+ actionInFlight = true;
261
+ renderUpdateSection(section, currentView, { busyAction: action });
262
+ try {
263
+ currentView = await sendConsentAction(action);
264
+ } catch (error) {
265
+ currentView = failedUpdateView(currentView, error);
266
+ } finally {
267
+ actionInFlight = false;
268
+ renderUpdateSection(section, currentView);
269
+ }
270
+ });
271
+
272
+ chrome.runtime.onMessage.addListener(message => {
273
+ if (message?.type !== 'codex-overleaf/consent-update-state') return;
274
+ currentView = message.view;
275
+ if (!actionInFlight) renderUpdateSection(section, currentView);
276
+ });
277
+
278
+ sendConsentAction('get')
279
+ .then(view => {
280
+ currentView = view;
281
+ renderUpdateSection(section, view);
282
+ })
283
+ .catch(error => renderUpdateSection(section, failedUpdateView(null, error)));
284
+ };
285
+
286
+ if (document.readyState === 'loading') {
287
+ document.addEventListener('DOMContentLoaded', start, { once: true });
288
+ } else {
289
+ Promise.resolve().then(start);
290
+ }
291
+ }
292
+
293
+ function ensureUpdateSection() {
294
+ let section = document.querySelector('.updates');
295
+ if (!section) {
296
+ section = document.createElement('section');
297
+ section.className = 'updates';
298
+ section.setAttribute('aria-label', 'Stable updates');
299
+ (nativeInstall || status).insertAdjacentElement('afterend', section);
300
+ }
301
+ section.classList.add('consent-updates');
302
+ section.replaceChildren();
303
+ return section;
304
+ }
305
+
306
+ async function sendConsentAction(action) {
307
+ const type = {
308
+ get: 'codex-overleaf/consent-update-get-state',
309
+ check: 'codex-overleaf/consent-update-check',
310
+ install: 'codex-overleaf/consent-update-install',
311
+ later: 'codex-overleaf/consent-update-later',
312
+ retry: 'codex-overleaf/consent-update-check'
313
+ }[action];
314
+ const response = await chrome.runtime.sendMessage({ type });
315
+ if (!response?.ok) {
316
+ const error = new Error(response?.error?.message || 'Update action failed.');
317
+ error.code = response?.error?.code || 'update_failed';
318
+ throw error;
319
+ }
320
+ return response.result;
321
+ }
322
+
323
+ function failedUpdateView(view, error) {
324
+ return {
325
+ ...(view || {}),
326
+ state: {
327
+ ...(view?.state || {}),
328
+ state: 'failed',
329
+ code: error?.code || 'update_failed',
330
+ message: error?.message || 'Update failed.'
331
+ },
332
+ progress: { value: 0, determinate: true, phase: 'failed' },
333
+ actions: { retry: true }
334
+ };
335
+ }
336
+
337
+ function renderUpdateSection(section, view = {}, options = {}) {
338
+ const focusedAction = document.activeElement?.dataset?.updateAction || '';
339
+ const state = view?.state || { state: 'idle' };
340
+ const progress = view?.progress || { value: 0, determinate: true, phase: 'idle' };
341
+ const copy = getUpdateCopy(state, view);
342
+ const card = document.createElement('div');
343
+ card.className = 'consent-update-card state-' + (state.state || 'idle');
344
+
345
+ const head = document.createElement('div');
346
+ head.className = 'consent-update-head';
347
+ const title = document.createElement('strong');
348
+ title.textContent = copy.title;
349
+ const version = document.createElement('span');
350
+ version.textContent = copy.version;
351
+ head.append(title, version);
352
+
353
+ const detail = document.createElement('p');
354
+ detail.className = 'consent-update-detail';
355
+ detail.textContent = copy.detail;
356
+
357
+ const progressEl = document.createElement('div');
358
+ progressEl.className = 'consent-update-progress' + (progress.determinate ? '' : ' is-indeterminate');
359
+ progressEl.setAttribute('role', 'progressbar');
360
+ progressEl.setAttribute('aria-label', copy.phase);
361
+ progressEl.setAttribute('aria-valuemin', '0');
362
+ progressEl.setAttribute('aria-valuemax', '100');
363
+ progressEl.setAttribute('aria-valuetext', copy.phase);
364
+ if (progress.determinate) progressEl.setAttribute('aria-valuenow', String(progress.value || 0));
365
+ const progressFill = document.createElement('span');
366
+ progressFill.style.width = Math.max(0, Math.min(100, Number(progress.value || 0))) + '%';
367
+ progressEl.append(progressFill);
368
+
369
+ const phase = document.createElement('div');
370
+ phase.className = 'consent-update-phase';
371
+ phase.textContent = copy.phase;
372
+
373
+ const actions = document.createElement('div');
374
+ actions.className = 'consent-update-actions';
375
+ for (const action of getVisibleActions(view, options.busyAction)) {
376
+ const actionButton = document.createElement('button');
377
+ actionButton.type = 'button';
378
+ actionButton.dataset.updateAction = action.id;
379
+ actionButton.className = action.primary ? 'update-primary' : 'update-secondary';
380
+ actionButton.textContent = action.label;
381
+ actionButton.disabled = Boolean(options.busyAction) || action.disabled;
382
+ actions.append(actionButton);
383
+ }
384
+
385
+ const live = document.createElement('div');
386
+ live.className = 'consent-update-live';
387
+ live.setAttribute('aria-live', 'polite');
388
+ live.textContent = ['committed', 'rolled_back', 'failed'].includes(state.state) ? copy.phase : '';
389
+
390
+ card.append(head, detail);
391
+ if (!['idle', 'update_available'].includes(state.state) || options.busyAction === 'check') {
392
+ card.append(progressEl, phase);
393
+ }
394
+ card.append(actions, live);
395
+ if (state.state === 'failed' && (state.code || state.message)) {
396
+ const details = document.createElement('details');
397
+ const summary = document.createElement('summary');
398
+ summary.textContent = utx('Technical details', '技术详情');
399
+ const code = document.createElement('code');
400
+ code.textContent = [state.code, state.message].filter(Boolean).join(': ');
401
+ details.append(summary, code);
402
+ card.append(details);
403
+ }
404
+ section.replaceChildren(card);
405
+ if (focusedAction) {
406
+ section.querySelector('[data-update-action="' + focusedAction + '"]:not(:disabled)')?.focus();
407
+ }
408
+ }
409
+
410
+ function getVisibleActions(view = {}, busyAction = '') {
411
+ const state = view.state?.state || 'idle';
412
+ if (busyAction) {
413
+ return [{ id: busyAction, label: getBusyLabel(busyAction), primary: true, disabled: true }];
414
+ }
415
+ if (state === 'update_available') {
416
+ return [
417
+ { id: 'later', label: utx('Later', '稍后提醒'), primary: false },
418
+ { id: 'install', label: utx('Update now', '立即更新'), primary: true }
419
+ ];
420
+ }
421
+ if (['staged', 'waiting_for_idle'].includes(state)) {
422
+ return [{ id: 'later', label: utx('Later', '稍后提醒'), primary: false }];
423
+ }
424
+ if (['downloading', 'applying', 'awaiting_health', 'checking'].includes(state)) return [];
425
+ if (['failed', 'rolled_back'].includes(state)) {
426
+ return [{ id: 'retry', label: utx('Retry', '重试'), primary: true }];
427
+ }
428
+ return [{ id: 'check', label: utx('Check latest', '检查更新'), primary: false }];
429
+ }
430
+
431
+ function getBusyLabel(action) {
432
+ return {
433
+ check: utx('Checking...', '正在检查...'),
434
+ retry: utx('Checking...', '正在检查...'),
435
+ install: utx('Starting update...', '正在启动更新...'),
436
+ later: utx('Saving...', '正在保存...')
437
+ }[action] || utx('Working...', '处理中...');
438
+ }
439
+
440
+ function getUpdateCopy(state, view) {
441
+ const current = state.currentVersion ? 'v' + state.currentVersion : '';
442
+ const latest = state.latestVersion ? 'v' + state.latestVersion : '';
443
+ const blocker = formatBlocker(state.blocker);
444
+ const map = {
445
+ idle: [utx('Stable updates', '稳定版更新'), utx('Up to date', '已是最新版'), utx('Automatically checks signed stable releases.', '自动检查经过签名验证的稳定版本。')],
446
+ checking: [utx('Checking for updates', '正在检查更新'), '', utx('Checking the latest signed stable release', '正在检查最新签名稳定版本')],
447
+ update_available: [utx('Update available', '发现新版本'), latest, view.snoozed ? utx('Reminder paused. Update remains ready here.', '提醒已暂停,仍可随时从这里更新。') : utx('Signed stable update for the extension and Native Host.', '扩展与 Native Host 的签名稳定更新。')],
448
+ downloading: [utx('Downloading update', '正在下载更新'), latest, utx('Downloading and verifying the coordinated update', '正在下载并验证协调更新')],
449
+ staged: [utx('Update ready', '更新已就绪'), latest, utx('Verified and ready to install at a safe point.', '已完成验证,将在安全时机安装。')],
450
+ waiting_for_idle: [utx('Waiting for a safe point', '正在等待安全时机'), latest, blocker],
451
+ applying: [utx('Installing update', '正在安装更新'), latest, utx('Installing extension and Native Host', '正在安装扩展与 Native Host')],
452
+ awaiting_health: [utx('Checking updated components', '正在检查更新后的组件'), latest, utx('Restarting and checking both components', '正在重启并检查两个组件')],
453
+ committed: [utx('Update complete', '更新完成'), latest || current, utx('Extension and Native Host were updated together.', '扩展与 Native Host 已同步更新。')],
454
+ rolled_back: [utx('Previous version restored', '已恢复上一版本'), current, utx('Health check failed, so the previous coordinated pair was restored.', '健康检查失败,已恢复上一组协调版本。')],
455
+ failed: [utx('Update could not continue', '更新无法继续'), latest, state.message || utx('Check the details and retry.', '请查看详情后重试。')]
456
+ };
457
+ const value = map[state.state] || map.idle;
458
+ return { title: value[0], version: value[1], detail: value[2], phase: value[2] };
459
+ }
460
+
461
+ function formatBlocker(blocker) {
462
+ const labels = {
463
+ unsaved: utx('Overleaf has not confirmed that this document is saved.', 'Overleaf 尚未确认当前文档已保存。'),
464
+ active_run: utx('A Codex task is still running.', 'Codex 任务仍在运行。'),
465
+ cancelling: utx('Cancellation is still settling.', '取消操作仍在收尾。'),
466
+ storage_write: utx('Extension state is still being saved.', '扩展状态仍在保存。'),
467
+ review_action: utx('Accept or Undo is still running.', '接受或撤销操作仍在进行。'),
468
+ dialog_open: utx('A confirmation dialog is open.', '确认对话框仍处于打开状态。'),
469
+ native_project_locked: utx('Native Host still holds a project lock.', 'Native Host 仍持有项目锁。'),
470
+ native_run_active: utx('Native Host is still processing a task.', 'Native Host 仍在处理任务。')
471
+ };
472
+ return labels[blocker] || utx('Waiting until every Overleaf tab is saved and idle.', '正在等待所有 Overleaf 标签页完成保存并进入空闲状态。');
473
+ }
474
+
475
+ function utx(english, chinese) {
476
+ const locale = document.documentElement?.lang || 'en';
477
+ return /^zh\b/i.test(locale) ? chinese : english;
478
+ }
479
+
480
+ function injectConsentUpdateStyles() {
481
+ if (document.getElementById('codex-overleaf-consent-update-styles')) return;
482
+ const style = document.createElement('style');
483
+ style.id = 'codex-overleaf-consent-update-styles';
484
+ style.textContent = [
485
+ 'body { width:344px; padding:16px; background:#171918; color:#eceeec; font-family:"Avenir Next","Segoe UI",sans-serif; }',
486
+ 'h1 { font-size:16px; letter-spacing:-.01em; }',
487
+ '.version-status { color:#a8ada9; }',
488
+ '.status-icon { min-width:10px; min-height:10px; width:10px; height:10px; box-shadow:0 0 0 3px rgba(255,255,255,.04); }',
489
+ '.status-icon.compatible { background:#7fd58a; } .status-icon.update-available { background:#d7b96e; } .status-icon.incompatible { background:#d87972; }',
490
+ '#open-panel { border-color:#39403c; background:#242825; color:#dfe4df; font-weight:600; }',
491
+ '#status { color:#9ba19c; font-size:12px; }',
492
+ '.consent-updates { margin-top:14px; padding-top:13px; border-top:1px solid #303431; }',
493
+ '.consent-update-card { padding-top:1px; }',
494
+ '.consent-update-head { display:flex; align-items:baseline; justify-content:space-between; gap:12px; }',
495
+ '.consent-update-head strong { font-size:13px; color:#f0f2f0; } .consent-update-head span { color:#85d99a; font-size:11px; font-weight:700; }',
496
+ '.consent-update-detail,.consent-update-phase { color:#a9aea9; font-size:12px; line-height:1.45; }',
497
+ '.consent-update-progress { position:relative; height:4px; margin:12px 0 8px; overflow:hidden; border-radius:999px; background:#2c312e; }',
498
+ '.consent-update-progress span { display:block; height:100%; border-radius:inherit; background:#4f9ee8; transition:width .25s ease; }',
499
+ '.consent-update-progress.is-indeterminate span { width:42% !important; animation:consent-progress 1.25s ease-in-out infinite; }',
500
+ '.consent-update-actions { display:flex; justify-content:flex-end; gap:8px; margin-top:12px; }',
501
+ '.consent-update-actions button { width:auto; min-width:92px; margin:0; padding:7px 12px; font-weight:650; }',
502
+ '.consent-update-actions .update-primary { border-color:#4a91d7; background:#3578bd; }',
503
+ '.consent-update-actions .update-secondary { border-color:#3c433f; background:transparent; color:#d5dad6; }',
504
+ '.consent-update-actions button:focus-visible { outline:2px solid #79b8f2; outline-offset:2px; }',
505
+ '.consent-update-live { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); }',
506
+ '.consent-update-card details { margin-top:10px; color:#a9aea9; font-size:11px; } .consent-update-card details code { background:#202320; color:#c9ceca; }',
507
+ '@keyframes consent-progress { 0% { transform:translateX(-110%); } 100% { transform:translateX(240%); } }',
508
+ '@media (prefers-reduced-motion:reduce) { .consent-update-progress.is-indeterminate span { animation:none; width:65% !important; } }'
509
+ ].join('\n');
510
+ document.head.append(style);
511
+ }
247
512
  })();
@@ -12,7 +12,7 @@
12
12
  const MIN_NATIVE_VERSION = '1.0.0';
13
13
  const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
14
14
  const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
15
- const BUILD_TARGET_VERSION = '1.9.3';
15
+ const BUILD_TARGET_VERSION = '1.9.5';
16
16
  const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
17
17
  const REQUIRED_CAPABILITIES = Object.freeze([
18
18
  'bridgePing',