⋯ 300 unchanged lines
}
.visual-stage { display: grid;
gap: 12px;
min-width: 0;
}
.visual-split { display: grid;
grid-template-columns: minmax(130px, 0.8fr) minmax(0, 1.35fr);
grid-template-columns: minmax(0, 1fr);
gap: 10px;
align-items: stretch;
}
⋯ 103 unchanged lines
.segment.write { background: var(--write-bg);
color: #6f4d06;
box-shadow: inset 0 0 0 2px rgb(217 154 33 / 38%);
}
.segment.read { background: var(--read-bg);
color: #165d43;
box-shadow: inset 0 0 0 2px rgb(39 132 95 / 34%);
}
.segment.miss {⋯ 29 unchanged lines
.hash-row { display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
margin: 12px 0 8px;
margin: 0;
}
.hash-row:empty { display: none;
}
.hash-pill { padding: 6px 8px;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 26px;
padding: 4px 9px;
border-radius: 999px;
background: #f0eee7;
color: #2d2b27;
font-size: 0.68rem;
font-weight: 700;
line-height: 1.1;
white-space: nowrap;
}
.hash-pill.write { background: var(--write-bg);
color: #6f4d06;
box-shadow: inset 0 0 0 1px rgb(217 154 33 / 30%);
}
.hash-pill.read { background: var(--read-bg);
color: #165d43;
box-shadow: inset 0 0 0 1px rgb(39 132 95 / 28%);
}
.hash-pill.miss { background: var(--miss-bg);
color: #793125;
}
.visual-note { min-height: 36px;
margin: 0 0 12px;
margin: 0;
color: #3f3b34;
font-size: 0.78rem;
}
.controls { display: flex;
flex-wrap: wrap;
gap: 8px;
padding-top: 11px;
padding-top: 12px;
border-top: 1px solid var(--soft-line);
}
⋯ 32 unchanged lines
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px 8px;
margin-top: 11px;
margin-top: 0;
color: var(--muted);
font-size: 0.66rem;
}
⋯ 90 unchanged lines
}
.visual-column { position: static;
position: sticky;
top: 0;
z-index: 20;
order: -1;
margin: 0 0 22px;
}
.visual-panel { position: sticky;
top: 0;
z-index: 10;
max-height: 34vh;
overflow-y: auto;
position: static;
border-radius: 0 0 var(--radius) var(--radius);
margin: 0 -20px 22px;
margin: 0 -20px;
border-left: 0;
border-right: 0;
}
.visual-split { grid-template-columns: minmax(120px, 0.9fr) minmax(0, 1.4fr);
grid-template-columns: minmax(0, 1fr);
}
.visual-top {⋯ 36 unchanged lines
}
.visual-split { grid-template-columns: minmax(82px, 1fr) minmax(0, 2fr);
grid-template-columns: minmax(0, 1fr);
gap: 8px;
}
⋯ 7 unchanged lines
}
.chat-box { max-height: 96px;
overflow-y: auto;
max-height: none;
overflow: visible;
}
.bubble {⋯ 347 unchanged lines
subtitle: "No cache yet. Just a request with a beginning.",
note: "Prompt caching becomes easier once you look at the request as a sequence.",
hash: [{ text: "prefix: none", tone: "" }], chat: [
["user", "Help me plan a launch."],
["assistant", "Sure. What are you launching?"],
],
chat: [["user", "Help me plan a launch."]],
lanes: [
{ label: "Request",
⋯ 59 unchanged lines
subtitle: "Tools come before the system prompt in the prefix.",
note: "Same system prompt, different request shape, different cache.",
hash: [
{ text: "before: hash A", tone: "read" }, { text: "after: hash B", tone: "miss" }, { text: "existing shape: cache A", tone: "read" }, { text: "changed shape: cache B", tone: "miss" }, ],
chat: [
["user", "Use the docs if you need them."],
["assistant", "I can search the docs first."],
],
lanes: [
{ label: "Before tool",
label: "Existing shape",
marker: 55,
segments: [
["system read", "system prompt", 55, "system"],
["message", "messages", 45, "message"],
],
},
{ label: "After tool",
label: "Changed shape",
marker: 68,
segments: [
["tool", "tool", 20, "tool"],
⋯ 9 unchanged lines
subtitle: "Schemas can create a separate cache lane.",
note: "Normal chat and JSON-shaped calls may both be correct and still use different caches.",
hash: [
{ text: "chat: cache A", tone: "read" }, { text: "json: cache C", tone: "write" }, { text: "existing shape: cache A", tone: "read" }, { text: "changed shape: cache C", tone: "write" }, ],
chat: [["user", "Return the plan as JSON this time."]],
lanes: [
{ label: "Normal chat lane",
label: "Existing shape",
marker: 54,
segments: [
["system read", "system prompt", 54, "system"],
["message", "messages", 46, "message"],
],
},
{ label: "Structured lane",
label: "Changed shape",
marker: 76,
segments: [
["schema", "schema", 24, "schema"],
⋯ 16 unchanged lines
lanes: [
{ label: "Large app prompt",
marker: 86,
marker: 76,
segments: [
["system read", "huge system prompt", 86, "system"],
["message", "messages", 14, "message"],
["system read", "huge system prompt", 76, "system"],
["message", "user", 10, "message"],
["message", "assistant", 14, "message"],
],
},
],
⋯ 20 unchanged lines
title: "Marker placement",
subtitle: "Cache stable content. Stop before changing content.",
note: "Toggle the marker. The good version stops before the changing task.",
hash: [
{ text: "good: read X", tone: "read" }, { text: "bad: write Y/Z", tone: "write" }, ],
hash: [],
chat: [
["user", "Use the conversation so far."],
["assistant", "Then run a fresh task instruction."],
⋯ 312 unchanged lines
});
if (scene.markerToggle) { const result = document.createElement("span"); result.className = `hash-pill ${goodMarker ? "read" : "write"}`; result.textContent = goodMarker ? "good: read X" : "bad: write Y/Z";
hashRow.appendChild(result);
const state = document.createElement("span"); state.className = `hash-pill ${goodMarker ? "read" : "write"}`; state.textContent = goodMarker ? "stable prefix" : "moving prefix";
⋯ 61 unchanged lines
}
window.addEventListener("scroll", queueSceneSync, { passive: true }); window.addEventListener("resize", queueSceneSync); window.addEventListener("resize", () => { queueSceneSync();
});
render(activeSceneId);
syncSceneToHeading();
</script>
</body>
</html>