Ten short tapes in demos/ record first steps, selection stats, menus and the palette, find and replace, formats, charts, freeze/sort/filter, CSV import, JEV functions and decimal arithmetic, in the dark reference palette. make demos renders them into demos/out/ with smaller copies for the README in demos/out/media/. The JEV tape talks to demos/fakejev, a stand-in service that answers by keywords, never the real one. A ttyd wrapper makes Option send Alt in the recorded xterm.js on macOS, so Alt+F opens menus.
- commit
- 50dbc93db640cae2d16af83309029bebbefb4c45
- parent
- 4139b6502829ea7681fdac866397bf0f20a63135
- author
- Dami <dami.etoile@gmail.com>
| M | .gitignore | 1 | + |
| M | Makefile | 24 | ++++++++- |
| M | ROADMAP.md | 2 | +- |
| A | demos/ | 34 | +++++++++++ |
| A | demos/ | 31 | ++++++++++ |
| A | demos/ | 17 | ++++++ |
| A | demos/ | 7 | ++ |
| A | demos/ | 32 | ++++++++++ |
| A | demos/ | 92 | ++++++++++++++++++++++++++++++ |
| A | demos/ | 31 | ++++++++++ |
| A | demos/ | 27 | +++++++++ |
| A | demos/ | 31 | ++++++++++ |
| A | demos/ | 48 | ++++++++++++++++ |
| A | demos/ | 26 | ++++++++ |
| A | demos/ | 32 | ++++++++++ |
| A | demos/ | 12 | ++++ |
| A | demos/ | 6 | ++ |
| A | demos/ | 37 | ++++++++++++ |
| A | demos/ | 27 | +++++++++ |
@@ -7,7 +7,7 @@ GHOSTTY_SRC := $(DEPS)/ghostty-src
77 GHOSTTY_OUT := $(DEPS)/ghostty88 GHOSTTY_STAMP := $(GHOSTTY_OUT)/.built-$(GHOSTTY_COMMIT)99 10-10+1111 1212 build:1313 CGO_ENABLED=0 go build -o bin/012 ./cmd/012@@ -70,6 +70,28 @@ stress-load:
7070 oracle:7171 cd oracle && go test -count=1 ./...7272 73+# Demo GIFs from the VHS tapes in demos/ into demos/out/ (gitignored),74+# plus smaller copies for the README in demos/out/media/ (12 fps, a75+# 64-color palette, the last frame held). Needs vhs 0.12+ (go install76+# github.com/charmbracelet/vhs@latest), ttyd and ffmpeg. The JEV demo77+# talks to demos/fakejev, started here, never the real service.78+# DEMOS=jev renders just one.79+DEMOS ?= $(basename $(notdir $(wildcard demos/*.tape)))80+FAKEJEV_ADDR := 127.0.0.1:879981+82+demos: build83+ @for t in vhs ttyd ffmpeg; do command -v $$t >/dev/null || { echo "demos need $$t (vhs: go install github.com/charmbracelet/vhs@latest; ttyd, ffmpeg: brew install ttyd ffmpeg)"; exit 1; }; done84+ CGO_ENABLED=0 go build -o bin/fakejev ./demos/fakejev85+ mkdir -p demos/out86+ bin/fakejev -addr $(FAKEJEV_ADDR) & pid=$$!; trap "kill $$pid" EXIT; \87+ export DEMOS_TTYD="$$(command -v ttyd)" PATH="$(CURDIR)/demos/lib:$$PATH"; \88+ cd demos && for d in $(DEMOS); do echo "vhs $$d.tape"; vhs -q $$d.tape || exit 1; done89+ mkdir -p demos/out/media90+ for d in $(DEMOS); do ffmpeg -v error -y -i demos/out/$$d.gif -filter_complex \91+ "fps=12,tpad=stop_mode=clone:stop_duration=2,split[a][b];[a]palettegen=max_colors=64:stats_mode=diff[p];[b][p]paletteuse=dither=none:diff_mode=rectangle" \92+ demos/out/media/$$d.gif || exit 1; done93+ @ls -l demos/out/media94+7395 libghostty: $(GHOSTTY_STAMP)7496 7597 # The commit matches the one pinned by go.mitchellh.com/libghostty.@@ -78,7 +78,7 @@ Avoid in the main binary: DuckDB and automerge (both need cgo).
7878 - Macros: replay the command log; Starlark (`go.starlark.net`) for scripts, with step limits.7979 - Serve over SSH with `charm.land/wish/v2`.8080 - Decimal mode for currency (`cockroachdb/apd/v3`), opt-in. Done: File > Settings > Decimal arithmetic, boundary in the README.81-- Demos in CI with VHS tapes; screenshots with freeze.81+- Demos in CI with VHS tapes; screenshots with freeze. Tapes done (`make demos`, `demos/`); not yet run in CI.8282 8383 ## Suggested order8484 @@ -0,0 +1,34 @@
1+# Charts: Insert > Chart charts the table around the active cell; the2+# editor bar switches the type with Left/Right, and the chart redraws as3+# its data changes. Terminals with the kitty graphics protocol get a real4+# image; this recording shows the text rendering every terminal gets.5+Output out/charts.gif6+Source lib/setup.tape7+8+Hide9+Type "../bin/012 data/spending.csv" Enter10+Sleep 2s11+Show12+13+Sleep 800ms14+Alt+i15+Sleep 900ms16+Up17+Sleep 500ms18+Enter19+Sleep 1.8s20+Right21+Sleep 1.5s22+Right23+Sleep 1.5s24+Right25+Sleep 1.5s26+Left@600ms 327+Sleep 800ms28+Enter29+Sleep 600ms30+Escape31+Sleep 600ms32+Down@200ms 3 Right@200ms 233+Type "1400" Enter34+Sleep 2.5s@@ -0,0 +1,31 @@
1+Item,Category,Supplier,Qty,Price2+Stapler,Desk,Umbrella,34,59.003+Paper A4,Paper,Initech,18,12.504+Pens blue,Writing,Acme,47,19.995+Pens red,Writing,Initech,82,19.996+Folders,Paper,Globex,71,0.997+Notebook,Paper,Globex,56,7.998+Tape,Desk,Globex,43,3.759+Scissors,Desk,Acme,73,3.7510+Glue stick,Desk,Acme,65,59.0011+Markers,Writing,Initech,87,12.5012+Envelopes,Paper,Acme,2,0.9913+Clips,Desk,Globex,11,12.5014+Binder,Paper,Umbrella,14,59.0015+Ruler,Desk,Umbrella,17,24.0016+Eraser,Writing,Initech,112,39.9017+Calculator,Tech,Globex,89,0.9918+Labels,Paper,Globex,64,1.4919+Toner,Tech,Umbrella,77,12.5020+Desk lamp,Furniture,Umbrella,61,39.9021+Chair mat,Furniture,Umbrella,69,0.9922+Whiteboard,Furniture,Acme,24,59.0023+Sticky notes,Paper,Initech,45,7.9924+Highlighter,Writing,Umbrella,86,4.2025+Cable ties,Tech,Acme,32,3.7526+USB stick,Tech,Initech,46,7.9927+Mouse,Tech,Globex,3,12.5028+Keyboard,Tech,Umbrella,4,24.0029+Headset,Tech,Acme,28,59.0030+Monitor arm,Furniture,Umbrella,5,12.5031+Webcam,Tech,Globex,98,39.90@@ -0,0 +1,17 @@
1+Region,Quarter,Units,Revenue2+North,Q1,106,"$2,252.50"3+North,Q2,315,"$6,296.85"4+North,Q3,186,"$3,718.14"5+North,Q4,224,"$4,477.76"6+South,Q1,323,"$6,863.75"7+South,Q2,326,"$6,031.00"8+South,Q3,165,"$3,298.35"9+South,Q4,361,"$7,216.39"10+East,Q1,173,"$3,200.50"11+East,Q2,192,"$4,080.00"12+East,Q3,83,"$1,659.17"13+East,Q4,254,"$4,699.00"14+West,Q1,203,"$3,755.50"15+West,Q2,336,"$6,216.00"16+West,Q3,206,"$4,377.50"17+West,Q4,139,"$2,778.61"@@ -0,0 +1,32 @@
1+# Decimal arithmetic: binary floating point makes $4.35 come to 4342+# whole cents and $0.10 + $0.20 miss 0.3. File > Settings > Decimal3+# arithmetic, here from the palette, makes this file's money math exact.4+Output out/decimal.gif5+Source lib/setup.tape6+7+Hide8+Type "../bin/012" Enter9+Sleep 1.5s10+Show11+12+Sleep 600ms13+Type "Coffee" Tab Type "$4.35" Enter14+Type "Tip" Tab Type "$0.10" Enter15+Type "Fee" Tab Type "$0.20" Enter16+Type "Cents" Tab Type "=INT(B1*100)" Enter17+Type "Is 0.30?" Tab Type "=B2+B3=0.3" Enter18+Type "Residue" Tab Type "=0.1+0.2-0.3" Enter19+Sleep 1.5s20+Ctrl+K21+Type@120ms "decimal"22+Sleep 1s23+Enter24+Sleep 2s25+Alt+f26+Sleep 500ms27+Up@400ms 228+Sleep 500ms29+Right30+Sleep 2s31+Escape@300ms 232+Sleep 1s@@ -0,0 +1,92 @@
1+// Command fakejev is a stand-in for the TypeSafe JEV service, for demo2+// recordings: it answers every question about a value by looking for3+// complaint words in the value, so a demo never needs a real key and4+// always records the same answers. Point 012 at it with5+//6+// TYPESAFE_API_KEY=demo TYPESAFE_BASE_URL=http://127.0.0.1:8799 ./bin/0127+package main8+9+import (10+ "encoding/json"11+ "flag"12+ "io"13+ "log"14+ "net/http"15+ "slices"16+ "strings"17+ "time"18+)19+20+// negative words mark a value as a complaint.21+var negative = []string{"crushed", "broken", "late", "wrong", "refund", "never", "missing"}22+23+func main() {24+ addr := flag.String("addr", "127.0.0.1:8799", "listen address")25+ delay := flag.Duration("delay", 600*time.Millisecond, "time to answer, so Loading… shows")26+ flag.Parse()27+ http.HandleFunc("/v1/systemone", func(w http.ResponseWriter, r *http.Request) {28+ body, _ := io.ReadAll(r.Body)29+ time.Sleep(*delay)30+ answer, err := answerFor(body)31+ if err != nil {32+ http.Error(w, err.Error(), http.StatusBadRequest)33+ return34+ }35+ json.NewEncoder(w).Encode(map[string]any{36+ "model": "jev-demo", "answers": map[string]any{"answer": answer},37+ "usage": map[string]int{"input_tokens": 1, "output_tokens": 1},38+ })39+ })40+ log.Printf("fake JEV service on http://%s", *addr)41+ log.Fatal(http.ListenAndServe(*addr, nil))42+}43+44+// answerFor answers the request's question: yes-or-no, a choice of45+// labels, or a score on levels.46+func answerFor(body []byte) (map[string]any, error) {47+ var req struct {48+ State json.RawMessage `json:"state"`49+ Questions map[string]struct {50+ Type string `json:"type"`51+ Criteria json.RawMessage `json:"criteria"`52+ } `json:"questions"`53+ }54+ if err := json.Unmarshal(body, &req); err != nil {55+ return nil, err56+ }57+ text := strings.ToLower(string(req.State))58+ bad := slices.ContainsFunc(negative, func(w string) bool { return strings.Contains(text, w) })59+ q := req.Questions["answer"]60+ switch q.Type {61+ case "noul":62+ p := 0.0763+ if bad {64+ p = 0.9465+ }66+ return map[string]any{"type": "noul", "noul": p}, nil67+ case "choice":68+ var labels map[string]any69+ json.Unmarshal(q.Criteria, &labels)70+ keys := make([]string, 0, len(labels))71+ for l := range labels {72+ keys = append(keys, l)73+ }74+ slices.Sort(keys)75+ pick := keys[len(keys)-1]76+ if bad {77+ pick = keys[0]78+ }79+ probs := map[string]float64{}80+ for _, l := range keys {81+ probs[l] = 0.1 / float64(max(len(keys)-1, 1))82+ }83+ probs[pick] = 0.984+ return map[string]any{"type": "choice", "choice": pick, "confidence": 0.9, "probabilities": probs}, nil85+ }86+ score, probs := 0.2, map[string]float64{"0": 0.8, "1": 0.15, "2": 0.05}87+ if bad {88+ score, probs = 1.8, map[string]float64{"0": 0.05, "1": 0.15, "2": 0.8}89+ }90+ return map[string]any{"type": "score", "score": score, "confidence": 0.8,91+ "legend": map[string]any{"0": "low", "1": "mid", "2": "high"}, "probabilities": probs}, nil92+}@@ -0,0 +1,31 @@
1+# Find and replace: matches light up as you type and the active cell2+# follows the current one. Find and replace keeps the query, takes the3+# replacement on the same bar, and Alt+A replaces every match.4+Output out/find-replace.gif5+Source lib/setup.tape6+7+Hide8+Type "../bin/012 data/inventory.csv" Enter9+Sleep 2s10+Show11+12+Sleep 800ms13+Ctrl+F14+Sleep 500ms15+Type@150ms "acme"16+Sleep 1.2s17+Enter18+Sleep 800ms19+Enter20+Sleep 800ms21+Escape22+Sleep 600ms23+Ctrl+K24+Type@100ms "replace"25+Sleep 800ms26+Enter27+Sleep 800ms28+Type@120ms "Acme Co."29+Sleep 1s30+Alt+a31+Sleep 3s@@ -0,0 +1,27 @@
1+# First steps: type like in Sheets. Tab moves right, Enter goes down and2+# back to the column where the Tabs began, = starts a formula, the arrows3+# point at cells while typing one, and totals follow their inputs.4+Output out/first-steps.gif5+Source lib/setup.tape6+7+Hide8+Type "../bin/012" Enter9+Sleep 1.5s10+Show11+12+Sleep 800ms13+Type "Item" Tab Type "Price" Tab Type "Qty" Tab Type "Total" Enter14+Type "Coffee" Tab Type "$4.35" Tab Type "3" Tab15+Type "=" Sleep 300ms Left@400ms 2 Type "*" Sleep 300ms Left16+Sleep 700ms17+Enter18+Type "Bagel" Tab Type "$2.80" Tab Type "2" Tab Type "=B3*C3" Enter19+Type "Juice" Tab Type "$3.90" Tab Type "4" Tab Type "=B4*C4" Enter20+Type "Total" Tab@300ms 321+Type "=SUM(D2:D4)"22+Sleep 700ms23+Enter24+Sleep 800ms25+Up@300ms 3 Left26+Type "6" Enter27+Sleep 3s@@ -0,0 +1,31 @@
1+# Formats: select the amounts, make them Currency rounded from2+# Format > Number, then bold the header row. Formats follow Sheets'3+# presets, and a SUM of currency is currency too.4+Output out/formats.gif5+Source lib/setup.tape6+7+Hide8+Type "../bin/012 data/spending.csv" Enter9+Sleep 2s10+Show11+12+Sleep 800ms13+Down Right14+Ctrl+Shift+Right15+Ctrl+Shift+Down16+Sleep 1s17+Alt+o18+Sleep 700ms19+Right20+Sleep 1s21+Down@400ms 822+Sleep 800ms23+Enter24+Sleep 1.5s25+Ctrl+Up Ctrl+Left26+Ctrl+Shift+Right27+Ctrl+B28+Sleep 1s29+Ctrl+Down Down30+Type "Total" Tab Type "=SUM(B2:B7)" Tab Type "=SUM(C2:C7)" Tab Type "=SUM(D2:D7)" Enter31+Sleep 3s@@ -0,0 +1,48 @@
1+# Freeze, sort and filter: keep the header row on screen, sort by the2+# active column (frozen rows stay on top), then filter a column by its3+# values; the status line counts the hidden rows.4+Output out/freeze-sort-filter.gif5+Source lib/setup.tape6+7+Hide8+Type "../bin/012 data/inventory.csv" Enter9+Sleep 2s10+Show11+12+Sleep 800ms13+Alt+v14+Sleep 600ms15+Enter16+Sleep 600ms17+Down18+Sleep 500ms19+Enter20+Sleep 1s21+PageDown22+Sleep 1.5s23+Ctrl+Up Ctrl+Left Right@200ms 424+Alt+d25+Sleep 600ms26+Enter27+Sleep 600ms28+Down29+Sleep 600ms30+Enter31+Sleep 1.5s32+Left@200ms 333+Alt+d34+Sleep 500ms35+Type "c"36+Sleep 1s37+Alt+d38+Type "f"39+Sleep 1.2s40+Space41+Sleep 500ms42+Type@150ms "tech"43+Sleep 800ms44+Down45+Space46+Sleep 800ms47+Enter48+Sleep 3s@@ -0,0 +1,26 @@
1+# Import: File > Import lists the spreadsheets in the folder (CSV, TSV,2+# XLSX, SQLite, Parquet, Lotus .wk1); type to narrow, Enter imports in3+# the background with a progress bar. Entries become numbers and4+# currency as if typed.5+Output out/import.gif6+Source lib/setup.tape7+8+Hide9+Type "cd data && ../../bin/012" Enter10+Sleep 1.5s11+Show12+13+Sleep 800ms14+Alt+f15+Sleep 700ms16+Down@400ms 217+Sleep 700ms18+Enter19+Sleep 1.5s20+Type@200ms "sal"21+Sleep 1s22+Enter23+Sleep 2s24+Right@300ms 3 Down25+Ctrl+Shift+Down26+Sleep 3s@@ -0,0 +1,32 @@
1+# JEV functions: ask a model about each review, Sheets-style. Answers2+# arrive in the background (cells show Loading…), and the context line3+# shows the confidence. Recorded against demos/fakejev, a stand-in4+# service that `make demos` starts; no real key is involved.5+Output out/jev.gif6+Source lib/setup.tape7+8+Hide9+Type "export TYPESAFE_API_KEY=demo TYPESAFE_BASE_URL=http://127.0.0.1:8799" Enter10+Type "../bin/012" Enter11+Sleep 1.5s12+Show13+14+Sleep 600ms15+Type "Review" Tab Type "Complaint?" Tab Type "Sentiment" Tab Type "Urgency" Enter16+Type "Box arrived crushed" Enter17+Type "Fast and friendly" Enter18+Type "Wrong size, no reply" Enter19+Ctrl+G20+Type "B2" Enter21+Type@25ms '=JEV.TEST(A2, "Is this a complaint?")' Tab22+Type@25ms '=JEV.CLASSIFY(A2, "Sentiment", "negative, positive")' Tab23+Type@25ms '=JEV.SCORE(A2, "Urgency", "low, mid, high")' Enter24+Sleep 1.5s25+Ctrl+G26+Type "B2:D4" Enter27+Sleep 600ms28+Ctrl+D29+Sleep 2.5s30+Escape31+Right Down32+Sleep 2.5s@@ -0,0 +1,12 @@
1+# Shared settings for every demo: a fixed window, the dark reference2+# palette the golden screens use (e2e/palette_test.go), and a pace that3+# reads well. Tapes run from demos/, so the binary is ../bin/012.4+Set Shell "bash"5+Set FontSize 186+Set Width 11207+Set Height 6408+Set Padding 169+Set LineHeight 1.1510+Set TypingSpeed 55ms11+Set CursorBlink false12+Set Theme { "name": "012 dark", "background": "#1d1f21", "foreground": "#c5c8c6", "cursor": "#c5c8c6", "selection": "#373b41", "black": "#1d1f21", "red": "#cc6666", "green": "#b5bd68", "yellow": "#f0c674", "blue": "#81a2be", "magenta": "#b294bb", "cyan": "#8abeb7", "white": "#c5c8c6", "brightBlack": "#666666", "brightRed": "#d54e53", "brightGreen": "#b9ca4a", "brightYellow": "#e7c547", "brightBlue": "#7aa6da", "brightMagenta": "#c397d8", "brightCyan": "#70c0b1", "brightWhite": "#eaeaea" }@@ -0,0 +1,6 @@
1+2+# VHS starts ttyd by name. `make demos` puts this directory first on PATH3+# so the recorded xterm.js treats Option as Alt on macOS, as terminals4+# set up for it do; otherwise Alt+F types "f" instead of opening 012's5+# File menu. DEMOS_TTYD is the real ttyd.6+exec "${DEMOS_TTYD:?set DEMOS_TTYD to the real ttyd}" -t macOptionIsMeta=true "$@"@@ -0,0 +1,37 @@
1+# Menus and the command palette: Alt and a letter open the Sheets-style2+# menus, arrows walk them and the status line says what each item does;3+# Ctrl+K searches every command by name and shows its keys.4+Output out/menus-palette.gif5+Source lib/setup.tape6+7+Hide8+Type "../bin/012 data/inventory.csv" Enter9+Sleep 2s10+Show11+12+Sleep 800ms13+Alt+f14+Sleep 1.2s15+Down@400ms 316+Sleep 800ms17+Right@900ms 418+Sleep 500ms19+Right20+Sleep 1.2s21+Escape@300ms 222+Sleep 600ms23+Ctrl+K24+Sleep 1s25+Type@120ms "freeze"26+Sleep 1.2s27+Down@500ms 228+Sleep 800ms29+Enter30+Sleep 1.5s31+PageDown32+Sleep 1.5s33+Ctrl+K34+Type@120ms "width"35+Sleep 1.5s36+Escape37+Sleep 1s@@ -0,0 +1,27 @@
1+# Selection and live stats: select a column of numbers and the status2+# line shows its Sum, Avg and Count; widen the selection, select whole3+# columns, then everything. (VHS can't drive the mouse; clicking,4+# dragging and Shift+click select the same way.)5+Output out/selection.gif6+Source lib/setup.tape7+8+Hide9+Type "../bin/012 data/inventory.csv" Enter10+Sleep 2s11+Show12+13+Sleep 800ms14+Right@300ms 3 Down15+Sleep 600ms16+Ctrl+Shift+Down17+Sleep 2s18+Ctrl+Shift+Right19+Sleep 2s20+Escape21+Left22+Ctrl+Space23+Sleep 2s24+Ctrl+A25+Sleep 2s26+Escape27+Sleep 1s