Created
August 5, 2026 06:20
-
-
Save arajkumar/04f5b06a2ac3cc8f736e9ab01235685b to your computer and use it in GitHub Desktop.
pi #7609 — interactive fix walkthrough: resume a failed turn from /tree
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>pi #7609 — the fix, step by step</title> | |
| <style> | |
| :root { | |
| --bg: #0d1117; --panel: #161b22; --border: #30363d; --text: #e6edf3; | |
| --dim: #8b949e; --blue: #58a6ff; --red: #f85149; --green: #3fb950; | |
| --yellow: #d29922; --purple: #bc8cff; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; | |
| } | |
| * { box-sizing: border-box; } | |
| body { background: var(--bg); color: var(--text); font-family: -apple-system, "Segoe UI", Roboto, sans-serif; | |
| margin: 0; line-height: 1.55; } | |
| .wrap { max-width: 980px; margin: 0 auto; padding: 32px 24px 96px; } | |
| h1 { font-size: 26px; margin: 0 0 4px; } | |
| h2 { font-size: 20px; margin: 48px 0 12px; padding-top: 16px; border-top: 1px solid var(--border); } | |
| h3 { font-size: 16px; margin: 24px 0 8px; color: var(--blue); } | |
| a { color: var(--blue); } | |
| code { font-family: var(--mono); font-size: 0.88em; background: #21262d; padding: 2px 5px; border-radius: 4px; } | |
| pre { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; | |
| overflow-x: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.5; } | |
| pre code { background: none; padding: 0; } | |
| .meta { color: var(--dim); font-size: 14px; margin-bottom: 8px; } | |
| .pill { display: inline-block; font-family: var(--mono); font-size: 11.5px; padding: 2px 8px; border-radius: 10px; | |
| border: 1px solid var(--border); background: var(--panel); color: var(--dim); margin-right: 6px; } | |
| .file { color: var(--purple); font-family: var(--mono); font-size: 12.5px; } | |
| /* --- interactive stepper --- */ | |
| .stage { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 16px 0; } | |
| @media (max-width: 800px) { .stage { grid-template-columns: 1fr; } } | |
| .pane { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; min-height: 220px; } | |
| .pane h4 { margin: 0 0 10px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); font-weight: 600; } | |
| .tnode { font-family: var(--mono); font-size: 12.5px; border: 1px solid var(--border); border-radius: 6px; | |
| padding: 5px 10px; margin: 4px 0; position: relative; background: #1c2128; transition: all .35s; width: fit-content; max-width: 100%; } | |
| .tnode .role { color: var(--dim); margin-right: 6px; } | |
| .tnode.user { border-color: var(--blue); } | |
| .tnode.error { border-color: var(--red); color: #ffa198; } | |
| .tnode.ok { border-color: var(--green); } | |
| .tnode.abandoned { opacity: .45; border-style: dashed; } | |
| .tnode.target { box-shadow: 0 0 0 2px var(--yellow); } | |
| .tnode .leaf { position: absolute; right: -8px; top: 50%; transform: translate(100%, -50%); | |
| font-size: 10.5px; background: var(--yellow); color: #111; border-radius: 4px; padding: 1px 6px; font-weight: 700; } | |
| .tkids { margin-left: 22px; border-left: 1px solid var(--border); padding-left: 14px; } | |
| .chat { font-family: var(--mono); font-size: 12.5px; } | |
| .chat .u { color: var(--blue); margin: 6px 0; } | |
| .chat .e { color: var(--red); margin: 6px 0; transition: opacity .35s; } | |
| .chat .a { color: var(--green); margin: 6px 0; } | |
| .chat .ghost { opacity: 0; } | |
| .chat .cursor { color: var(--dim); } | |
| .stepbar { display: flex; align-items: center; gap: 12px; margin: 14px 0 6px; position: sticky; top: 0; | |
| background: var(--bg); padding: 10px 0; z-index: 5; } | |
| .stepbar button { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 6px; | |
| padding: 6px 16px; font-size: 14px; cursor: pointer; } | |
| .stepbar button:hover:not(:disabled) { border-color: var(--blue); } | |
| .stepbar button:disabled { opacity: .35; cursor: default; } | |
| .stepbar .dots { display: flex; gap: 6px; } | |
| .stepbar .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--border); cursor: pointer; } | |
| .stepbar .dot.on { background: var(--yellow); } | |
| .stepdesc { background: #1c2128; border-left: 3px solid var(--yellow); border-radius: 0 6px 6px 0; | |
| padding: 10px 14px; font-size: 14px; min-height: 66px; } | |
| .stepdesc b { color: var(--yellow); } | |
| table { border-collapse: collapse; width: 100%; font-size: 13.5px; margin: 10px 0; } | |
| th, td { border: 1px solid var(--border); padding: 7px 10px; text-align: left; vertical-align: top; } | |
| th { background: var(--panel); } | |
| .anno { color: var(--dim); font-size: 13px; margin: 4px 0 0; } | |
| .hl { background: rgba(210,153,34,.18); display: block; margin: 0 -16px; padding: 0 16px; } | |
| .k { color: #ff7b72; } .s { color: #a5d6ff; } .c { color: var(--dim); } .f { color: #d2a8ff; } | |
| ul { padding-left: 20px; } li { margin: 4px 0; } | |
| body { padding: 24px; } | |
| .wrap { max-width: 980px; margin: 0 auto; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <h2 style="border:none;margin-top:0;padding-top:0">Resume a failed turn from <code>/tree</code> — step by step</h2> | |
| <p style="color:var(--dim);font-size:14px">Interactive walkthrough for <a href="https://github.com/earendil-works/pi/issues/7609">earendil-works/pi#7609</a>. Use ← → keys or the buttons.</p> | |
| <div class="stepbar"> | |
| <button id="prev">← Prev</button> | |
| <div class="dots" id="dots"></div> | |
| <button id="next">Next →</button> | |
| </div> | |
| <div class="stepdesc" id="stepdesc"></div> | |
| <div class="stage"> | |
| <div class="pane"><h4>Session tree</h4><div id="tree"></div></div> | |
| <div class="pane"><h4>Chat (what you see)</h4><div class="chat" id="chat"></div></div> | |
| </div> | |
| </div> | |
| <script> | |
| const U1 = '<div class="tnode user"><span class="role">user</span>"Nice, what is the time?"{LEAF}</div>'; | |
| const E1 = '<div class="tnode error{AB}"><span class="role">assistant</span>✗ Connection error{LEAF}</div>'; | |
| const E2 = '<div class="tnode error{AB}"><span class="role">assistant</span>✗ Connection error{LEAF}</div>'; | |
| const A1 = '<div class="tnode ok"><span class="role">assistant</span>✓ "I can get the current time by running date…"{LEAF}</div>'; | |
| const LEAF = '<span class="leaf">LEAF</span>'; | |
| const fill = (t, o) => t.replace('{LEAF}', o.leaf ? LEAF : '').replace('{AB}', o.ab ? ' abandoned' : '') + (o.kids || ''); | |
| const wrap = k => '<div class="tkids">' + k + '</div>'; | |
| const steps = [ | |
| { | |
| d: '<b>1 · Failure.</b> You ask the time; the network dies. Pi auto-retries in-session (attempt 1 → <i>error₁</i>, attempt 2 → <i>error₂</i>), each persisted as a child of the previous one. Retries exhausted → <code>Retry failed after 3 attempts</code>. Leaf = error₂.', | |
| tree: () => fill(U1, {kids: wrap(fill(E1, {kids: wrap(fill(E2, {leaf:true}))}) )}), | |
| chat: '<div class="u">› Nice, what is the time?</div><div class="e">✗ Error: Retry failed after 3 attempts: Connection error.</div>' | |
| }, | |
| { | |
| d: '<b>2 · You select error₂ in /tree</b> (double-Esc, Enter). <b>Before the fix:</b> leaf already there → <i>"Already at this point"</i>, or a dead-end error node with an empty editor. <b>After:</b> <code>beginFailedTurnRetry(error₂.id)</code> runs.', | |
| tree: () => fill(U1, {kids: wrap(fill(E1, {kids: wrap(fill(E2, {leaf:true}))}) )}), | |
| chat: '<div class="u">› Nice, what is the time?</div><div class="e">✗ Error: Retry failed after 3 attempts: Connection error.</div>' | |
| }, | |
| { | |
| d: '<b>3 · Walk past the chain.</b> error₂\'s parent is error₁ — also a failed attempt, keep walking. error₁\'s parent is the user message — the last good message. <b>Target = user.</b>', | |
| tree: () => fill(U1, {kids: wrap(fill(E1, {kids: wrap(fill(E2, {leaf:true}))}) )}).replace('tnode user', 'tnode user target'), | |
| chat: '<div class="u">› Nice, what is the time?</div><div class="e">✗ Error: Retry failed after 3 attempts: Connection error.</div>' | |
| }, | |
| { | |
| d: '<b>4 · <code>branch(user)</code> + rebuild.</b> The leaf jumps to your prompt; error₁/error₂ become an <b>abandoned branch</b> — off the leaf path, so excluded from model context. The UI now clears and re-renders the chat…', | |
| tree: () => fill(U1, {leaf:true, kids: wrap(fill(E1, {ab:true, kids: wrap(fill(E2, {ab:true}))}) )}), | |
| chat: '<div class="u">› Nice, what is the time?</div><div class="e ghost">✗ Error: Retry failed after 3 attempts: Connection error.</div>' | |
| }, | |
| { | |
| d: '<b>5 · …and the error is gone.</b> Nothing on the active branch renders it. Then <code>continueTurn()</code> calls <code>agent.continue()</code> — legal, since the leaf is a user message — with the normal post-run loop (retry, compaction).', | |
| tree: () => fill(U1, {leaf:true, kids: wrap(fill(E1, {ab:true, kids: wrap(fill(E2, {ab:true}))}) )}), | |
| chat: '<div class="u">› Nice, what is the time?</div><div class="cursor">▍ retrying…</div>' | |
| }, | |
| { | |
| d: '<b>6 · Done.</b> The retried response appends as a child of your prompt — a <b>sibling</b> of the abandoned errors, which stay visible in /tree as history. Final shape: <code>user → [error₁ → error₂ (abandoned), ✓ answer]</code>.', | |
| tree: () => fill(U1, {kids: wrap(fill(E1, {ab:true, kids: wrap(fill(E2, {ab:true}))}) + fill(A1, {leaf:true}))}), | |
| chat: '<div class="u">› Nice, what is the time?</div><div class="a">✓ I can get the current time by running the date command…</div>' | |
| } | |
| ]; | |
| let cur = 0; | |
| const dots = document.getElementById('dots'); | |
| steps.forEach((_, i) => { | |
| const d = document.createElement('div'); | |
| d.className = 'dot'; d.onclick = () => go(i); | |
| dots.appendChild(d); | |
| }); | |
| function go(i) { | |
| cur = Math.max(0, Math.min(steps.length - 1, i)); | |
| document.getElementById('stepdesc').innerHTML = steps[cur].d; | |
| document.getElementById('tree').innerHTML = steps[cur].tree(); | |
| document.getElementById('chat').innerHTML = steps[cur].chat; | |
| [...dots.children].forEach((d, j) => d.className = 'dot' + (j === cur ? ' on' : '')); | |
| document.getElementById('prev').disabled = cur === 0; | |
| document.getElementById('next').disabled = cur === steps.length - 1; | |
| } | |
| document.getElementById('prev').onclick = () => go(cur - 1); | |
| document.getElementById('next').onclick = () => go(cur + 1); | |
| document.addEventListener('keydown', e => { | |
| if (e.key === 'ArrowRight') go(cur + 1); | |
| if (e.key === 'ArrowLeft') go(cur - 1); | |
| }); | |
| go(0); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment