Definition
Unfinished tasks linger in memory more vividly than completed ones — the brain holds open loops and pushes the user to close them. Designs that make the next step visible recruit this effect; designs that hide what comes next lose it.
Why it matters for ShurIQ reports
Multi-viewport dashboards and progressive intelligence briefs both benefit from visible open loops: a partial gap card that reveals more on click, a "5 of 7 viewports viewed" indicator, a teased next section at the foot of the current one. The reader's brain holds the unfinished read open until the recommendation closes it.
Takeaways
- Surface clear signifiers of what is still unread or unexplored; never bury the next step.
- Use partial reveals — a clipped chart edge, a "more" indicator — to invite continuation without demanding it.
- Pair Zeigarnik with goal-gradient: visible incompleteness near the goal accelerates completion most.
Visual motion language
Incomplete sections show a subtle marching-ants edge or a pulsing indicator; on completion, the marker resolves to a steady fade-in confirming closure.
Origins
Bluma Wulfovna Zeigarnik, 1920s — Lithuanian-Soviet psychologist's foundational memory study.
Cavalry scene
The script below builds this concept's motion in Cavalry through the Stallion bridge. Pipe to cavalry_run_script via MCP, or paste into Cavalry's JavaScript Editor.
// Laws of UX · zeigarnik-effect · Cavalry scene
// CORRECTED 2026-04-30 · verified from production page
// Motion family: opacity-stepped horizontal bands
// 7 stripes; stripes 1-6 muted; stripe 7 (bottom) cream/full-opacity AND truncated to 52% width
// (incompleteness encoded as horizontal width-truncation, NOT a 90° arc gap)
// Palette: dark navy + cream/purples
// To run: pipe to cavalry_run_script tool, or paste into Cavalry's JavaScript Editor
// Built 2026-04-30 by ShurAI
(function () {
var PREFIX = "claude_lawofux_zeigarnik-effect_";
var existing = api.getAllSceneLayers();
for (var i = 0; i < existing.length; i++) {
try {
var nm = api.getNiceName(existing[i]);
if (nm && nm.indexOf("claude_lawofux_") === 0) api.deleteLayer(existing[i]);
} catch (e) {}
}
var BG = "#1F2C3A";
var CREAM = "#D6CDB0";
var DARK = "#000000";
var bg = api.primitive("rectangle", PREFIX + "bg");
api.set(bg, { "generator.dimensions": [1080, 1080] });
api.setFill(bg, true);
api.set(bg, { "material.materialColor": BG });
// 7 stripes total. Geometry mirrors verified 566x68 stripe + 15px gutter -> scaled to canvas:
// full width = 880, stripe height = 100, gutter = 22 (step=122)
var fullW = 880;
var stripeH = 100;
var step = 122; // 100 + 22
var topY = (6 * step) / 2; // top stripe center
// Stripes 1-6: muted, full width
for (var j = 0; j < 6; j++) {
var s = api.primitive("rectangle", PREFIX + "stripe_" + j);
api.set(s, {
"generator.dimensions": [fullW, stripeH],
"position.x": 0,
"position.y": topY - j * step,
"opacity": 0
});
api.setFill(s, true);
api.set(s, { "material.materialColor": DARK });
var t0 = j * 4;
var t1 = t0 + 8;
api.keyframe(s, t0, { "opacity": 0 });
api.keyframe(s, t1, { "opacity": 22 }); // ~0.22 alpha-on-bg reads as muted
api.magicEasing(s, "opacity", t1, "EaseOut", "");
}
// Stripe 7: cream, full opacity, truncated to ~52% width and left-aligned
var truncW = Math.round(fullW * 0.52); // 458
var leftEdge = -fullW / 2; // -440
var s7 = api.primitive("rectangle", PREFIX + "stripe_6");
api.set(s7, {
"generator.dimensions": [truncW, stripeH],
"position.x": leftEdge + truncW / 2, // left-aligned
"position.y": topY - 6 * step,
"opacity": 0
});
api.setFill(s7, true);
api.set(s7, { "material.materialColor": CREAM });
api.keyframe(s7, 24, { "opacity": 0 });
api.keyframe(s7, 32, { "opacity": 100 });
api.magicEasing(s7, "opacity", 32, "EaseOut", "");
var layerCount = api.getAllSceneLayers().length;
console.log("scene built: zeigarnik-effect (" + layerCount + " layers)");
})();