codex-overleaf-link 1.9.3 → 1.9.4

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,201 @@
1
+ (function initCodexOverleafUpdateConsent(root, factory) {
2
+ 'use strict';
3
+
4
+ const api = factory();
5
+ root.CodexOverleafUpdateConsent = api;
6
+ if (typeof module === 'object' && module?.exports) {
7
+ module.exports = api;
8
+ }
9
+ })(typeof globalThis === 'object' ? globalThis : this, function createUpdateConsentApi() {
10
+ 'use strict';
11
+
12
+ const UPDATE_STATES = new Set([
13
+ 'idle',
14
+ 'checking',
15
+ 'update_available',
16
+ 'downloading',
17
+ 'staged',
18
+ 'waiting_for_idle',
19
+ 'applying',
20
+ 'awaiting_health',
21
+ 'committed',
22
+ 'rolled_back',
23
+ 'failed'
24
+ ]);
25
+ const EXECUTION_STATES = new Set([
26
+ 'downloading',
27
+ 'staged',
28
+ 'waiting_for_idle',
29
+ 'applying',
30
+ 'awaiting_health'
31
+ ]);
32
+ const TERMINAL_STATES = new Set(['committed', 'rolled_back', 'failed']);
33
+ const QUIET_FAILURE_CODES = new Set([
34
+ 'update_network_failed',
35
+ 'update_network_unavailable',
36
+ 'update_github_http_error'
37
+ ]);
38
+ const PROGRESS = Object.freeze({
39
+ idle: { value: 0, determinate: true, phase: 'idle' },
40
+ checking: { value: 0, determinate: false, phase: 'checking' },
41
+ update_available: { value: 0, determinate: true, phase: 'available' },
42
+ downloading: { value: 35, determinate: false, phase: 'downloading' },
43
+ staged: { value: 65, determinate: true, phase: 'staged' },
44
+ waiting_for_idle: { value: 65, determinate: true, phase: 'waiting' },
45
+ applying: { value: 82, determinate: false, phase: 'applying' },
46
+ awaiting_health: { value: 94, determinate: false, phase: 'health' },
47
+ committed: { value: 100, determinate: true, phase: 'committed' },
48
+ rolled_back: { value: 94, determinate: true, phase: 'rolled_back' },
49
+ failed: { value: 0, determinate: true, phase: 'failed' }
50
+ });
51
+
52
+ function normalizeUpdateState(value = {}, currentVersion = '') {
53
+ const blockers = Array.isArray(value.blockers)
54
+ ? value.blockers.map(safeToken).filter(Boolean).slice(0, 12)
55
+ : [safeToken(value.blocker)].filter(Boolean);
56
+ const state = UPDATE_STATES.has(value.state) ? value.state : 'idle';
57
+ return {
58
+ state,
59
+ managed: value.managed !== false,
60
+ currentVersion: safeVersion(value.currentVersion) || safeVersion(currentVersion),
61
+ latestVersion: safeVersion(value.latestVersion) || safeVersion(value.currentVersion) || safeVersion(currentVersion),
62
+ etag: String(value.etag || '').slice(0, 300),
63
+ blocker: blockers[0] || '',
64
+ blockers,
65
+ transactionId: safeId(value.transactionId),
66
+ lastCheckedAt: finiteNumber(value.lastCheckedAt),
67
+ stagedAt: finiteNumber(value.stagedAt),
68
+ postponeUntil: finiteNumber(value.postponeUntil),
69
+ code: safeToken(value.code),
70
+ message: String(value.message || '').replace(/\s+/g, ' ').trim().slice(0, 300)
71
+ };
72
+ }
73
+
74
+ function normalizeConsentState(value = {}) {
75
+ return {
76
+ schemaVersion: 1,
77
+ mode: 'consent',
78
+ snoozedVersion: safeVersion(value.snoozedVersion),
79
+ snoozedUntil: finiteNumber(value.snoozedUntil),
80
+ authorizedVersion: safeVersion(value.authorizedVersion),
81
+ authorizationId: safeId(value.authorizationId),
82
+ authorizedAt: finiteNumber(value.authorizedAt),
83
+ lastPromptedVersion: safeVersion(value.lastPromptedVersion),
84
+ lastPromptedAt: finiteNumber(value.lastPromptedAt)
85
+ };
86
+ }
87
+
88
+ function deriveViewModel(updateValue = {}, consentValue = {}, options = {}) {
89
+ const now = finiteNumber(options.now) || Date.now();
90
+ const state = normalizeUpdateState(updateValue, options.currentVersion);
91
+ const consent = normalizeConsentState(consentValue);
92
+ const newer = compareStableVersions(state.latestVersion, state.currentVersion) > 0;
93
+ const snoozed = newer &&
94
+ consent.snoozedVersion === state.latestVersion &&
95
+ consent.snoozedUntil > now;
96
+ const execution = EXECUTION_STATES.has(state.state);
97
+ const available = state.state === 'update_available' && newer;
98
+ const progress = getProgressModel(state);
99
+ const quietFailure = state.state === 'failed' && QUIET_FAILURE_CODES.has(state.code);
100
+
101
+ return {
102
+ state,
103
+ consent: {
104
+ mode: consent.mode,
105
+ snoozedVersion: consent.snoozedVersion,
106
+ snoozedUntil: consent.snoozedUntil,
107
+ authorizedVersion: consent.authorizedVersion,
108
+ authorizedAt: consent.authorizedAt
109
+ },
110
+ progress,
111
+ available,
112
+ snoozed,
113
+ execution,
114
+ showPanel: (available && !snoozed) || execution || state.state === 'rolled_back' ||
115
+ (state.state === 'failed' && !quietFailure && newer),
116
+ badge: getBadge(state, { available, snoozed }),
117
+ actions: {
118
+ check: ['idle', 'committed', 'rolled_back', 'failed'].includes(state.state),
119
+ install: available,
120
+ later: available || ['staged', 'waiting_for_idle'].includes(state.state),
121
+ retry: ['failed', 'rolled_back'].includes(state.state)
122
+ }
123
+ };
124
+ }
125
+
126
+ function getProgressModel(stateValue = {}) {
127
+ const state = normalizeUpdateState(stateValue);
128
+ const base = PROGRESS[state.state] || PROGRESS.idle;
129
+ return {
130
+ ...base,
131
+ blocker: state.blocker,
132
+ code: state.code,
133
+ message: state.message
134
+ };
135
+ }
136
+
137
+ function getBadge(state, flags) {
138
+ if (state.state === 'applying' || state.state === 'awaiting_health') {
139
+ return { text: '...', color: '#3578bd' };
140
+ }
141
+ if (state.state === 'failed' || state.state === 'rolled_back') {
142
+ return { text: '!', color: '#b85450' };
143
+ }
144
+ if (flags.available) {
145
+ return { text: 'UP', color: flags.snoozed ? '#6f6755' : '#3578bd' };
146
+ }
147
+ return { text: '', color: '#3578bd' };
148
+ }
149
+
150
+ function isExecutionState(value) {
151
+ return EXECUTION_STATES.has(String(value || ''));
152
+ }
153
+
154
+ function isTerminalState(value) {
155
+ return TERMINAL_STATES.has(String(value || ''));
156
+ }
157
+
158
+ function compareStableVersions(left, right) {
159
+ const a = parseVersion(left);
160
+ const b = parseVersion(right);
161
+ if (!a || !b) return 0;
162
+ for (let index = 0; index < 3; index += 1) {
163
+ if (a[index] !== b[index]) return a[index] > b[index] ? 1 : -1;
164
+ }
165
+ return 0;
166
+ }
167
+
168
+ function parseVersion(value) {
169
+ const match = String(value || '').match(/^(\d+)\.(\d+)\.(\d+)$/);
170
+ return match ? match.slice(1).map(Number) : null;
171
+ }
172
+
173
+ function safeVersion(value) {
174
+ return parseVersion(value) ? String(value) : '';
175
+ }
176
+
177
+ function safeToken(value) {
178
+ const token = String(value || '');
179
+ return /^[a-z0-9_.-]{1,100}$/i.test(token) ? token : '';
180
+ }
181
+
182
+ function safeId(value) {
183
+ const id = String(value || '');
184
+ return /^[a-z0-9-]{1,100}$/i.test(id) ? id : '';
185
+ }
186
+
187
+ function finiteNumber(value) {
188
+ const number = Number(value || 0);
189
+ return Number.isFinite(number) && number >= 0 ? number : 0;
190
+ }
191
+
192
+ return Object.freeze({
193
+ compareStableVersions,
194
+ deriveViewModel,
195
+ getProgressModel,
196
+ isExecutionState,
197
+ isTerminalState,
198
+ normalizeConsentState,
199
+ normalizeUpdateState
200
+ });
201
+ });
@@ -4046,3 +4046,149 @@
4046
4046
  #codex-overleaf-panel .recent-projects-degraded [data-recent-projects-retry] {
