@dodona/trace-component
v1.6.4
Published
- [Trace component](#trace-component) - [Usage](#usage) - [How it works](#how-it-works) - [Translations](#translations) - [CSS variables](#css-variables) - [Contributing](#contributing) - [Setup](#setup) - [Build](#build) - [Testing](#testin
Keywords
Readme
Table of contents
Trace component
Trace component is a modern webcomponent that visualizes the trace of a program. It is a reimplementation of pythontutor. All credits for the original idea go to Philip Guo.

Usage
To use the webcomponent, import the js and simply use the component in your html:
<tc-trace .trace=${trace} .translations="translations"></tc-trace>This trace is preferably generated by our json tracer. But you can write your own trace generator as long as it follows the format specified in our types. This component should also be compatible with the pythontutor trace format for now, but this might change in the future.
translations is an optional object that translates the information card shown at the first frame, plus the accessible names that screen readers announce. It should be of type TraceTranslations as defined in TraceComponent.ts. See Translations for the full list of keys.
You can listen to frame-change events to get the current frame of the trace for example to indicate the current line of code.
You can also add your trace frame by frame using the addFrame method. This method takes a Frame as defined in types.
const traceComponent = document.querySelector('tc-trace');
traceComponent.addFrame({...});A frame that you add or pass in the trace may share the entries of globals and heap that did not change with the frame before it, the way a consumer that receives the trace as deltas builds it. The component never uses the identity of an entry to detect a new step. An entry whose content changed has to be a new object: the component renders an object again when its identity changes, so an entry that is changed in place keeps its old rendering.
How it works
A trace is a list of frames, one per execution step. A frame holds the variables in scope and the heap they point into, and every value in it is either a primitive or an array whose first entry names its kind. trace_types.ts describes the whole format.
<tc-trace> turns each frame into a HeapLayout, which decides where every object of that step goes. It then renders the frame the reader picks: the first step shows the help card, a step that raises an uncaught exception shows a message, and every other step renders as a <tc-frame>.
Elements
| Element | Renders |
| --- | --- |
| <tc-trace> | the whole trace, under the step picker |
| <tc-frame-picker> | the slider and the buttons that pick a step |
| <tc-help-card> | the card that the first step shows |
| <tc-frame> | one step: its stack, its heap, and the arrows between them |
| <tc-stack>, <tc-stack-element> | the call stack, and one box on it |
| <tc-heap>, <tc-heap-element> | the objects of a step, and one of them |
| <tc-sub-element>, <tc-primitive>, <tc-reference> | a value, as text or as the start of an arrow |
| <tc-list>, <tc-tuple>, <tc-set>, <tc-dict>, <tc-grid> | a container, one element per type |
| <tc-instance>, <tc-class>, <tc-function> | an instance, a class, and a function |
| <tc-heap-primitive>, <tc-imported-faux-primitive>, <tc-other> | the remaining kinds of object |
| <tc-attribute-map> | the rows that pair a name with a value |
| <tc-exception-card> | an exception, at the top of a step or on its own |
Layout
HeapLayout places the objects of a step in rows:
- A chain of objects that link to one another shares a row, so that a linked list reads from left to right. Two objects belong to the same chain when they have the same shape and one references the other.
- A container of plain values that one value references renders inside that value, rather than in a box of its own. An object that more than one value references always keeps its box, so that the reader can see that the two share it.
- A sequence of at least two references, where each one leads to a sequence of plain values that nothing else references, renders as a grid: one row per inner sequence, with the outer indexes down the left and the inner ones along the top. A row that the object holds twice, as in
[[0] * 3] * 3, keeps the plain rendering, which shows that the rows are one and the same. - A method, a property and a few related types always render inside the object that holds them.
Every layout starts from the layout of the step before it, so an object stays in place for as long as the program can reach it, and a step only costs the work its own objects need.
Arrows
A value that points into the heap renders as a <tc-reference>, which announces its target to the heap and to the frame around it. ConnectionLayer then draws a curve from the reference to the object, in one SVG overlay that covers the step.
An arrow needs the position of elements that sit several shadow roots deep, which Lit reports per element only. DeepAwaitMixin closes that gap: an element that carries it waits for everything below it, and sends a deep-update event once it has rendered. <tc-frame> holds the arrows still while the elements move, and draws them once the whole subtree has settled. <tc-heap> uses the same moment to give an object a left margin, which keeps every arrow pointing to the right, away from the object it starts at.
Translations
The component ships English defaults only. Consumers pass a translations object to tc-trace; any key left out falls back to the English default, so partial objects are fine. Placeholders use the %{name} syntax and are substituted by fillTemplate.
Most keys are accessible names that are never shown on screen but are read aloud by screen readers, so they are easy to miss when translating.
| Key | Default | Defined in |
| --- | --- | --- |
| title | Drag the slider to walk through your code. | HelpCard.ts |
| text_1 | This window shows how your program works step by step. … | HelpCard.ts |
| text_2 | You can also use the %{previous} and %{next} buttons … | HelpCard.ts |
| picker_label | Execution steps | FramePicker.ts |
| slider_label | Execution step | FramePicker.ts |
| step_of | Step %{step} of %{total} | FramePicker.ts |
| first_step | First step | FramePicker.ts |
| previous_step | Previous step | FramePicker.ts |
| next_step | Next step | FramePicker.ts |
| last_step | Last step | FramePicker.ts |
| current_step | Current step | FramePicker.ts |
| call_stack | Call stack | FrameComponent.ts |
| heap_objects | Heap objects | FrameComponent.ts |
| exception_title | The debugger crashed | FrameComponent.ts |
| reference_to | reference to %{type} #%{id} | FrameComponent.ts |
| trace_label | Execution trace | TraceComponent.ts |
Python type names used as captions (list, dict, set, …) are deliberately left untranslated.
The fold/expand button in FoldableComponent.ts has no key on purpose: its accessible name is its visible caption (dict (512)), and aria-expanded carries the collapsed/expanded state, which screen readers announce in their own language. Prefer this over adding a translatable aria-label whenever a control's visible text is already a good name.
[!IMPORTANT] Adding a key here is only half the job. The keys are not translated in this repository — consumers own the translations. When you add one, it also has to be added to Papyros (both
enandnl, underPapyros.debuggerinsrc/frontend/state/Translations.ts), which is where the Dutch strings actually live, and from there it propagates to Dodona. A key that is added here but not in Papyros silently falls back to English.
CSS variables
To adjust the look of the trace, you can adjust the css variables defined in the styles file. Each colour carries the tone it comes from in the Material palette, where 0 is black and 100 is white.
Contributing
Setup
Install dependencies:
yarn installBuild
To build the JavaScript version of your component:
yarn buildTo watch files and rebuild when the files are modified, run the following command in a separate shell:
yarn build:watchTesting
yarn testThis builds the project and runs every test/*_test.js file in headless Chromium, against dist/ rather than src/, so the tests exercise the same entrypoint consumers import.
test/smoke_test.js renders every trace fixture in traces/ and fails on any rendering error. Any .json trace dropped into traces/ is picked up automatically, without touching the test. Other files in test/ cover specific behaviour and are named after the area they exercise.
Dev Server
This project uses modern-web.dev's @web/dev-server for previewing the project without additional build steps. Web Dev Server handles resolving Node-style "bare" import specifiers, which aren't supported in browsers. It also automatically transpiles JavaScript and adds polyfills to support older browsers. See modern-web.dev's Web Dev Server documentation for more information.
To run the dev server and open the project in a new browser tab:
yarn serveThere is a development HTML file located at /dev/index.html that you can view at http://localhost:8000/dev/index.html. Note that this command will serve your code using Lit's development mode (with more verbose errors).
Releasing
npm version 1.5.0
git push --follow-tagsnpm version bumps package.json, commits, and creates the matching v1.5.0 tag. Pushing the tag triggers publish.yml, which runs the linters, verifies that the tag matches package.json, publishes @dodona/trace-component to npm, and creates a GitHub release with generated notes. Pre-release versions (e.g. 1.5.0-beta.1) must be valid semver and are published under the next dist-tag instead of latest.
