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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
Tiles you edit 3 surfaces The editor Livelit splices Proof step editors Read-only tiles you can select 3 surfaces Livelit model (eye) Stepper steps Evaluation result (dev) Tiles you can only look at 16 surfaces Inspector bar Γ (context) Problems panel Explainer: form Explainer: examples Explainer: hazel blocks Explainer: variants Probe values Program value (#2635) Stopped / elaboration Refractor splices Projectors card: splices Tables, type, fold Proof boxes Kind / derivation insp. Agent: program view Plain text 17 surfaces Projectors card: header Outline sidebar Split-cell labels Livelit head line Livelit params line Explainer: inline code Explainer: non-hazel block Rule-failure messages Explainer: livelit use Stepper title bar Printarium Function chips Sample-focus crumbs Completion suffix Completion chips Names in inspectors Agent: messages
Every surface of the Hazel UI that shows syntax in normal use, on #2635 at 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:

Tiles you edit: 3A zipper with input handling: caret, selection, typing, and Hazel's right-click menu (CodeEditable).
Read-only tiles you can select: 3A zipper without edits: a caret, and in one case selection and the menu too (CodeSelectable, or the revealed model's sub-editor).
Tiles you can only look at: 16Tiles from Code.view with nothing around them: no caret, no selection, no menu.
Plain text: 17A string printed from the syntax (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.

SurfaceShowsWhat a click doesDrawn byChecked
The editor
Every mode: Scratch, Documentation, Tutorial, Exercises.
exp, pat, typCaret, 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.
expThe 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, derivationsEditable, 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.

SurfaceShowsWhat a click doesDrawn byChecked
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.
expCaret; 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).
valueCaret, 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.

SurfaceShowsWhat a click doesDrawn byChecked
The cursor inspector's bar and full view
The bottom bar, and the full view its ⤢ opens.
typ, expNone: no caret, no selection, no menu.CursorInspector.re:142-186 (CodeViewable)in the app
Γ, the context inspector
The Γ at the bottom bar's left.
typNone. 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, expNone (it reuses the inspector's views).ProblemSidebar.re:43-65in the code
The explainer's syntactic form
Right sidebar, Language Documentation: the form at the top.
exp, pat, typNone.ExplainThis.re:1732 (CodeWithStatics)in the app
The explainer's examples
Under the form, with each example's value.
exp, valueThe 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.
expNone (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, patPicks a variant; no caret.ExplainThis.re:375 (CodeViewable)in the code
Probe values, env and call args
Inline probes in the code.
valueProbe 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.
valueThe 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, expNone.EvalResult.re:872 (stopped)
EvalResult.re:1109 (elaboration)
in the code
Refractors' splices
Projector views beside the code.
expNone.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.
expNone.ProjectorView.re:1050 (default_render_splice)in the code
Table cells, the type projector, fold hovers
Projectors in the code.
exp, typThe 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, derivationsNone.StepperTargetBox.re:36
AssumptionBox.re:64
MissingStep.re:581
InductionCase.re:367
in the code
Kind and derivation inspectors
Derivation exercises.
kind, typNone.Kind.re:11
DrvCursorInspector.re:13
in the code
The coding agent's diffs and program view
The Hazel Coding Agent panel.
expNone.ToolResultView.re:15in 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).

SurfaceShowsWhat a click doesDrawn byChecked
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, typJumps 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).
patJumps 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, typAn 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.
valueHTML 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, snippetsHover 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.
expNone.ExplainThis.re:212 (pre)in the code
Derivation rule-failure messages
Derivation exercises.
derivation judgmentsNone.ExplainThis.re:628-667 (Segment.to_string)in the code
The explainer's livelit-use panel
With the caret on a livelit use.
expIts frame (^name(, labels, =, )) is text; its field values are tiles.ExplainThis.re:1817-1849in the code
The probe stepper's title bar
Over the stepper's steps, such as (p, [p.x, p.y]).
expNone (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.
valueNone.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, typUnfolds 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, typMoves between samples.SampleFocusBar.re:189-199, 403, 478in the code
The inline completion suggestion
After a partial name, such as ch after pea when peach is in scope.
expTab accepts it.Code.re:169 (in-unparsed-buffer)in the app
Completion quiver chips
Beside the caret.
delimitersInserts the delimiter.QuiverDec.re:139, 186-197in the code
Names in the inspectors and Problems rows
Such as a variable's name before its type.
names, labelsNone (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, typNone.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.

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

  1. 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.
  2. Make the explainer's examples selectable read-only tiles, and draw its inline code as tiles where the snippet parses, as fenced hazel blocks already are (^ticket(388)).
  3. 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.
  4. 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.
  5. 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).
  6. 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