Spaces:
Running on Zero
Running on Zero
feat: select idea board pages
Browse filesCo-authored-by: Codex <noreply@openai.com>
- README.md +1 -0
- static/app.js +61 -11
- static/styles.css +22 -0
README.md
CHANGED
|
@@ -81,6 +81,7 @@ the private Field Notes artifact.
|
|
| 81 |
The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
|
| 82 |
strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
|
| 83 |
Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
|
|
|
|
| 84 |
If the board is empty, `Plan` and `Rank` do not create placeholder pages; they prompt the user to write an idea or press
|
| 85 |
`Gap` first.
|
| 86 |
|
|
|
|
| 81 |
The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
|
| 82 |
strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
|
| 83 |
Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
|
| 84 |
+
Users can also click any Idea Board page to make it current before pressing `Plan`.
|
| 85 |
If the board is empty, `Plan` and `Rank` do not create placeholder pages; they prompt the user to write an idea or press
|
| 86 |
`Gap` first.
|
| 87 |
|
static/app.js
CHANGED
|
@@ -97,6 +97,12 @@ profileEl.addEventListener("input", (event) => {
|
|
| 97 |
saveSession();
|
| 98 |
});
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
async function runTurn(message) {
|
| 101 |
input.value = "";
|
| 102 |
submit.disabled = true;
|
|
@@ -216,18 +222,21 @@ function renderProvenance(data) {
|
|
| 216 |
}
|
| 217 |
|
| 218 |
function renderRestoredSession(data) {
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
| 227 |
} else {
|
| 228 |
renderProjects(data.top_projects || []);
|
| 229 |
}
|
| 230 |
-
exportButton.disabled =
|
| 231 |
} else {
|
| 232 |
renderScore(null);
|
| 233 |
renderWoodMap(null);
|
|
@@ -410,8 +419,12 @@ function renderIdeas(ideas) {
|
|
| 410 |
for (const idea of visibleIdeas(ideas)) {
|
| 411 |
const score = idea.score?.overall ? Number(idea.score.overall).toFixed(1) : "0.0";
|
| 412 |
const targets = (idea.targets || []).slice(0, 3).map(targetDisplayName).join(" · ");
|
| 413 |
-
const
|
| 414 |
-
item
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
item.innerHTML = `
|
| 416 |
<strong>${escapeHtml(idea.title)}</strong>
|
| 417 |
<p>${escapeHtml((idea.pitch || "").slice(0, 120))}</p>
|
|
@@ -429,6 +442,43 @@ function visibleIdeas(ideas) {
|
|
| 429 |
return current ? [current, ...remaining] : ideas.slice(-4).reverse();
|
| 430 |
}
|
| 431 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
function targetDisplayName(target) {
|
| 433 |
return targetProfileById.get(target)?.label || target;
|
| 434 |
}
|
|
|
|
| 97 |
saveSession();
|
| 98 |
});
|
| 99 |
|
| 100 |
+
ideasEl.addEventListener("click", (event) => {
|
| 101 |
+
const card = event.target.closest("[data-idea-id]");
|
| 102 |
+
if (!(card instanceof HTMLElement) || !ideasEl.contains(card)) return;
|
| 103 |
+
selectIdea(card.dataset.ideaId || "");
|
| 104 |
+
});
|
| 105 |
+
|
| 106 |
async function runTurn(message) {
|
| 107 |
input.value = "";
|
| 108 |
submit.disabled = true;
|
|
|
|
| 222 |
}
|
| 223 |
|
| 224 |
function renderRestoredSession(data) {
|
| 225 |
+
const idea = currentIdea();
|
| 226 |
+
const storedArtifact = session.last_artifact || null;
|
| 227 |
+
currentArtifact = !idea || storedArtifact?.title === idea.title ? storedArtifact : null;
|
| 228 |
+
const score = currentArtifact?.seal || idea?.score || null;
|
| 229 |
+
if (score) {
|
| 230 |
+
renderScore(score);
|
| 231 |
+
verdictEl.textContent = currentArtifact?.verdict || score.verdict || "UNWRITTEN";
|
| 232 |
+
overallEl.textContent = Number(currentArtifact?.overall || score.overall || 0).toFixed(1);
|
| 233 |
+
renderWoodMap(currentArtifact?.wood_map || null);
|
| 234 |
+
if (score.echoes?.length) {
|
| 235 |
+
renderCitations(score.echoes);
|
| 236 |
} else {
|
| 237 |
renderProjects(data.top_projects || []);
|
| 238 |
}
|
| 239 |
+
exportButton.disabled = !currentArtifact;
|
| 240 |
} else {
|
| 241 |
renderScore(null);
|
| 242 |
renderWoodMap(null);
|
|
|
|
| 419 |
for (const idea of visibleIdeas(ideas)) {
|
| 420 |
const score = idea.score?.overall ? Number(idea.score.overall).toFixed(1) : "0.0";
|
| 421 |
const targets = (idea.targets || []).slice(0, 3).map(targetDisplayName).join(" · ");
|
| 422 |
+
const selected = idea.id === session.current_idea_id;
|
| 423 |
+
const item = document.createElement("button");
|
| 424 |
+
item.type = "button";
|
| 425 |
+
item.className = `idea ${selected ? "current" : ""}`;
|
| 426 |
+
item.dataset.ideaId = idea.id || "";
|
| 427 |
+
item.setAttribute("aria-pressed", selected ? "true" : "false");
|
| 428 |
item.innerHTML = `
|
| 429 |
<strong>${escapeHtml(idea.title)}</strong>
|
| 430 |
<p>${escapeHtml((idea.pitch || "").slice(0, 120))}</p>
|
|
|
|
| 442 |
return current ? [current, ...remaining] : ideas.slice(-4).reverse();
|
| 443 |
}
|
| 444 |
|
| 445 |
+
function currentIdea() {
|
| 446 |
+
const ideas = Array.isArray(session.ideas) ? session.ideas : [];
|
| 447 |
+
return ideas.find((idea) => idea.id === session.current_idea_id) || ideas[ideas.length - 1] || null;
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
function selectIdea(ideaId) {
|
| 451 |
+
if (!ideaId || !Array.isArray(session.ideas)) return;
|
| 452 |
+
const idea = session.ideas.find((item) => item.id === ideaId);
|
| 453 |
+
if (!idea) return;
|
| 454 |
+
session.current_idea_id = idea.id;
|
| 455 |
+
if (Array.isArray(idea.targets) && idea.targets.length) {
|
| 456 |
+
session.targets = targetOptions.filter((option) => idea.targets.includes(option));
|
| 457 |
+
}
|
| 458 |
+
const score = idea.score || null;
|
| 459 |
+
if (score) {
|
| 460 |
+
verdictEl.textContent = score.verdict || "DRAFT";
|
| 461 |
+
overallEl.textContent = Number(score.overall || 0).toFixed(1);
|
| 462 |
+
renderScore(score);
|
| 463 |
+
ink.classList.toggle("bleed", String(score.verdict || "").startsWith("ECHO"));
|
| 464 |
+
ink.classList.toggle("gold", String(score.verdict || "").startsWith("UNWRITTEN"));
|
| 465 |
+
}
|
| 466 |
+
if (session.last_artifact?.title === idea.title) {
|
| 467 |
+
currentArtifact = session.last_artifact;
|
| 468 |
+
renderWoodMap(currentArtifact.wood_map || null);
|
| 469 |
+
exportButton.disabled = false;
|
| 470 |
+
} else {
|
| 471 |
+
currentArtifact = null;
|
| 472 |
+
renderWoodMap(null);
|
| 473 |
+
exportButton.disabled = true;
|
| 474 |
+
}
|
| 475 |
+
renderTargets(session.targets || []);
|
| 476 |
+
renderIdeas(session.ideas);
|
| 477 |
+
renderPlan([]);
|
| 478 |
+
corrections.textContent = `selected: ${idea.title}`;
|
| 479 |
+
saveSession();
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
function targetDisplayName(target) {
|
| 483 |
return targetProfileById.get(target)?.label || target;
|
| 484 |
}
|
static/styles.css
CHANGED
|
@@ -317,6 +317,28 @@ button:disabled {
|
|
| 317 |
border-radius: 0 8px 8px 0;
|
| 318 |
}
|
| 319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
.project strong,
|
| 321 |
.gap strong,
|
| 322 |
.idea strong {
|
|
|
|
| 317 |
border-radius: 0 8px 8px 0;
|
| 318 |
}
|
| 319 |
|
| 320 |
+
.idea {
|
| 321 |
+
width: 100%;
|
| 322 |
+
border-top: 0;
|
| 323 |
+
border-right: 0;
|
| 324 |
+
border-bottom: 0;
|
| 325 |
+
appearance: none;
|
| 326 |
+
text-align: left;
|
| 327 |
+
cursor: pointer;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.idea:hover,
|
| 331 |
+
.idea:focus-visible,
|
| 332 |
+
.idea.current {
|
| 333 |
+
border-left-color: var(--leaf);
|
| 334 |
+
background: rgba(255, 241, 196, 0.56);
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.idea:focus-visible {
|
| 338 |
+
outline: 2px solid rgba(47, 122, 73, 0.5);
|
| 339 |
+
outline-offset: 2px;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
.project strong,
|
| 343 |
.gap strong,
|
| 344 |
.idea strong {
|