diff --git a/.gitignore b/.gitignore
index 14a1b3f..231c729 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
__pycache__
.venv
*.pdf
+*.db
diff --git a/docker-compose.yml b/docker-compose.yml
index 7af6eaa..2328c3e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,4 +6,11 @@ services:
environment:
- HOST=0.0.0.0
- PORT=8000
+ # Stored solves live on the mounted volume so they survive restarts.
+ - DB_PATH=/data/solves.db
+ volumes:
+ - solves:/data
restart: unless-stopped
+
+volumes:
+ solves:
diff --git a/index.html b/index.html
index 608a6b4..bcd47da 100644
--- a/index.html
+++ b/index.html
@@ -862,7 +862,7 @@
}
const data = await resp.json();
if (!resp.ok) {pending.remove(); errBox.textContent = data.error || "Server error"; return;}
- renderSolution(data, pending);
+ renderSolution(data, pending, token);
} catch (e) {pending.remove(); errBox.textContent = e.message;}
finally {solvingHere = false; activeToken = null; setSolveDisabled(false);}
}
@@ -891,7 +891,7 @@
setInterval(pollStatus, 1000);
pollStatus();
- function renderSolution(s, placeholder) {
+ function renderSolution(s, placeholder, token) {
// Collapse any previously-shown solutions so the new one is the focus.
for (const d of document.querySelectorAll("#output details.solution")) d.open = false;
@@ -900,6 +900,19 @@
details.append(el("summary", {},
`Solution ${n} — ${s.status}, objective ${s.objective_value ?? "—"}`));
const out = details;
+
+ // A shareable permalink to this stored solve, looked up by its UUID.
+ if (token) {
+ const url = location.origin + location.pathname + "?solve=" + encodeURIComponent(token);
+ out.append(el("p", {}, el("button", {
+ class: "mini", type: "button",
+ onclick: (ev) => {
+ navigator.clipboard?.writeText(url);
+ ev.target.textContent = "Link copied!";
+ setTimeout(() => {ev.target.textContent = "Copy share link";}, 1500);
+ },
+ }, "Copy share link")));
+ }
out.append(el("p", {
html:
`Status: ${s.status} Objective: ${s.objective_value ?? "—"} ` +
@@ -1096,6 +1109,27 @@
"brass": 1,
"electrum": 2
})
+
+ // --- deep link: /?solve= loads a previously stored solve ---
+ async function loadSharedSolve(token) {
+ const out = document.getElementById("output");
+ const errBox = document.getElementById("error");
+ const pending = el("p", {class: "pending"}, "Loading shared solve…");
+ out.prepend(pending);
+ try {
+ const resp = await fetch("/solve/" + encodeURIComponent(token), {cache: "no-store"});
+ if (resp.status === 404) {
+ pending.remove();
+ errBox.textContent = "No stored solve found for that link (it may have been evicted).";
+ return;
+ }
+ const rec = await resp.json();
+ if (!resp.ok) {pending.remove(); errBox.textContent = rec.error || "Server error"; return;}
+ renderSolution(rec.solution, pending, rec.token);
+ } catch (e) {pending.remove(); errBox.textContent = e.message;}
+ }
+ const sharedToken = new URLSearchParams(location.search).get("solve");
+ if (sharedToken) loadSharedSolve(sharedToken);