Spaces:
Running on Zero
Running on Zero
fix: clarify builder-facing interface copy
Browse filesCo-authored-by: Codex <noreply@openai.com>
- static/app.js +19 -19
- static/index.html +5 -5
- tests/test_frontend_copy.py +42 -0
static/app.js
CHANGED
|
@@ -99,7 +99,7 @@ goalsEl.addEventListener("change", (event) => {
|
|
| 99 |
);
|
| 100 |
session.goals = goalOptions.filter((option) => checked.has(option));
|
| 101 |
syncCurrentIdeaGoals();
|
| 102 |
-
invalidateCurrentSeal("Goals updated. Press Ink or Plan to refresh the
|
| 103 |
saveSession();
|
| 104 |
renderGoals(session.goals);
|
| 105 |
renderIdeas(session.ideas || []);
|
|
@@ -166,7 +166,7 @@ async function runTurn(message) {
|
|
| 166 |
completed = true;
|
| 167 |
} catch (error) {
|
| 168 |
clearTurnWatchdog();
|
| 169 |
-
ink.textContent = `The
|
| 170 |
ink.classList.remove("thinking");
|
| 171 |
ink.classList.add("bleed");
|
| 172 |
} finally {
|
|
@@ -336,7 +336,7 @@ function resetSession() {
|
|
| 336 |
input.disabled = false;
|
| 337 |
setSessionControlsDisabled(false);
|
| 338 |
input.value = "";
|
| 339 |
-
ink.textContent = "The book is open.
|
| 340 |
ink.classList.remove("thinking", "bleed", "gold");
|
| 341 |
corrections.textContent = "Session reset.";
|
| 342 |
renderGoals(session.goals);
|
|
@@ -362,14 +362,14 @@ async function loadDemoSession() {
|
|
| 362 |
setSessionControlsDisabled(true);
|
| 363 |
ink.classList.remove("bleed", "gold");
|
| 364 |
ink.classList.add("thinking");
|
| 365 |
-
ink.textContent = "
|
| 366 |
corrections.textContent = "";
|
| 367 |
try {
|
| 368 |
const response = await fetch("/api/demo-session");
|
| 369 |
-
if (!response.ok) throw new Error(`
|
| 370 |
applyDemoSession(await response.json());
|
| 371 |
} catch (error) {
|
| 372 |
-
ink.textContent = `The
|
| 373 |
ink.classList.remove("thinking");
|
| 374 |
ink.classList.add("bleed");
|
| 375 |
} finally {
|
|
@@ -385,9 +385,9 @@ function applyDemoSession(data) {
|
|
| 385 |
session.profile = session.profile || {};
|
| 386 |
session.goals = Array.isArray(session.goals) ? session.goals : [];
|
| 387 |
session.last_response = data.response || session.last_response || "";
|
| 388 |
-
session.ui_status = `
|
| 389 |
currentArtifact = data.artifact || session.last_artifact || null;
|
| 390 |
-
ink.textContent = data.response || "
|
| 391 |
ink.classList.remove("thinking");
|
| 392 |
if (data.score) {
|
| 393 |
setVerdictDisplay(data.score.verdict, data.score.overall, data.score);
|
|
@@ -657,7 +657,7 @@ function renderProjectReferenceState() {
|
|
| 657 |
currentArtifact = null;
|
| 658 |
renderScore(null);
|
| 659 |
setVerdictDisplay("READY", 0, null);
|
| 660 |
-
sealCopyEl.textContent = "Project pages are shown below.
|
| 661 |
ink.classList.remove("bleed", "gold");
|
| 662 |
renderWoodMap(null);
|
| 663 |
}
|
|
@@ -670,7 +670,7 @@ function renderIdeas(ideas) {
|
|
| 670 |
if (ideaCountEl) ideaCountEl.textContent = ideas.length;
|
| 671 |
ideasEl.innerHTML = "";
|
| 672 |
if (!ideas.length) {
|
| 673 |
-
ideasEl.innerHTML = `<div class="empty">Your idea board is empty. Write an idea or
|
| 674 |
return;
|
| 675 |
}
|
| 676 |
for (const idea of visibleIdeas(ideas)) {
|
|
@@ -793,7 +793,7 @@ function renderScore(score) {
|
|
| 793 |
function renderWoodMap(map) {
|
| 794 |
woodMapEl.innerHTML = "";
|
| 795 |
if (!map?.dots?.length) {
|
| 796 |
-
woodMapEl.innerHTML = `<div class="wood"><div class="empty wood-empty">
|
| 797 |
return;
|
| 798 |
}
|
| 799 |
const field = document.createElement("div");
|
|
@@ -824,11 +824,11 @@ function renderWoodMap(map) {
|
|
| 824 |
`;
|
| 825 |
const caption = document.createElement("p");
|
| 826 |
caption.className = "wood-cap";
|
| 827 |
-
caption.textContent = map.caption || "Your
|
| 828 |
woodMapEl.append(field, legend, caption);
|
| 829 |
}
|
| 830 |
|
| 831 |
-
function renderProjects(projects, emptyMessage = "No
|
| 832 |
projectsEl.innerHTML = "";
|
| 833 |
if (!projects.length) {
|
| 834 |
projectsEl.innerHTML = `<div class="empty">${escapeHtml(emptyMessage)}</div>`;
|
|
@@ -849,7 +849,7 @@ function renderProjects(projects, emptyMessage = "No red ink yet.") {
|
|
| 849 |
function renderCitations(echoes) {
|
| 850 |
projectsEl.innerHTML = "";
|
| 851 |
if (!echoes.length) {
|
| 852 |
-
projectsEl.innerHTML = `<div class="empty">No
|
| 853 |
return;
|
| 854 |
}
|
| 855 |
for (const echo of echoes.slice(0, 5)) {
|
|
@@ -874,7 +874,7 @@ function renderCitations(echoes) {
|
|
| 874 |
function renderWhitespace(items) {
|
| 875 |
whitespaceEl.innerHTML = "";
|
| 876 |
if (!items.length) {
|
| 877 |
-
whitespaceEl.innerHTML = `<div class="empty">
|
| 878 |
return;
|
| 879 |
}
|
| 880 |
for (const item of items.slice(0, 4)) {
|
|
@@ -895,7 +895,7 @@ function renderWhitespace(items) {
|
|
| 895 |
function renderPlan(steps) {
|
| 896 |
planEl.innerHTML = "";
|
| 897 |
if (!steps.length) {
|
| 898 |
-
planEl.innerHTML = `<li class="empty">
|
| 899 |
return;
|
| 900 |
}
|
| 901 |
for (const step of steps) {
|
|
@@ -922,11 +922,11 @@ function setCommandDisabled(disabled) {
|
|
| 922 |
function startTurnWatchdog() {
|
| 923 |
clearTurnWatchdog();
|
| 924 |
sawTurnToken = false;
|
| 925 |
-
ink.textContent = "
|
| 926 |
ink.classList.add("thinking");
|
| 927 |
turnWatchdog = window.setTimeout(() => {
|
| 928 |
if (sawTurnToken) return;
|
| 929 |
-
ink.textContent = "Still
|
| 930 |
}, 2200);
|
| 931 |
}
|
| 932 |
|
|
@@ -1158,7 +1158,7 @@ function drawWoodMap(ctx, map, x, y, width, height, verdict) {
|
|
| 1158 |
|
| 1159 |
ctx.fillStyle = "#6b4e35";
|
| 1160 |
ctx.font = "800 18px Inter, sans-serif";
|
| 1161 |
-
ctx.fillText("
|
| 1162 |
|
| 1163 |
for (const dot of map.dots) {
|
| 1164 |
const px = x + (width * boundedPercent(dot.x)) / 100;
|
|
|
|
| 99 |
);
|
| 100 |
session.goals = goalOptions.filter((option) => checked.has(option));
|
| 101 |
syncCurrentIdeaGoals();
|
| 102 |
+
invalidateCurrentSeal("Goals updated. Press Ink or Plan to refresh the score.");
|
| 103 |
saveSession();
|
| 104 |
renderGoals(session.goals);
|
| 105 |
renderIdeas(session.ideas || []);
|
|
|
|
| 166 |
completed = true;
|
| 167 |
} catch (error) {
|
| 168 |
clearTurnWatchdog();
|
| 169 |
+
ink.textContent = `The advisor could not answer: ${error.message}`;
|
| 170 |
ink.classList.remove("thinking");
|
| 171 |
ink.classList.add("bleed");
|
| 172 |
} finally {
|
|
|
|
| 336 |
input.disabled = false;
|
| 337 |
setSessionControlsDisabled(false);
|
| 338 |
input.value = "";
|
| 339 |
+
ink.textContent = "The book is open. Describe an idea to start a new page.";
|
| 340 |
ink.classList.remove("thinking", "bleed", "gold");
|
| 341 |
corrections.textContent = "Session reset.";
|
| 342 |
renderGoals(session.goals);
|
|
|
|
| 362 |
setSessionControlsDisabled(true);
|
| 363 |
ink.classList.remove("bleed", "gold");
|
| 364 |
ink.classList.add("thinking");
|
| 365 |
+
ink.textContent = "Loading an example idea board.";
|
| 366 |
corrections.textContent = "";
|
| 367 |
try {
|
| 368 |
const response = await fetch("/api/demo-session");
|
| 369 |
+
if (!response.ok) throw new Error(`example session failed with ${response.status}`);
|
| 370 |
applyDemoSession(await response.json());
|
| 371 |
} catch (error) {
|
| 372 |
+
ink.textContent = `The example session could not be loaded: ${error.message}`;
|
| 373 |
ink.classList.remove("thinking");
|
| 374 |
ink.classList.add("bleed");
|
| 375 |
} finally {
|
|
|
|
| 385 |
session.profile = session.profile || {};
|
| 386 |
session.goals = Array.isArray(session.goals) ? session.goals : [];
|
| 387 |
session.last_response = data.response || session.last_response || "";
|
| 388 |
+
session.ui_status = `Example loaded: ${data.turn_count || 0} advisor turns`;
|
| 389 |
currentArtifact = data.artifact || session.last_artifact || null;
|
| 390 |
+
ink.textContent = data.response || "Example session loaded.";
|
| 391 |
ink.classList.remove("thinking");
|
| 392 |
if (data.score) {
|
| 393 |
setVerdictDisplay(data.score.verdict, data.score.overall, data.score);
|
|
|
|
| 657 |
currentArtifact = null;
|
| 658 |
renderScore(null);
|
| 659 |
setVerdictDisplay("READY", 0, null);
|
| 660 |
+
sealCopyEl.textContent = "Project pages are shown below. Write or select an idea to score it.";
|
| 661 |
ink.classList.remove("bleed", "gold");
|
| 662 |
renderWoodMap(null);
|
| 663 |
}
|
|
|
|
| 670 |
if (ideaCountEl) ideaCountEl.textContent = ideas.length;
|
| 671 |
ideasEl.innerHTML = "";
|
| 672 |
if (!ideas.length) {
|
| 673 |
+
ideasEl.innerHTML = `<div class="empty">Your idea board is empty. Write an idea or choose an under-explored direction.</div>`;
|
| 674 |
return;
|
| 675 |
}
|
| 676 |
for (const idea of visibleIdeas(ideas)) {
|
|
|
|
| 793 |
function renderWoodMap(map) {
|
| 794 |
woodMapEl.innerHTML = "";
|
| 795 |
if (!map?.dots?.length) {
|
| 796 |
+
woodMapEl.innerHTML = `<div class="wood"><div class="empty wood-empty">Score an idea to plot it on the map.</div></div>`;
|
| 797 |
return;
|
| 798 |
}
|
| 799 |
const field = document.createElement("div");
|
|
|
|
| 824 |
`;
|
| 825 |
const caption = document.createElement("p");
|
| 826 |
caption.className = "wood-cap";
|
| 827 |
+
caption.textContent = map.caption || "Your idea is plotted against the current project map.";
|
| 828 |
woodMapEl.append(field, legend, caption);
|
| 829 |
}
|
| 830 |
|
| 831 |
+
function renderProjects(projects, emptyMessage = "No nearby projects yet.") {
|
| 832 |
projectsEl.innerHTML = "";
|
| 833 |
if (!projects.length) {
|
| 834 |
projectsEl.innerHTML = `<div class="empty">${escapeHtml(emptyMessage)}</div>`;
|
|
|
|
| 849 |
function renderCitations(echoes) {
|
| 850 |
projectsEl.innerHTML = "";
|
| 851 |
if (!echoes.length) {
|
| 852 |
+
projectsEl.innerHTML = `<div class="empty">No nearby project echoes yet.</div>`;
|
| 853 |
return;
|
| 854 |
}
|
| 855 |
for (const echo of echoes.slice(0, 5)) {
|
|
|
|
| 874 |
function renderWhitespace(items) {
|
| 875 |
whitespaceEl.innerHTML = "";
|
| 876 |
if (!items.length) {
|
| 877 |
+
whitespaceEl.innerHTML = `<div class="empty">No under-explored directions are loaded yet.</div>`;
|
| 878 |
return;
|
| 879 |
}
|
| 880 |
for (const item of items.slice(0, 4)) {
|
|
|
|
| 895 |
function renderPlan(steps) {
|
| 896 |
planEl.innerHTML = "";
|
| 897 |
if (!steps.length) {
|
| 898 |
+
planEl.innerHTML = `<li class="empty">Press Plan to draft build steps for the selected idea.</li>`;
|
| 899 |
return;
|
| 900 |
}
|
| 901 |
for (const step of steps) {
|
|
|
|
| 922 |
function startTurnWatchdog() {
|
| 923 |
clearTurnWatchdog();
|
| 924 |
sawTurnToken = false;
|
| 925 |
+
ink.textContent = "Checking the current project map.";
|
| 926 |
ink.classList.add("thinking");
|
| 927 |
turnWatchdog = window.setTimeout(() => {
|
| 928 |
if (sawTurnToken) return;
|
| 929 |
+
ink.textContent = "Still comparing against nearby projects.";
|
| 930 |
}, 2200);
|
| 931 |
}
|
| 932 |
|
|
|
|
| 1158 |
|
| 1159 |
ctx.fillStyle = "#6b4e35";
|
| 1160 |
ctx.font = "800 18px Inter, sans-serif";
|
| 1161 |
+
ctx.fillText("IDEA MAP", x, y - 14);
|
| 1162 |
|
| 1163 |
for (const dot of map.dots) {
|
| 1164 |
const px = x + (width * boundedPercent(dot.x)) / 100;
|
static/index.html
CHANGED
|
@@ -77,7 +77,7 @@
|
|
| 77 |
|
| 78 |
<nav class="mobile-nav" aria-label="Sections">
|
| 79 |
<button type="button" class="active" data-tab="page">Page</button>
|
| 80 |
-
<button type="button" data-tab="proof">
|
| 81 |
<button type="button" data-tab="almanac">Board</button>
|
| 82 |
</nav>
|
| 83 |
|
|
@@ -183,7 +183,7 @@
|
|
| 183 |
</span>
|
| 184 |
<p id="ink" class="prophecy">
|
| 185 |
The book is open. Describe a project idea, compare it against the current map, then turn the result into
|
| 186 |
-
|
| 187 |
</p>
|
| 188 |
</article>
|
| 189 |
|
|
@@ -210,17 +210,17 @@
|
|
| 210 |
</section>
|
| 211 |
|
| 212 |
<section class="section">
|
| 213 |
-
<div class="eyebrow">
|
| 214 |
<div id="wood-map" class="wood-map"></div>
|
| 215 |
</section>
|
| 216 |
|
| 217 |
<section class="section">
|
| 218 |
-
<div class="eyebrow">Closest echoes</div>
|
| 219 |
<div id="projects" class="project-list"></div>
|
| 220 |
</section>
|
| 221 |
|
| 222 |
<section class="section">
|
| 223 |
-
<div class="eyebrow">
|
| 224 |
<div id="whitespace" class="whitespace-list"></div>
|
| 225 |
</section>
|
| 226 |
</aside>
|
|
|
|
| 77 |
|
| 78 |
<nav class="mobile-nav" aria-label="Sections">
|
| 79 |
<button type="button" class="active" data-tab="page">Page</button>
|
| 80 |
+
<button type="button" data-tab="proof">Evidence</button>
|
| 81 |
<button type="button" data-tab="almanac">Board</button>
|
| 82 |
</nav>
|
| 83 |
|
|
|
|
| 183 |
</span>
|
| 184 |
<p id="ink" class="prophecy">
|
| 185 |
The book is open. Describe a project idea, compare it against the current map, then turn the result into
|
| 186 |
+
concrete build steps.
|
| 187 |
</p>
|
| 188 |
</article>
|
| 189 |
|
|
|
|
| 210 |
</section>
|
| 211 |
|
| 212 |
<section class="section">
|
| 213 |
+
<div class="eyebrow">Idea map</div>
|
| 214 |
<div id="wood-map" class="wood-map"></div>
|
| 215 |
</section>
|
| 216 |
|
| 217 |
<section class="section">
|
| 218 |
+
<div class="eyebrow">Closest project echoes</div>
|
| 219 |
<div id="projects" class="project-list"></div>
|
| 220 |
</section>
|
| 221 |
|
| 222 |
<section class="section">
|
| 223 |
+
<div class="eyebrow">Under-explored directions</div>
|
| 224 |
<div id="whitespace" class="whitespace-list"></div>
|
| 225 |
</section>
|
| 226 |
</aside>
|
tests/test_frontend_copy.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_main_interface_copy_is_builder_facing() -> None:
|
| 5 |
+
html = Path("static/index.html").read_text(encoding="utf-8")
|
| 6 |
+
app_js = Path("static/app.js").read_text(encoding="utf-8")
|
| 7 |
+
combined = f"{html}\n{app_js}"
|
| 8 |
+
|
| 9 |
+
assert "Under-explored directions" in html
|
| 10 |
+
assert "Closest project echoes" in html
|
| 11 |
+
assert "Press Plan to draft build steps for the selected idea." in app_js
|
| 12 |
+
assert "Loading an example idea board." in app_js
|
| 13 |
+
|
| 14 |
+
stale_jargon = [
|
| 15 |
+
"No wax path pressed.",
|
| 16 |
+
"Gold has not gathered.",
|
| 17 |
+
"No red ink yet.",
|
| 18 |
+
"Demo rehearsal",
|
| 19 |
+
"demo rehearsal",
|
| 20 |
+
"press a new seal",
|
| 21 |
+
"The page is choosing its words.",
|
| 22 |
+
"Still riffling the inked pages.",
|
| 23 |
+
"YOU VS THE WOOD",
|
| 24 |
+
"current Wood",
|
| 25 |
+
]
|
| 26 |
+
for phrase in stale_jargon:
|
| 27 |
+
assert phrase not in combined
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_visible_static_shell_does_not_promote_submission_evidence() -> None:
|
| 31 |
+
html = Path("static/index.html").read_text(encoding="utf-8").lower()
|
| 32 |
+
|
| 33 |
+
promotional_terms = [
|
| 34 |
+
"judge",
|
| 35 |
+
"prize",
|
| 36 |
+
"submission",
|
| 37 |
+
"badge",
|
| 38 |
+
"build-small",
|
| 39 |
+
"hackathon criteria",
|
| 40 |
+
]
|
| 41 |
+
for term in promotional_terms:
|
| 42 |
+
assert term not in html
|