Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions frontend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,42 @@ async function useApiKey() {

// Returns true if the app may boot immediately; false means the gate is up and
// will boot the app once the visitor authenticates.
//
// Policy change (external review, Oct 2026): don't greet first-time visitors
// with a full-page Turnstile modal. Let them read the hero + docs first,
// then challenge only when they try to USE the workspace. The old modal
// loaded over a blurred page and read as "we don't want to show you our
// product" even though we do. We boot the public read-only surface right
// away and defer the gate to `ensureWorkspaceAuth` below.
async function ensureAuth() {
if (!AUTH.required) return true;
const stored = loadStoredToken();
if (stored) { AUTH.token = stored; return true; }
showGate();
return false;
// No stored token - but DON'T show the gate yet. Boot the app so the
// visitor can look around; the submit handlers will call
// `ensureWorkspaceAuth` right before firing a real job.
return true;
}

// Called by workspace-mutating actions (merge/inspect/evaluate/export submit,
// upload) to require a Turnstile pass or API key first. Returns a promise
// that resolves true once the visitor has a token, or false if they close
// the gate.
async function ensureWorkspaceAuth() {
if (!AUTH.required) return true;
if (AUTH.token) return true;
const stored = loadStoredToken();
if (stored) { AUTH.token = stored; return true; }
// Show the modal and wait for the exchange to complete.
return new Promise((resolve) => {
const check = () => {
if (AUTH.token) { resolve(true); return; }
if ($("#authGate").hidden) { resolve(false); return; }
setTimeout(check, 200);
};
showGate("Verify to run a job. This stays off until you submit.");
check();
});
}

async function api(path, opts = {}) {
Expand Down Expand Up @@ -174,6 +204,12 @@ async function checkHealth() {
const h = await api("/api/health");
el.textContent = `backend online · ${h.version}`;
line.classList.remove("bad");
// Mirror the version onto the hero credits strip so a reviewer can read
// "which release of the tool is this" without opening devtools.
const pill = $("#versionPill");
if (pill && h.version) {
pill.innerHTML = `<strong>Version:</strong> <code>${String(h.version).replace(/^mergese\.py, version /, "")}</code>`;
}
} catch (e) {
el.textContent = `backend offline (${e.message}) - append ?api=https://... to point elsewhere`;
line.classList.add("bad");
Expand Down Expand Up @@ -781,6 +817,13 @@ async function submit(path, body, opts) {
toast("Demo mode - backend disabled. Try locally with `python server/app.py`.", "err");
return null;
}
// Deferred Turnstile pass: show the gate only when the visitor actually
// tries to run a job, not on page load (external UX review, Oct 2026).
const authed = await ensureWorkspaceAuth();
if (!authed) {
toast(`${opts.kind} cancelled - verification not completed`, "err");
return null;
}
try {
const r = await api(path, { method: "POST", body: JSON.stringify(body) });
toast(`Started ${opts.kind} job ${r.job_id}`, "ok");
Expand Down
38 changes: 35 additions & 3 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<div class="subtitle">Post-hoc model merging for software engineering</div>
</div>
</div>
<!-- Below 600px the full menu collapses behind this toggle (CSS). The
details element gives us a native disclosure with no JavaScript. -->
<details class="nav-mobile">
<summary aria-label="Open menu">Menu</summary>
</details>
<nav>
<a href="#library">Library</a>
<a href="#inspect">Inspect</a>
Expand All @@ -63,6 +68,12 @@
<span class="spin"></span>
<span id="runningPillText">0 running</span>
</a>
<a href="https://github.com/srlabUsask/MergeSE/issues/new?template=bug_report.md"
target="_blank" rel="noopener" class="nav-util"
title="Open a GitHub issue; please include the job id if applicable">Report issue</a>
<a href="mailto:palash.roy101@gmail.com?subject=MergeSE%20API%20access%20request&body=Which%20tier%20do%20you%20need%2C%20and%20what%20will%20you%20use%20it%20for%3F"
class="nav-util"
title="Email the maintainer with your use case and expected volume">Request API access</a>
<a href="https://github.com/srlabUsask/MergeSE" id="ghLink" class="gh-link" target="_blank" rel="noopener">GitHub</a>
</nav>
</header>
Expand All @@ -73,24 +84,45 @@
<div class="hero-text">
<h1>Merge fine-tuned SE models <span class="grad">without retraining</span>.</h1>
<p class="lead">
TIES / DARE-TIES / averaging on any HuggingFace encoder - for
TIES / DARE-TIES / WUDI / PCB / task-vector averaging on
BERT &amp; RoBERTa-family encoders from the HuggingFace Hub &mdash; for
<strong>clone detection</strong>, <strong>vulnerability detection</strong>,
<strong>defect prediction</strong>, <strong>code-smell</strong>,
<strong>commit classification</strong> and more. One CLI, one web tool;
no retraining, no labels. Built alongside our ASE submission.
no retraining, no labels for the parameter-level methods.
<em>Architecture merging</em> (greedy) uses a small validation slice
to hill-climb layer swaps.
</p>
<div class="hero-cta">
<a class="btn btn-primary" href="#merge">Try a merge</a>
<a class="btn btn-ghost" href="#docs">Read the docs</a>
<a class="btn btn-ghost" href="https://github.com/srlabUsask/MergeSE/issues/new?template=bug_report.md"
target="_blank" rel="noopener">Report an issue</a>
</div>
<div class="status-line" id="healthLine">
<span class="dot pulse"></span>
<span id="healthText">connecting to backend...</span>
</div>
<!-- Research credibility strip. Updated per release so reviewers can
cite exact state. If you touch the version or paper link, update
it in server/app.py's /api/health response too. -->
<div class="credits">
<span class="credits-item"><strong>Paper:</strong> ASE 2026 Tool Track
&mdash;
<a href="https://github.com/srlabUsask/MergeSE/blob/main/CITATION.cff"
target="_blank" rel="noopener">cite</a></span>
<span class="credits-item"><strong>Code:</strong>
<a href="https://github.com/srlabUsask/MergeSE"
target="_blank" rel="noopener">srlabUsask/MergeSE</a></span>
<span class="credits-item"><strong>Maintainer:</strong>
<a href="mailto:palash.roy101@gmail.com">Palash R. Roy</a> &mdash;
University of Saskatchewan</span>
<span class="credits-item" id="versionPill" title="Live backend version"></span>
</div>
</div>
<div class="hero-card">
<div class="codecard">
<div class="codecard-head"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="cc-title">terminal</span></div>
<div class="codecard-head"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span><span class="cc-title">terminal &mdash; illustrative, not a live run</span></div>
<pre><code><span class="prompt">$</span> mergese inspect <span class="arg">microsoft/codebert-base</span> \
<span class="arg">microsoft/graphcodebert-base</span> \
--base <span class="arg">microsoft/codebert-base</span>
Expand Down
63 changes: 59 additions & 4 deletions frontend/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,12 @@ footer {
border: 1px solid var(--line-strong);
border-radius: var(--radius-sm);
color: var(--text);
padding: 9px 12px;
padding: 10px 12px;
font-family: inherit;
font-size: 13.5px;
font-size: 14px;
/* iOS tap-target spec is 44px minimum. Also makes mobile forms land
under the thumb rather than looking crammed. */
min-height: 40px;
transition: border-color .15s, box-shadow .15s;
}
.grid textarea {
Expand All @@ -232,7 +235,7 @@ footer {
}
.actions.inline { grid-column: auto; margin: 0; }
.actions .hint { color: var(--muted); font-size: 12.5px; }
.hint-inline { color: var(--muted); font-size: 11.5px; margin-top: -2px; }
.hint-inline { color: var(--muted); font-size: 13px; margin-top: 2px; line-height: 1.45; }

/* ----------------------- Model picker ----------------------- */

Expand Down Expand Up @@ -547,9 +550,61 @@ footer {
.docs-grid { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
.topbar nav a:not(.gh-link) { display: none; }
/* Collapse the main nav behind a <details> disclosure. The old rule
hid everything except the GitHub icon, which left visitors unable
to reach Library / Jobs / Docs on a phone - and no way to request
help if the service was broken. */
.topbar { flex-wrap: wrap; }
.topbar nav {
display: none;
width: 100%;
flex-direction: column;
align-items: stretch;
gap: 4px;
padding: 10px 0;
border-top: 1px solid var(--line);
margin-top: 8px;
}
.nav-mobile[open] + nav { display: flex; }
.topbar nav a { padding: 10px 8px; border-bottom: none; min-height: 44px; }
.topbar nav a.running-pill { align-self: flex-start; }
.grid { grid-template-columns: 1fr; }
}
/* The toggle is hidden above 600px (full nav is already visible). */
.nav-mobile {
display: none;
cursor: pointer;
user-select: none;
padding: 10px 14px;
border-radius: var(--radius-sm);
background: rgba(255,255,255,0.05);
color: var(--text);
font-size: 14px;
margin-left: auto;
}
.nav-mobile summary { list-style: none; }
.nav-mobile summary::-webkit-details-marker { display: none; }
@media (max-width: 600px) { .nav-mobile { display: inline-block; } }

/* Utility nav items (Report issue, Request API access) sit beside the
section anchors but read differently - softer colour, lighter weight. */
.topbar nav a.nav-util {
color: var(--muted);
font-size: 13px;
}
.topbar nav a.nav-util:hover { color: var(--text); }

/* Hero credits strip - compact row below the CTA buttons. */
.credits {
margin-top: 18px;
display: flex; flex-wrap: wrap; gap: 6px 18px;
font-size: 13px; color: var(--muted);
border-top: 1px solid var(--line);
padding-top: 14px;
}
.credits a { color: var(--muted); border-bottom: 1px dashed rgba(255,255,255,0.2); }
.credits a:hover { color: var(--text); border-bottom-color: var(--primary); }
.credits strong { color: var(--text); font-weight: 600; margin-right: 4px; }

/* ---------------------- Auth gate ---------------------- */
.auth-gate {
Expand Down
Loading