cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Docs: dialog answers in the macro API, the hosts table, recording

Dami committed c763843
The macro API documents the answer of every command that opens a
dialog, and a doc test checks each is given and runs the example. The
architecture's hosts tables say which components have which host and
why the in-package ones stay; the Gaps list keeps the cell entry and the
named ranges picker's changes. Both roadmap items move to Shipped.
commit
c763843914fa664858cccf0444d8354247d14516
parent
01489f73f55754c3033fc2bc1d4af671e4d73f87
author
Dami <dami.etoile@gmail.com>
kind
docs risk score 0.5 of 3, Jev's estimate
7 files changed, +151 -58
MROADMAP.md4+-
Mdocs/contributing/architecture.md60++++++++++++++++++++++---------
Mdocs/contributing/extending.md43++++++++++++----------
Mdocs/reference/macro-api.md35++++++++++++++++--
Mdocs/sheets/macros.md30++++++++++------
Minternal/ui/macro_doc_test.go24++++++++++++
Minternal/ui/macrorec.go13++++---
M ROADMAP.md +2 -2
@@ -20,7 +20,6 @@ Sizes: S (a day or two), M (about a week), L (weeks).
2020 |---|---|---|2121 | Open 012's XLSX output in Excel and LibreOffice before releases, as was done for Google Sheets | The writer is otherwise checked by excelize and 012's reader | S |2222 | Conditional formatting and validation, what's left: data bars and icon sets, "top 10", "duplicate values", date periods (this week, last month), rules moving with cells cut to another sheet, a dropdown's chip look, and custom checkbox values | Excel's other rule types come in as notes | M |23-| Remaining overlays get narrow hosts: filter picker, sort and choice bars, chart editor, shortcuts, named ranges, cell entry and prompts | Components testable without the model ([Architecture](docs/contributing/architecture.md#the-ui)) | M |2423 2524 ### 2. Spreadsheet features Sheets users reach for2625
@@ -43,7 +42,6 @@ rises in measured steps; see [Bounds of support](docs/contributing/limits.md#wha
4342 4443 | Item | Size |4544 |---|---|46-| Record dialog choices (sort bar, filter picker, find and replace, chart editor) and chart drags; let scripts run commands that open dialogs, with answers | M |4745 | Vim: `.` repeat, registers, marks, `cc`/`s`, command-line history, `:w!` | M |4846 | Hold-to-preview and Shift+Enter on terminals with the kitty keyboard protocol (`View.KeyboardEnhancements`) | S |4947 | Sixel chart images, redrawn on resize, for terminals without kitty graphics | M |
@@ -97,6 +95,7 @@ by a gateway dialing the iroh ticket.
9795 - Protected sheets and ranges that warn on edit: [Notes and protection](docs/sheets/notes-protection.md#protected-sheets-and-ranges)9896 - Charts (column, bar, line, area, pie, scatter; stacking, trend lines, axis and legend options), as images or text: [Charts](docs/sheets/charts.md)9997 - Macros, recorded or written in Starlark: [Macros](docs/sheets/macros.md)98+- Macros record the choices made in dialogs and chart drags, and scripts answer dialogs: [Macro scripting API](docs/reference/macro-api.md#dialogs)10099 - JEV functions, with the API key in the OS keychain: [JEV functions](docs/formulas/jev.md)101100 102101 **The terminal**
@@ -117,3 +116,4 @@ by a gateway dialing the iroh ticket.
117116 - A release checklist and `make dist`, release archives cross-compiled locally with SHA256SUMS: [Releasing](docs/contributing/releasing.md)118117 - `make stress-report` flags regressions against the last release's run, allowing for noise: [Observability](docs/contributing/observability.md#regressions-against-the-last-release)119118 - Grafana: recent traces and a trace view of the nested spans: [Observability](docs/contributing/observability.md#the-stack)119+- Every overlay, prompts and formula suggestions behind narrow hosts, most in packages of their own with fake-host tests: [Architecture](docs/contributing/architecture.md#the-ui)
M docs/contributing/architecture.md +43 -17
@@ -37,6 +37,11 @@ internal/ui the Bubble Tea model: modes, menus, overlays, rendering
3737 findbar find and replace, a bar on the context line3838 themepicker File > Settings > Theme, previewing as it moves3939 rules the conditional formatting and data validation panel40+ sortbar the sort bar: the columns and order to sort a range by41+ filterpick a filter column's values and condition42+ choicebar small questions on the context line answered with a key43+ shortcuts the keyboard shortcuts view44+ suggest formula suggestions and function signatures while typing4045 tabstrip the sheet tabs' layout and where each sheet was left4146 transfer imports running in the background and their progress4247 e2e/ end-to-end tests through libghostty (separate module, cgo)
@@ -362,9 +367,9 @@ draw. The components:
362367 |---|---|---|363368 | grid (embedded) | `grid` | the sheet shown, active cell, scroll, window size, selection; mapping rows and columns to the screen (rows as bands of lines, `bands.go`), frozen panes, moving and selecting (`grid.go`, `panes.go`, `selection.go`) |364369 | edit line | `lineedit.Line` | the one-line editor shared by cell entries, prompts and search fields (package `lineedit`) |365-| cell entry | `entry`, `assist` | typing into a cell, pointing at references, other sheets while pointing, formula suggestions and signatures (`entry.go`, `assist.go`) |370+| cell entry | `entry`, `suggest.List` | typing into a cell, pointing at references, other sheets while pointing (`entry.go`, `assistsheets.go`); formula suggestions and signatures (package `suggest`, with `assist.go`) |366371 | prompt | `prompt` | a question on the context line, typed or pointed at (`prompt.go`) |367-| overlays | `overlay.Overlay` | whatever has taken over input: menus (`menuoverlay.go`), the palette and pickers (package `picker`, with `palette.go`, `names.go`), the command line (package `cmdline`), the theme picker (package `themepicker`), the find bar (package `findbar`), the filter picker, the sort and choice bars, the chart editor and selection, the pivot editor (`pivoteditor.go`, `pivotactions.go`), the rules panel (package `rules`, with `rules.go`), the shortcuts |372+| overlays | `overlay.Overlay` | whatever has taken over input: menus (`menuoverlay.go`), the palette and pickers (package `picker`, with `palette.go`, `names.go`), the command line (package `cmdline`), the theme picker (package `themepicker`), the find bar (package `findbar`), the filter picker (package `filterpick`, with `filter.go`), the sort bar (package `sortbar`, with `sort.go`), choice bars (package `choicebar`, with `dialog.go`), the chart editor and selection (`charteditor.go`, `chartsel.go`), the pivot editor (`pivoteditor.go`, `pivotactions.go`), the rules panel (package `rules`, with `rules.go`), the shortcuts (package `shortcuts`, with `help.go`) |368373 | sheet tabs | `tabstrip.Strip` | where each sheet was left, the tab strip's scroll and layout (package `tabstrip`); what clicks on it do (`tabstrip.go`) |369374 | mouse | `mouseState` | drags, hover, double clicks, the fill handle (`mouse.go`, `fill.go`) |370375 | import | `transfer.Transfer` | the import in progress, its progress display and cancelling (package `transfer`); choosing and placing imports (`transfer.go`, `importplace.go`) |
@@ -389,23 +394,37 @@ methods off `ui.Model`'s exported API:
389394 | Package | Host | Methods |390395 |---|---|---|391396 | `picker` | `picker.Host` | theme, size, the edit line, close, record the answer to a command's question (5) |397+| `choicebar` | `choicebar.Host` | theme, close, record the key chosen (3); the choices are closures over the model |398+| `shortcuts` | `shortcuts.Host` | theme, size, close, the rows, built from the key bindings and the registry (4) |399+| `sortbar` | `sortbar.Host` | theme, size, the sheet, close, sort (recorded as the bar's command) (5) |400+| `filterpick` | `filterpick.Host` | theme, size, the edit line, close, the locale (5); what applying and cancelling do are callbacks, as the sheet's filter and a pivot's differ |392401 | `cmdline` | `cmdline.Host` | theme, size, the edit line, close, the commands to complete, run a line, fail (7) |402+| `suggest` | `suggest.Host` | theme, size, the edit line, whether an entry is being typed, the entry's sheet, the formula as parsed, where the formula bar's text starts (7) |393403 | `themepicker` | `themepicker.Host` | a picker's host, and the current theme, the themes directory, preview, keep (9) |394-| `findbar` | `findbar.Host` | theme, size, the edit line, the workbook, the sheet and cell shown, show a cell, note, mark modified, leave keeping the search, pass a click to the grid (10) |395-| `rules` | `rules.Host` | theme, size, the edit line, close, the sheet and selection, save a conditional format or a validation rule (as the commands macros record), note a rule removed or moved, the terminal's palette colors (10) |404+| `rules` | `rules.Host` | theme, size, the edit line, close, the sheet and selection, save a conditional format or a validation rule, follow a rule removed or moved (each recorded as the commands that do it), the terminal's palette colors (10) |405+| `findbar` | `findbar.Host` | theme, size, the edit line, the workbook, the trace, the sheet and cell shown, show a cell, note, run a replacement (recorded as Find and replace), leave keeping the search, pass a click to the grid (11) |396406 | `tabstrip`, `transfer` | none | they're handed a view or messages and draw what they're given |397407 398408 Components that stay in package `ui` declare an unexported host the model399-implements itself: `menuHost` for menus (9 methods; their items are the400-command registry and the menu bar's definitions), `macrosHost` for the401-macro manager (9; it's a few keys over a picker, and what they do is the402-macro machinery), and `pivotHost` for the pivot editor and its field403-picker (14; it opens the model's pickers, filter values and range prompt404-and comes back from them). The rest (the filter picker, the sort and405-choice bars, the chart editor and selection, the shortcuts, the named406-ranges picker) still keep the model itself. Commands still run in one407-place: a host's way to run one (`runFromOverlay`, `cmdline.Host.Run`)408-goes through `runCommand`, so macros record them and pivots guard them.409+implements itself:410+411+| Host | For | Methods | Why it stays |412+|---|---|---|---|413+| `namesHost` | the named ranges picker | 6 | a few keys over a picker; what they do is the model's name prompts and selection, which open the picker again |414+| `promptHost` | prompts | 8 | a prompt is one of the model's modes, not an overlay: ranges are pointed at with the grid's movement and drawn by the grid |415+| `menuHost` | menus | 9 | their items are the command registry and the menu bar's definitions |416+| `macrosHost` | the macro manager | 9 | a few keys over a picker; what they do is the macro machinery |417+| `pivotHost` | the pivot editor and its field picker | 16 | it opens the model's pickers, filter values and range prompt and comes back from them |418+| `chartHost` | the chart editor and a selected chart | 23 | besides the charts, the grid's geometry, the pointer shape, the chart menu and prompts, and passing keys and clicks on to the grid |419+420+The cell entry has no host: typing into a cell is the model's ENTER,421+EDIT and POINT modes, whose state is the model's edit line, active cell422+and pointer, and committing goes through validation, the undo step and423+the recorder, so an interface for it would be the model's own methods424+over again. Its suggestions, which only read the entry, are package425+`suggest`. Commands still run in one place: a host's way to run one426+(`runFromOverlay`, `cmdline.Host.Run`, `chartHost.runCommand`) goes427+through `runCommand`, so macros record them and pivots guard them.409428 410429 **Drawing.** `View` (`panel.go`) stacks the control panel, the header,411430 the grid rows and the status line, then composites the floating layers
@@ -426,8 +445,9 @@ hints) are what every overlay is drawn with.
426445 **Packages.** `theme`, `rowtext`, `formula`, `overlay` and `lineedit`427446 depend on nothing in `ui`, so they can be tested and measured alone. The428447 components in `picker`, `cmdline`, `themepicker`, `findbar`, `rules`,429-`tabstrip` and `transfer` build on them and reach the model only through their430-hosts, with unit tests of their own. A component moves out of package448+`sortbar`, `filterpick`, `choicebar`, `shortcuts`, `suggest`, `tabstrip`449+and `transfer` build on them and reach the model only through their450+hosts, with unit tests of their own against fake hosts. A component moves out of package431451 `ui` when its host stays small (about ten methods or fewer); one that432452 needs more keeps a narrow interface inside `ui` instead, since moving it433453 would mean exporting half of the model. New leaf packages are split off
@@ -488,7 +508,13 @@ session dropped) stops after waiting 30 s for a call to be taken. The whole run
488508 489509 Recording (`macrorec.go`) listens where actions happen: `runCommand` for490510 commands (with the answer to a prompt or choice bar), the entry commit,491-pastes, the fill handle, column borders and tab drags. The selection is511+pastes, the fill handle, column borders and tab drags. A dialog records512+itself when it acts (`recordDialog`), as its command answered with the513+choices made, a JSON object the recording writes as a dict; the command's514+`answer` hook makes the same choices and applies them when a script runs515+it with that answer. Charts moved or resized record as Edit chart with516+the fields changed (`chartmacro.go`), and the rules panel as the commands517+that change rules from a line of the file (`rulemacro.go`). The selection is492518 recorded lazily, as absolute `select()` or relative `move()`/`extend()`,493519 just before something acts on it. Files record the computer their macros494520 were made or trusted on (an id from `cmd/012`); a macro from elsewhere
M docs/contributing/extending.md +23 -20
@@ -57,13 +57,15 @@ of the model (reading the sheet or the selection, running a command,
5757 opening or closing an overlay, a note, the theme), which the model5858 implements, so it can be tested against a fake. When the host stays5959 small, about ten methods or fewer, the component lives in a package of60-its own under `internal/ui` (`picker`, `cmdline`, `findbar`,61-`themepicker`), declares `Host`, and the model implements it through the62-`host` adapter in `hosts.go`; parts that need no host at all take plain63-values (`tabstrip`, `transfer`). A component that needs more keeps an64-unexported host inside package `ui` (`menuHost`, `pivotHost`,65-`macrosHost`) rather than exporting half of `Model`. Either way, running a66-command goes through `runCommand` (pattern 3).60+its own under `internal/ui` (`picker`, `cmdline`, `findbar`, `sortbar`,61+`filterpick`, `suggest`), declares `Host`, and the model implements it62+through the `host` adapter in `hosts.go`; parts that need no host at all63+take plain values (`tabstrip`, `transfer`). A component that needs more,64+or that acts through the model's own machinery, keeps an unexported host65+inside package `ui` (`menuHost`, `pivotHost`, `chartHost`, `promptHost`)66+rather than exporting half of `Model`; the table in67+[Architecture](architecture.md#the-ui) says which and why. Either way,68+running a command goes through `runCommand` (pattern 3).6769 6870 ### 3. Actions go through commands6971
@@ -82,10 +84,14 @@ column borders and dragged tabs, each recorded where it happens with
8284 `Model.record`, and the selection, recorded as a state (where it is just8385 before something acts on it) rather than as the keys or clicks that moved8486 it. So movement keys, typing and mouse selection needn't become commands85-to be recorded, and a replay doesn't depend on the window's size. A new86-action that changes the workbook outside a command records itself the same87-way; anything unrecorded that changes the workbook while recording becomes88-a comment in the script, so a gap shows.87+to be recorded, and a replay doesn't depend on the window's size. A88+dialog opened by a command records what was chosen in it once it acts,89+as that command answered with the choices (`recordDialog`), and gives the90+command an `answer` hook that makes the same choices, so scripts can run91+it; its package writes and reads the answer. A new action that changes92+the workbook outside a command records itself the same way; anything93+unrecorded that changes the workbook while recording becomes a comment in94+the script, so a gap shows.8995 9096 ### 4. Side effects stay at the edges9197
@@ -216,15 +222,12 @@ a golden screen reviewed in the gallery for anything visible (see
216222 217223 Where the code doesn't follow the patterns yet:218224 219-- Some overlays still keep the whole `*Model` as their host rather than a220- narrower interface: the filter picker, the sort and choice bars, the221- chart editor and selection, the shortcuts and the named ranges picker222- (pattern 2). The cell entry, its suggestions and prompts are handed the223- model too.225+- The cell entry (typing into a cell, pointing at references) is the226+ model's own modes rather than a component with a host (pattern 2); see227+ [Architecture](architecture.md#the-ui) for why.224228 - Movement keys, typing, F4 in formulas, Alt+letter menus and direct mouse225229 manipulation act without a registered command (pattern 3). The macro226230 recorder covers what matters for replay without them (entries, the227- selection, pastes, the fill handle, column borders, dragged tabs), but228- dragging and resizing charts and the choices made in dialogs (the sort229- bar, the filter picker, find and replace, the chart editor) aren't230- recorded: a recording notes them as comments.231+ selection, pastes, the fill handle, column borders, dragged tabs and232+ charts, the choices made in dialogs), but changes made in the named233+ ranges picker aren't recorded: a recording notes them as comments.
M docs/reference/macro-api.md +32 -3
@@ -85,9 +85,10 @@ id, on the selection, as if picked. A command that asks something (a
8585 width, a name, a confirmation) needs `answer`: the text you'd type, a range8686 for a range question, the key of a choice (`"enter"`, `"d"`) or its8787 label, or the item picked from a list (View > Hidden sheets takes the88-sheet's name). Commands that open a dialog or another picker (Sort89-range, Insert chart, Named ranges) can't run in a script; nor can files,90-menus, help, undo or macros. The ids are those in the recorded scripts; a few:88+sheet's name). A command that opens a dialog takes what you'd choose in89+it as a dict (see [Dialogs](#dialogs)). Other pickers (Named ranges) can't90+run in a script; nor can files, menus, help, undo or macros. The ids are91+those in the recorded scripts; a few:9192 9293 | Id | Command |9394 |---|---|
@@ -101,11 +102,39 @@ menus, help, undo or macros. The ids are those in the recorded scripts; a few:
101102 | `data.sort_sheet_az`, `data.sort_range_az`, `data.sort_range_za`, `data.filter`, `data.filter_remove` | Sorting and filters |102103 | `data.define_name` (answer: the name) | Name the selection |103104 | `format.conditional_add`, `data.validation_add` (answer: the rule as a line of the file, see [The .012 format](../files/format.md#conditional-formats-and-data-validation)), `insert.checkbox`, `format.conditional_clear`, `data.validation_clear`, `data.checkbox_toggle` | Conditional formats, data validation and checkboxes |105+| `format.conditional_set`, `data.validation_set` (answer: `{"rule": 2, ...}`, the rule's number and its new line), `format.conditional_remove`, `data.validation_remove` (answer: the rule's number), `format.conditional_move` (answer: `{"rule": 2, "to": 1}`) | Change the rules of the sheet shown, numbered from 1 as the panel lists them |104106 | `sheet.new`, `sheet.duplicate`, `sheet.rename` (answer: the name), `sheet.delete` (answer: `"enter"` when it asks), `sheet.hide`, `sheet.unhide` (answer: the sheet's name) | Sheets |105107 | `view.freeze_rows1`, `view.freeze_cols1`, `view.freeze_rows0`, ... | Frozen panes |106108 107109 Record a macro that uses a command to see its id.108110 111+## Dialogs112+113+A command that opens a dialog runs in a script with `answer` set to what114+you'd choose in it, as a dict: the dialog doesn't open, and what it would115+do is done. A recording writes dialogs this way, so recording one is the116+quickest way to get its answer right. Fields left out keep what the117+dialog would start with.118+119+```python120+run("data.sort_range", answer={"by": [{"column": "B", "order": "desc"}, {"column": "A"}], "header": True})121+run("data.filter_column", answer={"column": "A", "hidden": ["North"]})122+run("chart.edit", answer={"chart": 1, "at": "F3", "width": 30})123+```124+125+| Id | Answer |126+|---|---|127+| `data.sort_range` | `by`: the columns to sort by, in order, each `{"column": "B"}`, with `"order": "desc"` for Z to A; `header`: whether the range's first row is a header that stays in place (left out: as the bar guesses). The range is the selection, or the data around the active cell |128+| `data.filter_column` | `column`: the column of the filter (left out: the active one); `hidden`: the values it hides; `condition` and `value`: the condition rows must meet, as a filter column is written in [the .012 format](../files/format.md) (`gt`, `contains`, ...), the value in en-US's form |129+| `edit.replace` | `find` and `replace`; `matchCase`, `wholeCell`, `regex`, `inFormulas` set to `True` to turn them on; `within`: `"all"` for every sheet or a range of the sheet shown (left out: the sheet shown); `cell`: replace only the match in that cell (left out: replace all) |130+| `insert.chart` | The chart as a line of the file: `type`, `data`, `at`, `width`, `height`, `header`, `labels`, `title` and the options, see [The .012 format](../files/format.md); what's left out is the chart Insert > Chart makes of the selection |131+| `chart.edit` | `chart`: the chart's number on the sheet shown, counting from 1 (left out: the chart the command would edit), and the fields of its line to change, as for `insert.chart`; `None` puts a field back to its default |132+| `data.pivot`, `data.pivot_edit` | The pivot table as a line of the file: `source`, `rows`, `columns`, `values`, `filters`, `rowTotals`, `columnTotals`, see [The .012 format](../files/format.md). `data.pivot` makes it on a new sheet, as Data > Pivot table does; `data.pivot_edit` changes the pivot of the sheet shown |133+134+A dict's keys are strings, and its values `None`, `True`, `False`,135+numbers, strings, lists and dicts; `run` hands it on as JSON with the136+keys in the order written.137+109138 ## Output and errors110139 111140 `print(...)` shows its last line on the context line when the macro ends.
M docs/sheets/macros.md +19 -11
@@ -46,17 +46,25 @@ What gets recorded:
4646 - pasted text (`paste_text(...)`), the fill handle (`fill(...)`), column4747 borders dragged or fitted (`set_width(...)`) and tabs dragged4848 (`move_sheet(...)`);49-- rules added in the conditional formatting and data validation panels,50- as the commands that add a rule from a line of the file51- (`run("format.conditional_add", answer='{"ranges":"B2:B9",...}')`), and52- items picked from a dropdown, as entries. Editing, removing or moving a53- rule in the panel is noted as a comment, as the dialogs below are.54-55-What doesn't: dialogs such as sorting by several columns, the filter56-picker, find and replace, the chart editor, dragging a chart, and undo.57-Anything like that which changes the workbook is noted in the script as a58-comment, `# Not recorded: sort A2:C9`, so the script never silently does59-less than you did. Files, menus, help and other macros are never part of a49+- what you choose in dialogs, as the command that opens the dialog with50+ your choices as its answer: the sort bar51+ (`run("data.sort_range", answer={"by": [{"column": "B"}], "header": True})`),52+ the filter picker's values and condition, find and replace, the chart53+ editor, and the pivot editor, each as the dialog left things when you54+ pressed Enter;55+- charts moved or resized, by keys or by dragging, as56+ `run("chart.edit", answer={"chart": 1, "at": "F3"})`, one call for a57+ move made in steps;58+- rules added, edited, removed or moved in the conditional formatting and59+ data validation panels, as the commands that do it from a line of the60+ file (`run("format.conditional_add", answer={"ranges": "B2:B9", ...})`,61+ `run("format.conditional_remove", answer=2)`), and items picked from a62+ dropdown, as entries.63+64+What doesn't: undo, and changes made in the named ranges picker. Anything65+like that which changes the workbook is noted in the script as a comment,66+`# Not recorded: undid: sort A2:C9`, so the script never silently does less67+than you did. Files, menus, help and other macros are never part of a6068 macro.6169 6270 ## Running
M internal/ui/macro_doc_test.go +24 -0
M internal/ui/macrorec.go +8 -5
@@ -21,6 +21,10 @@ import (
2121 // them, with the answer to the question they ask (a width, a name, a2222 // confirmation) once it's given;2323 // - entries, when they're stored (enter), and pastes of text;24+// - dialogs (the sort bar, the filter picker, find and replace, the25+// chart and pivot editors, the rules panel) once they've acted, as26+// the command that opens them answered with the choices made27+// (recordDialog), and charts moved or resized;2428 // - the fill handle, column borders dragged or fitted, and tabs dragged;2529 // - the selection, not the keys or clicks that made it: just before2630 // something is recorded, the selection it acts on is, as select() with
@@ -30,10 +34,9 @@ import (
3034 //3135 // With relative references, Ctrl+arrows, Home, Ctrl+Home and Ctrl+End are3236 // recorded as jump(), since where they land depends on the data. Anything33-// else that changes the workbook (a dialog such as sorting by several34-// columns, the filter picker, dragging a chart, undo) becomes a comment35-// saying it wasn't recorded, so the script never silently differs from36-// what was done.37+// else that changes the workbook (undo, the named ranges picker) becomes38+// a comment saying it wasn't recorded, so the script never silently39+// differs from what was done.3740 3841 // recorder is a recording in progress.3942 type recorder struct {
@@ -146,7 +149,7 @@ func (m *Model) recordingCommand(c *command) tea.Cmd {
146149 case m.mode == modePrompt || choosing:147150 r.pending = c.id // recorded once answered148151 case m.overlay != nil:149- // A dialog or Picker: what it does isn't recorded (see observe).152+ // A dialog records what it does when it's done (recordDialog).150153 case m.mode != modeError:151154 r.add(m, macro.Call("run", c.id))152155 }