Hazel reports · ^report(19) · 2026-10-19
The Hazel UI draws syntax as tiles on 22 of 39 surfaces, but only 6 take a caret
Cyrus asked for two things on 2026-10-16: livelits' model views as read-only tiles, with the selection and right-click menu tiles have in the editor, and the explainer's examples as read-only tiles too. Both asks assume some views print syntax as text. Matthew asked for the whole picture: every place the Hazel UI shows a term, pattern, type or value, and how it draws it. For the meeting with Cyrus on 2026-10-21 (^ticket(389); this report is ^ticket(393)).
Short answers. As of 2026-10-19, on #2635 (livelits × modular editors) at b44bb9c50f; 16 of the 39 surfaces were checked in the running app and the rest traced in the code.
- Most surfaces that show syntax draw it as tiles: 22 of 39. That counts every place the UI shows a term, pattern, type or value in normal use. Two more views are debug-only and are left out.
- Only 6 of them take a caret. Three are editors: the editor itself, a livelit's splices and the proof-step editors. Three are read-only: a livelit's model revealed with its eye, the stepper's steps, and dev's evaluation result. Only the revealed model has the tiles' whole selection UX (caret, Shift+Arrow selection, the inspector, and Hazel's right-click menu). The stepper and the result take a caret but open no menu.
- 16 surfaces draw tiles you can only look at: the inspector's bar and full view, Γ, the explainer's form and examples, probe values, the program's value, refractors and the Projectors card's splices, among others. They use the editor's tile renderer (
Code.view) but none of its input handling, so none of them take a caret, a selection or a right-click. - 17 surfaces print syntax as plain text: the outline, the Projectors card's header, inline code in explanations, a livelit's head and params lines (HTML inputs, by design), the stepper's title bar, the Printarium, folded function chips, completions, and the coding agent's messages.
- For Cyrus's asks, what is missing is selection, not tiles. The Projectors card's splices and the refractors are already tiles; only the card's one-line header is text. The explainer's examples are tiles too, in a locked cell editor. What all of them lack is the revealed model's read-only selection, and one shared read-only, selectable view could give it to every look-only surface.
b44bb9c50f, in four columns from most to least interactive (debug-only views left out). The full list gives the code behind each one and how it was checked.Four ways to draw syntax
Hazel's editor draws code as tiles: each token a span placed by the editor's layout, with holes, grout and indentation drawn the editor's way. The same renderer, Code.view, is called in many places besides the editor. What differs between those places is what comes with it: a zipper (the edit state with a caret) and an input handler, or nothing. That gives four kinds of surface, told apart here by what a click does, not by how the page looks:
CodeEditable).CodeSelectable, or the revealed model's sub-editor).Code.view with nothing around them: no caret, no selection, no menu.Printer.of_segment, Segment.to_string, Typ.pretty_print), shown in a span, an input or a tooltip.A surface is classed by what it does, not by its classes in the page. The Projectors card's header is a single element with the token class holding a printed string, so it looks like a tile but counts as text. The explainer's examples sit in an editor element that takes focus when clicked, but no caret appears and no menu opens, so they count as tiles to look at.
Every surface, by kind
Each row names a surface as someone using Hazel meets it, then gives what syntax it shows, what a click does, the code that draws it (paths under src/ on #2635 at b44bb9c50f), and whether it was checked in the running app or traced in the code.
Tiles you edit: 3
Only the places where a person writes code.
| Surface | Shows | What a click does | Drawn by | Checked |
|---|---|---|---|---|
| The editor Every mode: Scratch, Documentation, Tutorial, Exercises. | exp, pat, typ | Caret, selection, typing; right-click opens Hazel's menu (Select term, Cut, Copy, Paste, Add probe ...). | CellEditor.re:423 (CodeEditable) ScratchMode.re:687, TutorialMode.re:521, CodeExerciseMode.re:717 | in the app |
| A livelit's splices Inline, inside a livelit's GUI. | exp | The same editor, scoped to the splice: caret, selection, typing. | CodeEditable.re:968-976 (SubEditor.mk) | in the code |
| Proof step editors Theorem and derivation exercises. | exp, derivations | Editable, like the editor. | InductionCase.re:308 MissingStep.re:595 InductionStep.re:373 | in the code |
Read-only tiles you can select: 3
Read-only, with a caret. The revealed model is the one surface with everything tiles offer in the editor except edits: Perform refuses typing inside it. The stepper and the result show a caret but have no menu, and the stepper's caret does not move the inspector.
| Surface | Shows | What a click does | Drawn by | Checked |
|---|---|---|---|---|
| A livelit's model, revealed with the eye Click a livelit's eye: the model line under its head line. | exp (the model) | Caret, Shift+Arrow selection, the inspector reads it, right-click opens Hazel's menu; typing is refused. | LivelitProj.re (splice_view in the reveal pane) Perform.re (in_revealed_model refuses text edits) | in the app |
| The stepper's steps A value's Show steps, or the stepper view. | exp | Caret; no right-click menu, and the inspector does not follow it. | StepperEditor.re:133 (CodeSelectable) | in the app |
| The evaluation result Under each cell on dev. On #2635 a program's value is drawn by the program-value drawer instead (below). | value | Caret, and the inspector follows it; no right-click menu. | EvalResult.re:785 (CodeSelectable) CellEditor.re:335 | in the app |
Tiles you can only look at: 16
The largest group of tiles. Each is a Code.view of a segment, built for the view and thrown away, so there is no zipper to put a caret in. Γ is the costliest: it prints every type in the context whole, the prelude's included, so it draws 6,802 tiles for a four-line program.
| Surface | Shows | What a click does | Drawn by | Checked |
|---|---|---|---|---|
| The cursor inspector's bar and full view The bottom bar, and the full view its ⤢ opens. | typ, exp | None: no caret, no selection, no menu. | CursorInspector.re:142-186 (CodeViewable) | in the app |
| Γ, the context inspector The Γ at the bottom bar's left. | typ | None. Opening it on a four-line program draws 6,802 tiles: the prelude's types, each printed whole. | ContextInspector.re:12 (CodeViewable.view_typ) | in the app |
| The Problems panel's rows Right sidebar, Problems. | typ, exp | None (it reuses the inspector's views). | ProblemSidebar.re:43-65 | in the code |
| The explainer's syntactic form Right sidebar, Language Documentation: the form at the top. | exp, pat, typ | None. | ExplainThis.re:1732 (CodeWithStatics) | in the app |
| The explainer's examples Under the form, with each example's value. | exp, value | The editor takes focus, but no caret, no selection, no menu. | ExplainThis.re:457 (CellEditor, locked) CellEditor.re:453 (CodeEditable, read-only) | in the app |
| Fenced hazel blocks in docs Explanations, tutorial prompts, task references. | exp | None (a locked cell editor, as for examples). | ExplainThis.re:243 (CellEditor, locked) | in the code |
| The explainer's menu of form variants The specificity menu over the form. | exp, pat | Picks a variant; no caret. | ExplainThis.re:375 (CodeViewable) | in the code |
| Probe values, env and call args Inline probes in the code. | value | Probe actions (pin, step into, navigate); no caret, no tile menu. | ProbeProj.re:813, 872, 924, 1218 (flex_code, tiles) | in the app |
| The program's value (#2635) At the end of the program, on #2635. | value | The drawer takes focus; no caret; right-click offers only Show steps. | ProbeProj.re:2102 (program-value) ProbeProj.re:1081 (Show steps) | in the app |
| Stopped-evaluation and elaboration views When evaluation stops early, or is off. | value, exp | None. | EvalResult.re:872 (stopped) EvalResult.re:1109 (elaboration) | in the code |
| Refractors' splices Projector views beside the code. | exp | None. | RefractorView.re:276 (default_render_splice) ProjectorView.re:614-627 (simple_code, Code.view) | in the code |
| The Projectors card's splices Right sidebar, Projectors, under a docked projector's header. | exp | None. | ProjectorView.re:1050 (default_render_splice) | in the code |
| Table cells, the type projector, fold hovers Projectors in the code. | exp, typ | The projector's own controls; no caret in the syntax. | TableProj, TypeProj, FoldProj.re:65 (view_seg, flex_code) LivelitProj.re:1933-1936 (view_term) | in the code |
| Stepper boxes in proofs Theorem exercises. | exp, derivations | None. | StepperTargetBox.re:36 AssumptionBox.re:64 MissingStep.re:581 InductionCase.re:367 | in the code |
| Kind and derivation inspectors Derivation exercises. | kind, typ | None. | Kind.re:11 DrvCursorInspector.re:13 | in the code |
| The coding agent's diffs and program view The Hazel Coding Agent panel. | exp | None. | ToolResultView.re:15 | in the code |
Plain text: 17
Strings printed from syntax. Some are text by design: a livelit's head and params lines are inputs, to be edited as text. Some are text to save space (the Projectors card's header, the outline), and some because they are written as prose (inline code in explanations and tutorial prompts).
| Surface | Shows | What a click does | Drawn by | Checked |
|---|---|---|---|---|
| The Projectors card's header Right sidebar, Projectors: a docked projector's first line. | exp, pat (shortened) | None. One token-styled element holding the printed term, such as ^flag(true). | ProjectorView.re:564-586 (text_code) ProjectorView.re:632 (chip_syntax) | in the app |
| The outline sidebar Left sidebar. | names, typ | Jumps to the definition. | OutlineTree.re:80 (Typ.pretty_print) OutlineSidebar.re:1270 | in the app |
| Labels of split cells (outline, Problems) Cells headed by a pattern, such as (p, q). | pat | Jumps to the cell. | ScratchCell.re:85-118 (MarkerParse.to_text) OutlineSidebar.re:554-560 ProblemSidebar.re:358-421 | in the code |
| A livelit's head line Over the revealed model, such as ^flag. | exp, typ | An HTML text input: edits the head as text. | LivelitProj.re:1597-1615 (Segment.to_string, Typ.pretty_print) LivelitProj.re:1470 (input) | in the app |
| A livelit's params line Under the head line, for a livelit with parameters. | value | HTML inputs: edit each parameter as text. | LivelitProj.re:1707-1711 LivelitProj.re:1774 (input) | in the code |
| Inline `code` in explanations Explanations, tutorial prompts, task references. | names, literals, snippets | Hover highlights the term it names; no tiles. | ExplainThis.re:86-87, 146 (code_node) TutorialMode.re:556, 636 TutorialReferencePanel.re:113 | in the app |
| A fenced block that is not hazel, or does not parse Falls back to text. | exp | None. | ExplainThis.re:212 (pre) | in the code |
| Derivation rule-failure messages Derivation exercises. | derivation judgments | None. | ExplainThis.re:628-667 (Segment.to_string) | in the code |
| The explainer's livelit-use panel With the caret on a livelit use. | exp | Its frame (^name(, labels, =, )) is text; its field values are tiles. | ExplainThis.re:1817-1849 | in the code |
| The probe stepper's title bar Over the stepper's steps, such as (p, [p.x, p.y]). | exp | None (a tooltip repeats it). | CellEditor.re:384-391 ProbeSteps.re:197-212, 276-277 | in the app |
| The Probes panel's Printarium Right sidebar, Probes. | value | None. | ProbeSidebar.re:443-449 (Printer.of_segment) TutorialReferencePanel.re:170 | in the code |
| Folded function chips in values Such as <anon fun> under the explainer's examples, or <id@<Int>>. | value, typ | Unfolds the function. | FoldProj.re:91, 102 ExpToSegment.re:2131-2136 Transition.re:733 (Typ.pretty_print) | in the code |
| The sample-focus bar's breadcrumbs Over the editor while a probe sample is focused. | names, typ | Moves between samples. | SampleFocusBar.re:189-199, 403, 478 | in the code |
| The inline completion suggestion After a partial name, such as ch after pea when peach is in scope. | exp | Tab accepts it. | Code.re:169 (in-unparsed-buffer) | in the app |
| Completion quiver chips Beside the caret. | delimiters | Inserts the delimiter. | QuiverDec.re:139, 186-197 | in the code |
| Names in the inspectors and Problems rows Such as a variable's name before its type. | names, labels | None (the types beside them are tiles). | CursorInspector.re:19-26, 295, 343, 421, 711 ContextInspector.re:40, 64 ProblemCollection.re:238 | in the code |
| The coding agent's messages and tool calls The Hazel Coding Agent panel. | exp, typ | None. | AgentMessageMarkdown.re:37, 124-130 ToolResultView.re:26, 227 ChatMessagesView.re:114-143 (ErrorPrint) | in the code |
Debug-only
Two more views print syntax as text, but only for debugging. They are not counted above.
- The debug sidebar, raw mode (AST dumps): None. DebugSidebar.re:180-397, 599
- The debug console (F3 to F9) (exp, value): Browser console only. DebugConsole.re:17-92 ProbeText.re:70, 154
What this means for the open asks
Livelits' model views as read-only tiles (^ticket(387)). The model revealed with a livelit's eye already is what Cyrus asked for: read-only tiles with a caret, selection and the right-click menu. The two other views of a livelit's syntax are tiles as well. A refractor's splices and the Projectors card's splices both go through default_render_splice, which calls Code.view, so they are tiles to look at, without selection. Only the card's header (^flag(true)) is text. So the work is to give those two the revealed model's selection, not to turn text into tiles. (An earlier reading, the same morning, took default_render_splice for text; the code and the app both say tiles.) The head and params lines stay text inputs, by Matthew's call.
The explainer's examples (^ticket(388)). They are tiles: each example is a cell editor with ~locked=true, drawn read-only, with its value under it. Clicking one gives it focus and nothing else, so it shows no caret, no selection and no menu. What is text in the explainer is the inline code in its prose (such as true and false in the boolean literal's explanation), and any fenced block that is not marked hazel or does not parse.
What next
- Give the refractors' and the Projectors card's splices the revealed model's read-only selection: caret, Shift+Arrow and the right-click menu (
^ticket(387)). Decide whether the card's header stays text. - Make the explainer's examples selectable read-only tiles, and draw its inline
codeas tiles where the snippet parses, as fenced hazel blocks already are (^ticket(388)). - Build one read-only, selectable tile view, from the revealed model's, and use it wherever selecting helps: the inspector's full view, Γ, probe values and the program's value.
- Give the stepper and the evaluation result Hazel's right-click menu, and let the stepper's caret move the inspector, as the result's does.
- Find out why Γ costs what it does: 6,802 tiles for a four-line program, because every prelude type is printed whole (
^ticket(396), where opening Γ on the Scenes slide froze the browser). - Reproduce by hand a stale value seen once during this survey: on #2635, after switching from a Documentation slide to Scratch and pasting a program (by a scripted paste), the program's value showed the previous slide's value, marked stopped, until the page was reloaded.
How this was put together
- The build: #2635 (
integration/livelits-modular-editors) atb44bb9c50f, built with the dev profile and served locally. Dev's evaluation result was checked on #2698 at4c9cc34d3b(dev with Γ's long types), since #2635 draws a program's value differently. - In the app (16 surfaces): in the Claude desktop app's browser pane, on Documentation › Livelits › Overview, Documentation › Probes, and small programs in Scratch. For each surface: how many token elements it draws, whether a click puts a caret in it or moves the inspector, and whether a right-click opens Hazel's context menu. The control was the editor, where right-click opens the menu (Select term, Cut, Copy, Paste, Add probe).
- In the code: a read-only sweep of
src/webandsrc/haz3lcore/projectorsfor every view that draws syntax: theCode.viewfamily,CodeEditable,CodeSelectable, and every printer (Printer.of_segment,Segment.to_string,Typ.pretty_print,text_code), with each call traced to the surface a person sees. Printing that is never shown (copying to the clipboard, measuring widths, comparisons, the agent's model context) is left out. - Not covered: the exercise modes and the coding agent were traced in the code only, and hover cards were not surveyed. Each row of the table says how it was checked.
- The data: in this page's
<script type="application/json" id="report-data">element and in019/data/surfaces.json; the page is made by019/drivers/gen019.py. - #2635 is Matthew Hammer's branch, made and kept with Claude. Most of the views surveyed here come from dev, and are the Hazel team's work.