ac-revisit

AtCoder の復習問題を登録し、今日の一問を提案する userscript

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name ac-revisit
// @namespace https://github.com/yiwiy9/ac-revisit
// @homepageURL https://github.com/yiwiy9/ac-revisit
// @author yiwiy9
// @license MIT
// @version 0.1.0
// @description AtCoder の復習問題を登録し、今日の一問を提案する userscript
// @match https://atcoder.jp/*
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// ==/UserScript==
"use strict";
(() => {
  // src/shared/date.ts
  var MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1e3;
  var DEFAULT_REVIEW_INTERVAL_DAYS = 7;
  var REVIEW_INTERVAL_DAYS = Number.isInteger(7) && 7 >= 0 ? 7 : DEFAULT_REVIEW_INTERVAL_DAYS;
  function formatLocalDate(date) {
    const year = String(date.getFullYear());
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  }
  function parseLocalDateKey(value) {
    const [year, month, day] = value.split("-").map((part) => Number(part));
    return new Date(Date.UTC(year, month - 1, day));
  }
  function createLocalDateProvider(now = () => /* @__PURE__ */ new Date()) {
    return {
      today() {
        return formatLocalDate(now());
      }
    };
  }
  function createLocalDateMath() {
    return {
      isSameDay(left, right) {
        return left !== null && left === right;
      },
      elapsedDays(from, to) {
        const start = parseLocalDateKey(from);
        const end = parseLocalDateKey(to);
        return Math.round((end.getTime() - start.getTime()) / MILLISECONDS_PER_DAY);
      },
      isDue(registeredOn, today) {
        return this.elapsedDays(registeredOn, today) >= REVIEW_INTERVAL_DAYS;
      }
    };
  }

  // src/domain/candidate-selection.ts
  function createCandidateSelectionService({
    localDateMath = createLocalDateMath(),
    random = Math.random
  } = {}) {
    return {
      listDueCandidates(input) {
        return input.reviewItems.filter(
          (item) => localDateMath.isDue(item.registeredOn, input.today)
        );
      },
      pickOneCandidate(input) {
        const dueCandidates = this.listDueCandidates(input);
        if (dueCandidates.length === 0) {
          return {
            ok: false,
            error: { kind: "no_due_candidates" }
          };
        }
        const index = Math.min(dueCandidates.length - 1, Math.floor(random() * dueCandidates.length));
        return {
          ok: true,
          value: dueCandidates[index]
        };
      }
    };
  }

  // src/domain/daily-suggestion.ts
  function createDailySuggestionService({
    reviewStore,
    localDateMath,
    candidateSelectionService
  }) {
    return {
      ensureTodaySuggestion(input) {
        const latestWorkspace = reviewStore.readWorkspace();
        if (!latestWorkspace.ok) {
          return latestWorkspace;
        }
        if (localDateMath.isSameDay(latestWorkspace.value.dailyState.lastDailyEvaluatedOn, input.today)) {
          return success(latestWorkspace.value, false);
        }
        const nextCandidate = candidateSelectionService.pickOneCandidate({
          today: input.today,
          reviewItems: latestWorkspace.value.reviewItems
        });
        const nextDailyState = nextCandidate.ok ? {
          activeProblemId: nextCandidate.value.problemId,
          status: "incomplete",
          lastDailyEvaluatedOn: input.today
        } : {
          activeProblemId: null,
          status: "complete",
          lastDailyEvaluatedOn: input.today
        };
        const nextWorkspace = {
          reviewItems: latestWorkspace.value.reviewItems,
          dailyState: nextDailyState
        };
        const persistedWorkspace = reviewStore.writeWorkspace(nextWorkspace);
        if (!persistedWorkspace.ok) {
          return persistedWorkspace;
        }
        return success(
          persistedWorkspace.value,
          input.trigger === "bootstrap" && persistedWorkspace.value.dailyState.status === "incomplete" && localDateMath.isSameDay(
            persistedWorkspace.value.dailyState.lastDailyEvaluatedOn,
            input.today
          )
        );
      }
    };
  }
  function success(reviewWorkspace, shouldAutoOpenPopup) {
    return {
      ok: true,
      value: {
        reviewWorkspace,
        dailyState: reviewWorkspace.dailyState,
        shouldAutoOpenPopup
      }
    };
  }

  // src/domain/interaction-session.ts
  function createInteractionSessionValidator({
    localDateMath = createLocalDateMath()
  } = {}) {
    return {
      validate(input) {
        const statesMatch = input.expectedDailyState.activeProblemId === input.actualDailyState.activeProblemId && input.expectedDailyState.status === input.actualDailyState.status && input.expectedDailyState.lastDailyEvaluatedOn === input.actualDailyState.lastDailyEvaluatedOn;
        if (!statesMatch) {
          return { kind: "stale" };
        }
        if (!localDateMath.isSameDay(input.actualDailyState.lastDailyEvaluatedOn, input.today)) {
          return { kind: "stale" };
        }
        return { kind: "valid" };
      }
    };
  }

  // src/domain/review-mutation.ts
  function createReviewMutationService({
    reviewStore,
    candidateSelectionService = createCandidateSelectionService(),
    interactionSessionValidator = createInteractionSessionValidator()
  }) {
    return {
      registerProblem(input) {
        const latestWorkspace = readLatestWorkspace(reviewStore);
        if (!latestWorkspace.ok) {
          return latestWorkspace;
        }
        const alreadyTracked = latestWorkspace.value.reviewItems.some(
          (item) => item.problemId === input.problemId
        );
        if (alreadyTracked) {
          return success2(latestWorkspace.value);
        }
        return writeWorkspace(reviewStore, {
          reviewItems: [
            ...latestWorkspace.value.reviewItems,
            {
              problemId: input.problemId,
              problemTitle: input.problemTitle,
              registeredOn: input.today
            }
          ],
          dailyState: latestWorkspace.value.dailyState
        });
      },
      unregisterProblem(input) {
        const latestWorkspace = readLatestWorkspace(reviewStore);
        if (!latestWorkspace.ok) {
          return latestWorkspace;
        }
        const targetExists = latestWorkspace.value.reviewItems.some(
          (item) => item.problemId === input.problemId
        );
        if (!targetExists) {
          return success2(latestWorkspace.value);
        }
        const reviewItems = latestWorkspace.value.reviewItems.filter(
          (item) => item.problemId !== input.problemId
        );
        const isActiveProblem = latestWorkspace.value.dailyState.activeProblemId === input.problemId;
        return writeWorkspace(reviewStore, {
          reviewItems,
          dailyState: isActiveProblem ? {
            activeProblemId: null,
            status: "complete",
            lastDailyEvaluatedOn: latestWorkspace.value.dailyState.lastDailyEvaluatedOn
          } : latestWorkspace.value.dailyState
        });
      },
      completeTodayProblem(input) {
        const latestWorkspace = readLatestWorkspace(reviewStore);
        if (!latestWorkspace.ok) {
          return latestWorkspace;
        }
        if (interactionSessionValidator.validate({
          expectedDailyState: input.expectedDailyState,
          actualDailyState: latestWorkspace.value.dailyState,
          today: input.today
        }).kind === "stale") {
          return failure({ kind: "stale_session" });
        }
        const activeProblemId = latestWorkspace.value.dailyState.activeProblemId;
        if (latestWorkspace.value.dailyState.status !== "incomplete" || activeProblemId === null) {
          return failure({ kind: "today_problem_absent" });
        }
        const activeProblem = latestWorkspace.value.reviewItems.find(
          (item) => item.problemId === activeProblemId
        );
        if (activeProblem === void 0) {
          return failure({ kind: "today_problem_absent" });
        }
        const reviewItems = latestWorkspace.value.reviewItems.filter((item) => item.problemId !== activeProblem.problemId).concat({
          problemId: activeProblem.problemId,
          problemTitle: activeProblem.problemTitle,
          registeredOn: input.today
        });
        return writeWorkspace(reviewStore, {
          reviewItems,
          dailyState: {
            activeProblemId: activeProblem.problemId,
            status: "complete",
            lastDailyEvaluatedOn: latestWorkspace.value.dailyState.lastDailyEvaluatedOn
          }
        });
      },
      fetchNextTodayProblem(input) {
        const latestWorkspace = readLatestWorkspace(reviewStore);
        if (!latestWorkspace.ok) {
          return latestWorkspace;
        }
        if (interactionSessionValidator.validate({
          expectedDailyState: input.expectedDailyState,
          actualDailyState: latestWorkspace.value.dailyState,
          today: input.today
        }).kind === "stale") {
          return failure({ kind: "stale_session" });
        }
        if (latestWorkspace.value.dailyState.status !== "complete") {
          return failure({ kind: "today_problem_incomplete" });
        }
        const nextCandidate = candidateSelectionService.pickOneCandidate({
          today: input.today,
          reviewItems: latestWorkspace.value.reviewItems
        });
        if (!nextCandidate.ok) {
          return failure({ kind: "candidate_unavailable" });
        }
        return writeWorkspace(reviewStore, {
          reviewItems: latestWorkspace.value.reviewItems,
          dailyState: {
            activeProblemId: nextCandidate.value.problemId,
            status: "incomplete",
            lastDailyEvaluatedOn: latestWorkspace.value.dailyState.lastDailyEvaluatedOn
          }
        });
      }
    };
  }
  function readLatestWorkspace(reviewStore) {
    const result = reviewStore.readWorkspace();
    if (!result.ok) {
      return {
        ok: false,
        error: result.error
      };
    }
    return result;
  }
  function writeWorkspace(reviewStore, reviewWorkspace) {
    const result = reviewStore.writeWorkspace(reviewWorkspace);
    if (!result.ok) {
      return failure(result.error);
    }
    return success2(result.value);
  }
  function success2(reviewWorkspace) {
    return {
      ok: true,
      value: {
        reviewWorkspace
      }
    };
  }
  function failure(error) {
    return {
      ok: false,
      error
    };
  }

  // src/persistence/review-store.ts
  var SCHEMA_VERSION = 1;
  var PROBLEM_ID_PATTERN = /^[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+$/;
  var LOCAL_DATE_KEY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
  var REVIEW_STORE_KEY = "ac_revisit_workspace_v1";
  function createCanonicalReviewWorkspace() {
    return {
      reviewItems: [],
      dailyState: {
        activeProblemId: null,
        status: "complete",
        lastDailyEvaluatedOn: null
      }
    };
  }
  function createReviewStoreAdapter(storage) {
    return {
      readWorkspace() {
        let rawValue;
        try {
          rawValue = storage.get(REVIEW_STORE_KEY);
        } catch {
          return storageUnavailable();
        }
        if (rawValue === null) {
          return success3(createCanonicalReviewWorkspace());
        }
        const parsed = parseSchemaEnvelope(rawValue);
        return success3(parsed ?? createCanonicalReviewWorkspace());
      },
      writeWorkspace(input) {
        const normalizedWorkspace = normalizeWorkspace(
          validateWorkspace(input) ?? createCanonicalReviewWorkspace()
        );
        const payload = JSON.stringify({
          version: SCHEMA_VERSION,
          payload: normalizedWorkspace
        });
        try {
          storage.set(REVIEW_STORE_KEY, payload);
        } catch {
          return storageUnavailable();
        }
        return success3(normalizedWorkspace);
      }
    };
  }
  function parseSchemaEnvelope(rawValue) {
    let parsed;
    try {
      parsed = JSON.parse(rawValue);
    } catch {
      return null;
    }
    if (!isRecord(parsed)) {
      return null;
    }
    if (parsed.version !== SCHEMA_VERSION) {
      return null;
    }
    return validateWorkspace(parsed.payload);
  }
  function validateWorkspace(value) {
    if (!isRecord(value)) {
      return null;
    }
    const { reviewItems, dailyState } = value;
    if (!Array.isArray(reviewItems) || !isRecord(dailyState)) {
      return null;
    }
    const normalizedReviewItems = [];
    const knownProblemIds = /* @__PURE__ */ new Set();
    for (const item of reviewItems) {
      const normalizedItem = validateReviewItem(item);
      if (normalizedItem === null || knownProblemIds.has(normalizedItem.problemId)) {
        return null;
      }
      knownProblemIds.add(normalizedItem.problemId);
      normalizedReviewItems.push(normalizedItem);
    }
    const normalizedDailyState = validateDailyState(dailyState, knownProblemIds);
    if (normalizedDailyState === null) {
      return null;
    }
    return {
      reviewItems: normalizedReviewItems,
      dailyState: normalizedDailyState
    };
  }
  function validateReviewItem(value) {
    if (!isRecord(value)) {
      return null;
    }
    if (!isProblemId(value.problemId) || !isProblemTitle(value.problemTitle) || !isLocalDateKey(value.registeredOn)) {
      return null;
    }
    return {
      problemId: value.problemId,
      problemTitle: value.problemTitle,
      registeredOn: value.registeredOn
    };
  }
  function validateDailyState(value, problemIds) {
    const lastDailyEvaluatedOn = value.lastDailyEvaluatedOn;
    if (!(lastDailyEvaluatedOn === null || isLocalDateKey(lastDailyEvaluatedOn))) {
      return null;
    }
    if (value.status === "complete") {
      if (value.activeProblemId !== null && (!isProblemId(value.activeProblemId) || !problemIds.has(value.activeProblemId))) {
        return null;
      }
      return {
        activeProblemId: value.activeProblemId,
        status: "complete",
        lastDailyEvaluatedOn
      };
    }
    if (value.status === "incomplete") {
      if (!isProblemId(value.activeProblemId) || !problemIds.has(value.activeProblemId)) {
        return null;
      }
      return {
        activeProblemId: value.activeProblemId,
        status: "incomplete",
        lastDailyEvaluatedOn
      };
    }
    return null;
  }
  function normalizeWorkspace(input) {
    return {
      reviewItems: [...input.reviewItems].map((item) => ({
        problemId: item.problemId,
        problemTitle: item.problemTitle,
        registeredOn: item.registeredOn
      })).sort((left, right) => left.problemId.localeCompare(right.problemId)),
      dailyState: {
        activeProblemId: input.dailyState.activeProblemId,
        status: input.dailyState.status,
        lastDailyEvaluatedOn: input.dailyState.lastDailyEvaluatedOn
      }
    };
  }
  function isProblemId(value) {
    return typeof value === "string" && PROBLEM_ID_PATTERN.test(value);
  }
  function isProblemTitle(value) {
    return typeof value === "string" && value.trim().length > 0;
  }
  function isLocalDateKey(value) {
    if (typeof value !== "string" || !LOCAL_DATE_KEY_PATTERN.test(value)) {
      return false;
    }
    const [year, month, day] = value.split("-").map((part) => Number(part));
    const date = new Date(Date.UTC(year, month - 1, day));
    const normalized = [
      String(date.getUTCFullYear()).padStart(4, "0"),
      String(date.getUTCMonth() + 1).padStart(2, "0"),
      String(date.getUTCDate()).padStart(2, "0")
    ].join("-");
    return normalized === value;
  }
  function isRecord(value) {
    return typeof value === "object" && value !== null;
  }
  function success3(value) {
    return { ok: true, value };
  }
  function storageUnavailable() {
    return { ok: false, error: { kind: "storage_unavailable" } };
  }

  // src/presentation/popup-view-model.ts
  function createPopupViewModelFactory() {
    return {
      build(input) {
        const activeReviewItem = input.reviewItems.find(
          (item) => item.problemId === input.dailyState.activeProblemId
        );
        const hasActiveIncompleteSuggestion = input.dailyState.status === "incomplete" && input.dailyState.activeProblemId !== null;
        const hasCompletedTodaySuggestion = input.dailyState.status === "complete" && input.dailyState.activeProblemId !== null;
        const canFetchNextSuggestion = input.dailyState.status === "complete" && input.hasDueCandidates === true;
        const primaryActionKind = hasActiveIncompleteSuggestion === true ? "complete" : hasCompletedTodaySuggestion === true || canFetchNextSuggestion === true ? "fetch_next" : "complete";
        const primaryActionEnabled = hasActiveIncompleteSuggestion === true || canFetchNextSuggestion === true;
        return {
          todayLink: hasActiveIncompleteSuggestion ? { enabled: true, presentation: "normal" } : { enabled: false, presentation: "grayed" },
          todayLinkLabel: activeReviewItem?.problemTitle ?? "今日の一問はありません",
          description: hasActiveIncompleteSuggestion === true ? "今日の復習対象です。解き終えたら完了で記録できます。" : canFetchNextSuggestion === true ? "今日の一問は完了済みです。必要ならもう一問で次に進めます。" : hasCompletedTodaySuggestion === true ? "今日の一問は完了済みです。次に進める復習対象はありません。" : `復習対象がありません。追加した問題は${REVIEW_INTERVAL_DAYS}日後に今日の一問として表示されます。`,
          primaryAction: primaryActionEnabled ? { enabled: true, presentation: "normal" } : { enabled: false, presentation: "grayed" },
          primaryActionLabel: hasActiveIncompleteSuggestion === true ? "完了" : hasCompletedTodaySuggestion === true || canFetchNextSuggestion === true ? "もう一問" : "完了",
          primaryActionKind
        };
      }
    };
  }

  // src/presentation/popup-shell.ts
  var POPUP_ROOT_ID = "ac-revisit-popup-root";
  var POPUP_OVERLAY_ID = "ac-revisit-popup-overlay";
  var POPUP_PANEL_ID = "ac-revisit-popup-panel";
  var POPUP_HEADER_ID = "ac-revisit-popup-header";
  var POPUP_BODY_ID = "ac-revisit-popup-body";
  var POPUP_FOOTER_ID = "ac-revisit-popup-footer";
  var POPUP_TITLE_ID = "ac-revisit-popup-title";
  var POPUP_SECTION_TITLE_ID = "ac-revisit-popup-section-title";
  var POPUP_DESCRIPTION_ID = "ac-revisit-popup-description";
  var POPUP_CLOSE_ID = "ac-revisit-popup-close";
  var POPUP_DISMISS_ID = "ac-revisit-popup-dismiss";
  var POPUP_TODAY_LINK_ID = "ac-revisit-popup-today-link";
  var POPUP_ACTION_ID = "ac-revisit-popup-action";
  function createPopupStateLoader(dependencies) {
    const popupViewModelFactory = createPopupViewModelFactory();
    return {
      load(input) {
        const workspaceResult = input.mode === "workspace" ? success4(input.reviewWorkspace) : dependencies.readWorkspace?.() ?? failure2({
          kind: "storage_unavailable"
        });
        if (!workspaceResult.ok) {
          return workspaceResult;
        }
        const reviewWorkspace = workspaceResult.value;
        const hasDueCandidates = dependencies.listDueCandidates({
          today: input.today,
          reviewItems: reviewWorkspace.reviewItems
        }).length > 0;
        return success4({
          source: input.source,
          today: input.today,
          reviewWorkspace,
          viewModel: popupViewModelFactory.build({
            reviewItems: reviewWorkspace.reviewItems,
            dailyState: reviewWorkspace.dailyState,
            hasDueCandidates
          })
        });
      }
    };
  }
  function createPopupShellPresenter(documentRef = document, interactions = {}) {
    const interactionSessionValidator = createInteractionSessionValidator();
    let lastFocusedElement = null;
    let previousBodyPaddingRight = null;
    const presentPopup = (input) => {
      let popup = documentRef.getElementById(POPUP_ROOT_ID);
      if (popup === null) {
        popup = documentRef.createElement("section");
        popup.id = POPUP_ROOT_ID;
        popup.tabIndex = -1;
        popup.setAttribute("role", "dialog");
        popup.setAttribute("aria-modal", "true");
        popup.className = "modal fade";
        popup.style.display = "none";
        popup.style.paddingRight = "12px";
        const panel = documentRef.createElement("div");
        panel.id = POPUP_PANEL_ID;
        panel.className = "modal-dialog";
        panel.setAttribute("role", "document");
        popup.append(panel);
        const content = documentRef.createElement("div");
        content.className = "modal-content";
        panel.append(content);
        const header = documentRef.createElement("div");
        header.id = POPUP_HEADER_ID;
        header.className = "modal-header";
        content.append(header);
        const body = documentRef.createElement("div");
        body.id = POPUP_BODY_ID;
        body.className = "modal-body";
        content.append(body);
        const footer = documentRef.createElement("div");
        footer.id = POPUP_FOOTER_ID;
        footer.className = "modal-footer";
        content.append(footer);
        const overlay2 = documentRef.createElement("div");
        overlay2.id = POPUP_OVERLAY_ID;
        overlay2.className = "modal-backdrop fade";
        const title = documentRef.createElement("h4");
        title.id = POPUP_TITLE_ID;
        title.textContent = "ac-revisit";
        title.className = "modal-title";
        const closeButton2 = documentRef.createElement("button");
        closeButton2.id = POPUP_CLOSE_ID;
        closeButton2.type = "button";
        closeButton2.className = "close";
        closeButton2.setAttribute("aria-label", "閉じる");
        closeButton2.setAttribute("data-dismiss", "modal");
        const closeMark = documentRef.createElement("span");
        closeMark.setAttribute("aria-hidden", "true");
        closeMark.textContent = "×";
        closeButton2.append(closeMark);
        header.append(closeButton2);
        header.append(title);
        const sectionTitle = documentRef.createElement("h3");
        sectionTitle.id = POPUP_SECTION_TITLE_ID;
        sectionTitle.textContent = "今日の一問";
        sectionTitle.className = "h5";
        sectionTitle.style.marginTop = "0";
        sectionTitle.style.marginBottom = "0.375rem";
        body.append(sectionTitle);
        const description2 = documentRef.createElement("p");
        description2.id = POPUP_DESCRIPTION_ID;
        description2.textContent = "解く問題がある日は完了、終わった日はもう一問で次へ進めます。";
        description2.className = "small text-muted";
        description2.style.marginTop = "0";
        description2.style.marginBottom = "1.25rem";
        body.append(description2);
        const todayLink2 = documentRef.createElement("a");
        todayLink2.id = POPUP_TODAY_LINK_ID;
        todayLink2.className = "";
        todayLink2.setAttribute("href", "#");
        todayLink2.style.whiteSpace = "normal";
        todayLink2.style.wordBreak = "break-word";
        todayLink2.style.display = "inline-block";
        todayLink2.style.marginTop = "0";
        todayLink2.style.marginBottom = "1.25rem";
        todayLink2.style.lineHeight = "1.4";
        body.append(todayLink2);
        const actionButton2 = documentRef.createElement("button");
        actionButton2.id = POPUP_ACTION_ID;
        actionButton2.type = "button";
        actionButton2.className = "btn btn-primary";
        actionButton2.style.display = "block";
        body.append(actionButton2);
        const dismissButton2 = documentRef.createElement("button");
        dismissButton2.id = POPUP_DISMISS_ID;
        dismissButton2.type = "button";
        dismissButton2.textContent = "close";
        dismissButton2.className = "btn btn-default";
        dismissButton2.setAttribute("data-dismiss", "modal");
        footer.append(dismissButton2);
        documentRef.body.append(popup);
        documentRef.body.append(overlay2);
      }
      popup.dataset.source = input.source;
      popup.dataset.status = input.reviewWorkspace.dailyState.status;
      popup.dataset.activeProblemId = input.reviewWorkspace.dailyState.activeProblemId ?? "";
      popup.dataset.lastDailyEvaluatedOn = input.reviewWorkspace.dailyState.lastDailyEvaluatedOn ?? "";
      popup.dataset.state = "open";
      popup.setAttribute("aria-labelledby", POPUP_TITLE_ID);
      const overlay = documentRef.getElementById(POPUP_OVERLAY_ID);
      const closeButton = popup.querySelector(`#${POPUP_CLOSE_ID}`);
      const dismissButton = popup.querySelector(`#${POPUP_DISMISS_ID}`);
      const description = popup.querySelector(`#${POPUP_DESCRIPTION_ID}`);
      const todayLink = popup.querySelector(`#${POPUP_TODAY_LINK_ID}`);
      const actionButton = popup.querySelector(`#${POPUP_ACTION_ID}`);
      if (documentRef.activeElement instanceof HTMLElement && !popup.contains(documentRef.activeElement)) {
        lastFocusedElement = documentRef.activeElement;
      }
      if (todayLink !== null) {
        todayLink.textContent = input.viewModel.todayLinkLabel;
        if (input.viewModel.todayLink.enabled && input.reviewWorkspace.dailyState.activeProblemId !== null) {
          todayLink.href = toProblemPath(input.reviewWorkspace.dailyState.activeProblemId);
          todayLink.removeAttribute("aria-disabled");
          todayLink.removeAttribute("data-muted");
          todayLink.className = "";
          todayLink.style.pointerEvents = "";
          todayLink.style.color = "";
        } else {
          todayLink.removeAttribute("href");
          todayLink.setAttribute("aria-disabled", "true");
          todayLink.dataset.muted = "true";
          todayLink.className = "text-muted";
          todayLink.style.pointerEvents = "none";
          todayLink.style.color = "#777777";
        }
      }
      if (description !== null) {
        description.textContent = input.viewModel.description;
      }
      if (actionButton !== null) {
        actionButton.textContent = input.viewModel.primaryActionLabel;
        actionButton.disabled = !input.viewModel.primaryAction.enabled;
        if (input.viewModel.primaryAction.enabled) {
          actionButton.className = "btn btn-primary";
        } else {
          actionButton.className = "btn btn-default";
        }
        actionButton.style.cursor = input.viewModel.primaryAction.enabled ? "pointer" : "not-allowed";
      }
      if (overlay !== null) {
        overlay.onclick = () => {
          dismissPopup(popup);
        };
      }
      if (closeButton !== null) {
        closeButton.onclick = () => {
          dismissPopup(popup);
        };
      }
      if (dismissButton !== null) {
        dismissButton.onclick = () => {
          dismissPopup(popup);
        };
      }
      if (todayLink !== null) {
        todayLink.onclick = (event) => {
          const interactionState = revalidateInteraction({
            renderedInput: input,
            currentSource: input.source
          });
          if (interactionState.kind === "stale") {
            event.preventDefault();
          }
        };
      }
      if (actionButton !== null) {
        actionButton.onclick = (event) => {
          event.preventDefault();
          if (!input.viewModel.primaryAction.enabled) {
            return;
          }
          const interactionState = revalidateInteraction({
            renderedInput: input,
            currentSource: input.source
          });
          if (interactionState.kind === "stale") {
            return;
          }
          const nextPopup = interactions.runPrimaryAction?.({
            action: input.viewModel.primaryActionKind,
            source: interactionState.source,
            today: interactionState.today,
            expectedDailyState: interactionState.currentSnapshot.reviewWorkspace.dailyState
          });
          if (nextPopup !== null && nextPopup !== void 0) {
            presentPopup(nextPopup);
          }
        };
      }
      popup.onkeydown = (event) => {
        if (event.key === "Escape") {
          event.preventDefault();
          dismissPopup(popup);
        }
      };
      showModal(popup, overlay);
      popup.focus();
    };
    return presentPopup;
    function revalidateInteraction({
      renderedInput,
      currentSource
    }) {
      const today = interactions.getToday?.() ?? renderedInput.today;
      const maybeLatestSnapshot = interactions.loadReadonly?.({
        source: currentSource,
        today
      });
      if (interactions.loadReadonly !== void 0 && (maybeLatestSnapshot === null || maybeLatestSnapshot === void 0)) {
        return { kind: "stale" };
      }
      const latestSnapshot = maybeLatestSnapshot ?? renderedInput;
      if (interactionSessionValidator.validate({
        expectedDailyState: renderedInput.reviewWorkspace.dailyState,
        actualDailyState: latestSnapshot.reviewWorkspace.dailyState,
        today
      }).kind === "stale") {
        const refreshedSnapshot = interactions.refreshPopup?.({
          source: currentSource,
          today
        });
        if (refreshedSnapshot !== null && refreshedSnapshot !== void 0) {
          presentPopup(refreshedSnapshot);
        }
        return { kind: "stale" };
      }
      return {
        kind: "valid",
        currentSnapshot: latestSnapshot,
        source: currentSource,
        today
      };
    }
    function dismissPopup(popup) {
      if (popup.dataset.state === "closing") {
        return;
      }
      popup.dataset.state = "closing";
      const overlay = documentRef.getElementById(POPUP_OVERLAY_ID);
      popup.classList.remove("in");
      overlay?.classList.remove("in");
      const removePopup = () => {
        if (popup.isConnected) {
          popup.remove();
        }
        overlay?.remove();
        popup.style.display = "none";
        unlockPageScroll();
        if (lastFocusedElement !== null && documentRef.contains(lastFocusedElement)) {
          lastFocusedElement.focus();
        }
        lastFocusedElement = null;
      };
      const timerHost = documentRef.defaultView ?? window;
      timerHost.setTimeout(removePopup, 300);
    }
    function showModal(popup, overlay) {
      popup.style.display = "block";
      popup.classList.remove("in");
      overlay?.classList.remove("in");
      lockPageScroll();
      void popup.offsetWidth;
      popup.classList.add("in");
      overlay?.classList.add("in");
    }
    function lockPageScroll() {
      if (!documentRef.body.classList.contains("modal-open")) {
        documentRef.body.classList.add("modal-open");
        previousBodyPaddingRight = documentRef.body.style.paddingRight;
      }
      documentRef.body.style.paddingRight = "12px";
    }
    function unlockPageScroll() {
      documentRef.body.classList.remove("modal-open");
      documentRef.body.style.paddingRight = previousBodyPaddingRight ?? "";
    }
  }
  function success4(value) {
    return {
      ok: true,
      value
    };
  }
  function failure2(error) {
    return {
      ok: false,
      error
    };
  }
  function toProblemPath(problemId) {
    const [contestId, taskId] = problemId.split("/");
    if (contestId === void 0 || taskId === void 0) {
      return "#";
    }
    return `/contests/${contestId}/tasks/${taskId}`;
  }

  // src/runtime/atcoder-shell.ts
  var LEGACY_USER_HANDLE_SELECTOR = ".navbar-right .dropdown > .dropdown-toggle";
  var TOP_PAGE_MYPAGE_SELECTOR = ".header-mypage";
  var TOP_PAGE_MENU_SELECTOR = ".header-mypage_detail .header-mypage_list";
  var PROBLEM_HEADING_SELECTOR = ".col-sm-12 > span.h2";
  var PROBLEM_COMMENTARY_LINK_SELECTOR = `${PROBLEM_HEADING_SELECTOR} > a.btn`;
  var MENU_ENTRY_ID = "ac-revisit-menu-entry";
  var MENU_ENTRY_LINK_ID = "ac-revisit-menu-entry-link";
  var TOGGLE_BUTTON_ID = "ac-revisit-toggle-button";
  var TOGGLE_BUTTON_CLASS = "ac-revisit-toggle-button";
  function createAtCoderPageAdapter(windowRef = window, documentRef = document) {
    return {
      detectPage() {
        const path = windowRef.location.pathname;
        if (/^\/contests\/[^/]+\/tasks\/[^/]+$/.test(path)) {
          return { kind: "problem", path };
        }
        if (/^\/contests\/[^/]+\/submissions\/\d+$/.test(path)) {
          return { kind: "submission_detail", path };
        }
        return { kind: "other", path };
      },
      inspectHeaderShell() {
        const legacyMenuAnchor = findLegacyUserMenuAnchor(documentRef);
        const topPageMenuAnchor = findElement(documentRef, TOP_PAGE_MENU_SELECTOR);
        const hasLegacyUserMenu = legacyMenuAnchor !== null;
        const hasTopPageUserMenu = findElement(documentRef, TOP_PAGE_MYPAGE_SELECTOR) !== null && topPageMenuAnchor !== null;
        return {
          hasLegacyUserMenu,
          hasTopPageUserMenu,
          menuAnchor: legacyMenuAnchor === null ? topPageMenuAnchor === null ? { kind: "missing" } : { kind: "found", element: topPageMenuAnchor, insertMode: "append" } : { kind: "found", element: legacyMenuAnchor, insertMode: "append" },
          menuUserHandle: readTrimmedText(documentRef, LEGACY_USER_HANDLE_SELECTOR) ?? readTrimmedText(documentRef, TOP_PAGE_MYPAGE_SELECTOR)
        };
      },
      findToggleAnchor() {
        const page = this.detectPage();
        if (page.kind === "problem") {
          const commentaryLink = findElement(documentRef, PROBLEM_COMMENTARY_LINK_SELECTOR);
          if (commentaryLink !== null) {
            return {
              kind: "found",
              element: commentaryLink,
              insertMode: "afterend"
            };
          }
          const heading = findElement(documentRef, PROBLEM_HEADING_SELECTOR);
          return heading === null ? { kind: "missing" } : { kind: "found", element: heading, insertMode: "append" };
        }
        if (page.kind !== "submission_detail") {
          return { kind: "missing" };
        }
        const taskLink = findSubmissionProblemTaskLink(documentRef);
        return taskLink === null ? { kind: "missing" } : { kind: "found", element: taskLink, insertMode: "afterend" };
      },
      readProblemContextSource() {
        const page = this.detectPage();
        if (page.kind === "problem") {
          return {
            kind: "problem",
            pathname: page.path,
            problemTitleText: readOwnTextContent(documentRef, PROBLEM_HEADING_SELECTOR)
          };
        }
        if (page.kind === "submission_detail") {
          const taskLink = findSubmissionProblemTaskLink(documentRef);
          return {
            kind: "submission_detail",
            taskHref: taskLink?.getAttribute("href") ?? null,
            taskTitleText: readElementText(taskLink)
          };
        }
        return { kind: "other" };
      }
    };
  }
  function createAuthSessionGuard(pageAdapter) {
    return {
      resolveSession() {
        const headerShell = pageAdapter.inspectHeaderShell();
        if (headerShell.hasLegacyUserMenu || headerShell.hasTopPageUserMenu) {
          return {
            kind: "authenticated",
            userHandle: headerShell.menuUserHandle
          };
        }
        return { kind: "anonymous" };
      }
    };
  }
  function createMenuEntryAdapter(dependencies) {
    const documentRef = dependencies.documentRef ?? document;
    return {
      ensureEntryMounted() {
        if (documentRef.getElementById(MENU_ENTRY_ID) !== null) {
          return {
            ok: true,
            value: { mounted: false }
          };
        }
        const headerShell = dependencies.pageAdapter.inspectHeaderShell();
        if (headerShell.menuAnchor.kind === "missing") {
          return {
            ok: false,
            error: { kind: "anchor_missing" }
          };
        }
        const item = documentRef.createElement("li");
        item.id = MENU_ENTRY_ID;
        const link = documentRef.createElement("a");
        link.id = MENU_ENTRY_LINK_ID;
        link.href = "#";
        appendMenuLinkContents(link, documentRef, headerShell.menuAnchor.element);
        link.addEventListener("click", (event) => {
          event.preventDefault();
          dependencies.openPopup({
            source: "menu",
            today: dependencies.getToday()
          });
        });
        item.append(link);
        insertMenuItem(headerShell.menuAnchor.element, item);
        return {
          ok: true,
          value: { mounted: true }
        };
      }
    };
  }
  function createProblemContextResolver(pageAdapter) {
    return {
      resolveCurrentProblem() {
        const source = pageAdapter.readProblemContextSource();
        if (source.kind === "other") {
          return { kind: "not_applicable" };
        }
        if (source.kind === "problem") {
          if (source.problemTitleText === null) {
            return { kind: "unresolvable" };
          }
          const parsed2 = parseProblemPath(source.pathname);
          return parsed2 === null ? { kind: "unresolvable" } : {
            kind: "resolved",
            contestId: parsed2.contestId,
            problemId: parsed2.problemId,
            problemTitle: source.problemTitleText
          };
        }
        if (source.taskHref === null || source.taskTitleText === null) {
          return { kind: "unresolvable" };
        }
        const parsed = parseProblemPath(source.taskHref);
        return parsed === null ? { kind: "unresolvable" } : {
          kind: "resolved",
          contestId: parsed.contestId,
          problemId: parsed.problemId,
          problemTitle: source.taskTitleText
        };
      }
    };
  }
  function createToggleMountCoordinator(dependencies) {
    const documentRef = dependencies.documentRef ?? document;
    const problemContextResolver = createProblemContextResolver(dependencies.pageAdapter);
    return {
      mount() {
        const existingButton = documentRef.getElementById(TOGGLE_BUTTON_ID);
        const anchor = dependencies.pageAdapter.findToggleAnchor();
        if (anchor.kind === "missing") {
          return {
            ok: false,
            error: { kind: "anchor_missing" }
          };
        }
        const problem = problemContextResolver.resolveCurrentProblem();
        if (problem.kind !== "resolved") {
          return {
            ok: false,
            error: { kind: "problem_unresolvable" }
          };
        }
        const isRegistered = dependencies.resolveIsRegistered?.(problem.problemId) ?? false;
        if (existingButton instanceof HTMLButtonElement) {
          syncToggleButton(existingButton, isRegistered, problem.problemId);
          return {
            ok: true,
            value: {
              mounted: false,
              isRegistered
            }
          };
        }
        const button = documentRef.createElement("button");
        button.type = "button";
        button.id = TOGGLE_BUTTON_ID;
        syncToggleButton(button, isRegistered, problem.problemId);
        const getToday = dependencies.getToday;
        const onToggle = dependencies.onToggle;
        if (onToggle !== void 0 && getToday !== void 0) {
          button.addEventListener("click", () => {
            const currentRegistration = button.dataset.state === "registered";
            const nextRegistration = onToggle({
              problemId: problem.problemId,
              problemTitle: problem.problemTitle,
              today: getToday(),
              isRegistered: currentRegistration
            });
            if (typeof nextRegistration === "boolean") {
              syncToggleButton(button, nextRegistration, problem.problemId);
            }
          });
        }
        insertRelative(anchor, button);
        return {
          ok: true,
          value: {
            mounted: true,
            isRegistered
          }
        };
      }
    };
  }
  function findElement(root, selector) {
    const element = root.querySelector(selector);
    return element instanceof HTMLElement ? element : null;
  }
  function findLegacyUserMenuAnchor(documentRef) {
    const candidates = Array.from(documentRef.querySelectorAll("ul.dropdown-menu")).filter(
      (candidate) => candidate instanceof HTMLElement
    );
    for (const candidate of candidates) {
      const hasLogoutEntry = findMenuItem(candidate, (href, label) => {
        return href.startsWith("/logout") || href.includes("form_logout") || label.includes("ログアウト");
      });
      const hasSettingsEntry = findMenuItem(candidate, (href, label) => {
        return href.startsWith("/settings") || label.includes("設定");
      });
      if (hasLogoutEntry !== void 0 || hasSettingsEntry !== void 0) {
        return candidate;
      }
    }
    return candidates.at(0) ?? null;
  }
  function findSubmissionProblemTaskLink(root) {
    const rows = Array.from(root.querySelectorAll(".col-sm-12 table tr"));
    for (const row of rows) {
      if (!(row instanceof HTMLTableRowElement)) {
        continue;
      }
      const headingCell = row.querySelector("th");
      if (readElementText(headingCell) !== "問題") {
        continue;
      }
      const taskLink = row.querySelector('a[href*="/tasks/"]');
      if (taskLink instanceof HTMLAnchorElement) {
        return taskLink;
      }
    }
    return null;
  }
  function readTrimmedText(root, selector) {
    const element = root.querySelector(selector);
    return readElementText(element);
  }
  function readElementText(element) {
    if (!(element instanceof HTMLElement)) {
      return null;
    }
    const text = element.textContent?.trim();
    return text && text.length > 0 ? text : null;
  }
  function readOwnTextContent(root, selector) {
    const element = root.querySelector(selector);
    if (!(element instanceof HTMLElement)) {
      return null;
    }
    const text = Array.from(element.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent ?? "").join(" ").replace(/\s+/g, " ").trim();
    return text && text.length > 0 ? text : null;
  }
  function parseProblemPath(path) {
    const match = /^\/contests\/([A-Za-z0-9_-]+)\/tasks\/([A-Za-z0-9_-]+)$/.exec(path);
    if (match === null) {
      return null;
    }
    const [, contestId, taskId] = match;
    return {
      contestId,
      problemId: `${contestId}/${taskId}`
    };
  }
  function syncToggleButton(button, isRegistered, problemId) {
    button.className = [
      "btn",
      isRegistered ? "btn-warning" : "btn-default",
      "btn-sm",
      TOGGLE_BUTTON_CLASS
    ].join(" ");
    button.style.marginLeft = "0.5rem";
    button.style.verticalAlign = "middle";
    button.dataset.state = isRegistered ? "registered" : "unregistered";
    button.dataset.problemId = problemId;
    button.textContent = isRegistered ? "ac-revisit 解除" : "ac-revisit 追加";
  }
  function appendMenuLinkContents(link, documentRef, menuElement) {
    const icon = createMenuIcon(documentRef, menuElement);
    icon.dataset.icon = "true";
    icon.setAttribute("aria-hidden", "true");
    const spacer = documentRef.createTextNode(" ");
    const label = documentRef.createElement("span");
    label.textContent = "ac-revisit 操作";
    label.dataset.label = "true";
    link.append(icon, spacer, label);
  }
  function insertMenuItem(menuElement, item) {
    const logoutItem = findMenuItem(menuElement, (href, label) => {
      return href.startsWith("/logout") || href.includes("form_logout") || label.includes("ログアウト");
    });
    if (logoutItem instanceof HTMLElement) {
      const trailingDivider = logoutItem.previousElementSibling instanceof HTMLLIElement && logoutItem.previousElementSibling.classList.contains("divider") ? logoutItem.previousElementSibling : null;
      const insertionAnchor = trailingDivider ?? logoutItem;
      applyMenuItemStyling(item, insertionAnchor);
      insertionAnchor.insertAdjacentElement("beforebegin", item);
      return;
    }
    const settingsItem = findMenuItem(menuElement, (href, label) => {
      return href.startsWith("/settings") || label.includes("設定");
    });
    if (settingsItem instanceof HTMLElement) {
      applyMenuItemStyling(item, settingsItem);
      settingsItem.insertAdjacentElement("beforebegin", item);
      return;
    }
    const referenceItem = menuElement.querySelector("li");
    if (referenceItem instanceof HTMLElement) {
      applyMenuItemStyling(item, referenceItem);
    }
    menuElement.append(item);
  }
  function createMenuIcon(documentRef, menuElement) {
    const settingsIcon = findSettingsIcon(menuElement) ?? findSettingsIcon(documentRef);
    if (settingsIcon !== null) {
      return settingsIcon;
    }
    const fallbackIcon = documentRef.createElement("span");
    fallbackIcon.className = "glyphicon glyphicon-cog";
    return fallbackIcon;
  }
  function findSettingsIcon(root) {
    const settingsLink = findMenuItem(
      root,
      (href, label) => href.startsWith("/settings") || label.includes("設定")
    )?.querySelector("span.glyphicon, span.fa, i.glyphicon, i.fa, i.a-icon, span.a-icon");
    if (!(settingsLink instanceof HTMLElement)) {
      return null;
    }
    return settingsLink.cloneNode(true);
  }
  function applyMenuItemStyling(item, referenceItem) {
    const referenceLink = referenceItem.querySelector("a");
    const link = item.querySelector("a");
    if (referenceLink instanceof HTMLAnchorElement && link instanceof HTMLAnchorElement && !referenceItem.classList.contains("divider")) {
      link.className = referenceLink.className;
    }
  }
  function findMenuItem(root, predicate) {
    return Array.from(root.querySelectorAll("li")).find((candidate) => {
      const link = candidate.querySelector("a");
      const href = link?.getAttribute("href") ?? "";
      const label = link?.textContent?.trim() ?? "";
      return predicate(href, label);
    });
  }
  function insertRelative(anchor, element) {
    if (anchor.insertMode === "afterend") {
      anchor.element.insertAdjacentElement("afterend", element);
      return;
    }
    anchor.element.append(element);
  }

  // src/bootstrap/platform-ports.ts
  function createUserscriptPlatformPorts({
    rng = Math.random,
    dev = false,
    consoleRef = console,
    gmGetValue,
    gmSetValue
  } = {}) {
    const resolvedGMGetValue = gmGetValue ?? globalThis.GM_getValue;
    const resolvedGMSetValue = gmSetValue ?? globalThis.GM_setValue;
    return {
      rng,
      reviewStorage: {
        get(key) {
          return resolvedGMGetValue(key, null);
        },
        set(key, value) {
          resolvedGMSetValue(key, value);
        }
      },
      diagnosticSink: createDiagnosticSink({ dev, consoleRef })
    };
  }
  function createDiagnosticSink({
    dev,
    consoleRef
  }) {
    if (!dev) {
      return () => void 0;
    }
    return (event) => {
      consoleRef.debug(`ac-revisit:${event.code}`, event.component, event.operation);
    };
  }

  // src/bootstrap/userscript.ts
  function bootstrapUserscript(dependencies = {}) {
    const platform = resolvePlatformPorts(dependencies);
    const recordDiagnostic = createDiagnosticRecorder(platform.diagnosticSink);
    const pageAdapter = createAtCoderPageAdapter();
    const sessionGuard = createAuthSessionGuard(pageAdapter);
    const session = sessionGuard.resolveSession();
    if (session.kind === "anonymous") {
      return {
        session: "anonymous",
        menuEntryMounted: false,
        toggleMounted: false
      };
    }
    const page = pageAdapter.detectPage();
    const localDateProvider = createLocalDateProvider();
    const getToday = dependencies.getToday ?? (() => localDateProvider.today());
    const reviewStore = createReviewStoreAdapter(platform.reviewStorage);
    const localDateMath = createLocalDateMath();
    const candidateSelectionService = createCandidateSelectionService({
      localDateMath,
      random: platform.rng
    });
    const dailySuggestionService = createDailySuggestionService({
      reviewStore,
      localDateMath,
      candidateSelectionService
    });
    const reviewMutationService = createReviewMutationService({
      reviewStore,
      candidateSelectionService
    });
    const popupStateLoader = createPopupStateLoader({
      readWorkspace() {
        return reviewStore.readWorkspace();
      },
      listDueCandidates(input) {
        return candidateSelectionService.listDueCandidates(input);
      }
    });
    function loadWorkspacePopupState(input) {
      const result = popupStateLoader.load({
        mode: "workspace",
        source: input.source,
        today: input.today,
        reviewWorkspace: input.reviewWorkspace
      });
      if (!result.ok) {
        throw new Error("workspace popup state loading must not fail");
      }
      return result.value;
    }
    function refreshPopupState(input) {
      const result = dailySuggestionService.ensureTodaySuggestion({
        today: input.today,
        trigger: "menu"
      });
      if (!result.ok) {
        recordDiagnostic({
          code: result.error.kind,
          component: "DailySuggestionService",
          operation: "popup_refresh"
        });
        return null;
      }
      return loadWorkspacePopupState({
        source: input.source,
        today: input.today,
        reviewWorkspace: result.value.reviewWorkspace
      });
    }
    function loadReadonlyPopupState(input) {
      const result = popupStateLoader.load({
        mode: "readonly",
        source: input.source,
        today: input.today
      });
      if (!result.ok) {
        recordDiagnostic({
          code: result.error.kind,
          component: "PopupStateLoader",
          operation: "popup_readonly_load"
        });
        return null;
      }
      return result.value;
    }
    function runPopupPrimaryAction(input) {
      const result = input.action === "complete" ? reviewMutationService.completeTodayProblem({
        today: input.today,
        expectedDailyState: input.expectedDailyState
      }) : reviewMutationService.fetchNextTodayProblem({
        today: input.today,
        expectedDailyState: input.expectedDailyState
      });
      if (!result.ok) {
        if (result.error.kind === "storage_unavailable") {
          recordDiagnostic({
            code: result.error.kind,
            component: "ReviewMutationService",
            operation: "popup_primary_action"
          });
        }
        return refreshPopupState({
          source: input.source,
          today: input.today
        });
      }
      return loadWorkspacePopupState({
        source: input.source,
        today: input.today,
        reviewWorkspace: result.value.reviewWorkspace
      });
    }
    const defaultPopupPresenter = dependencies.openPopup === void 0 ? createPopupShellPresenter(document, {
      getToday,
      loadReadonly: loadReadonlyPopupState,
      refreshPopup: refreshPopupState,
      runPrimaryAction: runPopupPrimaryAction
    }) : null;
    function presentPopup(input) {
      if (defaultPopupPresenter === null) {
        dependencies.openPopup?.({
          source: input.source,
          today: input.today,
          dailyState: input.reviewWorkspace.dailyState
        });
        return;
      }
      defaultPopupPresenter(loadWorkspacePopupState(input));
    }
    const menuEntryAdapter = createMenuEntryAdapter({
      pageAdapter,
      getToday,
      openPopup(input) {
        const menuSuggestionResult = dailySuggestionService.ensureTodaySuggestion({
          today: input.today,
          trigger: "menu"
        });
        if (!menuSuggestionResult.ok) {
          recordDiagnostic({
            code: menuSuggestionResult.error.kind,
            component: "DailySuggestionService",
            operation: "menu_open_popup"
          });
          return;
        }
        presentPopup({
          source: input.source,
          today: input.today,
          reviewWorkspace: menuSuggestionResult.value.reviewWorkspace
        });
      }
    });
    const mountResult = menuEntryAdapter.ensureEntryMounted();
    if (!mountResult.ok) {
      recordDiagnostic({
        code: mountResult.error.kind,
        component: "MenuEntryAdapter",
        operation: "startup_menu_mount"
      });
    }
    const toggleMountCoordinator = createToggleMountCoordinator({
      pageAdapter,
      getToday,
      resolveIsRegistered(problemId) {
        const workspace = reviewStore.readWorkspace();
        if (!workspace.ok) {
          recordDiagnostic({
            code: workspace.error.kind,
            component: "ReviewStoreAdapter",
            operation: "startup_toggle_state_load"
          });
        }
        return workspace.ok && workspace.value.reviewItems.some((item) => item.problemId === problemId);
      },
      onToggle(input) {
        const result = input.isRegistered ? reviewMutationService.unregisterProblem({
          problemId: input.problemId,
          today: input.today
        }) : reviewMutationService.registerProblem({
          problemId: input.problemId,
          problemTitle: input.problemTitle,
          today: input.today
        });
        if (!result.ok) {
          if (result.error.kind === "storage_unavailable") {
            recordDiagnostic({
              code: result.error.kind,
              component: "ReviewMutationService",
              operation: "toggle_click"
            });
          }
          return input.isRegistered;
        }
        return result.value.reviewWorkspace.reviewItems.some(
          (item) => item.problemId === input.problemId
        );
      }
    });
    const toggleMountResult = page.kind === "problem" || page.kind === "submission_detail" ? toggleMountCoordinator.mount() : null;
    if (toggleMountResult !== null && !toggleMountResult.ok) {
      recordDiagnostic({
        code: toggleMountResult.error.kind,
        component: "ToggleMountCoordinator",
        operation: "startup_toggle_mount"
      });
    }
    const today = getToday();
    const dailySuggestionResult = dailySuggestionService.ensureTodaySuggestion({
      today,
      trigger: "bootstrap"
    });
    if (!dailySuggestionResult.ok) {
      recordDiagnostic({
        code: dailySuggestionResult.error.kind,
        component: "DailySuggestionService",
        operation: "startup_daily_suggestion"
      });
    }
    if (dailySuggestionResult.ok && dailySuggestionResult.value.shouldAutoOpenPopup) {
      presentPopup({
        source: "bootstrap",
        today,
        reviewWorkspace: dailySuggestionResult.value.reviewWorkspace
      });
    }
    return {
      session: "authenticated",
      menuEntryMounted: mountResult.ok && mountResult.value.mounted,
      toggleMounted: toggleMountResult?.ok === true && toggleMountResult.value.mounted
    };
  }
  function createDiagnosticRecorder(diagnosticSink) {
    return (event) => {
      diagnosticSink?.(event);
    };
  }
  function resolvePlatformPorts(dependencies) {
    const reviewStorage = dependencies.platform?.reviewStorage ?? dependencies.reviewStorage ?? createUserscriptPlatformPorts().reviewStorage;
    const rng = dependencies.platform?.rng ?? dependencies.rng ?? Math.random;
    const diagnosticSink = dependencies.platform?.diagnosticSink ?? dependencies.diagnosticSink ?? (() => void 0);
    return {
      rng,
      reviewStorage,
      diagnosticSink
    };
  }

  // src/main.ts
  if (false) {
    bootstrapUserscript({
      platform: createUserscriptPlatformPorts({
        dev: true,
        consoleRef: console
      })
    });
    logDevWorkspaceSnapshot(console);
  } else {
    bootstrapUserscript({
      platform: createUserscriptPlatformPorts()
    });
  }
})();