Time-Travel Debugger Demo
A minimal one-step playground for exercising the time-travel Python debugger. The bundled program calls a chain of functions, mutates a list, and computes a small statistic — enough surface to set breakpoints, step into/over/out, watch expressions, hover over identifiers, and scrub backward through history. See plan: .claude/plans/what-would-be-options-temporal-ritchie.md.
Debug a stats pipeline
Try the time-travel debugger
The program below builds a list of student records, scores them, and computes a simple grade distribution. It calls eight different functions, mutates state across them, and prints a summary at the end.
It works correctly — there’s no bug to find. The point is to explore the debugger.
Things to try
- A breakpoint is already set in the gutter on the score line. Click in the gutter to add, remove, or move breakpoints.
- Click Debug (next to Run). Execution pauses at your breakpoint —
the Variables tab shows the current
recordand locals. - Use Step Into (F11) to dive into
compute_score. Watch the Call Stack tab grow. Click frames to inspect each. - Use Step Over (F10) a few times, then hit Step Back (Shift+F10) to rewind. Notice that the debugger is no longer in the live execution state; sighted users also see the gutter marker change appearance.
- Open the Watch tab and add
len(records)andsum(scores) / max(len(scores), 1). - Hover over any identifier in the editor while paused — the value appears in a tooltip.
- Add a conditional breakpoint on the loop body: right-click the
breakpoint marker in the gutter and enter
record["grade"] == "A". - Drag the History scrubber to navigate the entire execution timeline.
Parameters experiment
The Output panel’s args: input is enabled for this demo. Try 3 5:
the first parameter limits the roster to three students, and the second
adds a five-point curve before grades are assigned. Run and Debug should
receive the same sys.argv.
Aliasing experiment
The function register_student deliberately appends to a shared default
list — a classic Python pitfall. After running, expand defaults and
students in the Variables tab — they share the same oid, and you’ll
see a ↔ alias badge.
# Student grade pipeline — exercises the debugger across many calls.
import sys
def make_record(name, raw_scores):
return {"name": name, "raw_scores": raw_scores, "grade": None}
def parse_parameters(argv):
limit = None
curve = 0.0
if len(argv) > 1 and argv[1] != "all":
limit = int(argv[1])
if len(argv) > 2:
curve = float(argv[2])
return limit, curve
def compute_score(record):
total = sum(record["raw_scores"])
return total / len(record["raw_scores"])
def assign_grade(score):
if score >= 90: return "A"
if score >= 80: return "B"
if score >= 70: return "C"
if score >= 60: return "D"
return "F"
def grade_distribution(records):
dist = {"A": 0, "B": 0, "C": 0, "D": 0, "F": 0}
for r in records:
dist[r["grade"]] += 1
return dist
def print_distribution(dist):
total = sum(dist.values())
for g in ["A", "B", "C", "D", "F"]:
bar = "#" * dist[g]
pct = 100 * dist[g] / max(total, 1)
print(f" {g}: {bar:<10} ({dist[g]}, {pct:.1f}%)")
def average(scores):
return sum(scores) / len(scores) if scores else 0.0
# Classic aliasing pitfall: the default list is shared across calls
# because it's evaluated once at function-definition time.
def register_student(name, raw_scores, students=[]):
record = make_record(name, raw_scores)
students.append(record)
return students
def main():
limit, curve = parse_parameters(sys.argv)
roster = [
("Ada", [95, 88, 92]),
("Linus", [72, 81, 78]),
("Grace", [98, 95, 91]),
("Alan", [60, 55, 70]),
("Margaret",[85, 89, 87]),
]
if limit is not None:
roster = roster[:limit]
records = []
scores = []
defaults = [] # what register_student returns first time
for name, raw in roster:
defaults = register_student(name, raw)
record = defaults[-1]
score = min(compute_score(record) + curve, 100)
record["grade"] = assign_grade(score)
scores.append(score)
records.append(record)
print(f"Parameters: limit={limit}, curve={curve}")
print(f"Average: {average(scores):.2f}")
print("Grade distribution:")
print_distribution(grade_distribution(records))
return records
main()