PageSourceSearch

https://www.willcodexquotareset.com/script.js?v=20260831-1

js willcodexquotareset.com collected 2026-10-04 19:40:58 UTC 32,575 bytes, 914 lines download raw bytes

1const FORECAST_API_URL = "/api/forecast";
2const {
3  DEFAULT_LATEST_RESET_AT,
4  cleanPost,
5  eventTerms,
6  imminentGpt56ReleaseTerms,
7  isActive,
8  isCodex,
9  publishedAt,
10  releaseTerms,
11  vaguepostTerms,
12} = globalThis.CodexForecast;
13const TIBO_PROFILE_URL = "https://x.com/thsottiaux/";
14const PUBLIC_APP_URL = "https://www.willcodexquotareset.com/";
15const ANALYTICS_MEASUREMENT_ID = "G-MJH801QS19";
16const ANALYTICS_CONSENT_KEY = "analytics-consent";
17const ANALYTICS_DISABLE_KEY = `ga-disable-${ANALYTICS_MEASUREMENT_ID}`;
18
19const fallbackIncidents = [
20  {
21    name: "Increased latency for Codex compaction for a subset of users",
22    status: "identified",
23    created_at: "2026-06-04T02:57:25Z",
24  },
25  {
26    name: "Elevated error rates on Codex, ChatGPT and Responses API",
27    status: "resolved",
28    created_at: "2026-06-03T18:09:00Z",
29  },
30];
31
32const elements = {
33  advice: document.querySelector("#advice-copy"),
34  analyticsAccept: document.querySelector("#analytics-accept"),
35  analyticsConsent: document.querySelector("#analytics-consent"),
36  analyticsDecline: document.querySelector("#analytics-decline"),
37  analyticsSettings: document.querySelector("#analytics-settings"),
38  breakdown: document.querySelector("#score-breakdown"),
39  daysSinceReset: document.querySelector("#days-since-reset"),
40  gauge: document.querySelector("#gauge"),
41  headerDot: document.querySelector("#header-status-dot"),
42  headerStatus: document.querySelector("#header-status-label"),
43  headlineSignal: document.querySelector("#headline-signal"),
44  headlineSignalCopy: document.querySelector("#headline-signal-copy"),
45  headlineSignalLabel: document.querySelector("#headline-signal-label"),
46  headlineSignalLink: document.querySelector("#headline-signal-link"),
47  headlineSignalReason: document.querySelector("#headline-signal-reason"),
48  lastChecked: document.querySelector("#last-checked-label"),
49  notificationEmail: document.querySelector("#notification-email"),
50  notificationForm: document.querySelector("#notification-form"),
51  notificationStatus: document.querySelector("#notification-status"),
52  notificationWebsite: document.querySelector("#notification-website"),
53  releaseEventCopy: document.querySelector("#release-event-copy"),
54  releaseEventTitle: document.querySelector("#release-event-title"),
55  riskTitle: document.querySelector("#risk-title"),
56  scoreHistory: document.querySelector("#score-history-list"),
57  shareButton: document.querySelector("#share-button"),
58  statusCopy: document.querySelector("#status-copy"),
59  statusTitle: document.querySelector("#status-title"),
60  summary: document.querySelector("#forecast-summary"),
61  tiboCopy: document.querySelector("#tibo-copy"),
62  tiboClassifier: document.querySelector("#tibo-classifier"),
63  tiboLink: document.querySelector("#tibo-link"),
64  tiboTitle: document.querySelector("#tibo-title"),
65  tiboTweet: document.querySelector("#tibo-tweet"),
66  tiboTweetCopy: document.querySelector("#tibo-tweet-copy"),
67  themeIcon: document.querySelector(".theme-icon"),
68  themeLabel: document.querySelector("#theme-label"),
69  themeToggle: document.querySelector("#theme-toggle"),
70};
71
72function savedAnalyticsConsent() {
73  try {
74    return localStorage.getItem(ANALYTICS_CONSENT_KEY);
75  } catch {
76    return null;
77  }
78}
79
80function loadGoogleAnalytics({ sendPageView = false } = {}) {
81  window[ANALYTICS_DISABLE_KEY] = false;
82  if (window.googleAnalyticsLoaded) {
83    window.gtag("consent", "update", {
84      ad_personalization: "denied",
85      ad_storage: "denied",
86      ad_user_data: "denied",
87      analytics_storage: "granted",
88    });
89    if (sendPageView) window.gtag("event", "page_view");
90    return;
91  }
92  window.googleAnalyticsLoaded = true;
93  window.dataLayer = window.dataLayer || [];
94  window.gtag = function gtag() {
95    window.dataLayer.push(arguments);
96  };
97
98  const tag = document.createElement("script");
99  tag.async = true;
100  tag.src = `https://www.googletagmanager.com/gtag/js?id=${ANALYTICS_MEASUREMENT_ID}`;
101  document.head.append(tag);
102
103  window.gtag("consent", "default", {
104    ad_personalization: "denied",
105    ad_storage: "denied",
106    ad_user_data: "denied",
107    analytics_storage: "granted",
108  });
109  window.gtag("js", new Date());
110  window.gtag("config", ANALYTICS_MEASUREMENT_ID);
111}
112
113function disableGoogleAnalytics() {
114  window[ANALYTICS_DISABLE_KEY] = true;
115  if (window.gtag) {
116    window.gtag("consent", "update", {
117      ad_personalization: "denied",
118      ad_storage: "denied",
119      ad_user_data: "denied",
120      analytics_storage: "denied",
121    });
122  }
123
124  const cookieNames = new Set(["_ga", `_ga_${ANALYTICS_MEASUREMENT_ID.slice(2)}`]);
125  for (const cookie of String(document.cookie || "").split(";")) {
126    const name = cookie.split("=", 1)[0].trim();
127    if (name === "_ga" || name.startsWith("_ga_")) cookieNames.add(name);
128  }
129
130  const hostname = window.location.hostname;
131  const rootDomain = hostname.split(".").slice(-2).join(".");
132  const domainAttributes = ["", `; Domain=${hostname}`, `; Domain=.${rootDomain}`];
133  for (const name of cookieNames) {
134    for (const domain of domainAttributes) {
135      document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax${domain}`;
136    }
137  }
138}
139
140function chooseAnalyticsConsent(choice) {
141  const previousChoice = savedAnalyticsConsent();
142  try {
143    localStorage.setItem(ANALYTICS_CONSENT_KEY, choice);
144  } catch {}
145  elements.analyticsConsent.hidden = true;
146  if (choice === "granted") {
147    loadGoogleAnalytics({ sendPageView: previousChoice === "denied" });
148  } else {
149    disableGoogleAnalytics();
150  }
151}
152
153const analyticsConsent = savedAnalyticsConsent();
154if (analyticsConsent === "granted") {
155  loadGoogleAnalytics();
156} else if (analyticsConsent !== "denied") {
157  elements.analyticsConsent.hidden = false;
158}
159
160function savedTheme() {
161  try {
162    return localStorage.getItem("theme");
163  } catch {
164    return null;
165  }
166}
167
168function renderTheme(theme) {
169  const isDark = theme === "dark";
170  document.documentElement.dataset.theme = isDark ? "dark" : "light";
171  elements.themeToggle.setAttribute("aria-pressed", String(isDark));
172  elements.themeToggle.setAttribute(
173    "aria-label",
174    `Switch to ${isDark ? "light" : "dark"} mode`,
175  );
176  elements.themeIcon.textContent = isDark ? "☀" : "☾";
177  elements.themeLabel.textContent = isDark ? "Light mode" : "Dark mode";
178}
179
180function toggleTheme() {
181  const theme = document.documentElement.dataset.theme === "dark" ? "light" : "dark";
182  renderTheme(theme);
183  try {
184    localStorage.setItem("theme", theme);
185  } catch {}
186}
187
188renderTheme(savedTheme() === "dark" ? "dark" : "light");
189
190let latestIncidents = fallbackIncidents;
191let latestForecastHistory = [];
192let latestResetAt = DEFAULT_LATEST_RESET_AT;
193let latestTiboSignal = emptyTiboSignal();
194let refreshInFlight = false;
195let notificationFormStartedAt = Date.now();
196
197const notificationMessages = {
198  confirmed: "Alerts are active. You will get an email when likelihood reaches 70% and when a quota reset is announced.",
199  invalid: "That alert link is invalid or has expired.",
200  unsubscribed: "Email alerts have been turned off.",
201};
202
203function hoursSinceDate(date) {
204  return Math.max(0, (Date.now() - date.getTime()) / 36e5);
205}
206
207function shortAge(hours) {
208  if (hours < 1) return `${Math.max(1, Math.round(hours * 60))}m`;
209  if (hours < 48) return `${Math.round(hours)}h`;
210  return `${Math.round(hours / 24)}d`;
211}
212
213function modelRead(post) {
214  if (post?.tweetAssessment) {
215    return {
216      confidence: post.tweetAssessment.resetSignalStrength / 100,
217      reason: post.tweetAssessment.reason,
218    };
219  }
220  return post?.vaguepostAssessment || null;
221}
222
223function visiblePostText(text) {
224  return cleanPost(text)
225    .replace(/^(?:@\w+\s+)+/, "")
226    .replace(/^hi[.!]?\s*/i, "");
227}
228
229function tiboPostUrl(post) {
230  try {
231    const url = new URL(post?.link);
232    const isXHost = url.hostname === "x.com" || url.hostname === "twitter.com";
233    const isTiboPost = /^\/thsottiaux\/status\/\d+\/?$/.test(url.pathname);
234    return isXHost && isTiboPost ? url.href : null;
235  } catch {
236    return null;
237  }
238}
239
240function renderTiboLink(post) {
241  const postUrl = tiboPostUrl(post);
242  elements.tiboLink.hidden = false;
243  elements.tiboLink.href = postUrl || TIBO_PROFILE_URL;
244  elements.tiboLink.textContent = postUrl ? "Open tweet on X ↗" : "Open @thsottiaux on X ↗";
245}
246
247function renderTiboTweet(post) {
248  const assessment = modelRead(post);
249  elements.tiboTweet.hidden = !post;
250  elements.tiboCopy.hidden = Boolean(post);
251  elements.tiboTweetCopy.textContent = post ? visiblePostText(post.title) : "";
252  elements.tiboClassifier.hidden = !assessment;
253  elements.tiboClassifier.textContent = assessment
254    ? `Model read · ${Math.round(assessment.confidence * 100)}% · ${assessment.reason}`
255    : "";
256}
257
258function signalPostDate(post) {
259  const value =
260    post?.resetStatus === "pending" && post?.activationAt
261      ? post.activationAt
262      : post?.resetStatus === "completed" && post?.effectiveAt
263        ? post.effectiveAt
264        : post?.publishedAt || post?.pubDate || 0;
265  const date = value instanceof Date ? value : new Date(value);
266  return Number.isNaN(date.getTime()) ? new Date(0) : date;
267}
268
269function matchedSignalReason(post, terms, label) {
270  const match = cleanPost(post?.title).match(terms)?.[0];
271  return match ? `Matched ${label} language: "${match}".` : `Matched ${label} language.`;
272}
273
274function latestHeadlineSignal(signal = emptyTiboSignal()) {
275  const candidates = [
276    ...[
277      ...(signal.pendingResetAnnouncements || []),
278      ...(signal.completedResetAnnouncements || []).filter(
279        (post) => post.source !== "confirmed-observation",
280      ),
281    ]
282      .filter((post) => hoursSinceDate(signalPostDate(post)) <= 72)
283      .map((post) => ({
284        label:
285          post.resetStatus === "pending"
286            ? "LATEST RESET ANNOUNCEMENT"
287            : "LATEST COMPLETED RESET POST",
288        post,
289        reason:
290          post.resetStatus === "pending"
291            ? "Explicit upcoming Codex quota-reset announcement. It has not happened yet."
292            : "Explicit completed Codex quota-reset post.",
293      })),
294    ...(signal.releaseHints || []).map((post) => ({
295      label: "LATEST PRODUCT-RELEASE HINT",
296      post,
297      reason: matchedSignalReason(post, releaseTerms, "product-release"),
298    })),
299    ...(signal.eventHints || []).map((post) => ({
300      label: "LATEST OPENAI EVENT HINT",
301      post,
302      reason: matchedSignalReason(post, eventTerms, "event"),
303    })),
304    ...(signal.vagueposts || []).map((post) => {
305      const assessment = modelRead(post);
306      return {
307        label:
308          post.tweetAssessment?.category === "reset_proposal"
309            ? "LATEST RESET PROPOSAL"
310            : "LATEST VAGUEPOST SIGNAL",
311        post,
312        reason: assessment
313          ? `Model read · ${Math.round(assessment.confidence * 100)}% · ${assessment.reason}`
314          : "Matched Codex/reset hint language.",
315      };
316    }),
317  ];
318
319  return candidates.sort(
320    (a, b) => signalPostDate(b.post).getTime() - signalPostDate(a.post).getTime(),
321  )[0] || null;
322}
323
324function renderHeadlineSignal(signal) {
325  const selection = latestHeadlineSignal(signal);
326  const post = selection?.post;
327  const postUrl = tiboPostUrl(post);
328  elements.headlineSignal.hidden = !post;
329  elements.headlineSignalLabel.textContent = selection?.label || "LATEST TIBO SIGNAL";
330  elements.headlineSignalCopy.textContent = post ? visiblePostText(post.title) : "";
331  elements.headlineSignalLink.href = postUrl || TIBO_PROFILE_URL;
332  elements.headlineSignalLink.textContent = postUrl
333    ? "Open tweet on X ↗"
334    : "Open @thsottiaux on X ↗";
335  elements.headlineSignalReason.hidden = !selection?.reason;
336  elements.headlineSignalReason.textContent = selection?.reason || "";
337}
338
339function emptyTiboSignal() {
340  return {
341    completedResetAnnouncements: [],
342    eventHints: [],
343    pendingResetAnnouncements: [],
344    releaseHints: [],
345    resetAnnouncements: [],
346    vagueposts: [],
347  };
348}
349
350function hydrateTiboSignal(signal) {
351  if (!signal || typeof signal !== "object") return null;
352  const dateKeys = [
353    "completedResetAnnouncements",
354    "eventHints",
355    "pendingResetAnnouncements",
356    "releaseHints",
357    "resetAnnouncements",
358    "vagueposts",
359  ];
360  const hydrated = { ...signal };
361
362  for (const key of dateKeys) {
363    hydrated[key] = (Array.isArray(signal[key]) ? signal[key] : []).map((post) => ({
364      ...post,
365      activationAt: post.activationAt ? new Date(post.activationAt) : null,
366      effectiveAt: post.effectiveAt ? new Date(post.effectiveAt) : null,
367      publishedAt: new Date(post.publishedAt || post.pubDate),
368    }));
369  }
370  if (signal.latestResetAt) hydrated.latestResetAt = new Date(signal.latestResetAt);
371  return hydrated;
372}
373
374// Both live server calculations and browser fallbacks use these same pure modules.
375function readTiboSignal(items, now = new Date(Date.now())) {
376  const signal = globalThis.CodexForecast.readTiboSignal(items, { latestResetAt, now });
377  latestResetAt = signal.latestResetAt;
378  return signal;
379}
380
381function resetAnnouncementStatus(post, now = new Date(Date.now())) {
382  return globalThis.CodexForecast.resetAnnouncementStatus(post, now);
383}
384
385function calculateForecast(incidents) {
386  return globalThis.CodexForecast.calculateForecast(
387    incidents,
388    { ...latestTiboSignal, latestResetAt },
389    new Date(Date.now()),
390  );
391}
392
393function getCopy(forecast) {
394  if (forecast.resetAnnounced) {
395    return {
396      advice: "Treat the forecast as certain, but do not count the new quota until it lands.",
397      summary:
398        "Tibo announced a Codex rate-limit reset in the next 48 hours. It has not happened yet, so the reset clock and cooldown have not moved.",
399      title: "Reset announced.",
400    };
401  }
402  if (forecast.hoursSinceReset < 24 && forecast.evidenceTier !== "proposal") {
403    return {
404      advice: "Tibo already pressed it. Spend responsibly, or do not.",
405      summary: `The latest Codex quota reset was confirmed ${shortAge(forecast.hoursSinceReset)} ago. This percentage estimates the chance of another reset in the next 48 hours.`,
406      title: "It already reset.",
407    };
408  }
409  if (forecast.score >= 72) {
410    return {
411      advice: "Find a suspiciously token-hungry side project.",
412      summary: "There is explicit talk of another reset, with supporting signals. It is still a proposal, not an announcement.",
413      title: "Use it or potentially lose it.",
414    };
415  }
416  if (forecast.score >= 48) {
417    return {
418      advice: "Maybe stop hoarding. Keep a meaty task nearby.",
419      summary: "There are enough signals to raise an eyebrow, but nothing is guaranteed.",
420      title: "Worth a tactical token burn.",
421    };
422  }
423  if (forecast.score >= 26) {
424    return {
425      advice: "Normal building conditions. Check Tibo before panic-spending.",
426      summary: "Some signals are present, but the public data is not making a strong case yet.",
427      title: "Do not force it.",
428    };
429  }
430  return {
431    advice: "Keep your tokens. The reset button is having a quiet afternoon.",
432    summary: "The incident desk is quiet, or a reset happened too recently.",
433    title: "Probably not today.",
434  };
435}
436
437function renderForecast(forecast) {
438  const copy = getCopy(forecast);
439  document.querySelector("#score-value").textContent = forecast.score;
440  elements.gauge.style.setProperty("--score", forecast.score);
441  elements.riskTitle.textContent = copy.title;
442  elements.summary.textContent = copy.summary;
443  elements.advice.textContent = copy.advice;
444  elements.daysSinceReset.textContent = forecast.daysSinceReset;
445  elements.breakdown.innerHTML = forecast.breakdown
446    .map(
447      (item) =>
448        item.label === "public reset announcement"
449          ? '<span class="chip chip-positive">100% public reset announcement</span>'
450          : `<span class="chip ${item.points > 0 ? "chip-positive" : "chip-negative"}">${
451          item.points > 0 ? "+" : ""
452        }${item.points} ${item.label}</span>`,
453    )
454    .join("");
455}
456
457function shareChangeCopy(history = []) {
458  const labelCopy = {
459    "Codex evidence cap rebalanced": "older Codex evidence made room for fresher reports",
460    "Codex issues in last 3 days": "recent Codex issues",
461    "Codex issues 3-7 days ago": "Codex issues from earlier this week",
462    "OpenAI event hint": "an OpenAI event hint",
463    "OpenAI team vagueposting": "Tibo vagueposted",
464    "high-pain wording": "high-pain Codex wording",
465    "major incident severity": "a major Codex incident",
466    "other incidents": "other OpenAI incidents",
467    "public reset announcement": "Tibo announced a reset",
468    "confirmed reset": "the Codex quota reset landed",
469    "product-release hint": "a product-release hint",
470    "imminent GPT-5.6 release": "an imminent GPT-5.6 release",
471    "recent Codex pain": "recent Codex pain",
472    "recent-reset cooldown": "reset cooldown eased",
473    "reset proposal": "Tibo proposed another reset",
474    "uncertainty ceiling": "the estimate stays below announcement-level confidence",
475    "time since last reset": "another day without a reset",
476  };
477  const seen = new Set();
478
479  return history
480    .flatMap((entry) => entry.changes || [])
481    .filter((change) => {
482      if (seen.has(change.label)) return false;
483      seen.add(change.label);
484      return change.label !== "baseline";
485    })
486    .slice(0, 2)
487    .map((change) => {
488      const label = labelCopy[change.label] || change.label;
489      return `${label} (${change.delta > 0 ? "+" : ""}${change.delta})`;
490    })
491    .join("; ");
492}
493
494function shareJoke(score) {
495  if (score >= 72) return "My tokens have entered witness protection.";
496  if (score >= 48) return "I am eyeing every unnecessary refactor.";
497  if (score >= 26) return "My tokens are wearing tiny life jackets.";
498  return "The reset button remains emotionally unavailable.";
499}
500
501function forecastShareText() {
502  const score = Number(document.querySelector("#score-value").textContent) || 0;
503  const headlineSignal = latestHeadlineSignal(latestTiboSignal);
504  const movement = shareChangeCopy(latestForecastHistory);
505  const parts = [`Codex reset forecast: ${score}% in the next 48h.`];
506
507  if (movement) parts.push(`Lately: ${movement}.`);
508  if (headlineSignal) {
509    parts.push(`Tibo: "${visiblePostText(headlineSignal.post.title).slice(0, 72)}"`);
510  }
511  parts.push(shareJoke(score));
512
513  const suffix = `\n\n${PUBLIC_APP_URL}`;
514  return `${parts.join("\n\n").slice(0, 280 - suffix.length)}${suffix}`;
515}
516
517async function copyText(text) {
518  const textarea = document.createElement("textarea");
519  textarea.value = text;
520  textarea.setAttribute("readonly", "");
521  textarea.style.position = "fixed";
522  textarea.style.opacity = "0";
523  document.body.append(textarea);
524  textarea.select();
525  const copied = document.execCommand("copy");
526  textarea.remove();
527  if (copied) return;
528
529  try {
530    await navigator.clipboard.writeText(text);
531  } catch {
532    throw new Error("Copy command failed");
533  }
534}
535
536async function shareForecast() {
537  const originalText = "Copy forecast";
538
539  try {
540    await copyText(forecastShareText());
541    elements.shareButton.textContent = "Copied tweet";
542  } catch {
543    elements.shareButton.textContent = "Copy failed";
544  }
545
546  setTimeout(() => {
547    elements.shareButton.textContent = originalText;
548  }, 1800);
549}
550
551function formatHistoryTime(value) {
552  return new Intl.DateTimeFormat([], {
553    day: "numeric",
554    hour: "numeric",
555    minute: "2-digit",
556    month: "short",
557  }).format(new Date(value));
558}
559
560function historyChangeLabel(change) {
561  const label = typeof change === "string" ? change : change.label;
562  const delta = typeof change === "string" ? 1 : change.delta;
563  const expired =
564    delta < 0 &&
565    (change.details || []).some((detail) => detail.action === "Signal expired after 48h");
566  let labels = {
567    "OpenAI event hint": "Tibo hinted at an OpenAI event",
568    "OpenAI team vagueposting": "Tibo posted a possible reset hint",
569    "public reset announcement": "Tibo announced an upcoming reset",
570    "reset proposal": "Tibo proposed another reset",
571    "product-release hint": "Tibo hinted at a product release",
572    "imminent GPT-5.6 release": "Tibo hinted at an imminent GPT-5.6 release",
573  };
574
575  if (expired) {
576    labels = {
577      "OpenAI event hint": "Tibo's OpenAI event hint expired after 48h",
578      "OpenAI team vagueposting": "Tibo's possible reset hint expired after 48h",
579      "reset proposal": "Tibo's reset proposal expired after 48h",
580      "product-release hint": "Tibo's product-release hint expired after 48h",
581      "imminent GPT-5.6 release": "Tibo's imminent GPT-5.6 release hint expired after 48h",
582    };
583  } else if (delta < 0) {
584    labels = {
585      "OpenAI event hint": "Tibo's OpenAI event hint stopped counting",
586      "OpenAI team vagueposting": "Tibo's possible reset hint stopped counting",
587      "reset proposal": "Tibo's reset proposal weakened",
588      "product-release hint": "Tibo's product-release hint stopped counting",
589      "imminent GPT-5.6 release": "Tibo's imminent GPT-5.6 release hint stopped counting",
590    };
591  }
592
593  return labels[label] || label;
594}
595
596function tweetDetailUrl(detail) {
597  try {
598    const url = new URL(detail?.url);
599    const isXHost = ["x.com", "www.x.com", "twitter.com", "www.twitter.com"].includes(
600      url.hostname,
601    );
602    const isStatus = /^\/[^/]+\/status\/\d+\/?$/.test(url.pathname);
603    return detail?.kind === "tweet" && isXHost && isStatus ? url.href : null;
604  } catch {
605    return null;
606  }
607}
608
609function renderHistoryTweet(detail, tweetUrl) {
610  const card = document.createElement("div");
611  card.className = "score-history-tweet";
612
613  const heading = document.createElement("div");
614  heading.className = "score-history-tweet-heading";
615
616  const author = document.createElement("span");
617  author.className = "score-history-tweet-author";
618  author.append("Tibo ");
619
620  const handle = document.createElement("b");
621  handle.textContent = "@thsottiaux";
622  author.append(handle);
623
624  const link = document.createElement("a");
625  link.href = tweetUrl;
626  link.rel = "noreferrer";
627  link.target = "_blank";
628  link.textContent = "Open on X ↗";
629
630  const quote = document.createElement("blockquote");
631  quote.textContent = detail.name;
632
633  heading.append(author, link);
634  card.append(heading, quote);
635  return card;
636}
637
638function renderForecastHistory(history = []) {
639  latestForecastHistory = history;
640  elements.scoreHistory.replaceChildren();
641
642  if (!history.length) {
643    const empty = document.createElement("li");
644    empty.className = "score-history-empty";
645    empty.textContent = "No score changes recorded in the last 7 days.";
646    elements.scoreHistory.append(empty);
647    return;
648  }
649
650  for (const entry of history) {
651    const item = document.createElement("li");
652    item.className = "score-history-entry";
653
654    const meta = document.createElement("div");
655    meta.className = "score-history-meta";
656
657    const time = document.createElement("time");
658    time.dateTime = entry.at;
659    time.textContent = formatHistoryTime(entry.at);
660
661    const score = document.createElement("span");
662    score.textContent = `${entry.fromScore}% → ${entry.toScore}%`;
663    meta.append(time, score);
664
665    const changes = document.createElement("ul");
666    changes.className = "score-history-changes";
667
668    for (const change of entry.changes) {
669      const changeItem = document.createElement("li");
670      const delta = document.createElement("strong");
671      delta.className = change.delta > 0 ? "history-positive" : "history-negative";
672      delta.textContent = `${change.delta > 0 ? "+" : ""}${change.delta} pts`;
673
674      const label = document.createElement("span");
675      label.className = "score-history-label";
676      label.textContent = historyChangeLabel(change);
677
678      const copy = document.createElement("div");
679      copy.className = "score-history-copy";
680      copy.append(label);
681
682      for (const detail of change.details || []) {
683        const tweetUrl = tweetDetailUrl(detail);
684        if (tweetUrl) {
685          copy.append(renderHistoryTweet(detail, tweetUrl));
686          continue;
687        }
688
689        const description = document.createElement("small");
690        description.className = "score-history-detail";
691        description.append(`${detail.action}: `);
692
693        if (detail.url) {
694          const link = document.createElement("a");
695          link.href = detail.url;
696          link.rel = "noreferrer";
697          link.target = "_blank";
698          link.textContent = detail.name;
699          description.append(link);
700        } else {
701          description.append(detail.name);
702        }
703
704        copy.append(description);
705      }
706
707      changeItem.append(delta, copy);
708      changes.append(changeItem);
709    }
710
711    item.append(meta, changes);
712    elements.scoreHistory.append(item);
713  }
714}
715
716function renderStatus(incidents, isFallback = false) {
717  const openIncidents = incidents.filter(isActive);
718  const openCodex = openIncidents.filter(isCodex);
719  const label = `${openIncidents.length} open incident${openIncidents.length === 1 ? "" : "s"}`;
720
721  elements.headerDot.className = `status-dot ${openIncidents.length ? "status-dot-bad" : "status-dot-good"}`;
722  elements.headerStatus.textContent = openIncidents.length
723    ? `OpenAI status: ${label}`
724    : "OpenAI status clear";
725  elements.statusTitle.textContent = openIncidents.length ? label : "No open incidents";
726
727  if (openCodex.length) {
728    elements.statusCopy.textContent = `${openCodex[0].name}.${isFallback ? " Cached fallback." : ""}`;
729  } else {
730    elements.statusCopy.textContent = isFallback
731      ? "The status source failed during the last server refresh."
732      : "No open Codex-specific incident in the cached official feed.";
733  }
734}
735
736function renderTiboSignal(signal, isFallback = false) {
737  const pendingReset = [...(signal.pendingResetAnnouncements || [])].sort(
738    (a, b) => signalPostDate(b) - signalPostDate(a),
739  )[0];
740  const completedResets = [...(signal.completedResetAnnouncements || [])].sort(
741    (a, b) => signalPostDate(b) - signalPostDate(a),
742  );
743  const completedReset =
744    completedResets.find((post) => post.source !== "confirmed-observation") ||
745    completedResets[0];
746  const vaguepost = (signal.vagueposts || [])[0];
747  renderHeadlineSignal(signal);
748
749  if (pendingReset) {
750    elements.tiboTitle.textContent = `Tibo announced an upcoming reset ${shortAge(
751      hoursSinceDate(pendingReset.publishedAt),
752    )} ago`;
753    renderTiboTweet(pendingReset);
754    renderTiboLink(pendingReset);
755    return;
756  }
757
758  if (completedReset && hoursSinceDate(signalPostDate(completedReset)) <= 72) {
759    if (completedReset.source === "confirmed-observation") {
760      elements.tiboTitle.textContent = `Codex quota reset confirmed ${shortAge(
761        hoursSinceDate(completedReset.publishedAt),
762      )} ago`;
763      renderTiboTweet();
764      elements.tiboCopy.textContent =
765        "The reset was confirmed from observed quota state; no completion post is available yet.";
766      elements.tiboLink.hidden = true;
767      return;
768    }
769
770    elements.tiboTitle.textContent = `Tibo posted a completed reset ${shortAge(
771      hoursSinceDate(signalPostDate(completedReset)),
772    )} ago`;
773    renderTiboTweet(completedReset);
774    renderTiboLink(completedReset);
775    return;
776  }
777
778  if (vaguepost) {
779    elements.tiboTitle.textContent = "Possible vaguepost detected";
780    renderTiboTweet(vaguepost);
781    renderTiboLink(vaguepost);
782    return;
783  }
784
785  renderTiboTweet();
786  renderTiboLink();
787  elements.tiboTitle.textContent = isFallback ? "Feed unavailable" : "No fresh reset bait";
788  elements.tiboCopy.textContent = isFallback
789    ? "The public post mirror failed during the last server refresh. Check @thsottiaux manually."
790    : "Best-effort public feed checked. Nothing obvious in the latest posts.";
791}
792
793function renderReleaseEventSignal(signal, isFallback = false) {
794  const hint = [
795    ...signal.eventHints.map((post) => ({ post, title: "Possible event hint detected" })),
796    ...signal.releaseHints.map((post) => ({ post, title: "Possible release hint detected" })),
797  ].sort((a, b) => b.post.publishedAt - a.post.publishedAt)[0];
798
799  if (hint) {
800    elements.releaseEventTitle.textContent = hint.title;
801    elements.releaseEventCopy.textContent = hint.post.title;
802    return;
803  }
804
805  elements.releaseEventTitle.textContent = isFallback ? "Party calendar unavailable" : "No launch confetti yet";
806  elements.releaseEventCopy.textContent = isFallback
807    ? "The public post mirror failed during the last server refresh."
808    : "No fresh release, party, or event hints in the latest posts.";
809}
810
811async function refreshForecast() {
812  if (refreshInFlight) return;
813  refreshInFlight = true;
814
815  try {
816    const response = await fetch(FORECAST_API_URL, { headers: { Accept: "application/json" } });
817    if (!response.ok) throw new Error(`Forecast API returned ${response.status}`);
818    const data = await response.json();
819    if (!data.forecast) throw new Error("Forecast API omitted the authoritative forecast");
820    latestIncidents = data.incidents.length ? data.incidents : fallbackIncidents;
821    latestTiboSignal = hydrateTiboSignal(data.tiboSignal);
822    if (!latestTiboSignal) {
823      throw new Error("Forecast API omitted the authoritative Tibo signal");
824    }
825    latestResetAt = new Date(data.forecast.latestResetAt);
826
827    renderStatus(latestIncidents, Boolean(data.sourceErrors.status));
828    renderTiboSignal(latestTiboSignal, Boolean(data.sourceErrors.tibo) || !data.tiboPosts.length);
829    renderReleaseEventSignal(
830      latestTiboSignal,
831      Boolean(data.sourceErrors.tibo) || !data.tiboPosts.length,
832    );
833    renderForecast(data.forecast);
834    renderForecastHistory(data.history);
835    elements.lastChecked.textContent = `Server checks sources every 30 min · last checked ${new Date(
836      data.fetchedAt,
837    ).toLocaleTimeString([], {
838      hour: "2-digit",
839      minute: "2-digit",
840    })}`;
841  } catch (error) {
842    latestIncidents = fallbackIncidents;
843    latestTiboSignal = readTiboSignal([]);
844    renderStatus(fallbackIncidents, true);
845    renderTiboSignal(latestTiboSignal, true);
846    renderReleaseEventSignal(latestTiboSignal, true);
847    renderForecast(calculateForecast(fallbackIncidents));
848    renderForecastHistory();
849    elements.lastChecked.textContent = "Forecast server unavailable; showing bundled snapshot";
850    console.warn("Forecast fallback in use:", error);
851  } finally {
852    refreshInFlight = false;
853  }
854}
855
856function renderNotificationStatus(message, isError = false, isConfirmed = false) {
857  elements.notificationStatus.hidden = false;
858  elements.notificationStatus.className = `notification-status ${
859    isError ? "notification-status-error" : "notification-status-success"
860  }${isConfirmed ? " notification-status-confirmed" : ""}`;
861  elements.notificationStatus.textContent = message;
862}
863
864async function subscribeForAlerts(event) {
865  event.preventDefault();
866  const button = elements.notificationForm.querySelector("button");
867  button.disabled = true;
868  button.textContent = "Sending...";
869
870  try {
871    const response = await fetch("/api/subscribe", {
872      method: "POST",
873      headers: { "Content-Type": "application/json" },
874      body: JSON.stringify({
875        email: elements.notificationEmail.value,
876        startedAt: notificationFormStartedAt,
877        website: elements.notificationWebsite.value,
878      }),
879    });
880    const data = await response.json().catch(() => ({}));
881    if (!response.ok) throw new Error(data.error || "Unable to start email alerts.");
882
883    elements.notificationEmail.value = "";
884    elements.notificationWebsite.value = "";
885    notificationFormStartedAt = Date.now();
886    renderNotificationStatus(data.message);
887  } catch (error) {
888    renderNotificationStatus(error.message, true);
889  } finally {
890    button.disabled = false;
891    button.textContent = "Notify me";
892  }
893}
894
895elements.notificationForm.addEventListener("submit", subscribeForAlerts);
896elements.analyticsAccept.addEventListener("click", () => chooseAnalyticsConsent("granted"));
897elements.analyticsDecline.addEventListener("click", () => chooseAnalyticsConsent("denied"));
898elements.analyticsSettings.addEventListener("click", () => {
899  elements.analyticsConsent.hidden = false;
900});
901elements.shareButton.addEventListener("click", shareForecast);
902elements.themeToggle.addEventListener("click", toggleTheme);
903
904const notificationState = new URLSearchParams(window.location.search).get("notification");
905if (notificationMessages[notificationState]) {
906  renderNotificationStatus(
907    notificationMessages[notificationState],
908    notificationState === "invalid",
909    notificationState === "confirmed",
910  );
911  window.history.replaceState({}, "", `${window.location.pathname}${window.location.hash}`);
912}
913
914refreshForecast();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.