- commit
- 714359a26716563da3ad9bfbc721cdb8acb5c467
- parent
- 0ce529e721879ae186f3eab2cb169cf17a6841c2
- parent
- 62a3e613539d08e782dfa36d25f0afcf63e8b965
- author
- Dami <dami.etoile@gmail.com>
@@ -24,9 +24,11 @@ Noise that stays becomes invisible, and then real problems hide in it.
2424 reader would look in, link to it from elsewhere, and restructure when2525 appending would make a wall of text. Shipped roadmap items move to its2626 Shipped list as one line.27-- Diagrams are Mermaid code blocks, never ASCII art; screens and terminal28- output stay code blocks. Draw one where a flow, a state machine or a29- sequence would otherwise take paragraphs30- ([the docs site](docs/contributing/site.md#diagrams)).27+- Diagrams are Mermaid code blocks, never ASCII art. Draw one where a28+ flow, a state machine or a sequence would otherwise take paragraphs29+ ([the docs site](docs/contributing/site.md#diagrams)). 012's screen is30+ a picture, a still drawn from a golden screen, never text in a code31+ block ([Pictures of 012](docs/contributing/site.md#pictures-of-012));32+ code blocks are for what a reader types or a shell prints.3133 - No scratch files, debug tests, temporary samples or build outputs (`*.test`, binaries) in commits; `make lint` refuses compiled files and anything over 2 MB.3234 - Visual changes are reviewed in the golden gallery (`make screens`).@@ -68,7 +68,7 @@ e2e: $(GHOSTTY_STAMP)
6868 # Rewrite golden screens and build e2e/testdata/screens/gallery.html for6969 # visual review. Review the diff and the gallery before committing.7070 screens: $(GHOSTTY_STAMP)71- cd e2e && PKG_CONFIG_PATH=$(GHOSTTY_OUT)/share/pkgconfig go test -count=1 -run TestScreens ./... -update71+ cd e2e && PKG_CONFIG_PATH=$(GHOSTTY_OUT)/share/pkgconfig go test -count=1 -run 'TestScreens|TestStills' ./... -update7272 7373 # Stress: fetch real datasets into .deps/stress, run the benchmarks built7474 # with -tags stress, print a summary and record the run in@@ -1,10 +1,16 @@
1-# Pipelines: in a nushell session, a table goes through 012 and on.2-# ls's table goes in as NUON, 012 shows its sizes in the Size format, a3-# selection is sent back, and nu carries on with sizes that are still4-# file sizes. Needs nu on the PATH.1+# Pipelines: in a nushell session, a table goes through 012 and on with2+# the sheet command. ls's table shows its sizes in the Size format, a3+# selection is sent back when 012 quits (--send selection, so it doesn't4+# ask), and nu carries on with sizes that are still file sizes. Needs nu5+# on the PATH.56 Output out/pipeline.gif67 Source lib/setup.tape78 Source lib/nu.tape9+Hide10+Type "^012 nu --install-module ../bin/012.nu" Enter11+Sleep 1s12+Type "use ../bin/012.nu *; clear" Enter13+Sleep 1s814 915 Show1016 Sleep 600ms@@ -12,7 +18,7 @@ Type "ls data | select name size"
1218 Sleep 400ms1319 Enter1420 Sleep 2.2s15-Type "ls data | select name size | to nuon | ^012 --pipe | from nuon | where size > 400b"21+Type "ls data | select name size | sheet --send selection | where size > 400b"1622 Sleep 600ms1723 Enter1824 Sleep 2s@@ -21,8 +27,6 @@ Ctrl+G
2127 Type "A1:B4" Enter2228 Sleep 1s2329 Ctrl+Q24-Sleep 1.6s25-Enter2630 Sleep 2.5s2731 Screenshot out/stills/pipeline-result.png2832 Sleep 1s@@ -75,7 +75,9 @@ the site's first commit for every page there has been.
7575 and the sidebar is generated from the tree: `_category_.json` for7676 folders, `title` and `sidebar_position` for pages7777 ([Docs](testing.md#docs) has the rules the tree follows).78-- Images in `docs/media/` are bundled from the pages that show them.78+- Images in `docs/media/` are bundled from the pages that show them;79+ an image marked `#gh-dark-mode-only` or `#gh-light-mode-only` shows in80+ that color mode only ([Pictures of 012](#pictures-of-012)).7981 - Prism has no nushell grammar, so `nu` code blocks use a small one in8082 `website/src/prism/nushell.ts`, loaded by the swizzled8183 `website/src/theme/prism-include-languages.ts`.@@ -105,8 +107,8 @@ are IBM Plex Mono, body text IBM Plex Sans, both bundled with the site.
105107 106108 Diagrams are Mermaid, in a `mermaid` code block, never ASCII art: the107109 same source is drawn on the site and on GitHub. A code block of text108-stays for what is text on a terminal: screens, mockups of them and109-command output.110+stays for text a reader types or a shell prints; 012's own screen is a111+picture ([Pictures of 012](#pictures-of-012)).110112 111113 - Draw one where a flow, a state machine or a sequence would otherwise112114 take paragraphs: a flowchart for data moving through the code, a state@@ -121,3 +123,50 @@ command output.
121123 (Docusaurus's component, swizzled) draws with Mermaid's `base` theme122124 and variables read from the palette in `custom.css` for the color123125 mode shown, so diagrams don't set their own styles.126+127+## Pictures of 012128+129+A page shows 012 as a picture: a still of the screen in the site's130+color mode, or a recording. A screen typed out in a code block reads as131+a test fixture, loses the colors that carry meaning, and wraps on a132+narrow page, so `scripts/doclint` fails a code block that draws one133+(box-drawing lines with the menu bar or the notebook's toolbar, or the134+grid's column headers).135+136+A still is a [golden screen](testing.md#golden-screens) drawn as a PNG.137+Each is an entry of `docScreens` in `e2e/stills_test.go`: its name,138+the screen whose setup it records, and a terminal size that reads on a139+page (80 columns, unless the state needs more). `make screens` records140+it on a dark and a light terminal and draws both into `docs/media`:141+142+```go143+{name: "evaluate", from: "evaluate", cols: 80, rows: 12},144+// docs/media/evaluate-dark.png and docs/media/evaluate-light.png145+```146+147+The drawing is Go (`e2e/stilldraw_test.go`): the golden's cells in the148+[reference palettes](testing.md#golden-screens), text in Go Mono, and149+box drawing, blocks, braille and the chrome's symbols drawn as a150+terminal draws them, so frames join and every machine draws the same151+picture. Each PNG records which golden it was drawn from, and152+`make e2e` fails when a golden has changed since.153+154+A page shows both pictures together, marked as GitHub marks an image for155+one color mode; `custom.css` shows the site's the same way:156+157+```md158+159+160+```161+162+- The alt text says what the picture shows, the state and what to look163+ at, the same words on both. Write a range in it as `B3 to B5`: the site164+ reads `:B5` in text as a directive and drops it.165+- One still where the state is the point: what a feature looks like166+ before a page explains it, or a state words describe badly (a box,167+ marks in the grid). The prose keeps what the picture can't: keys,168+ limits, why.169+- A flow of several steps is a recording, a VHS tape in `demos/`170+ ([Demo recordings](testing.md#demo-recordings)).171+- `scripts/doccheck` fails on a still no page shows, or one shown172+ without its other color mode.@@ -101,7 +101,10 @@ with light-terminal variants. The states are listed in
101101 `e2e/screenlist_test.go`, with the fixtures they share in102102 `e2e/screensetup_test.go`. `make screens` rewrites them and builds103103 `e2e/testdata/screens/gallery.html` with dark and light reference palettes.104-Every visual change is reviewed there before it's committed.104+Every visual change is reviewed there before it's committed. It also105+draws the docs' stills, the screens `docScreens` lists, into106+`docs/media` ([Pictures of 012](site.md#pictures-of-012)); `make e2e`107+fails on one not drawn from its golden as it is.105108 106109 ## The excelize oracle107110 @@ -219,11 +222,12 @@ and a line in the folder's README. `make site` builds them into the
219222 [docs site](site.md) and fails on any link or anchor it can't resolve.220223 221224 `make lint` runs `scripts/doclint`, which flags wording that narrates222-history (see [CLAUDE.md](../../CLAUDE.md)), and `scripts/doccheck`, which225+history (see [CLAUDE.md](../../CLAUDE.md)) and code blocks that draw226+012's screen ([Pictures of 012](site.md#pictures-of-012)), and `scripts/doccheck`, which223227 checks the tree above, that relative links and anchors resolve, that224228 every `docs/...md` path Go code names exists, that every file in225-`docs/media` is shown by a doc and made by a tape, that every tape226-records something a doc shows, and that a page the site has published229+`docs/media` is shown by a doc and made by a tape or drawn from a golden230+screen, that every tape and still is shown by a doc, and that a page the site has published227231 isn't gone without a redirect ([The docs site](site.md#old-addresses-keep-working)).228232 Unit tests check what the docs say about229233 the code: menu paths lead to menu items, command ids and keys exist and@@ -52,6 +52,14 @@ follow-up task.
5252 5353 ## Visual rules5454 55+The palette over a sheet shows most of them at once: bars that fill56+the width, an overlay framed in light lines with its title in the top57+border and a count in the bottom one, keys drawn as chips, and the58+status line saying what the highlighted item does.59+60+61+62+5563 - Style only through `theme` roles (`internal/ui/theme`). No inline5664 `lipgloss.NewStyle()` in views. Need a new role? Add it to `theme` with a5765 comment saying what it's for, in both the dark and light variants.@@ -99,7 +107,7 @@ there.
99107 | Entries failing validation | A dotted underline, and `Invalid:` with the rule on the context line |100108 | Values an array spilled | Italic, and `Spilled from B2` on the context line |101109 | Linked files' rows, following, paused or failing | Italic; `●`, `‖` or `!` beside the tab's name and on the context line, with the state in words (`Following`, `Paused`, the error) |102-| A notebook's cells and outputs | `❯` before the tab's name; `▌` left of the active cell and `▎` left of the others selected; a heavy box around the cell being edited; the prompt's and the box's words for its state (`[*]:` and `running`, `waiting`, `failed`, `stale`); `■` for `▶` while it runs; `● live` and the rows it printed for a cell running as a stream; `output hidden` for a hidden output, and which rows show for one scrolled; `×` before an error; an output's grid entered by `OUTPUT` and a green bar, its pointer and selection in reverse video as on a sheet; an output sent to a sheet in italic, with `Output of files` on the context line; a problem nu finds in the cell being written curly-underlined, in words on the context line with the caret on it |110+| A notebook's cells and outputs | Marks and words, listed in [Notebooks](../nushell/notebooks.md#reading-a-cell): the bar left of the active cell, a heavy box while it's edited, the prompt's and the box's words for its state, `×` before an error, `OUTPUT` in an output's grid, an output sent to a sheet in italic |103111 | A table's header row | Bold and underlined, when the table styles its header; `Table Sales, column Amount` on the context line |104112 | Pivot table results | Their headings (`SUM of Units`, `Grand Total`), and a note in words when an edit is refused |105113 | Protected ranges | A question in words before an edit (`A1:B2 is protected.`) |@@ -18,11 +18,11 @@ the pointer moves: the cells the active cell's formula reads in green,
1818 the formulas that read the active cell in magenta and bold. Without1919 color both are in reverse video, dependents bold as well. The context2020 line lists them all, those off screen with an arrow toward them and21-those on other sheets with their sheet:21+those on other sheets with their sheet (`C7 reads B3, Rate, Data!B2:B9;22+read by D7, A40↓, Summary!B2 +3 more`):2223 23-```24-C7 reads B3, Rate, Data!B2:B9; read by D7, A40↓, Summary!B2 +3 more25-```24+25+2626 2727 Alt+; again stops. What counts as a link:2828 @@ -64,13 +64,8 @@ Evaluate Formula, opens a box over the grid with the active cell's
6464 formula. The part computed next is underlined, and the line under it6565 says what it computes:6666 67-```68-┌─ Evaluate D1 ────────────────────────────────────────┐69-│ =IF(TRUE,SUM(A2:A3)*2,A3) │70-├──────────────────────────────────────────────────────┤71-│ Next SUM(A2:A3) = 5 │72-└───────────────────────────────────────────── 2 of 5 ─┘73-```67+68+7469 7570 - Enter puts the value in its place, in italic, and underlines the next7671 part, until the formula's value is all that's left; Enter then startsBinary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
Binary file.
@@ -12,7 +12,7 @@ of their own type. 012 is a spreadsheet for those tables. A table can go
1212 through 012 in the middle of a pipeline, and nushell can run inside 0121313 in a notebook, its cells' outputs live parts of the sheets.1414 15-15+1616 1717 ## Three ways to use them together1818 @@ -38,27 +38,28 @@ works as before.
3838 3939 The tab looks and works like Jupyter's: a toolbar over the cells, a4040 code cell's pipeline in a box with its prompt at the left and a `▶` that41-runs it at the right, its output under it, and note cells drawn as text:41+runs it at the right, its output under it, and note cells drawn as text.4242 43-```44- ▶ Run ■ Stop ↻ Restart ▶▶ Run all │ + Add ✂ Cut ⧉ Copy ⎘ Paste │ Code ▾ nu ○ idle45- big: reads $files; read as $big, nu.big in formulas ──────────────── Enter edit46- Files47- What's big in the repo, from ls.48-49-▌ ╭─ big ───────────────────────────────────────────── ✓ <1s ─╮50-▌ [2]: │ big = $files | where size > 1kb │ ▶51-▌ │ | sort-by size --reverse │52-▌ ╰────────────────────────────────────────────────────────────╯53-▌ Out[2]: name size54-▌ 1 README.md 9.8 kB55-▌ 2 012 4.2 kB56-```43+44+45+46+### Reading a cell47+48+Every state shows in words or marks as well as in color, so a notebook49+reads the same on a terminal without it:50+51+| Where | Shows |52+|---|---|53+| The tab | `❯` before the notebook's name |54+| Left of the cells | A bar at the active cell, blue in command mode and green in edit mode, and a thinner one at the others selected ([Keys](#keys)) |55+| The box | Light lines, and heavy ones around the cell being edited |56+| The prompt | How many runs came before this one: `[2]:`, `[*]:` while it runs, `[ ]:` before it has; `Out[2]:` marks the output of that run |57+| The box's top border | The cell's name at the left, and at the right how its run stands (below): `waiting`, `running`, `● live` and the rows printed for a cell run as a [stream](#streams), ✓ and how long it took, `failed`, `stale`, or `saved` for an output read from the file |58+| Right of the box | `▶` runs the cell, `■` stops it while it runs |59+| The output | What it is ([below](#outputs)): a table or a record as a grid, its column names where a sheet has letters; `×` before an error; `output hidden`; which rows show in its window. Worked in, `OUTPUT` in the mode indicator, the bar left of it green, and its pointer and selection in reverse video, as on a sheet ([Outputs as grids](#outputs-as-grids)) |60+| The context line | What the cell reads and how others read it (`big: reads $files; read as $big, nu.big in formulas`), the keys that apply, and what nu says about the cell being written, underlined curly in the cell ([Writing a cell](#writing-a-cell)) |5761 58-The prompt says how many runs came before this one (`[2]:`, `[*]:` while59-it runs, `[ ]:` before it has), and `Out[2]:` marks the output of that60-run. The box's top border names the cell and says at its right how its61-run stands, `saved` for an output read from the file:62+A cell's run goes through these states:6263 6364 ```mermaid6465 stateDiagram-v2@@ -82,6 +83,8 @@ stateDiagram-v2
8283 A source is shown whole: a line too long for the screen wraps before a8384 pipe where it can, its next rows indented.8485 86+### Outputs87+8588 Outputs are drawn by what they are, with values formatted as cells8689 ([Types](types.md)):8790 @@ -132,7 +135,8 @@ indicator says `OUTPUT`, and the bar left of it turns green, as for a
132135 cell edited): an active cell moves with the arrows, and the grid's keys,133136 menus and mouse work on it as on a sheet, in its window:134137 135-138+139+136140 137141 - Shift+arrows, Ctrl+A or a drag select a range, and the status line138142 shows its Sum, Avg and Count; Ctrl+C copies it, as TSV to the system@@ -176,6 +180,8 @@ notebook's column and scroll with the cells; on the sheet it has room,
176180 its editor, images where the terminal draws them, and it follows the177181 cell's next run, as the region it charts does.178182 183+### Note cells184+179185 Note cells are Markdown: headings, **bold**, *italic*, `code`, links (the180186 terminal opens them), lists and quotes. They're drawn as text, without a181187 box or a prompt, and show their Markdown in a box only while edited.@@ -196,11 +202,10 @@ stateDiagram-v2
196202 Output --> Command: Esc197203 ```198204 199-A bar at the left marks the active cell: `▌`, blue in command mode and200-green in edit mode, where the cell's box is drawn in heavy lines too, so201-the mode reads without color. Shift+Up and Shift+Down (or `K` and `J`,202-or Shift+click) select the cells passed over as well, marked `▎`, and203-the commands below that act on "the cells" act on all of them: run,205+The bar left of the active cell and the box's lines say which mode206+you're in ([Reading a cell](#reading-a-cell)). Shift+Up and Shift+Down207+(or `K` and `J`, or Shift+click) select the cells passed over as well,208+and the commands below that act on "the cells" act on all of them: run,204209 delete, copy, cut, move, make notes or code, hide or clear outputs.205210 206211 | Key | In command mode |@@ -281,12 +286,8 @@ keywords, operators; flags stay plain), and `nu --ide-check` what's
281286 wrong. A problem is underlined with a curly line, and with the caret on282287 it the context line says what nu said:283288 284-```285- [ ] not run286-│ $files | sort-by size --revrse287- ~~~~~~~~288-The `sort-by` command doesn't have flag `revrse`.289-```289+290+290291 291292 Tab asks `nu --ide-complete` too, after the notebook's own names, so it292293 completes flags, subcommands and paths as well as cells and commands.@@ -422,7 +423,8 @@ nu.files, 3, FALSE)`), and charts and pivot tables use. It's also a
422423 [table](../sheets/tables.md#notebook-outputs-and-linked-files) by the423424 cell's name, read by column: `=SUM(big[size])`, `=COUNTIF(app[status],424425 500)`, with `big` alone its rows under the header. A cell without a425-name is given one first.426+name is given one first. On the region's cells the context line says427+whose output they are (`Output of big`).426428 427429 ```mermaid428430 sequenceDiagram@@ -5,23 +5,25 @@ sidebar_position: 2
55 66 # Pipelines77 8-012 can be a stage in a pipeline: a table comes in on standard input,9-you look at it or edit it in the terminal, and with `--pipe` the result10-goes out on standard output for the next command.8+012 can be a stage in a pipeline: a table comes in, you look at it or9+edit it in the terminal, and what you send back goes on to the next10+command. With the [`sheet` command](#the-sheet-command) installed11+(`012 nu --install-module`), that stage is one word:1112 1213 ```nu13-ls | select name size | to nuon | ^012 --pipe | from nuon | where size > 1kb14+ls | select name size | sheet --send selection | where size > 1kb1415 ```1516 16-17+1718 19+`sheet` is shorthand for20+`to nuon | ^012 --pipe | from nuon`:1821 [`to nuon`](https://www.nushell.sh/commands/docs/to_nuon.html) writes1922 the table in NUON, nushell's own notation, so file sizes, durations and2023 dates arrive as themselves, and2124 [`from nuon`](https://www.nushell.sh/commands/docs/from_nuon.html) reads22-what 012 sends back with the same types ([Types](types.md)). With the23-[`sheet` command](#the-sheet-command) installed, the same stage is24-`ls | select name size | sheet | where size > 1kb`.25+what 012 sends back with the same types ([Types](types.md)). Without the26+module, write it out.2527 2628 The screen is the terminal's own (`/dev/tty`, or the console on2729 Windows), not standard input or output, so the pipeline's data never@@ -97,31 +97,36 @@ copied, cut or moved.
9797 9898 ## Borders9999 100-Format > Borders draws lines along the edges of the selected cells: all101-of them, the outline (outer), the lines between the cells (inner), or102-one side; None removes them. Lines are thin, thick or double, whichever103-of Thin lines, Thick lines and Double lines was picked last (thin at104-first), as Sheets' border style keeps its choice. Alt+Shift+1 to 4 draw105-the top, right, bottom and left border, Alt+Shift+7 the outline and106-Alt+Shift+6 clears them ([keys](../reference/keys.md#formatting)).100+101+102+103+Select the cells, then Format > Borders:104+105+- **All**, **Outer** (the outline) or **Inner** (the lines between the106+ cells) draws those edges; **Top**, **Bottom**, **Left** and **Right**107+ one side; **None** removes them.108+- **Thin lines**, **Thick lines** and **Double lines** pick the line the109+ next border is drawn with, thin until you pick, as Sheets' border110+ style keeps its choice.111+- Alt+Shift+1 to 4 draw the top, right, bottom and left border,112+ Alt+Shift+7 the outline and Alt+Shift+6 clears them113+ ([keys](../reference/keys.md#formatting)).114+115+Where two cells share an edge, the heavier line shows, and drawing an116+edge again from one side replaces the other side's line. Borders are117+part of a cell's formatting: they fall back on rows and columns, travel118+with copies and go with Clear formatting. The outline of whole columns119+or rows runs along the sheet's own edges too: over row 1 and under the120+last row, or down column A and the last column.107121 108122 The grid draws borders with box-drawing characters, which terminals109-join where lines meet (`┌─┬─┐`, `╔═╤═╗`). A line down the side of a cell110-takes the first column of the cell to its right, which is padding111-anyway. A terminal has no thinner line between two rows of characters,112-so a line along the top of a row takes a line of the screen above it,113-as tables printed in a terminal do: a bordered table is taller than a114-plain one. Where two cells share an edge, the heavier line shows, and115-drawing an edge again from one side replaces the other side's line.116-Borders are part of a cell's formatting: they fall back on rows and117-columns, travel with copies and go with Clear formatting. The outline118-of whole columns or rows runs along the sheet's own edges too: over119-row 1 and under the last row, or down column A and the last column.120-121-Unicode has no joint where a double line meets a thick one, so there122-the joint is drawn thick, the heavier look, and the double line starts123-beside it with a thick stub: a double line under the headers of a124-table with a thick outline reads `┣━═══════╪═══════━┫`.123+join where lines meet. A line down the side of a cell takes the first124+column of the cell to its right, which is padding anyway. A terminal125+has no thinner line between two rows of characters, so a line along126+the top of a row takes a line of the screen above it, as tables printed127+in a terminal do: a bordered table is taller than a plain one. Unicode128+has no joint where a double line meets a thick one, so there the joint129+is drawn thick, and the double line starts beside it.125130 126131 ### Border colors127132 @@ -5,95 +5,137 @@ sidebar_position: 7
55 66 # Conditional formatting and data validation77 8+Rules make cells show and check what they hold, as Sheets' do.9+Conditional formatting colors a cell by its value, or draws a bar or an10+icon in it; data validation limits what a cell takes, and gives it a11+dropdown or a checkbox. Both only change how cells look and what they12+accept, never a value already there.13+14+15+16+817 ## Conditional formatting918 10-Format > Conditional formatting opens the sheet's rules in a panel at the11-right of the grid, as Sheets' sidebar: `+ Add rule` adds one over the12-selection, Enter edits the highlighted rule, Del removes it and13-Shift+Up/Down moves it. In a rule's form, Up/Down pick a line,14-Left/Right change a choice, Space flips a check, text lines are typed15-into, Enter saves and Esc goes back without saving. Every change is an16-undo step.19+Select the cells, then Format > Conditional formatting. The sheet's20+rules open in a panel at the right of the grid, as Sheets' sidebar:1721 18-22+23+1924 20-A rule applies to ranges (`A2:A100`, or several: `A2:A9,C2:C9`) and is21-one of:22-23-- **Single color**: cells that meet a condition get a text color, a fill,24- and bold, italic, underline or strikethrough. The conditions are25- Sheets': is empty, is not empty, text contains, does not contain,26- starts with, ends with, is exactly (ignoring case), date is, is before,27- is after, greater than, greater than or equal to, less than, less than28- or equal to, is equal to, is not equal to, is between, is not between,29- and custom formula is; and Excel's, which compare a cell with the rest30- of the rule's cells: top values and bottom values (the highest or31- lowest 1 to 1000, ties included), top and bottom percent, above and32- below average, and duplicate and unique values (text ignoring case).33- A value may be a formula (`=B2`), and a custom formula (`=$C2>100`) is34- written for the first cell of the first range: its relative references35- move with each cell, as a copied formula's would.36-- **Color scale**: every number in the ranges is shaded along two or three37- colors, from its minimum to its maximum; each point is the minimum or38- maximum value, a number, a percent of the way between them, or a39- percentile, and a midpoint is optional.40-- **Data bar**: every number gets a bar across its cell, as long as the41- number is far from the shortest point to the longest. Automatic points42- run from zero (or the lowest number, when that's below zero) to the43- highest; either may be a number, a percent or a percentile. Bars are44- drawn in eighth blocks (`██▍`) in one of the named colors, so a bar45- ends within a column, and text they run under is drawn in reverse46- video; Show bar only hides the numbers.47-- **Icon set**: every number gets an icon at its cell's left by the48- thresholds it reaches, each a percent of the way from lowest to49- highest, a number or a percentile: arrows (`↓ → ↑`, in 3, 4 or 5),50- circles filling up (`○ ◑ ●`, in 3, 4 or 5), symbols (`✗ ! ✓`) or51- rating bars (`▂ ▄ ▆ █`, in 4 or 5), the lowest red, the highest green52- and those between yellow (rating bars blue). Reverse icons gives the53- lowest values the highest icon, and Show icon only hides the numbers.54-55-Dates in "date is", "is before" and "is after" are a date, or a period56-as Sheets and Excel name them: today, tomorrow, yesterday, the past57-week, month or year, and this, last or next week, month or year (weeks58-run Sunday to Saturday). A date is in a period, before its first day or59-after its last.60-61-Data bars and icons are glyphs, so they read without color as they do62-in it; their colors are the terminal's, as the other rules' are.25+- **Add a rule**: `+ Add rule` makes one over the selection and opens26+ its form. Up and Down pick a line, Left and Right change a choice,27+ Space flips a check, text lines are typed into; Enter saves and Esc28+ goes back without saving.29+- **Change one**: Enter edits the highlighted rule, Del removes it, and30+ Shift+Up and Shift+Down move it up or down the list.31+- **Take cells out of every rule**: Format > Clear conditional formats32+ on the selection.33+34+Every change is an undo step. A rule applies to ranges (`A2:A100`, or35+several: `A2:A9,C2:C9`), and is one of four kinds:36+37+| Rule | Does | Shows |38+|---|---|---|39+| Single color | Formats the cells that meet a [condition](#conditions) | A text color, a fill, and bold, italic, underline or strikethrough |40+| Color scale | Shades every number from its [lowest point to its highest](#points) | A fill along two or three colors |41+| Data bar | Draws a bar across every number, as long as the number is far from the shortest point to the longest | A bar in one of the named colors; the text over it in reverse video. Show bar only hides the numbers |42+| Icon set | Puts an icon at the left of every number by the thresholds it reaches | Arrows (3, 4 or 5), circles filling up (3, 4 or 5), symbols (3) or rating bars (4 or 5), the lowest red, the highest green, those between yellow (rating bars blue). Reverse icons turns them around; Show icon only hides the numbers |43+44+Data bars and icons are characters (eighth blocks, arrows, circles), so45+they read without color as they do in it.46+47+### Conditions48+49+A single-color rule formats the cells that meet its condition; a value50+in it may be a formula (`=B2`).51+52+| Condition | A cell meets it when |53+|---|---|54+| Is empty, is not empty | It's blank, or not |55+| Text contains, does not contain, starts with, ends with, is exactly | Its text does, ignoring case |56+| Date is, is before, is after | Its date is, is before or is after a date or a [period](#dates-and-periods) |57+| Greater than, greater than or equal to, less than, less than or equal to, is equal to, is not equal to, is between, is not between | Its number compares so |58+| Custom formula is | The formula is TRUE for it |59+| Top values, bottom values | It's among the highest or lowest 1 to 1000 of the rule's cells, ties included |60+| Top percent, bottom percent | It's in the highest or lowest percent of them |61+| Above average, below average | It's above or below their average |62+| Duplicate values, unique values | Another of them holds the same (text ignoring case), or none does |63+64+The last four rows are Excel's, and compare a cell with the rest of the65+rule's cells; the others are Sheets'. A custom formula (`=$C2>100`) is66+written for the first cell of the first range: its relative references67+move with each cell, as a copied formula's would.68+69+#### Dates and periods70+71+A date condition takes a date, or a period as Sheets and Excel name72+them: today, tomorrow, yesterday, the past week, month or year, and73+this, last or next week, month or year (weeks run Sunday to Saturday). A74+date is in a period, before its first day or after its last.75+76+### Points77+78+A color scale runs from a minimum to a maximum, with an optional79+midpoint, and a data bar from its shortest point to its longest. Each80+point is the lowest or highest value, a number, a percent of the way81+between them, or a percentile. A data bar's automatic points run from82+zero (or the lowest number, when that's below zero) to the highest. An83+icon set's thresholds are each a percent of the way from lowest to84+highest, a number or a percentile.85+86+### Which rule wins, and colors6387 6488 Rules are tried in order, and the first that applies to a cell formats65-it, so a rule higher in the list wins. The pointer and the selection draw66-over a rule's colors; its text styles show through them.89+it, so a rule higher in the list wins. The pointer and the selection90+draw over a rule's colors; its text styles show through them.6791 6892 Colors are named (red, yellow, green, cyan, blue, magenta) rather than6993 picked from a color wheel, because each is one of the terminal's 16 ANSI70-colors: the terminal's palette or the color scheme ([Themes](../terminal/themes.md))71-decides what they look like, and a fill's text is drawn in whichever of72-black or white reads on it. A text color that wouldn't read on a rule's73-fill takes the fill's ink instead. Color scales blend the scheme's colors74-(or the terminal's, as it reports them) and pick readable text for each75-shade.94+colors: the terminal's palette or the color scheme95+([Themes](../terminal/themes.md)) decides what they look like, and a96+fill's text is drawn in whichever of black or white reads on it. A text97+color that wouldn't read on a rule's fill takes the fill's ink instead.98+Color scales blend the scheme's colors (or the terminal's, as it reports99+them) and pick readable text for each shade.76100 77101 Rules only change how cells look, so they may go on a pivot table's78102 results. They follow inserted and deleted rows and columns, and a rule79-whose cells are all deleted goes. Format > Clear conditional formats80-takes the selection out of every rule.103+whose cells are all deleted goes.81104 82105 ## Data validation83106 84-Data > Data validation opens the sheet's validation rules in the same85-panel; Insert > Dropdown opens it with a dropdown for the selection, and86-Insert > Checkbox makes the selection checkboxes at once. The criteria:107+Select the cells, then Data > Data validation: the sheet's validation108+rules open in the same panel, and work as conditional formats' do.109+Insert > Dropdown opens it with a dropdown for the selection, and110+Insert > Checkbox makes the selection checkboxes at once.111+112+87113 88114 | Criteria | Cells may hold | Shows |89115 |---|---|---|90-| Dropdown | one of the items typed (`Yes, No, Maybe`), ignoring case | the value on a chip, `▐Yes ▾▌`, or ▾ at the right of the cell |91-| Dropdown (from a range) | one of the values in a range, on any sheet (`Lists!A1:A20`) | a chip, or ▾ |116+| Dropdown | One of the items typed (`Yes, No, Maybe`), ignoring case | The value on a chip, or ▾ at the right of the cell |117+| Dropdown (from a range) | One of the values in a range, on any sheet (`Lists!A1:A20`) | A chip, or ▾ |92118 | Checkbox | TRUE or FALSE, or values of its own (`Yes` and `No`); a blank cell is unchecked | `[✓]` or `[ ]`, centered |93-| Number | a number between, not between, equal to, greater than ... values | |94-| Date | any date, or one on, after, before, between ... dates | |95-| Text length | text whose length is between, less than ... | |96-| Custom formula is | anything for which the formula (`=B2<=C2`) is TRUE | |119+| Number | A number between, not between, equal to, greater than ... values | |120+| Date | Any date, or one on, after, before, between ... dates | |121+| Text length | Text whose length is between, less than ... | |122+| Custom formula is | Anything for which the formula (`=B2<=C2`) is TRUE | |123+124+Blanks are always valid, and a formula is checked by the value it125+computes. Each cell has one rule: a rule added over cells that had126+another takes them from it. Validation can't go on a pivot table's127+results, which take no entries. Data > Remove data validation takes the128+selection out of every rule.129+130+### Dropdowns and checkboxes131+132+- **Pick an item**: Alt+Down, or a click on the ▾, opens a dropdown's133+ list under the cell; type to search, and Enter enters the item as if134+ typed. When the active cell has no dropdown, Alt+Down opens the135+ column's filter.136+- **Check a box**: Space, or a click on the box, checks or unchecks the137+ selected checkboxes (all checked when the active one isn't, as in138+ Sheets); Space anywhere else starts an entry as usual.97139 98140 A dropdown's Display is Sheets' too: Chip draws its value between99141 rounded ends in reverse video, with the ▾ inside (new dropdowns), Arrow@@ -102,12 +144,7 @@ Checked and Unchecked values, when set, are what checking and
102144 unchecking enter instead of TRUE and FALSE; with only a checked value,103145 unchecking leaves the cell blank.104146 105-Alt+Down, or a click on the ▾, opens a dropdown's list under the cell:106-type to search, Enter enters the item as if typed. Space, or a click on107-the box, checks or unchecks the selected checkboxes (all checked when108-the active one isn't, as in Sheets); Space anywhere else starts an entry109-as usual. When the active cell has no dropdown, Alt+Down opens the110-column's filter.147+### Rejected or warned111148 112149 Each rule either **rejects** an invalid entry or **shows a warning**.113150 Rejected, the entry stays open with Sheets' message on the context line@@ -115,13 +152,7 @@ Rejected, the entry stays open with Sheets' message on the context line
115152 fixing or Esc. Warned, it goes in, and the cell's text gets a dotted116153 underline; on the active cell the context line says why ("Invalid:117154 Input must be ..."). A rule's help text, when it has one, replaces its118-own words there. A formula is checked by the value it computes. Blanks119-are always valid.120-121-Each cell has one rule: a rule added over cells that had another takes122-them from it. Validation can't go on a pivot table's results, which take123-no entries. Data > Remove data validation takes the selection out of124-every rule.155+own words there.125156 126157 What validation checks:127158 @@ -10,6 +10,9 @@ Sheets' and Excel's tables. Formulas read it by those names,
1010 `=SUM(Sales[Amount])`, so they keep reading the right cells as the table1111 grows and its columns move ([Structured references](../formulas/references.md#tables-by-column-name)).1212 13+14+15+1316 ## Making one1417 1518 Format > Convert to table (Ctrl+Alt+T) makes a table of the selection, or@@ -25,18 +28,9 @@ A blank header is named after its place (`Column3`) and a repeat
2528 numbered (`Amount2`), in the cell too, so the header shows what formulas2629 use. A selection of one row gains an empty row of data below it.2730 28-```29- A B C30- 1 Region Units Amount31- 2 North 2 1032- 3 South 3 2033- 4 East 5 3034-```35-36-Made a table named `Sales`, this reads `Sales[Amount]` as C2:C4 and37-`Sales[#All]` as A1:C4; a value typed in row 5 joins it.38-39-On a table's cell the context line names the table and the column, and31+The table above is A1:D7, named `Sales`: `Sales[Amount]` reads D2:D732+and `Sales[#All]` reads A1:D7, and a value typed in row 8 joins it. On33+a table's cell the context line names the table and the column, and4034 how a formula reads it: `Table Sales, column Amount: Sales[Amount] in4135 formulas`.4236 @@ -33,7 +33,10 @@ func TestPanicKeepsWork(t *testing.T) {
3333 if !visible {3434 t.Error("the cursor is hidden after the crash")3535 }36- text := strings.ReplaceAll(s.screen(), "\n", "")36+ // The lines wrap at the terminal's width, dropping the space a37+ // line ends on, so compare without whitespace.38+ flat := func(s string) string { return strings.Join(strings.Fields(s), "") }39+ text := flat(s.screen())3740 recovery, _ := filepath.Glob(filepath.Join(dir, ".config", "012", "recovery", "*budget-*.012"))3841 reports, _ := filepath.Glob(filepath.Join(dir, ".config", "012", "crashes", "crash-*.txt"))3942 if len(recovery) != 1 || len(reports) != 1 {@@ -41,7 +44,7 @@ func TestPanicKeepsWork(t *testing.T) {
4144 }4245 for _, want := range []string{"012: stopped on an internal error (a panic in " + strings.Replace(where, "command", "a command", 1), "unsaved changes kept in " + recovery[0] + "; open budget.012 again to restore them",4346 "a report is in " + reports[0]} {44- if !strings.Contains(text, want) {47+ if !strings.Contains(text, flat(want)) {4548 t.Errorf("the screen lacks %q:\n%s", want, s.screen())4649 }4750 }@@ -15,6 +15,7 @@ require (
1515 github.com/xuri/efp v0.0.1 // indirect1616 github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect1717 golang.org/x/crypto v0.53.0 // indirect18+ golang.org/x/image v0.45.0 // indirect1819 golang.org/x/net v0.56.0 // indirect19- golang.org/x/text v0.38.0 // indirect20+ golang.org/x/text v0.41.0 // indirect2021 )@@ -24,9 +24,13 @@ golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
2424 golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=2525 golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=2626 golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=27+golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=28+golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=2729 golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=2830 golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=2931 golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=3032 golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=33+golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=34+golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=3135 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=3236 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=@@ -37,6 +37,7 @@ func TestMain(m *testing.M) {
3737 fmt.Fprintf(os.Stderr, "building 012: %v\n%s", err, out)3838 os.Exit(1)3939 }40+ addDocScreens()4041 code := m.Run()4142 os.RemoveAll(dir)4243 os.Exit(code)@@ -0,0 +1,281 @@
1+package e2e2+3+import (4+ "image"5+ "image/color"6+ "math"7+)8+9+// Box drawing and block elements, drawn to the cell's edges as10+// terminals draw them, so a frame's lines meet across cells.11+12+// boxArms gives each box-drawing character the weight of its four arms,13+// up, right, down and left: 0 none, 1 light, 2 heavy, 3 double.14+var boxArms = map[rune]string{15+ '─': "0101", '━': "0202", '│': "1010", '┃': "2020",16+ '╴': "0001", '╵': "1000", '╶': "0100", '╷': "0010",17+ '╸': "0002", '╹': "2000", '╺': "0200", '╻': "0020",18+ '╼': "0201", '╽': "1020", '╾': "0102", '╿': "2010",19+ '╭': "0110", '╮': "0011", '╯': "1001", '╰': "1100",20+}21+22+// boxRuns are the characters U+250C to U+254B and U+2550 to U+256C, in23+// order, as their arms.24+const (25+ boxRun1 = "0110 0210 0120 0220 0011 0012 0021 0022 1100 1200 2100 2200 1001 1002 2001 2002 " +26+ "1110 1210 2110 1120 2120 2210 1220 2220 1011 1012 2011 1021 2021 2012 1022 2022 " +27+ "0111 0112 0211 0212 0121 0122 0221 0222 1101 1102 1201 1202 2101 2102 2201 2202 " +28+ "1111 1112 1211 1212 2111 1121 2121 2112 2211 1122 1221 2212 1222 2122 2221 2222"29+ boxRun2 = "0303 3030 0310 0130 0330 0013 0031 0033 1300 3100 3300 1003 3001 3003 " +30+ "1310 3130 3330 1013 3031 3033 0313 0131 0333 1303 3101 3303 1313 3131 3333"31+)32+33+// boxDashes are the dashed lines: vertical or not, heavy or not, and34+// how many dashes a cell.35+var boxDashes = map[rune]struct {36+ vertical, heavy bool37+ n int38+}{39+ '┄': {false, false, 3}, '┅': {false, true, 3}, '┆': {true, false, 3}, '┇': {true, true, 3},40+ '┈': {false, false, 4}, '┉': {false, true, 4}, '┊': {true, false, 4}, '┋': {true, true, 4},41+ '╌': {false, false, 2}, '╍': {false, true, 2}, '╎': {true, false, 2}, '╏': {true, true, 2},42+}43+44+func init() {45+ for i, arms := range splitFields(boxRun1) {46+ boxArms[rune(0x250c+i)] = arms47+ }48+ for i, arms := range splitFields(boxRun2) {49+ boxArms[rune(0x2550+i)] = arms50+ }51+}52+53+func splitFields(s string) []string {54+ var out []string55+ for i := 0; i+4 <= len(s); i += 5 {56+ out = append(out, s[i:i+4])57+ }58+ return out59+}60+61+// boxCell is one cell being drawn: its rectangle, center and the widths62+// of its lines.63+type boxCell struct {64+ img *image.NRGBA65+ cell image.Rectangle66+ c color.RGBA67+ mx, my float32 // the center68+ t, d float32 // a light line's width; a double line's offset from center69+ w [4]int // arms' weights70+}71+72+// boxDrawing draws U+2500 to U+257F; false for one it doesn't know.73+func boxDrawing(img *image.NRGBA, r rune, cell image.Rectangle, c color.RGBA) bool {74+ t := float32(lineWidth(cell.Dx()))75+ b := boxCell{img: img, cell: cell, c: c, t: t, d: t * 2,76+ mx: float32(cell.Min.X) + float32(cell.Dx())/2, my: float32(cell.Min.Y) + float32(cell.Dy())/2}77+ if dash, ok := boxDashes[r]; ok {78+ b.dashes(dash.vertical, dash.heavy, dash.n)79+ return true80+ }81+ if r >= '╱' && r <= '╳' {82+ b.diagonal(r)83+ return true84+ }85+ arms, ok := boxArms[r]86+ if !ok {87+ return false88+ }89+ for i := range b.w {90+ b.w[i] = int(arms[i] - '0')91+ }92+ if r >= '╭' && r <= '╰' {93+ b.rounded(r)94+ return true95+ }96+ for i := range b.w {97+ b.arm(i)98+ }99+ return true100+}101+102+func (b *boxCell) thickness(weight int) float32 {103+ if weight == 2 {104+ return 2.5 * b.t105+ }106+ return b.t107+}108+109+// rect fills the part of arm i from s0 to its cell edge, s measured from110+// the center outward, and q across it (down or right positive).111+func (b *boxCell) rect(i int, s0, q0, q1 float32) {112+ half := float32(b.cell.Dx()) / 2113+ if i%2 == 0 {114+ half = float32(b.cell.Dy()) / 2115+ }116+ var x0, y0, x1, y1 float32117+ switch i {118+ case 0:119+ x0, x1, y0, y1 = b.mx+q0, b.mx+q1, b.my-half, b.my-s0120+ case 1:121+ x0, x1, y0, y1 = b.mx+s0, b.mx+half, b.my+q0, b.my+q1122+ case 2:123+ x0, x1, y0, y1 = b.mx+q0, b.mx+q1, b.my+s0, b.my+half124+ default:125+ x0, x1, y0, y1 = b.mx-half, b.mx-s0, b.my+q0, b.my+q1126+ }127+ rd := func(v float32) int { return int(math.Round(float64(v))) }128+ fill(b.img, image.Rect(rd(x0), rd(y0), rd(x1), rd(y1)), b.c)129+}130+131+// arm draws arm i, joined to the lines across it.132+func (b *boxCell) arm(i int) {133+ k := b.w[i]134+ if k == 0 {135+ return136+ }137+ perpA, perpB, opp := b.w[(i+1)%4], b.w[(i+3)%4], b.w[(i+2)%4]138+ if k != 3 {139+ th := b.thickness(k)140+ s0 := -th / 2141+ switch perp := max(perpA, perpB); {142+ case perp == 3 && opp == 0 && min(perpA, perpB) == 0:143+ s0 = -b.d - b.t/2 // a corner: reach the far line144+ case perp == 3 && opp == 0:145+ s0 = b.d - b.t/2 // a tee: stop at the near line146+ case perp == 3:147+ s0 = -b.d - b.t/2148+ case perp > 0:149+ s0 = -b.thickness(perp) / 2150+ }151+ b.rect(i, s0, -th/2, th/2)152+ return153+ }154+ for _, o := range []float32{-1, 1} {155+ b.rect(i, b.doubleStart(i, o), o*b.d-b.t/2, o*b.d+b.t/2)156+ }157+}158+159+// doubleStart is where the line of double arm i on side o starts: at160+// the inner line of a double arm on its side, the outer line of one on161+// the other side, or the line across it.162+func (b *boxCell) doubleStart(i int, o float32) float32 {163+ side, other := 2, 0 // for a horizontal arm, o > 0 is down164+ if i%2 == 0 {165+ side, other = 1, 3166+ }167+ if o < 0 {168+ side, other = other, side169+ }170+ switch {171+ case b.w[side] == 3:172+ return b.d - b.t/2173+ case b.w[other] == 3:174+ return -b.d - b.t/2175+ case b.w[side] > 0 || b.w[other] > 0:176+ return -b.thickness(max(b.w[side], b.w[other])) / 2177+ }178+ return -b.t / 2179+}180+181+// rounded draws ╭ ╮ ╯ ╰: a quarter circle between two arms.182+func (b *boxCell) rounded(r rune) {183+ w, h := float32(b.cell.Dx()), float32(b.cell.Dy())184+ cx, cy, rad := w/2, h/2, w/2185+ var a0, a1, ox, oy float32186+ switch r {187+ case '╭':188+ ox, oy, a0, a1 = rad, rad, 1.5*math.Pi, math.Pi189+ case '╮':190+ ox, oy, a0, a1 = -rad, rad, -math.Pi/2, 0191+ case '╯':192+ ox, oy, a0, a1 = -rad, -rad, math.Pi/2, 0193+ default:194+ ox, oy, a0, a1 = rad, -rad, math.Pi/2, math.Pi195+ }196+ fillShape(b.img, b.cell, b.c, func(s *shape) {197+ pts := arc(cx+ox, cy+oy, rad, a0, a1, 16)198+ if oy > 0 {199+ pts = append(pts, pt{cx, h + 1})200+ } else {201+ pts = append(pts, pt{cx, -1})202+ }203+ s.stroke(pts, b.t)204+ })205+}206+207+func (b *boxCell) diagonal(r rune) {208+ w, h := float32(b.cell.Dx()), float32(b.cell.Dy())209+ fillShape(b.img, b.cell, b.c, func(s *shape) {210+ if r != '╲' {211+ s.seg(pt{0, h}, pt{w, 0}, b.t)212+ }213+ if r != '╱' {214+ s.seg(pt{0, 0}, pt{w, h}, b.t)215+ }216+ })217+}218+219+// dashes draws a dashed line of n dashes a cell.220+func (b *boxCell) dashes(vertical, heavy bool, n int) {221+ th := b.thickness(1)222+ if heavy {223+ th = b.thickness(2)224+ }225+ length, i := float32(b.cell.Dx()), 1226+ if vertical {227+ length, i = float32(b.cell.Dy()), 2228+ }229+ step := length / float32(n)230+ for k := range n {231+ s0 := -length/2 + step*float32(k) + step/4232+ b.segment(i, s0, s0+step/2, th)233+ }234+}235+236+// segment fills a stretch of the line along arm i's axis from s0 to s1,237+// measured from the center.238+func (b *boxCell) segment(i int, s0, s1, th float32) {239+ rd := func(v float32) int { return int(math.Round(float64(v))) }240+ if i == 2 {241+ fill(b.img, image.Rect(rd(b.mx-th/2), rd(b.my+s0), rd(b.mx+th/2), rd(b.my+s1)), b.c)242+ return243+ }244+ fill(b.img, image.Rect(rd(b.mx+s0), rd(b.my-th/2), rd(b.mx+s1), rd(b.my+th/2)), b.c)245+}246+247+// quadrants are U+2596 to U+259F as the quarters they fill: 1 upper248+// left, 2 upper right, 4 lower left, 8 lower right.249+var quadrants = [10]int{4, 8, 1, 1 | 4 | 8, 1 | 8, 1 | 2 | 4, 1 | 2 | 8, 2, 2 | 4, 2 | 4 | 8}250+251+// blockElement draws U+2580 to U+259F.252+func blockElement(img *image.NRGBA, r rune, cell image.Rectangle, c color.RGBA) bool {253+ x0, y0, w, h := cell.Min.X, cell.Min.Y, cell.Dx(), cell.Dy()254+ ex := func(n int) int { return x0 + (w*n+4)/8 } // n eighths across255+ ey := func(n int) int { return y0 + (h*n+4)/8 } // n eighths down256+ box := func(xa, ya, xb, yb int) { fill(img, image.Rect(xa, ya, xb, yb), c) }257+ switch {258+ case r == '▀':259+ box(x0, y0, cell.Max.X, ey(4))260+ case r >= '▁' && r <= '█':261+ box(x0, ey(8-int(r-'▀')), cell.Max.X, cell.Max.Y)262+ case r >= '▉' && r <= '▏':263+ box(x0, y0, ex(8-int(r-'█')), cell.Max.Y)264+ case r == '▐':265+ box(ex(4), y0, cell.Max.X, cell.Max.Y)266+ case r >= '░' && r <= '▓':267+ fill(img, cell, color.NRGBA{c.R, c.G, c.B, uint8(64 * (r - '░' + 1))})268+ case r == '▔':269+ box(x0, y0, cell.Max.X, ey(1))270+ case r == '▕':271+ box(ex(7), y0, cell.Max.X, cell.Max.Y)272+ default:273+ q := quadrants[r-'▖']274+ for bit, qr := range [4][4]int{{x0, y0, ex(4), ey(4)}, {ex(4), y0, cell.Max.X, ey(4)}, {x0, ey(4), ex(4), cell.Max.Y}, {ex(4), ey(4), cell.Max.X, cell.Max.Y}} {275+ if q&(1<<bit) != 0 {276+ box(qr[0], qr[1], qr[2], qr[3])277+ }278+ }279+ }280+ return true281+}@@ -0,0 +1,245 @@
1+package e2e2+3+import (4+ "fmt"5+ "html"6+ "image/color"7+ "strconv"8+ "strings"9+10+ "golang.org/x/text/width"11+)12+13+// A golden screen read back into cells, for drawing it as a still. The14+// HTML is what renderHTML writes: one line of text per row, runs of15+// cells with the same style in a span, colors as palette variables.16+17+// ink is a color as the golden names it: the terminal's default18+// foreground or background, a palette entry or an RGB value.19+type ink struct {20+ kind inkKind21+ palette int22+ rgb color.RGBA23+}24+25+type inkKind uint826+27+const (28+ inkNone inkKind = iota // the default: fg or bg as the slot says29+ inkFg30+ inkBg31+ inkPalette32+ inkRGB33+)34+35+// underline styles, as the golden's text-decoration names them.36+const (37+ ulNone = iota38+ ulSingle39+ ulDouble40+ ulWavy41+ ulDotted42+ ulDashed43+)44+45+// stillCell is one cell of the screen.46+type stillCell struct {47+ r rune // 0 for the second half of a wide character48+ fg, bg, ulInk ink49+ bold, italic, faint bool50+ underline int51+ strike, wide, filled bool52+}53+54+// parseScreen reads a golden's HTML into rows of cols cells.55+func parseScreen(src string, cols, rows int) ([][]stillCell, error) {56+ src = strings.TrimPrefix(src, `<pre class="screen">`)57+ src = strings.TrimSuffix(src, "</pre>")58+ grid := make([][]stillCell, rows)59+ for y := range grid {60+ grid[y] = make([]stillCell, cols)61+ }62+ for y, line := range strings.Split(src, "\n") {63+ if y >= rows {64+ return nil, fmt.Errorf("more than %d rows", rows)65+ }66+ if err := parseLine(grid[y], line); err != nil {67+ return nil, fmt.Errorf("row %d: %w", y, err)68+ }69+ }70+ return grid, nil71+}72+73+// parseLine fills row from one line of the golden.74+func parseLine(row []stillCell, line string) error {75+ x := 076+ var st stillCell77+ for line != "" {78+ switch {79+ case strings.HasPrefix(line, `<span style="`):80+ end := strings.Index(line, `">`)81+ if end < 0 {82+ return fmt.Errorf("unclosed span")83+ }84+ st = styleOf(line[len(`<span style="`):end])85+ line = line[end+2:]86+ case strings.HasPrefix(line, "</span>"):87+ st = stillCell{}88+ line = line[len("</span>"):]89+ default:90+ end := strings.IndexByte(line, '<')91+ if end < 0 {92+ end = len(line)93+ }94+ var err error95+ if x, err = putText(row, x, html.UnescapeString(line[:end]), st); err != nil {96+ return err97+ }98+ line = line[end:]99+ }100+ }101+ return nil102+}103+104+// putText writes text into row from column x in style st and returns105+// the column after it.106+func putText(row []stillCell, x int, text string, st stillCell) (int, error) {107+ for _, r := range text {108+ w := 1109+ if k := width.LookupRune(r).Kind(); k == width.EastAsianWide || k == width.EastAsianFullwidth {110+ w = 2111+ }112+ if x+w > len(row) {113+ return x, fmt.Errorf("wider than %d columns", len(row))114+ }115+ c := st116+ c.r, c.wide, c.filled = r, w == 2, true117+ row[x] = c118+ if w == 2 {119+ row[x+1] = st120+ row[x+1].filled = true121+ }122+ x += w123+ }124+ return x, nil125+}126+127+// styleOf reads a span's CSS.128+func styleOf(css string) stillCell {129+ var c stillCell130+ for _, decl := range strings.Split(css, ";") {131+ prop, val, _ := strings.Cut(decl, ":")132+ switch prop {133+ case "color":134+ c.fg = inkOf(val)135+ case "background":136+ c.bg = inkOf(val)137+ case "font-weight":138+ c.bold = val == "bold"139+ case "font-style":140+ c.italic = val == "italic"141+ case "opacity":142+ c.faint = true143+ case "text-decoration":144+ decorate(&c, val)145+ }146+ }147+ return c148+}149+150+// decorate reads a text-decoration: underline with its style and color,151+// or line-through.152+func decorate(c *stillCell, val string) {153+ f := strings.Fields(val)154+ if len(f) == 0 {155+ return156+ }157+ if f[0] == "line-through" {158+ c.strike = true159+ return160+ }161+ c.underline = ulSingle162+ for _, w := range f[1:] {163+ switch w {164+ case "double":165+ c.underline = ulDouble166+ case "wavy":167+ c.underline = ulWavy168+ case "dotted":169+ c.underline = ulDotted170+ case "dashed":171+ c.underline = ulDashed172+ default:173+ c.ulInk = inkOf(w)174+ }175+ }176+}177+178+// inkOf reads a CSS color as the goldens write them.179+func inkOf(v string) ink {180+ switch {181+ case v == "var(--fg)":182+ return ink{kind: inkFg}183+ case v == "var(--bg)":184+ return ink{kind: inkBg}185+ case strings.HasPrefix(v, "var(--vt-palette-"):186+ n, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(v, "var(--vt-palette-"), ")"))187+ return ink{kind: inkPalette, palette: n}188+ case strings.HasPrefix(v, "#") && len(v) == 7:189+ n, _ := strconv.ParseUint(v[1:], 16, 32)190+ return ink{kind: inkRGB, rgb: rgba(uint32(n))}191+ }192+ return ink{}193+}194+195+func rgba(v uint32) color.RGBA {196+ return color.RGBA{R: uint8(v >> 16), G: uint8(v >> 8), B: uint8(v), A: 0xff}197+}198+199+// stillPalette is a terminal's colors: the reference palettes the200+// gallery shows.201+type stillPalette struct {202+ ansi [16]uint32203+ fg, bg uint32204+}205+206+var (207+ darkStill = stillPalette{darkANSI, 0xc5c8c6, 0x1d1f21}208+ lightStill = stillPalette{lightANSI, 0x1d1f21, 0xfafafa}209+)210+211+// color resolves an ink; def is what inkNone means in its slot.212+func (p stillPalette) color(k ink, def inkKind) color.RGBA {213+ if k.kind == inkNone {214+ k.kind = def215+ }216+ switch k.kind {217+ case inkFg:218+ return rgba(p.fg)219+ case inkPalette:220+ return rgba(p.xterm(k.palette))221+ case inkRGB:222+ return k.rgb223+ }224+ return rgba(p.bg)225+}226+227+// xterm is palette entry n: the 16 ANSI colors, then xterm's 6x6x6 cube228+// and gray ramp.229+func (p stillPalette) xterm(n int) uint32 {230+ switch {231+ case n < 16:232+ return p.ansi[n]233+ case n < 232:234+ n -= 16235+ level := func(v int) uint32 {236+ if v == 0 {237+ return 0238+ }239+ return uint32(55 + 40*v)240+ }241+ return level(n/36)<<16 | level(n/6%6)<<8 | level(n%6)242+ }243+ g := uint32(8 + 10*(n-232))244+ return g<<16 | g<<8 | g245+}@@ -0,0 +1,229 @@
1+package e2e2+3+import (4+ "fmt"5+ "image"6+ "image/color"7+ "image/draw"8+ "math"9+10+ "golang.org/x/image/font"11+ "golang.org/x/image/font/gofont/gomono"12+ "golang.org/x/image/font/gofont/gomonobold"13+ "golang.org/x/image/font/gofont/gomonobolditalic"14+ "golang.org/x/image/font/gofont/gomonoitalic"15+ "golang.org/x/image/font/opentype"16+ "golang.org/x/image/math/fixed"17+ "golang.org/x/image/vector"18+)19+20+// Drawing a screen's cells as a picture of a terminal: text in Go Mono21+// (bundled with golang.org/x/image, so every machine draws the same22+// letters), and box drawing, blocks, braille and the symbols 012 draws23+// its chrome with drawn as shapes, as terminals draw them, so lines join24+// across cells.25+26+const (27+ stillFontSize = 26 // pixels; stills are drawn at twice the size they show28+ stillPad = 28 // around the screen, inside the window29+ stillRadius = 18 // the window's corners30+)31+32+// stillFaces are Go Mono's four styles at the still's size.33+type stillFaces struct {34+ regular, bold, italic, boldItalic font.Face35+ cw, ch, ascent int36+}37+38+func loadFaces() (*stillFaces, error) {39+ var fs stillFaces40+ for _, f := range []struct {41+ ttf []byte42+ dst *font.Face43+ }{{gomono.TTF, &fs.regular}, {gomonobold.TTF, &fs.bold}, {gomonoitalic.TTF, &fs.italic}, {gomonobolditalic.TTF, &fs.boldItalic}} {44+ parsed, err := opentype.Parse(f.ttf)45+ if err != nil {46+ return nil, err47+ }48+ face, err := opentype.NewFace(parsed, &opentype.FaceOptions{Size: stillFontSize, DPI: 72, Hinting: font.HintingNone})49+ if err != nil {50+ return nil, err51+ }52+ *f.dst = face53+ }54+ adv, _ := fs.regular.GlyphAdvance('0')55+ m := fs.regular.Metrics()56+ fs.cw = adv.Round()57+ fs.ch = int(math.Round(stillFontSize * 1.3))58+ fs.ascent = (fs.ch-(m.Ascent+m.Descent).Round())/2 + m.Ascent.Round()59+ return &fs, nil60+}61+62+func (fs *stillFaces) face(c stillCell) font.Face {63+ switch {64+ case c.bold && c.italic:65+ return fs.boldItalic66+ case c.bold:67+ return fs.bold68+ case c.italic:69+ return fs.italic70+ }71+ return fs.regular72+}73+74+// drawStill draws grid as a terminal window in palette p. It returns the75+// runes it had no way to draw.76+func drawStill(grid [][]stillCell, p stillPalette, fs *stillFaces) (*image.NRGBA, []rune) {77+ cols, rows := len(grid[0]), len(grid)78+ w, h := cols*fs.cw+2*stillPad, rows*fs.ch+2*stillPad79+ img := image.NewNRGBA(image.Rect(0, 0, w, h))80+ window(img, p)81+ var missing []rune82+ for y, row := range grid {83+ for x := range row {84+ origin := image.Pt(stillPad+x*fs.cw, stillPad+y*fs.ch)85+ if !drawCell(img, row, x, origin, p, fs) {86+ missing = append(missing, row[x].r)87+ }88+ }89+ }90+ return img, missing91+}92+93+// window fills a rounded rectangle with the terminal's background, with94+// a hairline border so the window reads on a page of the same color.95+func window(img *image.NRGBA, p stillPalette) {96+ b := img.Bounds()97+ w, h := float32(b.Dx()), float32(b.Dy())98+ border := blend(rgba(p.bg), rgba(p.fg), 0.18)99+ fillShape(img, b, border, func(s *shape) { s.roundRect(0, 0, w, h, stillRadius) })100+ fillShape(img, b, rgba(p.bg), func(s *shape) { s.roundRect(2, 2, w-2, h-2, stillRadius-2) })101+}102+103+// drawCell draws cell x of row at origin; false when its rune has no104+// glyph.105+func drawCell(img *image.NRGBA, row []stillCell, x int, origin image.Point, p stillPalette, fs *stillFaces) bool {106+ c := row[x]107+ span := 1108+ if c.wide {109+ span = 2110+ }111+ r := image.Rect(origin.X, origin.Y, origin.X+span*fs.cw, origin.Y+fs.ch)112+ if c.r == 0 && c.filled {113+ return true // the second half of a wide character, drawn with the first114+ }115+ bg := p.color(c.bg, inkBg)116+ if c.bg.kind != inkNone {117+ draw.Draw(img, image.Rect(origin.X, origin.Y, origin.X+fs.cw, origin.Y+fs.ch), image.NewUniform(bg), image.Point{}, draw.Src)118+ }119+ fg := p.color(c.fg, inkFg)120+ if c.faint {121+ fg = blend(bg, fg, 0.6)122+ }123+ ok := true124+ if c.r != 0 && c.r != ' ' && c.r != ' ' {125+ ok = drawGlyph(img, c, r, fg, fs)126+ }127+ lines(img, c, r, fg, p, fs)128+ return ok129+}130+131+// drawGlyph draws c's rune: as a shape when it's one terminals draw132+// themselves, else from the font.133+func drawGlyph(img *image.NRGBA, c stillCell, r image.Rectangle, fg color.RGBA, fs *stillFaces) bool {134+ if drawn := drawSpecial(img, c.r, r, fg, fs); drawn {135+ return true136+ }137+ face := fs.face(c)138+ if _, ok := face.GlyphAdvance(c.r); !ok {139+ return false140+ }141+ adv, _ := face.GlyphAdvance(c.r)142+ d := font.Drawer{Dst: img, Src: image.NewUniform(fg), Face: face}143+ d.Dot = fixed.P(r.Min.X, r.Min.Y+fs.ascent)144+ d.Dot.X += (fixed.I(r.Dx()) - adv) / 2145+ d.DrawString(string(c.r))146+ return true147+}148+149+// lines draws c's underline and strikethrough.150+func lines(img *image.NRGBA, c stillCell, r image.Rectangle, fg color.RGBA, p stillPalette, fs *stillFaces) {151+ t := lineWidth(fs.cw)152+ if c.strike {153+ y := r.Min.Y + fs.ascent - stillFontSize*3/10154+ fill(img, image.Rect(r.Min.X, y, r.Max.X, y+t), fg)155+ }156+ if c.underline == ulNone {157+ return158+ }159+ col := fg160+ if c.ulInk.kind != inkNone {161+ col = p.color(c.ulInk, inkFg)162+ }163+ y := r.Min.Y + fs.ascent + t*2164+ switch c.underline {165+ case ulDouble:166+ fill(img, image.Rect(r.Min.X, y-t, r.Max.X, y), col)167+ fill(img, image.Rect(r.Min.X, y+t, r.Max.X, y+2*t), col)168+ case ulWavy:169+ wavy(img, r, y, t, col)170+ case ulDotted:171+ for x := r.Min.X; x < r.Max.X; x += 2 * t {172+ fill(img, image.Rect(x, y, x+t, y+t), col)173+ }174+ case ulDashed:175+ for x := r.Min.X; x < r.Max.X; x += r.Dx() / 2 {176+ fill(img, image.Rect(x, y, x+r.Dx()*3/10, y+t), col)177+ }178+ default:179+ fill(img, image.Rect(r.Min.X, y, r.Max.X, y+t), col)180+ }181+}182+183+// wavy draws a curly underline: one wave a cell, in phase across cells.184+func wavy(img *image.NRGBA, r image.Rectangle, y, t int, col color.RGBA) {185+ amp := float32(t) * 1.2186+ fillShape(img, image.Rect(r.Min.X, y-2*t-2, r.Max.X, y+2*t+2), col, func(s *shape) {187+ var pts [][2]float32188+ w := float32(r.Dx())189+ for i := 0; i <= 12; i++ {190+ fx := w * float32(i) / 12191+ pts = append(pts, [2]float32{fx, float32(2*t+2) + amp*float32(math.Sin(2*math.Pi*float64(i)/12))})192+ }193+ s.stroke(pts, float32(t))194+ })195+}196+197+// lineWidth is a light line's width for cells cw wide.198+func lineWidth(cw int) int { return max(1, (cw+4)/8) }199+200+func fill(img *image.NRGBA, r image.Rectangle, c color.Color) {201+ draw.Draw(img, r, image.NewUniform(c), image.Point{}, draw.Over)202+}203+204+// blend mixes a toward b by t.205+func blend(a, b color.RGBA, t float64) color.RGBA {206+ mix := func(x, y uint8) uint8 { return uint8(math.Round(float64(x)*(1-t) + float64(y)*t)) }207+ return color.RGBA{mix(a.R, b.R), mix(a.G, b.G), mix(a.B, b.B), 0xff}208+}209+210+// fillShape rasterizes the shape build makes, in r's coordinates, and211+// fills it with c.212+func fillShape(img *image.NRGBA, r image.Rectangle, c color.Color, build func(*shape)) {213+ s := &shape{z: vector.NewRasterizer(r.Dx(), r.Dy())}214+ s.z.DrawOp = draw.Over215+ build(s)216+ s.z.Draw(img, r, image.NewUniform(c), image.Point{})217+}218+219+func runeList(rs []rune) string {220+ seen := map[rune]bool{}221+ var out string222+ for _, r := range rs {223+ if !seen[r] {224+ seen[r] = true225+ out += fmt.Sprintf(" %q", r)226+ }227+ }228+ return out229+}@@ -0,0 +1,157 @@
1+package e2e2+3+import (4+ "bytes"5+ "crypto/sha256"6+ "encoding/binary"7+ "encoding/hex"8+ "fmt"9+ "hash/crc32"10+ "image"11+ "image/png"12+ "os"13+ "path/filepath"14+ "strings"15+ "testing"16+)17+18+// Stills are pictures of 012 for the docs, drawn from golden screens:19+// each docScreen is recorded again from another screen's setup, on a20+// terminal the size a page shows well, dark and light, and drawn in its21+// reference palette to docs/media/<name>-dark.png and <name>-light.png.22+// `make screens` records and draws them; `make e2e` fails when a23+// still wasn't drawn from its golden as it is.24+type docScreen struct {25+ name, from string // docs/media/<name>-*.png; the screen whose setup it records26+ cols, rows uint1627+}28+29+var docScreens = []docScreen{30+ {name: "trace-view", from: "trace-view", cols: 80, rows: 14},31+ {name: "evaluate", from: "evaluate", cols: 80, rows: 12},32+ {name: "rules-bars", from: "rules-bars", cols: 80, rows: 12},33+ {name: "rules-panel", from: "rules-panel", cols: 100, rows: 12},34+ {name: "table", from: "table", cols: 80, rows: 13},35+ {name: "layout", from: "layout", cols: 80, rows: 28},36+ {name: "palette", from: "palette", cols: 80, rows: 18},37+ {name: "notebook-cells", from: "notebook-cells", cols: 80, rows: 26},38+ {name: "notebook-nu-error", from: "notebook-nu-error", cols: 80, rows: 16},39+ {name: "notebook-grid", from: "notebook-grid", cols: 80, rows: 24},40+}41+42+// stillsVersion changes when the drawing does, so every still is43+// drawn again.44+const stillsVersion = "1"45+46+// addDocScreens adds the docs' screens, docs-<name> and47+// docs-<name>-light, to the golden screens once every screen they're48+// recorded from is listed.49+func addDocScreens() {50+ byName := map[string]screen{}51+ for _, sc := range screens {52+ byName[sc.name] = sc53+ }54+ for _, d := range docScreens {55+ from, ok := byName[d.from]56+ if !ok {57+ panic("docScreens: no golden screen " + d.from)58+ }59+ sc := from60+ sc.name, sc.opts.cols, sc.opts.rows = "docs-"+d.name, d.cols, d.rows61+ light := sc62+ light.name += "-light"63+ light.opts.light = true64+ screens = append(screens, sc, light)65+ }66+}67+68+func TestStills(t *testing.T) {69+ faces, err := loadFaces()70+ if err != nil {71+ t.Fatal(err)72+ }73+ for _, d := range docScreens {74+ for _, v := range []struct {75+ suffix string76+ p stillPalette77+ }{{"dark", darkStill}, {"light", lightStill}} {78+ screen := "docs-" + d.name79+ if v.suffix == "light" {80+ screen += "-light"81+ }82+ t.Run(d.name+"-"+v.suffix, func(t *testing.T) {83+ path := filepath.Join("..", "docs", "media", d.name+"-"+v.suffix+".png")84+ checkStill(t, screen, int(d.cols), int(d.rows), v.p, faces, path)85+ })86+ }87+ }88+}89+90+// checkStill draws the still of screen into path under -update, and91+// otherwise checks the one there was drawn from the golden as it is.92+func checkStill(t *testing.T, screen string, cols, rows int, p stillPalette, fs *stillFaces, path string) {93+ src, err := os.ReadFile(filepath.Join("testdata", "screens", screen+".html"))94+ if err != nil {95+ t.Fatal(err)96+ }97+ sum := sha256.Sum256([]byte(fmt.Sprintf("%s\n%v\n%s", stillsVersion, p, src)))98+ want := hex.EncodeToString(sum[:])99+ if !*update {100+ if got := pngText(path, "012-golden"); got != want {101+ t.Errorf("%s is not drawn from the golden %s as it is; run make screens", path, screen)102+ }103+ return104+ }105+ grid, err := parseScreen(string(src), cols, rows)106+ if err != nil {107+ t.Fatalf("%s: %v", screen, err)108+ }109+ img, missing := drawStill(grid, p, fs)110+ if len(missing) > 0 {111+ t.Errorf("%s: no glyph for%s", screen, runeList(missing))112+ }113+ if err := writePNG(path, img, "012-golden", want); err != nil {114+ t.Fatal(err)115+ }116+}117+118+// writePNG writes img with a text chunk key=value after its header, so119+// the check can tell which golden it was drawn from without drawing it120+// again (drawing isn't bit for bit the same on every CPU).121+func writePNG(path string, img image.Image, key, value string) error {122+ var buf bytes.Buffer123+ enc := png.Encoder{CompressionLevel: png.BestCompression}124+ if err := enc.Encode(&buf, img); err != nil {125+ return err126+ }127+ b := buf.Bytes()128+ const header = 8 + 8 + 13 + 4 // signature, then IHDR's length, type, data and CRC129+ data := []byte(key + "\x00" + value)130+ chunk := binary.BigEndian.AppendUint32(nil, uint32(len(data)))131+ chunk = append(chunk, "tEXt"...)132+ chunk = append(chunk, data...)133+ chunk = binary.BigEndian.AppendUint32(chunk, crc32.ChecksumIEEE(chunk[4:]))134+ out := append(append(append([]byte{}, b[:header]...), chunk...), b[header:]...)135+ return os.WriteFile(path, out, 0o644)136+}137+138+// pngText is the value of the PNG's text chunk named key, or "".139+func pngText(path, key string) string {140+ b, err := os.ReadFile(path)141+ if err != nil || len(b) < 8 {142+ return ""143+ }144+ for b = b[8:]; len(b) >= 12; {145+ n := int(binary.BigEndian.Uint32(b))146+ if len(b) < 12+n {147+ return ""148+ }149+ if string(b[4:8]) == "tEXt" {150+ if k, v, ok := strings.Cut(string(b[8:8+n]), "\x00"); ok && k == key {151+ return v152+ }153+ }154+ b = b[12+n:]155+ }156+ return ""157+}@@ -0,0 +1,231 @@
1+package e2e2+3+import (4+ "image"5+ "image/color"6+ "math"7+8+ "golang.org/x/image/vector"9+)10+11+// shape builds a filled outline for the rasterizer. Every polygon is12+// wound the same way, so overlapping parts add up rather than cancel,13+// except holes, wound the other way.14+type shape struct {15+ z *vector.Rasterizer16+}17+18+type pt = [2]float3219+20+// poly adds a closed polygon; hole winds it the other way.21+func (s *shape) poly(pts []pt, hole bool) {22+ var area float3223+ for i, p := range pts {24+ q := pts[(i+1)%len(pts)]25+ area += p[0]*q[1] - q[0]*p[1]26+ }27+ if (area < 0) != hole {28+ for i, j := 0, len(pts)-1; i < j; i, j = i+1, j-1 {29+ pts[i], pts[j] = pts[j], pts[i]30+ }31+ }32+ s.z.MoveTo(pts[0][0], pts[0][1])33+ for _, p := range pts[1:] {34+ s.z.LineTo(p[0], p[1])35+ }36+ s.z.ClosePath()37+}38+39+// arc is n+1 points along a circle's arc from a0 to a1 (radians, y down).40+func arc(cx, cy, r, a0, a1 float32, n int) []pt {41+ pts := make([]pt, 0, n+1)42+ for i := 0; i <= n; i++ {43+ a := float64(a0 + (a1-a0)*float32(i)/float32(n))44+ pts = append(pts, pt{cx + r*float32(math.Cos(a)), cy + r*float32(math.Sin(a))})45+ }46+ return pts47+}48+49+func (s *shape) disk(cx, cy, r float32) {50+ pts := arc(cx, cy, r, 0, 2*math.Pi, 32)51+ s.poly(pts[:32], false)52+}53+54+// ring is a circle's outline t wide, centered on radius r.55+func (s *shape) ring(cx, cy, r, t float32) {56+ outer := arc(cx, cy, r+t/2, 0, 2*math.Pi, 32)57+ inner := arc(cx, cy, r-t/2, 0, 2*math.Pi, 32)58+ s.poly(outer[:32], false)59+ s.poly(inner[:32], true)60+}61+62+// pie is the slice of a disk from a0 to a1.63+func (s *shape) pie(cx, cy, r, a0, a1 float32) {64+ s.poly(append([]pt{{cx, cy}}, arc(cx, cy, r, a0, a1, 24)...), false)65+}66+67+// seg is a straight line t wide from a to b.68+func (s *shape) seg(a, b pt, t float32) {69+ dx, dy := b[0]-a[0], b[1]-a[1]70+ l := float32(math.Hypot(float64(dx), float64(dy)))71+ if l == 0 {72+ return73+ }74+ nx, ny := -dy/l*t/2, dx/l*t/275+ s.poly([]pt{{a[0] + nx, a[1] + ny}, {b[0] + nx, b[1] + ny}, {b[0] - nx, b[1] - ny}, {a[0] - nx, a[1] - ny}}, false)76+}77+78+// stroke is a line through pts, t wide, with round joins and ends.79+func (s *shape) stroke(pts []pt, t float32) {80+ for i := range pts {81+ if i > 0 {82+ s.seg(pts[i-1], pts[i], t)83+ }84+ s.disk(pts[i][0], pts[i][1], t/2)85+ }86+}87+88+// roundRect is a rectangle with corners of radius r.89+func (s *shape) roundRect(x0, y0, x1, y1, r float32) {90+ var pts []pt91+ pts = append(pts, arc(x1-r, y0+r, r, -math.Pi/2, 0, 8)...)92+ pts = append(pts, arc(x1-r, y1-r, r, 0, math.Pi/2, 8)...)93+ pts = append(pts, arc(x0+r, y1-r, r, math.Pi/2, math.Pi, 8)...)94+ pts = append(pts, arc(x0+r, y0+r, r, math.Pi, 3*math.Pi/2, 8)...)95+ s.poly(pts, false)96+}97+98+// symbol draws in a square the cell's width, centered on the cell:99+// (u, v) from 0 to 1 across it.100+type symbol struct {101+ *shape102+ x0, y0, side float32103+}104+105+func (y symbol) p(u, v float32) pt { return pt{y.x0 + u*y.side, y.y0 + v*y.side} }106+107+func (y symbol) tri(a, b, c pt) {108+ y.poly([]pt{y.p(a[0], a[1]), y.p(b[0], b[1]), y.p(c[0], c[1])}, false)109+}110+111+func (y symbol) line(t float32, uv ...float32) {112+ var pts []pt113+ for i := 0; i+1 < len(uv); i += 2 {114+ pts = append(pts, y.p(uv[i], uv[i+1]))115+ }116+ y.stroke(pts, t*y.side)117+}118+119+func (y symbol) box(u0, v0, u1, v1, t float32) {120+ y.line(t, u0, v0, u1, v0, u1, v1, u0, v1, u0, v0)121+}122+123+func (y symbol) rect(u0, v0, u1, v1 float32) {124+ y.poly([]pt{y.p(u0, v0), y.p(u1, v0), y.p(u1, v1), y.p(u0, v1)}, false)125+}126+127+// symbols are the shapes 012 draws its chrome with that fonts draw at128+// odd sizes or not at all: triangles, circles, checks and the notebook129+// toolbar's icons.130+var symbols = map[rune]func(y symbol){131+ '▶': func(y symbol) { y.tri(pt{.2, .12}, pt{.92, .5}, pt{.2, .88}) },132+ '▸': func(y symbol) { y.tri(pt{.3, .28}, pt{.76, .5}, pt{.3, .72}) },133+ '◀': func(y symbol) { y.tri(pt{.8, .12}, pt{.08, .5}, pt{.8, .88}) },134+ '◂': func(y symbol) { y.tri(pt{.7, .28}, pt{.24, .5}, pt{.7, .72}) },135+ '▼': func(y symbol) { y.tri(pt{.08, .22}, pt{.92, .22}, pt{.5, .86}) },136+ '▾': func(y symbol) { y.tri(pt{.22, .34}, pt{.78, .34}, pt{.5, .7}) },137+ '▲': func(y symbol) { y.tri(pt{.08, .8}, pt{.92, .8}, pt{.5, .16}) },138+ '▴': func(y symbol) { y.tri(pt{.22, .68}, pt{.78, .68}, pt{.5, .32}) },139+ '■': func(y symbol) { y.rect(.14, .14, .86, .86) },140+ '●': func(y symbol) { y.disk(y.p(.5, .5)[0], y.p(.5, .5)[1], .4*y.side) },141+ '○': func(y symbol) { circle(y, 0, 0) },142+ '◔': func(y symbol) { circle(y, -math.Pi/2, 0) },143+ '◑': func(y symbol) { circle(y, -math.Pi/2, math.Pi/2) },144+ '◕': func(y symbol) { circle(y, -math.Pi/2, math.Pi) },145+ '✓': func(y symbol) { y.line(.12, .14, .54, .4, .8, .88, .2) },146+ '✗': func(y symbol) { y.line(.12, .2, .2, .8, .8); y.line(.12, .8, .2, .2, .8) },147+ '❯': func(y symbol) { y.line(.16, .3, .16, .72, .5, .3, .84) },148+ '⧉': func(y symbol) { y.box(.08, .08, .64, .64, .08); y.box(.36, .36, .92, .92, .08) },149+ '⎘': func(y symbol) { y.box(.16, .16, .84, .94, .08); y.rect(.34, .04, .66, .26) },150+ '↻': reload,151+ '↵': func(y symbol) {152+ y.line(.09, .84, .12, .84, .62, .3, .62)153+ y.tri(pt{.06, .62}, pt{.36, .38}, pt{.36, .86})154+ },155+ '✂': func(y symbol) {156+ y.ring(y.p(.22, .26)[0], y.p(.22, .26)[1], .13*y.side, .08*y.side)157+ y.ring(y.p(.22, .74)[0], y.p(.22, .74)[1], .13*y.side, .08*y.side)158+ y.line(.09, .32, .34, .94, .7)159+ y.line(.09, .32, .66, .94, .3)160+ },161+}162+163+// circle is an outline with the slice from a0 to a1 filled.164+func circle(y symbol, a0, a1 float32) {165+ c := y.p(.5, .5)166+ y.ring(c[0], c[1], .36*y.side, .09*y.side)167+ if a1 > a0 {168+ y.pie(c[0], c[1], .38*y.side, a0, a1)169+ }170+}171+172+// reload is ↻: most of a circle, clockwise, with the arrowhead at its173+// end.174+func reload(y symbol) {175+ c := y.p(.5, .52)176+ r := .32 * y.side177+ a0, a1 := float32(-math.Pi/5), float32(1.4*math.Pi)178+ y.stroke(arc(c[0], c[1], r, a0, a1, 24), .09*y.side)179+ end := pt{c[0] + r*float32(math.Cos(float64(a1))), c[1] + r*float32(math.Sin(float64(a1)))}180+ dir := pt{-float32(math.Sin(float64(a1))), float32(math.Cos(float64(a1)))}181+ n := pt{-dir[1], dir[0]}182+ h := .22 * y.side183+ y.poly([]pt{184+ {end[0] + dir[0]*h, end[1] + dir[1]*h},185+ {end[0] + n[0]*h*.8, end[1] + n[1]*h*.8},186+ {end[0] - n[0]*h*.8, end[1] - n[1]*h*.8},187+ }, false)188+}189+190+// drawSpecial draws r as terminals draw it rather than from the font:191+// box drawing, blocks and braille fill the cell and join their192+// neighbors, and symbols are drawn at one size. False when r is text.193+func drawSpecial(img *image.NRGBA, r rune, cell image.Rectangle, c color.RGBA, fs *stillFaces) bool {194+ switch {195+ case r >= 0x2500 && r <= 0x257f:196+ return boxDrawing(img, r, cell, c)197+ case r >= 0x2580 && r <= 0x259f:198+ return blockElement(img, r, cell, c)199+ case r >= 0x2800 && r <= 0x28ff:200+ braille(img, r, cell, c)201+ return true202+ }203+ draw, ok := symbols[r]204+ if !ok {205+ return false206+ }207+ // A symbol is as big as a capital letter, centered on one, and may208+ // reach a little into the cells beside it, as fonts draw them.209+ side := float32(stillFontSize) * .9210+ area := cell.Inset(-cell.Dx() / 4)211+ mid := float32(fs.ascent+cell.Dx()/4) - float32(stillFontSize)*.33212+ fillShape(img, area, c, func(s *shape) {213+ draw(symbol{shape: s, x0: (float32(area.Dx()) - side) / 2, y0: mid - side/2, side: side})214+ })215+ return true216+}217+218+// braille draws the dots of a braille pattern, two columns of four.219+func braille(img *image.NRGBA, r rune, cell image.Rectangle, c color.RGBA) {220+ bits := int(r - 0x2800)221+ // dots 1-3 and 7 down the left, 4-6 and 8 down the right222+ pos := [8][2]int{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {0, 3}, {1, 3}}223+ w, h := float32(cell.Dx()), float32(cell.Dy())224+ fillShape(img, cell, c, func(s *shape) {225+ for i, p := range pos {226+ if bits&(1<<i) != 0 {227+ s.disk(w*(.27+.46*float32(p[0])), h*(.14+.24*float32(p[1])), w*.14)228+ }229+ }230+ })231+}@@ -0,0 +1,12 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EVAL</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Evaluate</span> <span style="font-weight:bold">B8</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> =<span style="color:var(--vt-palette-4);font-weight:bold;font-style:italic;text-decoration:underline">719.466666666667</span><span style="font-weight:bold;text-decoration:underline">*</span><span style="color:var(--vt-palette-4);font-weight:bold;font-style:italic;text-decoration:underline">4</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────────────────────────────────┤</span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Next</span> <span style="font-weight:bold;text-decoration:underline">AVERAGE(Expenses)*C2</span> <span style="color:var(--vt-palette-0)">=</span> <span style="color:var(--vt-palette-4);font-style:italic">2877.86666666667</span> <span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">2</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Transit 968+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> Total 2158.410+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">8</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2877.8667</span><span style="background:var(--vt-palette-6)"> </span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>12+The underlined part is computed next <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">evaluate</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file@@ -0,0 +1,12 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EVAL</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Evaluate</span> <span style="font-weight:bold">B8</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> =<span style="color:var(--vt-palette-6);font-weight:bold;font-style:italic;text-decoration:underline">719.466666666667</span><span style="font-weight:bold;text-decoration:underline">*</span><span style="color:var(--vt-palette-6);font-weight:bold;font-style:italic;text-decoration:underline">4</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────────────────────────────────┤</span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Next</span> <span style="font-weight:bold;text-decoration:underline">AVERAGE(Expenses)*C2</span> <span style="color:var(--vt-palette-8)">=</span> <span style="color:var(--vt-palette-6);font-style:italic">2877.86666666667</span> <span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">2</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 968+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.410+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">8</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2877.8667</span><span style="background:var(--vt-palette-6)"> </span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>12+The underlined part is computed next <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">evaluate</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file@@ -0,0 +1,28 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C4</span><span style="background:var(--vt-palette-7)"> </span> Tram 28 and the castle, before the crowds3+4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">C</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Trip to Lisbon6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┏━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━┓</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span>Day <span style="color:var(--vt-palette-0)">│</span>Cost <span style="color:var(--vt-palette-0)">│</span>Notes <span style="color:var(--vt-palette-0)">┃</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┣━════════╪═════════╪════════━┫</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span>Train, <span style="color:var(--vt-palette-0)">┃</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span>book the <span style="color:var(--vt-palette-0)">┃</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span>early <span style="color:var(--vt-palette-0)">┃</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span>Fri <span style="color:var(--vt-palette-0)">│</span> 120 <span style="color:var(--vt-palette-0)">│</span>fare <span style="color:var(--vt-palette-0)">┃</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┠─────────┼─────────┼─────────┨</span>14+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Tram</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">28</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>15+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">and</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>16+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">castle,</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>17+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">before</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>18+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>19+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">4</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>Sat <span style="color:var(--vt-palette-0)">│</span> 45 <span style="color:var(--vt-palette-0)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">crowds</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0)">┃</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┠─────────┼─────────┼─────────┨</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span>Sun <span style="color:var(--vt-palette-0)">│</span> 60 <span style="color:var(--vt-palette-0)">│</span>Fly home <span style="color:var(--vt-palette-0)">┃</span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┠─────────┼─────────┼─────────┨</span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">┃</span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┃</span>Total <span style="color:var(--vt-palette-0)">│</span> 225 <span style="color:var(--vt-palette-0)">│</span> <span style="color:var(--vt-palette-0)">┃</span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0)">┗━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━┛</span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,28 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C4</span><span style="background:var(--vt-palette-8)"> </span> Tram 28 and the castle, before the crowds3+4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">C</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Trip to Lisbon6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┏━━━━━━━━━┯━━━━━━━━━┯━━━━━━━━━┓</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span>Day <span style="color:var(--vt-palette-7)">│</span>Cost <span style="color:var(--vt-palette-7)">│</span>Notes <span style="color:var(--vt-palette-7)">┃</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┣━════════╪═════════╪════════━┫</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span>Train, <span style="color:var(--vt-palette-7)">┃</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span>book the <span style="color:var(--vt-palette-7)">┃</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span>early <span style="color:var(--vt-palette-7)">┃</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span>Fri <span style="color:var(--vt-palette-7)">│</span> 120 <span style="color:var(--vt-palette-7)">│</span>fare <span style="color:var(--vt-palette-7)">┃</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┠─────────┼─────────┼─────────┨</span>14+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Tram</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">28</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>15+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">and</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>16+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">castle,</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>17+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">before</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>18+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">the</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>19+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">4</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>Sat <span style="color:var(--vt-palette-7)">│</span> 45 <span style="color:var(--vt-palette-7)">│</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">crowds</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-7)">┃</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┠─────────┼─────────┼─────────┨</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span>Sun <span style="color:var(--vt-palette-7)">│</span> 60 <span style="color:var(--vt-palette-7)">│</span>Fly home <span style="color:var(--vt-palette-7)">┃</span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┠─────────┼─────────┼─────────┨</span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">┃</span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┃</span>Total <span style="color:var(--vt-palette-7)">│</span> 225 <span style="color:var(--vt-palette-7)">│</span> <span style="color:var(--vt-palette-7)">┃</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-7)">┗━━━━━━━━━┷━━━━━━━━━┷━━━━━━━━━┛</span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,26 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+ ▶ Run <span style="color:var(--vt-palette-8)">■</span> <span style="color:var(--vt-palette-8)">Stop</span> ↻ Restart ▶▶ Run all <span style="color:var(--vt-palette-8)">│</span> + Add ✂ ⧉ <span style="color:var(--vt-palette-8)">⎘</span> <span style="color:var(--vt-palette-8)">│</span> Code ▾ <span style="color:var(--vt-palette-0)">nu</span> <span style="color:var(--vt-palette-0)">○</span> <span style="color:var(--vt-palette-0)">idle</span>3+ <span style="color:var(--vt-palette-0)">cell</span> <span style="color:var(--vt-palette-0)">3:</span> <span style="color:var(--vt-palette-0)">reads</span> <span style="color:var(--vt-palette-0)">$files</span> <span style="color:var(--vt-palette-8)">─────────────────────────</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+↑↓</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">select</span>4+ <span style="font-weight:bold;text-decoration:underline">Files</span>5+ What's big in the repo, from <span style="color:var(--vt-palette-4)">ls</span>.6+7+ <span style="color:var(--vt-palette-8)">╭─</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>8+ <span style="color:var(--vt-palette-0)">[1]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">▶</span>9+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>10+ <span style="color:var(--vt-palette-0)">Out[1]:</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">name</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">type</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">size</span><span style="background:var(--vt-palette-7)"> </span>11+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB12+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> CLAUDE.md file 1.0 kB13+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB14+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB15+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> go.mod file 812 B16+17+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-4)">╭──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)"><1s</span> <span style="color:var(--vt-palette-4)">─╮</span>18+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-0)">[2]:</span> <span style="color:var(--vt-palette-4)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">></span> <span style="color:var(--vt-palette-1)">1kb</span> <span style="color:var(--vt-palette-4)">│</span> <span style="color:var(--vt-palette-4)">▶</span>19+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-4)">╰──────────────────────────────────────────────────────────────────╯</span>20+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-0)">Out[2]:</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">name</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">type</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">size</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="color:var(--vt-palette-4)">▌</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB22+<span style="color:var(--vt-palette-4)">▌</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB23+<span style="color:var(--vt-palette-4)">▌</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB24+25+ <span style="color:var(--vt-palette-8)">╭──────────────────────────────────────────────────────────────────╮</span>26+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,26 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+ ▶ Run <span style="color:var(--vt-palette-8)">■</span> <span style="color:var(--vt-palette-8)">Stop</span> ↻ Restart ▶▶ Run all <span style="color:var(--vt-palette-8)">│</span> + Add ✂ ⧉ <span style="color:var(--vt-palette-8)">⎘</span> <span style="color:var(--vt-palette-8)">│</span> Code ▾ <span style="color:var(--vt-palette-8)">nu</span> <span style="color:var(--vt-palette-8)">○</span> <span style="color:var(--vt-palette-8)">idle</span>3+ <span style="color:var(--vt-palette-8)">cell</span> <span style="color:var(--vt-palette-8)">3:</span> <span style="color:var(--vt-palette-8)">reads</span> <span style="color:var(--vt-palette-8)">$files</span> <span style="color:var(--vt-palette-8)">─────────────────────────</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+↑↓</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">select</span>4+ <span style="font-weight:bold;text-decoration:underline">Files</span>5+ What's big in the repo, from <span style="color:var(--vt-palette-6)">ls</span>.6+7+ <span style="color:var(--vt-palette-8)">╭─</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>8+ <span style="color:var(--vt-palette-8)">[1]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">▶</span>9+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>10+ <span style="color:var(--vt-palette-8)">Out[1]:</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">name</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">type</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">size</span><span style="background:var(--vt-palette-8)"> </span>11+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB12+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> CLAUDE.md file 1.0 kB13+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB14+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB15+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> go.mod file 812 B16+17+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-4)">╭──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)"><1s</span> <span style="color:var(--vt-palette-4)">─╮</span>18+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-8)">[2]:</span> <span style="color:var(--vt-palette-4)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">></span> <span style="color:var(--vt-palette-3)">1kb</span> <span style="color:var(--vt-palette-4)">│</span> <span style="color:var(--vt-palette-4)">▶</span>19+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-4)">╰──────────────────────────────────────────────────────────────────╯</span>20+<span style="color:var(--vt-palette-4)">▌</span> <span style="color:var(--vt-palette-8)">Out[2]:</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">name</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">type</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">size</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="color:var(--vt-palette-4)">▌</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB22+<span style="color:var(--vt-palette-4)">▌</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB23+<span style="color:var(--vt-palette-4)">▌</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB24+25+ <span style="color:var(--vt-palette-8)">╭──────────────────────────────────────────────────────────────────╮</span>26+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,24 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">OUTPUT</span><span style="background:var(--vt-palette-6)"> </span>2+ ▶ Run <span style="color:var(--vt-palette-8)">■</span> <span style="color:var(--vt-palette-8)">Stop</span> ↻ Restart ▶▶ Run all <span style="color:var(--vt-palette-8)">│</span> + Add ✂ ⧉ <span style="color:var(--vt-palette-8)">⎘</span> <span style="color:var(--vt-palette-8)">│</span> Code ▾ <span style="color:var(--vt-palette-0)">nu</span> <span style="color:var(--vt-palette-0)">○</span> <span style="color:var(--vt-palette-0)">idle</span>3+ <span style="color:var(--vt-palette-0)">Output</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-8)">───────────────</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+F</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">find</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">full-screen</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">back</span>4+ What's big in the repo, from <span style="color:var(--vt-palette-4)">ls</span>.5+6+ <span style="color:var(--vt-palette-8)">╭─</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>7+ <span style="color:var(--vt-palette-0)">[1]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">▶</span>8+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>9+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-0)">Out[1]:</span> <span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">name</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">type</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">size</span><span style="background:var(--vt-palette-4)"> </span>10+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">012</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">file</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">4.2</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">kB</span><span style="background:var(--vt-palette-4)"> </span>11+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">2</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">CLAUDE.md</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">file</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">1.0</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">kB</span><span style="background:var(--vt-palette-4)"> </span>12+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB13+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB14+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> go.mod file 812 B15+16+ <span style="color:var(--vt-palette-8)">╭──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>17+ <span style="color:var(--vt-palette-0)">[2]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">></span> <span style="color:var(--vt-palette-1)">1kb</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">▶</span>18+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>19+ <span style="color:var(--vt-palette-0)">Out[2]:</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">name</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">type</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">size</span><span style="background:var(--vt-palette-7)"> </span>20+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB21+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB22+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB23+24+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="font-weight:bold">name</span> <span style="font-weight:bold">to</span> <span style="font-weight:bold">size,</span> <span style="font-weight:bold">rows</span> <span style="font-weight:bold">1</span> <span style="font-weight:bold">to</span> <span style="font-weight:bold">2</span> <span style="color:var(--vt-palette-0)">Count</span> 6</pre>\ No newline at end of file@@ -0,0 +1,24 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">OUTPUT</span><span style="background:var(--vt-palette-6)"> </span>2+ ▶ Run <span style="color:var(--vt-palette-8)">■</span> <span style="color:var(--vt-palette-8)">Stop</span> ↻ Restart ▶▶ Run all <span style="color:var(--vt-palette-8)">│</span> + Add ✂ ⧉ <span style="color:var(--vt-palette-8)">⎘</span> <span style="color:var(--vt-palette-8)">│</span> Code ▾ <span style="color:var(--vt-palette-8)">nu</span> <span style="color:var(--vt-palette-8)">○</span> <span style="color:var(--vt-palette-8)">idle</span>3+ <span style="color:var(--vt-palette-8)">Output</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">───────────────</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+F</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">find</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">full-screen</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">back</span>4+ What's big in the repo, from <span style="color:var(--vt-palette-6)">ls</span>.5+6+ <span style="color:var(--vt-palette-8)">╭─</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>7+ <span style="color:var(--vt-palette-8)">[1]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">▶</span>8+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>9+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-8)">Out[1]:</span> <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">name</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">type</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">size</span><span style="background:var(--vt-palette-4)"> </span>10+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">012</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">file</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">4.2</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">kB</span><span style="background:var(--vt-palette-4)"> </span>11+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">2</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">CLAUDE.md</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">file</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">1.0</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">kB</span><span style="background:var(--vt-palette-4)"> </span>12+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB13+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB14+<span style="color:var(--vt-palette-2)">▌</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> go.mod file 812 B15+16+ <span style="color:var(--vt-palette-8)">╭──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>17+ <span style="color:var(--vt-palette-8)">[2]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">></span> <span style="color:var(--vt-palette-3)">1kb</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">▶</span>18+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>19+ <span style="color:var(--vt-palette-8)">Out[2]:</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">name</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">type</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">size</span><span style="background:var(--vt-palette-8)"> </span>20+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB21+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB22+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB23+24+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="font-weight:bold">name</span> <span style="font-weight:bold">to</span> <span style="font-weight:bold">size,</span> <span style="font-weight:bold">rows</span> <span style="font-weight:bold">1</span> <span style="font-weight:bold">to</span> <span style="font-weight:bold">2</span> <span style="color:var(--vt-palette-8)">Count</span> 6</pre>\ No newline at end of file@@ -0,0 +1,16 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EDIT</span><span style="background:var(--vt-palette-6)"> </span>2+ ▶ Run <span style="color:var(--vt-palette-8)">■</span> <span style="color:var(--vt-palette-8)">Stop</span> ↻ Restart ▶▶ Run all <span style="color:var(--vt-palette-8)">│</span> + Add ✂ ⧉ <span style="color:var(--vt-palette-8)">⎘</span> <span style="color:var(--vt-palette-8)">│</span> Code ▾ <span style="color:var(--vt-palette-0)">nu</span> <span style="color:var(--vt-palette-0)">○</span> <span style="color:var(--vt-palette-0)">idle</span>3+ <span style="color:var(--vt-palette-3)">The</span> <span style="color:var(--vt-palette-3)">`sort-by`</span> <span style="color:var(--vt-palette-3)">command</span> <span style="color:var(--vt-palette-3)">doesn't</span> <span style="color:var(--vt-palette-3)">have</span> <span style="color:var(--vt-palette-3)">flag</span> <span style="color:var(--vt-palette-3)">`revrse`.</span> <span style="color:var(--vt-palette-8)">─</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">done</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Tab</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">complete</span>4+5+ <span style="color:var(--vt-palette-8)">╭──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>6+ <span style="color:var(--vt-palette-0)">[2]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">></span> <span style="color:var(--vt-palette-1)">1kb</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">▶</span>7+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>8+ <span style="color:var(--vt-palette-0)">Out[2]:</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">name</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">type</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">size</span><span style="background:var(--vt-palette-7)"> </span>9+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB10+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB11+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB12+13+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-2)">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓</span>14+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-0)">[</span> <span style="color:var(--vt-palette-0)">]:</span> <span style="color:var(--vt-palette-2)">┃</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">sort-by</span> <span style="color:var(--vt-palette-2)">size</span> <span style="text-decoration:underline wavy var(--vt-palette-1)">--revrse</span> <span style="color:var(--vt-palette-2)">┃</span> <span style="color:var(--vt-palette-2)">▶</span>15+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-2)">┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛</span>16+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,16 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EDIT</span><span style="background:var(--vt-palette-6)"> </span>2+ ▶ Run <span style="color:var(--vt-palette-8)">■</span> <span style="color:var(--vt-palette-8)">Stop</span> ↻ Restart ▶▶ Run all <span style="color:var(--vt-palette-8)">│</span> + Add ✂ ⧉ <span style="color:var(--vt-palette-8)">⎘</span> <span style="color:var(--vt-palette-8)">│</span> Code ▾ <span style="color:var(--vt-palette-8)">nu</span> <span style="color:var(--vt-palette-8)">○</span> <span style="color:var(--vt-palette-8)">idle</span>3+ <span style="color:var(--vt-palette-3)">The</span> <span style="color:var(--vt-palette-3)">`sort-by`</span> <span style="color:var(--vt-palette-3)">command</span> <span style="color:var(--vt-palette-3)">doesn't</span> <span style="color:var(--vt-palette-3)">have</span> <span style="color:var(--vt-palette-3)">flag</span> <span style="color:var(--vt-palette-3)">`revrse`.</span> <span style="color:var(--vt-palette-8)">─</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">complete</span>4+5+ <span style="color:var(--vt-palette-8)">╭──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)"><1s</span> <span style="color:var(--vt-palette-8)">─╮</span>6+ <span style="color:var(--vt-palette-8)">[2]:</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">></span> <span style="color:var(--vt-palette-3)">1kb</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">▶</span>7+ <span style="color:var(--vt-palette-8)">╰──────────────────────────────────────────────────────────────────╯</span>8+ <span style="color:var(--vt-palette-8)">Out[2]:</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">name</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">type</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">size</span><span style="background:var(--vt-palette-8)"> </span>9+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB10+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB11+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB12+13+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-2)">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓</span>14+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-8)">[</span> <span style="color:var(--vt-palette-8)">]:</span> <span style="color:var(--vt-palette-2)">┃</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">sort-by</span> <span style="color:var(--vt-palette-2)">size</span> <span style="text-decoration:underline wavy var(--vt-palette-1)">--revrse</span> <span style="color:var(--vt-palette-2)">┃</span> <span style="color:var(--vt-palette-2)">▶</span>15+<span style="color:var(--vt-palette-2)">▌</span> <span style="color:var(--vt-palette-2)">┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛</span>16+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,18 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Search</span> <span style="font-weight:bold">the</span> <span style="font-weight:bold">menus</span> <span style="color:var(--vt-palette-8)">─────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-0)">Type</span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">command,</span> <span style="color:var(--vt-palette-0)">e.g.</span> <span style="color:var(--vt-palette-0)">save</span> <span style="color:var(--vt-palette-0)">or</span> <span style="color:var(--vt-palette-0)">width</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────────────────────────────────┤</span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">New</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">File</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Open <span style="color:var(--vt-palette-0)">File</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+O</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Import <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Save <span style="color:var(--vt-palette-0)">File</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+S</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Save as <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as CSV <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as TSV <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as XLSX <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as SQLite <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as JSON <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as NUON <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as HTML <span style="color:var(--vt-palette-0)">File</span> <span style="color:var(--vt-palette-0)">›</span> <span style="color:var(--vt-palette-0)">Download</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">226</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">226</span> <span style="color:var(--vt-palette-8)">─┘</span>18+Start a new, empty sheet <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Up/Down</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">move</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file@@ -0,0 +1,18 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Search</span> <span style="font-weight:bold">the</span> <span style="font-weight:bold">menus</span> <span style="color:var(--vt-palette-8)">─────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Type</span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">command,</span> <span style="color:var(--vt-palette-8)">e.g.</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">or</span> <span style="color:var(--vt-palette-8)">width</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────────────────────────────────┤</span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">New</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">File</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Open <span style="color:var(--vt-palette-8)">File</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+O</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Import <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Save <span style="color:var(--vt-palette-8)">File</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Save as <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as CSV <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as TSV <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as XLSX <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as SQLite <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as JSON <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as NUON <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> Download as HTML <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">226</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">226</span> <span style="color:var(--vt-palette-8)">─┘</span>18+Start a new, empty sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file@@ -0,0 +1,12 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E3</span><span style="background:var(--vt-palette-7)"> </span> Art3+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+Down</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">choose</span> <span style="color:var(--vt-palette-0)">from</span> <span style="color:var(--vt-palette-0)">the</span> <span style="color:var(--vt-palette-0)">list</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">E</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">Name</span> <span style="font-weight:bold">Points</span> <span style="font-weight:bold">Change</span> <span style="font-weight:bold">Grade</span> <span style="font-weight:bold">Track</span> <span style="font-weight:bold">Passed</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Ada <span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--bg);background:var(--vt-palette-4)">9</span>2 <span style="color:var(--vt-palette-2)">↑</span> 12 <span style="color:var(--vt-palette-3)">◕</span> <span style="color:var(--vt-palette-7)">▐</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Math</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">▾</span><span style="color:var(--vt-palette-7)">▌</span> [✓]7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-5);font-style:italic">Bo</span> <span style="color:var(--vt-palette-4)">███▉</span> 48 <span style="color:var(--vt-palette-1)">↓</span> -8 <span style="color:var(--vt-palette-3)">◔</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Art</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▾</span> [ ]8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Cy <span style="color:var(--vt-palette-4)">██████</span>75 <span style="color:var(--vt-palette-3)">→</span> 3 <span style="color:var(--vt-palette-3)">◑</span> <span style="color:var(--vt-palette-7)">▐</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Math</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">▾</span><span style="color:var(--vt-palette-7)">▌</span> [✓]9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-5);font-style:italic">Bo</span> <span style="color:var(--vt-palette-4)">██▍</span> 30 <span style="color:var(--vt-palette-1)">↓</span> -2 <span style="color:var(--vt-palette-1)">○</span> <span style="color:var(--vt-palette-0)">▾</span> [ ]10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Dee <span style="color:var(--vt-palette-4)">█████</span><span style="color:var(--bg);background:var(--vt-palette-4)">100</span> <span style="color:var(--vt-palette-2)">↑</span> 15 <span style="color:var(--vt-palette-2)">●</span> <span style="color:var(--vt-palette-7)">▐</span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Music</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">▾</span><span style="color:var(--vt-palette-7)">▌</span> [✓]11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span>12+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> scores.012 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file@@ -0,0 +1,12 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E3</span><span style="background:var(--vt-palette-8)"> </span> Art3+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">choose</span> <span style="color:var(--vt-palette-8)">from</span> <span style="color:var(--vt-palette-8)">the</span> <span style="color:var(--vt-palette-8)">list</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">E</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Name</span> <span style="font-weight:bold">Points</span> <span style="font-weight:bold">Change</span> <span style="font-weight:bold">Grade</span> <span style="font-weight:bold">Track</span> <span style="font-weight:bold">Passed</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Ada <span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--bg);background:var(--vt-palette-4)">9</span>2 <span style="color:var(--vt-palette-2)">↑</span> 12 <span style="color:var(--vt-palette-3)">◕</span> <span style="color:var(--vt-palette-8)">▐</span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Math</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">▾</span><span style="color:var(--vt-palette-8)">▌</span> [✓]7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-5);font-style:italic">Bo</span> <span style="color:var(--vt-palette-4)">███▉</span> 48 <span style="color:var(--vt-palette-1)">↓</span> -8 <span style="color:var(--vt-palette-3)">◔</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Art</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▾</span> [ ]8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Cy <span style="color:var(--vt-palette-4)">██████</span>75 <span style="color:var(--vt-palette-3)">→</span> 3 <span style="color:var(--vt-palette-3)">◑</span> <span style="color:var(--vt-palette-8)">▐</span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Math</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">▾</span><span style="color:var(--vt-palette-8)">▌</span> [✓]9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-5);font-style:italic">Bo</span> <span style="color:var(--vt-palette-4)">██▍</span> 30 <span style="color:var(--vt-palette-1)">↓</span> -2 <span style="color:var(--vt-palette-1)">○</span> <span style="color:var(--vt-palette-8)">▾</span> [ ]10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Dee <span style="color:var(--vt-palette-4)">█████</span><span style="color:var(--bg);background:var(--vt-palette-4)">100</span> <span style="color:var(--vt-palette-2)">↑</span> 15 <span style="color:var(--vt-palette-2)">●</span> <span style="color:var(--vt-palette-8)">▐</span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Music</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">▾</span><span style="color:var(--vt-palette-8)">▌</span> [✓]11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span>12+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> scores.012 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file@@ -0,0 +1,12 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">RULES</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Task3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Task</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">Owner</span> <span style="font-weight:bold">Hours</span> <span style="font-weight:bold">Done</span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Conditional</span> <span style="font-weight:bold">format</span> <span style="font-weight:bold">rules</span> <span style="color:var(--vt-palette-8)">───────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-2);text-decoration:line-through">Write</span><span style="text-decoration:line-through"> </span><span style="color:var(--vt-palette-2);text-decoration:line-through">spec</span> Ann <span style="color:var(--vt-palette-0)">▾</span><span style="background:#b58900"> </span><span style="color:#1d1f21;background:#b58900">12</span><span style="background:#b58900"> </span> [✓] <span style="color:var(--vt-palette-8)">│</span> + Add rule <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Build UI Bo <span style="color:var(--vt-palette-0)">▾</span><span style="background:#c82829"> </span><span style="color:#ffffff;background:#c82829">40</span><span style="background:#c82829"> </span> [ ] <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-2);text-decoration:line-through">123</span> A2:A6 Custom formula is =$D2 <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-2);text-decoration:line-through">Tests</span> Cy <span style="color:var(--vt-palette-0)">▾</span><span style="background:#bf5915"> </span><span style="color:#ffffff;background:#bf5915">26</span><span style="background:#bf5915"> </span> [✓] <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="background:#718c00"> </span><span style="background:#9e8a00"> </span><span style="background:#bc680e"> </span><span style="background:#c82829"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">C2:C6</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Color</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">scale</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Docs <span style="text-decoration:underline dotted var(--vt-palette-3)">Dee</span> <span style="color:var(--vt-palette-0)">▾</span><span style="background:#888b00"> </span><span style="color:#1d1f21;background:#888b00">6</span><span style="background:#888b00"> </span> [ ] <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-15);background:var(--vt-palette-5);font-weight:bold">123</span><span style="background:var(--vt-palette-5)"> </span> C7 Greater than 80 <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Release Ann <span style="color:var(--vt-palette-0)">▾</span><span style="background:#718c00"> </span><span style="color:var(--vt-palette-0);background:#718c00">3</span><span style="background:#718c00"> </span> [ ] <span style="color:var(--vt-palette-8)">└────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-0)">rules</span> <span style="color:var(--vt-palette-8)">─┘</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> Total <span style="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-5);font-weight:bold">87</span><span style="background:var(--vt-palette-5)"> </span>12+<span style="color:var(--vt-palette-0)">The</span> <span style="color:var(--vt-palette-0)">first</span> <span style="color:var(--vt-palette-0)">rule</span> <span style="color:var(--vt-palette-0)">that</span> <span style="color:var(--vt-palette-0)">matches</span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">cell</span> <span style="color:var(--vt-palette-0)">wins</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Del</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">remove</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+↑/↓</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">move</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file@@ -0,0 +1,12 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">RULES</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Task3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Task</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">Owner</span> <span style="font-weight:bold">Hours</span> <span style="font-weight:bold">Done</span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Conditional</span> <span style="font-weight:bold">format</span> <span style="font-weight:bold">rules</span> <span style="color:var(--vt-palette-8)">───────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-2);text-decoration:line-through">Write</span><span style="text-decoration:line-through"> </span><span style="color:var(--vt-palette-2);text-decoration:line-through">spec</span> Ann <span style="color:var(--vt-palette-8)">▾</span><span style="background:#f0c674"> </span><span style="color:#1d1f21;background:#f0c674">12</span><span style="background:#f0c674"> </span> [✓] <span style="color:var(--vt-palette-8)">│</span> + Add rule <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Build UI Bo <span style="color:var(--vt-palette-8)">▾</span><span style="background:#cc6666"> </span><span style="color:#000000;background:#cc6666">40</span><span style="background:#cc6666"> </span> [ ] <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-2);text-decoration:line-through">123</span> A2:A6 Custom formula is =$D2 <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-2);text-decoration:line-through">Tests</span> Cy <span style="color:var(--vt-palette-8)">▾</span><span style="background:#de966d"> </span><span style="color:#1d1f21;background:#de966d">26</span><span style="background:#de966d"> </span> [✓] <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="background:#b5bd68"> </span><span style="background:#dcc370"> </span><span style="background:#e4a56f"> </span><span style="background:#cc6666"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">C2:C6</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Color</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">scale</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Docs <span style="text-decoration:underline dotted var(--vt-palette-3)">Dee</span> <span style="color:var(--vt-palette-8)">▾</span><span style="background:#c9c06c"> </span><span style="color:#1d1f21;background:#c9c06c">6</span><span style="background:#c9c06c"> </span> [ ] <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">123</span><span style="background:var(--vt-palette-5)"> </span> C7 Greater than 80 <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Release Ann <span style="color:var(--vt-palette-8)">▾</span><span style="background:#b5bd68"> </span><span style="color:#1d1f21;background:#b5bd68">3</span><span style="background:#b5bd68"> </span> [ ] <span style="color:var(--vt-palette-8)">└────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">rules</span> <span style="color:var(--vt-palette-8)">─┘</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total <span style="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">87</span><span style="background:var(--vt-palette-5)"> </span>12+<span style="color:var(--vt-palette-8)">The</span> <span style="color:var(--vt-palette-8)">first</span> <span style="color:var(--vt-palette-8)">rule</span> <span style="color:var(--vt-palette-8)">that</span> <span style="color:var(--vt-palette-8)">matches</span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">cell</span> <span style="color:var(--vt-palette-8)">wins</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">remove</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+↑/↓</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file@@ -0,0 +1,13 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D3</span><span style="background:var(--vt-palette-7)"> </span> 2803+<span style="color:var(--vt-palette-0)">Table</span> <span style="color:var(--vt-palette-0)">Sales,</span> <span style="color:var(--vt-palette-0)">column</span> <span style="font-weight:bold">Amount</span><span style="color:var(--vt-palette-0)">:</span> <span style="color:var(--vt-palette-0)">Sales[Amount]</span> <span style="color:var(--vt-palette-0)">in</span> <span style="color:var(--vt-palette-0)">formulas</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">D</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-4);font-weight:bold;text-decoration:underline">Region</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-4);font-weight:bold;text-decoration:underline">Month</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-4);font-weight:bold;text-decoration:underline">Units</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-4);font-weight:bold;text-decoration:underline">Amount</span><span style="text-decoration:underline"> </span> Total6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> North Jan 12 480 26807+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">South</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Jan</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">280</span><span style="background:var(--vt-palette-6)"> </span> Per unit8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> East Feb 20 800 409+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">North</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Feb</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">360</span><span style="background:var(--vt-palette-7)"> </span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> West Mar 15 60011+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">South</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Mar</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">160</span><span style="background:var(--vt-palette-7)"> </span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,13 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D3</span><span style="background:var(--vt-palette-8)"> </span> 2803+<span style="color:var(--vt-palette-8)">Table</span> <span style="color:var(--vt-palette-8)">Sales,</span> <span style="color:var(--vt-palette-8)">column</span> <span style="font-weight:bold">Amount</span><span style="color:var(--vt-palette-8)">:</span> <span style="color:var(--vt-palette-8)">Sales[Amount]</span> <span style="color:var(--vt-palette-8)">in</span> <span style="color:var(--vt-palette-8)">formulas</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">D</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-6);font-weight:bold;text-decoration:underline">Region</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-6);font-weight:bold;text-decoration:underline">Month</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-6);font-weight:bold;text-decoration:underline">Units</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-6);font-weight:bold;text-decoration:underline">Amount</span><span style="text-decoration:underline"> </span> Total6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> North Jan 12 480 26807+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">South</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Jan</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">280</span><span style="background:var(--vt-palette-6)"> </span> Per unit8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> East Feb 20 800 409+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">North</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Feb</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">360</span><span style="background:var(--vt-palette-8)"> </span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> West Mar 15 60011+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">South</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Mar</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">160</span><span style="background:var(--vt-palette-8)"> </span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,14 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B7</span><span style="background:var(--vt-palette-7)"> </span> =SUM(B3:B5)3+<span style="color:var(--vt-palette-0)">B7</span> <span style="color:var(--vt-palette-0)">reads</span> Expenses<span style="color:var(--vt-palette-0)">;</span> <span style="color:var(--vt-palette-0)">read</span> <span style="color:var(--vt-palette-0)">by</span> C7, B30↓ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+'</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">list</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+;</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">stop</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Household budget 20266+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> 47+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Rent <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">1450</span><span style="background:var(--vt-palette-2)"> </span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Groceries<span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">612.4</span><span style="background:var(--vt-palette-2)"> </span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Transit <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">96</span><span style="background:var(--vt-palette-2)"> </span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.4</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">539.6</span><span style="background:var(--vt-palette-5)"> </span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> 2877.866713+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span></pre>\ No newline at end of file@@ -0,0 +1,14 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B7</span><span style="background:var(--vt-palette-8)"> </span> =SUM(B3:B5)3+<span style="color:var(--vt-palette-8)">B7</span> <span style="color:var(--vt-palette-8)">reads</span> Expenses<span style="color:var(--vt-palette-8)">;</span> <span style="color:var(--vt-palette-8)">read</span> <span style="color:var(--vt-palette-8)">by</span> C7, B30↓ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+'</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">list</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+;</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">stop</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> 47+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">1450</span><span style="background:var(--vt-palette-2)"> </span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries<span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">612.4</span><span style="background:var(--vt-palette-2)"> </span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">96</span><span style="background:var(--vt-palette-2)"> </span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.4</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">539.6</span><span style="background:var(--vt-palette-5)"> </span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> 2877.866713+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span></pre>\ No newline at end of file@@ -13,8 +13,10 @@ var sizeUnits = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
1313 1414 // Size shows v bytes as nushell shows a file size: whole bytes below1515 // 1000 ("512 B"), and above that dec decimals of the largest unit, a16-// thousand times apart, that keeps the number under 1000 once rounded17-// ("1.6 kB", "1.0 MB"). Negative sizes show a minus sign.16+// thousand times apart, that keeps the number under 1000. Like nushell,17+// it cuts the digits past dec rather than rounding them, so 2998 bytes18+// is "2.9 kB", never a unit it hasn't reached. Negative sizes show a19+// minus sign.1820 func Size(v float64, dec int) string { return SizeIn(v, dec, locale.Canonical) }1921 2022 // SizeIn is Size as shown in loc: 1,6 kB in de-DE.@@ -27,23 +29,22 @@ func SizeIn(v float64, dec int, loc *locale.Locale) string {
2729 return sign + "∞ B"2830 }2931 unit := 030- for unit < len(sizeUnits)-1 && roundTo(v, 0) >= 1000 {31- v /= 100032- unit++33- }34- if unit > 0 && roundTo(v, dec) >= 1000 && unit < len(sizeUnits)-1 {32+ for unit < len(sizeUnits)-1 && v >= 1000 {3533 v /= 10003634 unit++3735 }3836 pat := "0"3937 if unit > 0 && dec > 0 {4038 pat += "." + strings.Repeat("0", dec)39+ } else {40+ dec = 04141 }42- return sign + FormatIn(v, pat, loc) + " " + sizeUnits[unit]42+ return sign + FormatIn(truncTo(v, dec), pat, loc) + " " + sizeUnits[unit]4343 }4444 45-// roundTo rounds v to dec decimals, halves away from zero.46-func roundTo(v float64, dec int) float64 {45+// truncTo cuts v to dec decimals. The nudge keeps a value that is46+// exactly on a digit, such as 1.6 held as 1.5999999, from losing it.47+func truncTo(v float64, dec int) float64 {4748 p := math.Pow10(dec)48- return math.Round(v*p) / p49+ return math.Trunc(v*p*(1+1e-12)) / p4950 }@@ -15,12 +15,16 @@ func TestSize(t *testing.T) {
1515 {0, 1, "0 B"},1616 {512, 1, "512 B"},1717 {999, 1, "999 B"},18- {999.6, 1, "1.0 kB"},18+ {999.6, 1, "999 B"},1919 {1000, 1, "1.0 kB"},2020 {1646, 1, "1.6 kB"},21- {1646, 0, "2 kB"},22- {1646, 2, "1.65 kB"},23- {999_999, 1, "1.0 MB"},21+ {1646, 0, "1 kB"},22+ {1646, 2, "1.64 kB"},23+ {999_999, 1, "999.9 kB"},24+ {2998, 1, "2.9 kB"},25+ {1650, 1, "1.6 kB"},26+ {1_999_999, 1, "1.9 MB"},27+ {-2998, 1, "-2.9 kB"},2428 {2_000_000, 1, "2.0 MB"},2529 {1.5e9, 1, "1.5 GB"},2630 {1.2e12, 1, "1.2 TB"},@@ -68,7 +68,7 @@ func (g *Guarded) Init() (cmd tea.Cmd) {
6868 }6969 7070 // Update implements tea.Model.71-func (g *Guarded) Update(msg tea.Msg) (_ tea.Model, cmd tea.Cmd) {71+func (g *Guarded) Update(msg tea.Msg) (next tea.Model, cmd tea.Cmd) {7272 if c, ok := msg.(crashMsg); ok {7373 if g.crash == nil {7474 g.crash = c.crash@@ -81,7 +81,7 @@ func (g *Guarded) Update(msg tea.Msg) (_ tea.Model, cmd tea.Cmd) {
8181 defer func() {8282 if r := recover(); r != nil {8383 g.crash = caught("update", r)84- cmd = tea.Quit84+ next, cmd = g, tea.Quit // never nil: Bubble Tea draws what Update returns8585 }8686 }()8787 if crashTest != nil {@@ -89,8 +89,8 @@ func (g *Guarded) Update(msg tea.Msg) (_ tea.Model, cmd tea.Cmd) {
8989 return g, c9090 }9191 }92- next, cmd := g.m.Update(msg)93- if m, ok := next.(*Model); ok {92+ inner, cmd := g.m.Update(msg)93+ if m, ok := inner.(*Model); ok {9494 g.m = m9595 }9696 return g, g.guard(cmd)@@ -48,9 +48,14 @@ func TestGuardUpdatePanicKeepsWork(t *testing.T) {
4848 m.filename = "book.012"4949 press(t, m, "mine", "<enter>")5050 g := Guard(m)51- if _, cmd := g.Update(f12); !isQuit(cmd) {51+ next, cmd := g.Update(f12)52+ if !isQuit(cmd) {5253 t.Fatal("a panic in Update didn't quit")5354 }55+ if next != g {56+ t.Fatalf("Update returned %v after a panic; Bubble Tea draws what it returns", next)57+ }58+ next.View()5459 if _, cmd := g.Update(tea.KeyPressMsg{Code: 'x', Text: "x"}); cmd != nil {5560 t.Error("a crashed model took more input")5661 }@@ -75,7 +75,7 @@ func TestFormatCommandsCoverSheetsMenu(t *testing.T) {
7575 {"format.financial", " (1,450.00) "},7676 {"format.currency_rounded", " -$1,450 "},7777 {"format.duration", "-34800:00:00"}, // uses the padding: nothing to its right78- {"format.size", " -1.5 kB "},78+ {"format.size", " -1.4 kB "}, // cut, not rounded, as nushell shows it7979 {"format.plain_text", " -1450 "},8080 {"format.automatic", " -1450 "},8181 }@@ -8,8 +8,10 @@
88 // page front matter with a title and a sidebar_position (tree.go);99 // - every docs/...md path Go code names exists;1010 // - every file in docs/media is shown by some doc, comes from a VHS tape11-// in demos/ (its Output or a Screenshot), and no GIF is over maxGIF;12-// - every tape in demos/ records something a doc shows;11+// in demos/ (its Output or a Screenshot) or is a still of a golden12+// screen (e2e/stills_test.go), and no GIF is over maxGIF;13+// - every tape in demos/ records something a doc shows, and every14+// still is shown, its dark and light pictures together;1315 // - every docs page the site has published is still a page, or has a1416 // redirect from its address in website/redirects.json (redirects.go).1517 //@@ -180,8 +182,8 @@ func checkLinks(docs map[string]*doc) []string {
180182 out = append(out, where+": no such file")181183 continue182184 }183- if anchor == "" {184- continue185+ if anchor == "" || modeOnly[anchor] {186+ continue // the color mode a still shows in, checked in checkMedia185187 }186188 td, ok := docs[target]187189 if !ok {@@ -197,22 +199,60 @@ func checkLinks(docs map[string]*doc) []string {
197199 var (198200 tapeOutput = regexp.MustCompile(`(?m)^Output\s+"?out/([^"\s]+)"?`)199201 tapeScreenshot = regexp.MustCompile(`(?m)^Screenshot\s+"?out/stills/([^"\s]+)"?`)202+ // docScreen is an entry of e2e's docScreens, a still drawn from a203+ // golden screen as <name>-dark.png and <name>-light.png.204+ docScreen = regexp.MustCompile(`(?m)^\s*\{name: "([^"]+)", from:`)200205 )201206 202-// checkMedia reports media no doc shows, media no tape makes, GIFs over203-// maxGIF and tapes whose recordings no doc shows.207+// stillsSource lists the stills of golden screens.208+const stillsSource = "e2e/stills_test.go"209+210+// modeOnly are the fragments GitHub and the site show an image by: only211+// in dark mode, or only in light.212+var modeOnly = map[string]bool{"gh-dark-mode-only": true, "gh-light-mode-only": true}213+214+// checkMedia reports media no doc shows, media nothing makes, GIFs over215+// maxGIF, tapes and stills no doc shows, and stills shown without their216+// other color mode.204217 func checkMedia(docs map[string]*doc) []string {205- shown := map[string]bool{}218+ // shown maps a media path to the fragment each doc shows it with.219+ shown := map[string]map[string]string{}206220 for _, dc := range docs {207221 for _, l := range dc.links {208- file, _, _ := strings.Cut(l.target, "#")222+ file, frag, _ := strings.Cut(l.target, "#")209223 if file != "" && !strings.Contains(file, "://") {210- shown[path.Clean(path.Join(path.Dir(dc.path), file))] = true224+ p := path.Clean(path.Join(path.Dir(dc.path), file))225+ if shown[p] == nil {226+ shown[p] = map[string]string{}227+ }228+ shown[p][dc.path] = frag211229 }212230 }213231 }232+ madeBy := map[string]string{} // media file name -> the tape or stills list233+ out := tapeMedia(shown, madeBy)234+ out = append(out, stillMedia(shown, madeBy)...)235+ media, _ := filepath.Glob("docs/media/*")236+ for _, m := range media {237+ m = filepath.ToSlash(m)238+ name := path.Base(m)239+ if shown[m] == nil {240+ out = append(out, m+": no doc shows it")241+ }242+ if madeBy[name] == "" {243+ out = append(out, m+": nothing makes it: no tape in demos/ (Output or Screenshot), nor docScreens in "+stillsSource)244+ }245+ if fi, err := os.Stat(m); err == nil && path.Ext(m) == ".gif" && fi.Size() > maxGIF {246+ out = append(out, fmt.Sprintf("%s: %d KB, over %d KB", m, fi.Size()>>10, maxGIF>>10))247+ }248+ }249+ return out250+}251+252+// tapeMedia records what each tape makes in madeBy and reports tapes253+// whose recordings no doc shows.254+func tapeMedia(shown map[string]map[string]string, madeBy map[string]string) []string {214255 tapes, _ := filepath.Glob("demos/*.tape")215- madeBy := map[string]string{} // media file name -> tape216256 var out []string217257 for _, t := range tapes {218258 src, err := os.ReadFile(t)@@ -227,27 +267,49 @@ func checkMedia(docs map[string]*doc) []string {
227267 madeBy[m[1]] = t228268 }229269 }230- if !slices.ContainsFunc(made, func(name string) bool { return shown["docs/media/"+name] }) {270+ if !slices.ContainsFunc(made, func(name string) bool { return shown["docs/media/"+name] != nil }) {231271 out = append(out, t+": no doc shows its recording or stills (docs/media/"+strings.Join(made, ", docs/media/")+")")232272 }233273 }234- media, _ := filepath.Glob("docs/media/*")235- for _, m := range media {236- m = filepath.ToSlash(m)237- name := path.Base(m)238- if !shown[m] {239- out = append(out, m+": no doc shows it")240- }241- if madeBy[name] == "" {242- out = append(out, m+": no tape in demos/ makes it (Output or Screenshot)")274+ return out275+}276+277+// stillMedia records the stills of golden screens in madeBy, and reports278+// one no doc shows, or one shown without its other color mode: a doc279+// shows <name>-dark.png#gh-dark-mode-only and280+// <name>-light.png#gh-light-mode-only together.281+func stillMedia(shown map[string]map[string]string, madeBy map[string]string) []string {282+ src, err := os.ReadFile(stillsSource)283+ if err != nil {284+ return []string{stillsSource + ": " + err.Error()}285+ }286+ var out []string287+ for _, m := range docScreen.FindAllStringSubmatch(string(src), -1) {288+ dark, light := "docs/media/"+m[1]+"-dark.png", "docs/media/"+m[1]+"-light.png"289+ madeBy[path.Base(dark)], madeBy[path.Base(light)] = stillsSource, stillsSource290+ if shown[dark] == nil && shown[light] == nil {291+ out = append(out, stillsSource+": no doc shows the still "+m[1])243292 }244- if fi, err := os.Stat(m); err == nil && path.Ext(m) == ".gif" && fi.Size() > maxGIF {245- out = append(out, fmt.Sprintf("%s: %d KB, over %d KB", m, fi.Size()>>10, maxGIF>>10))293+ for _, d := range sortedKeys(mergeKeys(shown[dark], shown[light])) {294+ if shown[dark][d] != "gh-dark-mode-only" || shown[light][d] != "gh-light-mode-only" {295+ out = append(out, d+": shows the still "+m[1]+" without both "+path.Base(dark)+"#gh-dark-mode-only and "+path.Base(light)+"#gh-light-mode-only")296+ }246297 }247298 }248299 return out249300 }250301 302+func mergeKeys(a, b map[string]string) map[string]bool {303+ out := map[string]bool{}304+ for k := range a {305+ out[k] = true306+ }307+ for k := range b {308+ out[k] = true309+ }310+ return out311+}312+251313 func sortedKeys[V any](m map[string]V) []string {252314 keys := make([]string, 0, len(m))253315 for k := range m {@@ -10,6 +10,9 @@
1010 // "doclint:allow" and a reason, in a comment on that line (or an HTML1111 // comment in Markdown).1212 //13+// It also refuses a code block that draws 012's screen as text: a14+// picture of 012 is a still drawn from a golden screen.15+//1316 // Usage: go run ./scripts/doclint [dirs or files...]1417 package main1518 @@ -90,7 +93,7 @@ func main() {
9093 fmt.Printf("%s:%d: %s: %s\n", f.file, f.line, f.why, strings.TrimSpace(f.text))9194 }9295 if len(found) > 0 {93- fmt.Printf("doclint: %d lines describe development rather than the code\n", len(found))96+ fmt.Printf("doclint: %d findings\n", len(found))9497 os.Exit(1)9598 }9699 }@@ -140,16 +143,21 @@ func markdown(path string) []finding {
140143 }141144 defer f.Close()142145 var out []finding143- fenced := false146+ fenced, start := false, 0147+ var block []string144148 sc := bufio.NewScanner(f)145149 sc.Buffer(make([]byte, 1<<20), 1<<20)146150 for n := 1; sc.Scan(); n++ {147151 l := sc.Text()148152 if strings.HasPrefix(strings.TrimSpace(l), "```") {149- fenced = !fenced153+ if fenced && drawnScreen(block) {154+ out = append(out, finding{path, start, "```", screenWhy})155+ }156+ fenced, start, block = !fenced, n, nil150157 continue151158 }152159 if fenced {160+ block = append(block, l)153161 continue154162 }155163 out = append(out, check(path, n, stripCode(l))...)@@ -157,6 +165,26 @@ func markdown(path string) []finding {
157165 return out158166 }159167 168+// screenWhy is what a code block drawing 012's screen is told.169+const screenWhy = "a code block drawing 012's screen; show a still of it instead (docs/contributing/site.md#pictures-of-012)"170+171+var (172+ boxDrawing = regexp.MustCompile(`[\x{2500}-\x{257F}]`)173+ // chrome is the menu bar or the notebook's toolbar.174+ chrome = regexp.MustCompile(`File\s+Edit\s+View|▶ Run|■ Stop`)175+ // columnHeaders are the grid's column letters over its cells.176+ columnHeaders = regexp.MustCompile(`(?m)^\s*A\s{2,}B\s{2,}C(\s|$)`)177+)178+179+// drawnScreen reports whether a code block's lines draw 012's screen as180+// text: box drawing with the menu bar or the toolbar, or the grid's181+// column headers. Command output (tables a shell prints, logs) is none182+// of these.183+func drawnScreen(lines []string) bool {184+ text := strings.Join(lines, "\n")185+ return boxDrawing.MatchString(text) && chrome.MatchString(text) || columnHeaders.MatchString(text)186+}187+160188 var inlineCode = regexp.MustCompile("`[^`]*`")161189 162190 // stripCode drops inline code spans, which quote code rather than narrate.@@ -247,6 +247,18 @@ kbd {
247247 margin: 1rem 0;248248 }249249 250+/* Stills of 012 come in a dark and a light picture, marked as GitHub251+ marks them (#gh-dark-mode-only), and the site shows the one for its252+ color mode. Each draws its own window, so it has no border. */253+[data-theme='light'] .markdown img[src$='#gh-dark-mode-only'],254+[data-theme='dark'] .markdown img[src$='#gh-light-mode-only'] {255+ display: none;256+}257+258+.markdown img[src$='-mode-only'] {259+ border: 0;260+}261+250262 /* ---------- Navbar: the control panel's first line ---------- */251263 252264 .navbar {