4047
4047
  margin-top: 8px;
4048
4048
  }
4049
+
4050
+ /* v1.9.4: consent-driven coordinated update notice. */
4051
+ #codex-overleaf-panel .codex-update-notice {
4052
+ display: grid;
4053
+ grid-template-columns: minmax(0, 1fr) auto;
4054
+ gap: 14px;
4055
+ align-items: center;
4056
+ margin: 0;
4057
+ padding: 11px 13px;
4058
+ border-bottom: 1px solid var(--tl-border, #2a2f2c);
4059
+ background:
4060
+ linear-gradient(100deg, rgba(53, 120, 189, 0.13), transparent 58%),
4061
+ var(--tl-surface-1, #181a19);
4062
+ }
4063
+
4064
+ #codex-overleaf-panel .codex-update-notice[hidden] {
4065
+ display: none;
4066
+ }
4067
+
4068
+ #codex-overleaf-panel .codex-update-notice-body {
4069
+ display: grid;
4070
+ min-width: 0;
4071
+ gap: 2px;
4072
+ }
4073
+
4074
+ #codex-overleaf-panel .codex-update-notice-eyebrow {
4075
+ color: #7fb5e9;
4076
+ font-size: 10px;
4077
+ font-weight: 750;
4078
+ letter-spacing: 0.07em;
4079
+ text-transform: uppercase;
4080
+ }
4081
+
4082
+ #codex-overleaf-panel .codex-update-notice strong {
4083
+ color: var(--tl-fg-1, #eceeec);
4084
+ font-size: 12.5px;
4085
+ }
4086
+
4087
+ #codex-overleaf-panel .codex-update-notice-detail {
4088
+ overflow: hidden;
4089
+ color: var(--tl-fg-3, #9da39e);
4090
+ font-size: 11px;
4091
+ line-height: 1.35;
4092
+ text-overflow: ellipsis;
4093
+ }
4094
+
4095
+ #codex-overleaf-panel .codex-update-notice-actions {
4096
+ display: flex;
4097
+ gap: 6px;
4098
+ align-items: center;
4099
+ }
4100
+
4101
+ #codex-overleaf-panel .codex-update-notice-actions button {
4102
+ width: auto;
4103
+ min-width: 58px;
4104
+ margin: 0;
4105
+ padding: 5px 8px;
4106
+ border: 1px solid var(--tl-border-strong, #414844);
4107
+ border-radius: 5px;
4108
+ background: transparent;
4109
+ color: var(--tl-fg-2, #d5dad6);
4110
+ font-size: 10.5px;
4111
+ font-weight: 700;
4112
+ cursor: pointer;
4113
+ }
4114
+
4115
+ #codex-overleaf-panel .codex-update-notice-actions button.is-primary {
4116
+ border-color: #4a91d7;
4117
+ background: #3578bd;
4118
+ color: #fff;
4119
+ }
4120
+
4121
+ #codex-overleaf-panel .codex-update-notice-actions button:focus-visible {
4122
+ outline: 2px solid #79b8f2;
4123
+ outline-offset: 2px;
4124
+ }
4125
+
4126
+ #codex-overleaf-panel .codex-update-notice-progress {
4127
+ position: relative;
4128
+ height: 3px;
4129
+ margin-top: 6px;
4130
+ overflow: hidden;
4131
+ border-radius: 999px;
4132
+ background: #2c312e;
4133
+ }
4134
+
4135
+ #codex-overleaf-panel .codex-update-notice-progress > span {
4136
+ display: block;
4137
+ height: 100%;
4138
+ border-radius: inherit;
4139
+ background: #4f9ee8;
4140
+ transition: width 250ms ease;
4141
+ }
4142
+
4143
+ #codex-overleaf-panel .codex-update-notice-progress.is-indeterminate > span {
4144
+ width: 42% !important;
4145
+ animation: codex-update-notice-progress 1.25s ease-in-out infinite;
4146
+ }
4147
+
4148
+ #codex-overleaf-panel .codex-update-notice[data-state="waiting_for_idle"] {
4149
+ background:
4150
+ linear-gradient(100deg, rgba(199, 160, 83, 0.13), transparent 58%),
4151
+ var(--tl-surface-1, #181a19);
4152
+ }
4153
+
4154
+ #codex-overleaf-panel .codex-update-notice[data-state="waiting_for_idle"] .codex-update-notice-eyebrow {
4155
+ color: #d4b86f;
4156
+ }
4157
+
4158
+ #codex-overleaf-panel .codex-update-notice[data-state="committed"] .codex-update-notice-eyebrow {
4159
+ color: var(--tl-ok, #7fd58a);
4160
+ }
4161
+
4162
+ #codex-overleaf-panel .codex-update-notice[data-state="failed"],
4163
+ #codex-overleaf-panel .codex-update-notice[data-state="rolled_back"] {
4164
+ background:
4165
+ linear-gradient(100deg, rgba(184, 84, 80, 0.12), transparent 58%),
4166
+ var(--tl-surface-1, #181a19);
4167
+ }
4168
+
4169
+ #codex-overleaf-panel .codex-update-notice[data-state="failed"] .codex-update-notice-eyebrow,
4170
+ #codex-overleaf-panel .codex-update-notice[data-state="rolled_back"] .codex-update-notice-eyebrow {
4171
+ color: var(--tl-fail, #d87972);
4172
+ }
4173
+
4174
+ @keyframes codex-update-notice-progress {
4175
+ 0% { transform: translateX(-110%); }
4176
+ 100% { transform: translateX(240%); }
4177
+ }
4178
+
4179
+ @media (max-width: 720px) {
4180
+ #codex-overleaf-panel .codex-update-notice {
4181
+ grid-template-columns: 1fr;
4182
+ }
4183
+
4184
+ #codex-overleaf-panel .codex-update-notice-actions {
4185
+ justify-content: flex-end;
4186
+ }
4187
+ }
4188
+
4189
+ @media (prefers-reduced-motion: reduce) {
4190
+ #codex-overleaf-panel .codex-update-notice-progress.is-indeterminate > span {
4191
+ width: 65% !important;
4192
+ animation: none;
4193
+ }
4194
+ }