Files
claude BotandClaude Opus 5.5 02b51fed77 Scheduled searches with a weekly review of new and rising repositories
A search can now be saved with a schedule (every N hours, daily, or weekly at
a given day and time). An internal scheduler runs it and keeps a snapshot of
the results on a /data volume (JSON files, no new dependency). The new Watch
tab lists the scheduled searches; the review page of each one shows, for any
run of its history, the repositories never seen before and the ones that
gained the most stars since the previous run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 16:36:50 +02:00

362 lines
19 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Searchgit</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%236366f1'/%3E%3Ccircle cx='14' cy='14' r='6' fill='none' stroke='white' stroke-width='3'/%3E%3Cpath d='m19 19 5 5' stroke='white' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E">
<link rel="stylesheet" href="style.css">
<script>
// Apply the saved theme and language before first paint to avoid a flash.
try {
var t = localStorage.getItem('searchgit.theme');
if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t;
var l = localStorage.getItem('searchgit.lang');
if (l) document.documentElement.lang = l;
} catch (e) {}
</script>
</head>
<body>
<header class="topbar">
<div class="brand">
<svg viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="8"/><circle cx="14" cy="14" r="6"/><path d="m19 19 5 5"/></svg>
<span>Searchgit</span>
</div>
<nav id="views" class="views" data-i18n-aria="viewsAria">
<a href="#" data-view="search">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<span data-i18n="viewSearch">Search</span>
</a>
<a href="#saved" data-view="saved">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2.5 2M9 2h6"/></svg>
<span data-i18n="viewSaved">Watch</span>
<span id="newTotal" class="pill" hidden></span>
</a>
</nav>
<form id="searchForm" class="search view-search" role="search">
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<input id="q" type="search" autocomplete="off" spellcheck="false" data-i18n-aria="searchAria" data-i18n-ph="searchPh">
<select id="in" class="chip-select" data-i18n-aria="inAria">
<option value="" data-i18n="inAll">Everywhere</option>
<option value="name" data-i18n="inName">Name</option>
<option value="description" data-i18n="inDesc">Description</option>
<option value="readme" data-i18n="inReadme">README</option>
</select>
</form>
<div class="actions">
<button id="settingsBtn" class="icon-btn" type="button" data-i18n-title="settings" data-i18n-aria="settings">
<!-- Gear icon (Lucide "settings", ISC license) -->
<svg viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
<button id="themeBtn" class="icon-btn" type="button" data-i18n-title="theme" data-i18n-aria="theme">
<svg class="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
<svg class="moon" viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
</button>
</div>
<div class="progress" aria-hidden="true"></div>
</header>
<div id="viewSearch" class="view">
<section class="filters">
<select id="language" data-i18n-aria="languageAria"><option value="" data-i18n="langAll">All languages</option></select>
<select id="stars" data-i18n-aria="starsAria">
<option value="" data-i18n="starsAll">Any stars</option>
<option value="10">★ ≥ 10</option>
<option value="100">★ ≥ 100</option>
<option value="1000">★ ≥ 1k</option>
<option value="10000">★ ≥ 10k</option>
</select>
<select id="pushed" data-i18n-aria="pushedAria">
<option value="" data-i18n="pushedAll">Any activity</option>
<option value="1w" data-i18n="pushed1w">Pushed &lt; 1 week</option>
<option value="1m" data-i18n="pushed1m">Pushed &lt; 1 month</option>
<option value="6m" data-i18n="pushed6m">Pushed &lt; 6 months</option>
<option value="1y" data-i18n="pushed1y">Pushed &lt; 1 year</option>
<option value="2y" data-i18n="pushed2y">Pushed &lt; 2 years</option>
</select>
<select id="created" data-i18n-aria="createdAria">
<option value="" data-i18n="createdAll">Any creation date</option>
<option value="1w" data-i18n="created1w">Created &lt; 1 week</option>
<option value="1m" data-i18n="created1m">Created &lt; 1 month</option>
<option value="3m" data-i18n="created3m">Created &lt; 3 months</option>
<option value="6m" data-i18n="created6m">Created &lt; 6 months</option>
<option value="1y" data-i18n="created1y">Created &lt; 1 year</option>
<option value="5y" data-i18n="created5y">Created &lt; 5 years</option>
</select>
<select id="license" data-i18n-aria="licenseAria">
<option value="" data-i18n="licenseAll">Any license</option>
<option value="mit">MIT</option>
<option value="apache-2.0">Apache 2.0</option>
<option value="gpl-3.0">GPL 3.0</option>
<option value="agpl-3.0">AGPL 3.0</option>
<option value="lgpl-3.0">LGPL 3.0</option>
<option value="mpl-2.0">MPL 2.0</option>
<option value="bsd-3-clause">BSD 3-Clause</option>
<option value="bsd-2-clause">BSD 2-Clause</option>
<option value="isc">ISC</option>
<option value="unlicense">Unlicense</option>
</select>
<input id="topic" class="filter-input" type="text" autocomplete="off" spellcheck="false" data-i18n-ph="topicPh" data-i18n-aria="topicAria">
<div class="toggles">
<button id="goodfirst" class="toggle" type="button" aria-pressed="false" data-i18n="goodFirst" data-i18n-title="goodFirstTitle">Good first issues</button>
<button id="forks" class="toggle" type="button" aria-pressed="false" data-i18n="withForks" data-i18n-title="withForksTitle">Forks</button>
<button id="archived" class="toggle" type="button" aria-pressed="false" data-i18n="withArchived" data-i18n-title="withArchivedTitle">Archived</button>
</div>
<button id="resetBtn" class="link-btn" type="button" data-i18n="reset" hidden>Reset</button>
<div class="end">
<span id="count" class="muted"></span>
<select id="sort" data-i18n-aria="sortAria">
<option value="" data-i18n="sortBest">Best match</option>
<option value="stars" data-i18n="sortStars">Most stars</option>
<option value="forks" data-i18n="sortForks">Most forks</option>
<option value="updated" data-i18n="sortUpdated">Recently updated</option>
<option value="help-wanted-issues" data-i18n="sortHelp">Help wanted issues</option>
</select>
<button id="saveBtn" class="btn tool" type="button" data-i18n-title="saveBtnTitle">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2.5 2M9 2h6"/></svg>
<span class="lbl" data-i18n="saveBtn">Schedule</span>
</button>
<button id="exportBtn" class="btn tool" type="button" data-i18n-title="exportTitle">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12M7 10l5 5 5-5M4 17v3h16v-3"/></svg>
<span class="lbl">CSV</span>
</button>
</div>
</section>
<div id="error" class="error-banner" hidden></div>
<main id="list" class="list"></main>
<div id="more" class="more" hidden>
<button id="moreBtn" class="btn" type="button" data-i18n="more">Load more</button>
</div>
<div id="empty" class="empty" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<p><strong data-i18n="emptyTitle">No matching repositories.</strong></p>
<p class="muted" data-i18n="emptyHint">Loosen a filter or change the search words.</p>
</div>
</div>
<!-- Saved (scheduled) searches -->
<div id="viewSaved" class="view" hidden>
<section class="filters">
<h1 class="view-title" data-i18n="savedTitle">Scheduled searches</h1>
<div class="end">
<span id="savedCount" class="muted small"></span>
<a href="#" class="btn tool">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
<span class="lbl" data-i18n="savedNew">New from a search</span>
</a>
</div>
</section>
<div id="savedError" class="error-banner" hidden></div>
<main id="savedList" class="list"></main>
<div id="savedEmpty" class="empty" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2.5 2M9 2h6"/></svg>
<p><strong data-i18n="savedEmptyTitle">No scheduled search yet.</strong></p>
<p class="muted" data-i18n="savedEmptyHint"></p>
</div>
</div>
<!-- Review of one saved search -->
<div id="viewReview" class="view" hidden>
<section class="filters review-head">
<a href="#saved" class="icon-btn" data-i18n-title="back" data-i18n-aria="back">
<svg viewBox="0 0 24 24"><path d="m15 18-6-6 6-6"/></svg>
</a>
<div class="review-title">
<h1 id="revName" class="view-title"></h1>
<div id="revSched" class="muted small"></div>
</div>
<div class="end">
<select id="runSel" data-i18n-aria="runAria"></select>
<button id="revRunBtn" class="btn tool" type="button" data-i18n-title="runNowTitle">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 4v16l13-8z"/></svg>
<span class="lbl" data-i18n="runNow">Run now</span>
</button>
<button id="revOpenBtn" class="btn tool" type="button" data-i18n-title="openInSearchTitle">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
<span class="lbl" data-i18n="openInSearch">Open in search</span>
</button>
<button id="revEditBtn" class="icon-btn" type="button" data-i18n-title="edit" data-i18n-aria="edit">
<svg viewBox="0 0 24 24"><path d="M4 20h4L19 9l-4-4L4 16zM14 6l4 4"/></svg>
</button>
</div>
</section>
<section class="filters review-bar">
<div id="revMode" class="seg" role="radiogroup" data-i18n-aria="revModeAria">
<button type="button" role="radio" data-mode="new"></button>
<button type="button" role="radio" data-mode="rising"></button>
<button type="button" role="radio" data-mode="all"></button>
</div>
<span id="revInfo" class="muted small"></span>
</section>
<div id="revError" class="error-banner" hidden></div>
<main id="revList" class="list"></main>
<div id="revEmpty" class="empty" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 3 2.7 5.6 6.1.8-4.5 4.3 1.1 6.1L12 16.9l-5.4 2.9 1.1-6.1-4.5-4.3 6.1-.8z"/></svg>
<p><strong id="revEmptyTitle"></strong></p>
<p id="revEmptyHint" class="muted"></p>
</div>
</div>
<footer class="status">
<span id="conn" class="conn"></span>
<a id="ghQuery" class="gh-query" target="_blank" rel="noopener" hidden></a>
</footer>
<dialog id="settingsDlg" class="settings" aria-labelledby="settingsTitle">
<div class="dlg">
<header>
<h2 id="settingsTitle" data-i18n="settings">Settings</h2>
<button id="closeSettings" class="icon-btn" type="button" data-i18n-aria="close">
<svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg>
</button>
</header>
<div class="set-body">
<nav id="setNav" class="set-nav" role="tablist" aria-orientation="vertical">
<button type="button" role="tab" data-tab="locale">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
<span data-i18n="tabLocale">Localization</span>
</button>
<button type="button" role="tab" data-tab="interface">
<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></svg>
<span data-i18n="tabInterface">Interface</span>
</button>
<button type="button" role="tab" data-tab="github">
<svg viewBox="0 0 24 24"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="8" r="2.5"/><path d="M6 8.5v7M18 10.5c0 4-6 3-11 5.5"/></svg>
<span>GitHub</span>
</button>
</nav>
<div class="set-panels">
<!-- Localization -->
<div class="set-panel" role="tabpanel" data-panel="locale">
<section class="set-section">
<h3 data-i18n="language">Language</h3>
<div id="langSwitch" class="seg" role="radiogroup" data-i18n-aria="language">
<button type="button" role="radio" data-lang="en">English</button>
<button type="button" role="radio" data-lang="fr">Français</button>
</div>
</section>
</div>
<!-- Interface -->
<div class="set-panel" role="tabpanel" data-panel="interface" hidden>
<section class="set-section">
<h3 data-i18n="themeTitle">Theme</h3>
<div id="themeSwitch" class="seg" role="radiogroup" data-i18n-aria="themeTitle">
<button type="button" role="radio" data-theme-opt="system" data-i18n="themeSystem">System</button>
<button type="button" role="radio" data-theme-opt="light" data-i18n="themeLight">Light</button>
<button type="button" role="radio" data-theme-opt="dark" data-i18n="themeDark">Dark</button>
</div>
<p class="muted small hint" data-i18n="themeHelp"></p>
</section>
<section class="set-section">
<h3 data-i18n="listTitle">Result list</h3>
<div class="field-grid">
<span class="lbl" data-i18n="density">Density</span>
<div id="densitySwitch" class="seg" role="radiogroup" data-i18n-aria="density">
<button type="button" role="radio" data-density="compact" data-i18n="compact">Compact</button>
<button type="button" role="radio" data-density="comfortable" data-i18n="comfortable">Comfortable</button>
</div>
<span class="lbl" data-i18n="perPage">Results per page</span>
<div id="perPageSwitch" class="seg" role="radiogroup" data-i18n-aria="perPage">
<button type="button" role="radio" data-per-page="30">30</button>
<button type="button" role="radio" data-per-page="50">50</button>
<button type="button" role="radio" data-per-page="100">100</button>
</div>
</div>
</section>
</div>
<!-- GitHub -->
<div class="set-panel" role="tabpanel" data-panel="github" hidden>
<section class="set-section">
<h3 data-i18n="tokenTitle">Access token</h3>
<p id="tokenStatus" class="state-line"></p>
<p class="muted small" data-i18n="tokenHelp"></p>
</section>
<section class="set-section">
<h3 data-i18n="quotaTitle">Search quota</h3>
<p id="quotaStatus" class="state-line"></p>
<p class="muted small" data-i18n="quotaHelp"></p>
</section>
<section class="set-section">
<h3 data-i18n="syntaxTitle">Search syntax</h3>
<p class="muted small" data-i18n="syntaxHelp"></p>
<ul class="syntax">
<li><code>user:torvalds</code> <span data-i18n="synUser"></span></li>
<li><code>org:golang</code> <span data-i18n="synOrg"></span></li>
<li><code>topic:cli topic:tui</code> <span data-i18n="synTopics"></span></li>
<li><code>"log viewer"</code> <span data-i18n="synPhrase"></span></li>
<li><code>NOT electron</code> <span data-i18n="synNot"></span></li>
<li><code>size:&lt;5000</code> <span data-i18n="synSize"></span></li>
</ul>
</section>
</div>
</div>
</div>
</div>
</dialog>
<!-- Create / edit a scheduled search -->
<dialog id="saveDlg" class="save-dlg" aria-labelledby="saveTitle">
<form id="saveForm" class="dlg" method="dialog">
<header>
<h2 id="saveTitle"></h2>
<button id="closeSave" class="icon-btn" type="button" data-i18n-aria="close">
<svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg>
</button>
</header>
<div class="save-body">
<label class="lbl" for="svName" data-i18n="svName">Name</label>
<input id="svName" class="text-input" type="text" maxlength="120" required autocomplete="off">
<span class="lbl" data-i18n="svQuery">GitHub query</span>
<code id="svQuery" class="query-box"></code>
<label class="lbl" for="svEvery" data-i18n="svEvery">Frequency</label>
<div class="sched-row">
<select id="svEvery" class="text-input">
<option value="week" data-i18n="everyWeek">Every week</option>
<option value="day" data-i18n="everyDay">Every day</option>
<option value="hours" data-i18n="everyHours">Every N hours</option>
</select>
<select id="svWeekday" class="text-input" data-i18n-aria="svWeekday"></select>
<input id="svTime" class="text-input" type="time" value="08:00" required data-i18n-aria="svTime">
<span id="svHoursWrap" class="hours-wrap">
<input id="svHours" class="text-input" type="number" min="1" max="168" value="24" data-i18n-aria="svHours">
<span class="muted small" data-i18n="hoursUnit">hours</span>
</span>
</div>
<span class="lbl" data-i18n="svMax">Results kept</span>
<div id="svMax" class="seg" role="radiogroup" data-i18n-aria="svMax">
<button type="button" role="radio" data-max="30">30</button>
<button type="button" role="radio" data-max="50">50</button>
<button type="button" role="radio" data-max="100">100</button>
</div>
<span class="lbl" data-i18n="svEnabled">Active</span>
<label class="check"><input id="svEnabled" type="checkbox" checked> <span data-i18n="svEnabledHelp">Run on schedule</span></label>
</div>
<p id="svHint" class="muted small save-hint"></p>
<footer class="dlg-foot">
<button id="svDelete" class="btn danger" type="button" data-i18n="delete" hidden>Delete</button>
<span class="grow"></span>
<button id="svCancel" class="btn" type="button" data-i18n="cancel">Cancel</button>
<button id="svSubmit" class="btn primary" type="submit" data-i18n="save">Save</button>
</footer>
</form>
</dialog>
<div id="toast" class="toast" hidden></div>
<script src="app.js"></script>
</body>
</html>