nandi/frqpublic Fork 0
4f04b91
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Start the terminal client the way a launch starts the window one

frq.tui came up on a client that had restored nothing, was signed into nothing
and was connected to nothing: it seeded a demo buffer and called ui/run, so
every real room was empty with nobody in it — which reads as a broken screen
rather than as a client nobody told to start.

The startup was the first half of -main and a second entry point could only
copy it, so it moves into frq.app/start! and takes the backend's timers as an
argument. A window passes vidya's pair, its title and its size; a terminal
passes its own and leaves out the two things it has not got, plus the media
plane — a call paints frames into a texture, and there is no texture here.

What is left of frq.tui is the terminal: the point-to-cell scale, the size
poll, and a headless screenshot. The demo buffer stays behind --demo, which is
what it was always for — a picture that is not waiting on a server — and
--wait= is how long the client gets before one is taken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-01T23:04:54-07:00 Browse files
4f04b91 parent: ab83b42
modified README.md +32 -20
@@ -65,33 +65,45 @@ cargo run --release --bin freeq-server # in the freeq checkout
6565 The screens are hiccup over glimmer's reconciler, and the reconciler does not
6666 know what is under it — so the same tree paints into a terminal through
6767 jolt-native's `libjolttui`, which exports libvidya's retained-tree ABI over a
68-grid of cells instead of a GPU window. `src/frq/tui.jolt` is the whole of what
69-that costs on this side: it requires `glimmer-tui.core` after `frq.app`, so the
70-backend installed last is the terminal, and seeds a `#tui` buffer so a session
71-started for a look at the layout has a conversation in it.
68+grid of cells instead of a GPU window.
69+
70+It is the client, not a preview of it. `frq.app/start!` is what a launch does —
71+the saved settings, the rooms this client has been in, the sign-in that
72+connects itself — and `src/frq/tui.jolt` hands it the terminal's timers instead
73+of the window's. Nothing in `frq.app` changed.
7274
7375 ```bash
74-just tui # the terminal, until Ctrl-Q
76+nix run .#tui # or: just tui
7577 just tui --headless --cols=90 --rows=60 # one screenshot on stdout
78+just tui --headless --demo # a buffer of its own, no server
79+just tui --headless --wait=9000 # long enough to have connected
7680 ```
7781
7882 The headless one is `tui_headless` — the same layout and the same painting with
7983 the writer taken off the end — which is what a screenshot in a bug report or a
80-CI check should be.
81-
82-Two things are unpinned here, because the terminal backend is not in a
83-jolt-native release yet: `libjolttui.so` comes out of a jolt-native checkout's
84-target directory (`JOLT_NATIVE=…`, or beside this tree), and `glimmer-tui` is
85-resolved from the same checkout. Both become pins like every other when it
86-ships.
87-
88-What a terminal has not got, frq does without: pictures, avatars, the lightbox
89-and calls draw nothing. And frq's spacing is written in points, for a window —
90-`scripts/tui.bb` hands the backend `:points-per-cell 8` so those numbers land
91-in cells, but `below-messages` in `src/frq/app.jolt` is point *arithmetic*
92-rather than a point *length*, and a scale cannot fix it: it reserves about
93-thirteen rows more than the compose bar needs, so the bottom of the backlog is
94-pushed out of the list.
84+CI check should be. It paints once and prints, so `--wait=` is how long the
85+client is given first: the default is a picture of the connect screen, because
86+that is where a client is a moment after launch, and `--demo` fills a `#tui`
87+buffer for a screenshot that is not waiting on a server at all.
88+
89+Logs go to stderr, which in a terminal session is the screen frq is painting.
90+Send them somewhere: `nix run .#tui 2>/tmp/frq.log`.
91+
92+What a terminal has not got, frq does without: pictures, avatars and the
93+lightbox draw nothing, and calls are off — the media plane paints frames into
94+a texture, and there is no texture here. And frq's spacing is written in
95+points, for a window — the backend is handed `:points-per-cell 8` so those
96+numbers land in cells, but `below-messages` in `src/frq/app.jolt` is point
97+*arithmetic* rather than a point *length*, and a scale cannot fix it: it
98+reserves about thirteen rows more than the compose bar needs, so the bottom of
99+the backlog is pushed out of the list.
100+
101+Two things are unpinned, because the terminal backend is not in a jolt-native
102+release yet: `libjolttui.so` comes out of a jolt-native checkout's target
103+directory (`JOLT_NATIVE=…`, or beside this tree) for `just tui`, and the flake
104+carries a second `jolt-native-tui` input at the rev that has it — its own input
105+rather than a bump, so the window half stays on the release the rest of the
106+tree names. Both become one pin when it ships.
95107
96108 ## Signing in
97109
@@ -65,33 +65,45 @@ cargo run --release --bin freeq-server # in the freeq checkout
65 The screens are hiccup over glimmer's reconciler, and the reconciler does not65 The screens are hiccup over glimmer's reconciler, and the reconciler does not
66 know what is under it — so the same tree paints into a terminal through66 know what is under it — so the same tree paints into a terminal through
67 jolt-native's `libjolttui`, which exports libvidya's retained-tree ABI over a67 jolt-native's `libjolttui`, which exports libvidya's retained-tree ABI over a
68-grid of cells instead of a GPU window. `src/frq/tui.jolt` is the whole of what68+grid of cells instead of a GPU window.
69-that costs on this side: it requires `glimmer-tui.core` after `frq.app`, so the69+
70-backend installed last is the terminal, and seeds a `#tui` buffer so a session70+It is the client, not a preview of it. `frq.app/start!` is what a launch does —
71-started for a look at the layout has a conversation in it.71+the saved settings, the rooms this client has been in, the sign-in that
72+connects itself — and `src/frq/tui.jolt` hands it the terminal's timers instead
73+of the window's. Nothing in `frq.app` changed.
72 74
73 ```bash75 ```bash
74-just tui # the terminal, until Ctrl-Q76+nix run .#tui # or: just tui
75 just tui --headless --cols=90 --rows=60 # one screenshot on stdout77 just tui --headless --cols=90 --rows=60 # one screenshot on stdout
78+just tui --headless --demo # a buffer of its own, no server
79+just tui --headless --wait=9000 # long enough to have connected
76 ```80 ```
77 81
78 The headless one is `tui_headless` — the same layout and the same painting with82 The headless one is `tui_headless` — the same layout and the same painting with
79 the writer taken off the end — which is what a screenshot in a bug report or a83 the writer taken off the end — which is what a screenshot in a bug report or a
80-CI check should be.84+CI check should be. It paints once and prints, so `--wait=` is how long the
81-85+client is given first: the default is a picture of the connect screen, because
82-Two things are unpinned here, because the terminal backend is not in a86+that is where a client is a moment after launch, and `--demo` fills a `#tui`
83-jolt-native release yet: `libjolttui.so` comes out of a jolt-native checkout's87+buffer for a screenshot that is not waiting on a server at all.
84-target directory (`JOLT_NATIVE=…`, or beside this tree), and `glimmer-tui` is88+
85-resolved from the same checkout. Both become pins like every other when it89+Logs go to stderr, which in a terminal session is the screen frq is painting.
86-ships.90+Send them somewhere: `nix run .#tui 2>/tmp/frq.log`.
87-91+
88-What a terminal has not got, frq does without: pictures, avatars, the lightbox92+What a terminal has not got, frq does without: pictures, avatars and the
89-and calls draw nothing. And frq's spacing is written in points, for a window —93+lightbox draw nothing, and calls are off — the media plane paints frames into
90-`scripts/tui.bb` hands the backend `:points-per-cell 8` so those numbers land94+a texture, and there is no texture here. And frq's spacing is written in
91-in cells, but `below-messages` in `src/frq/app.jolt` is point *arithmetic*95+points, for a window — the backend is handed `:points-per-cell 8` so those
92-rather than a point *length*, and a scale cannot fix it: it reserves about96+numbers land in cells, but `below-messages` in `src/frq/app.jolt` is point
93-thirteen rows more than the compose bar needs, so the bottom of the backlog is97+*arithmetic* rather than a point *length*, and a scale cannot fix it: it
94-pushed out of the list.98+reserves about thirteen rows more than the compose bar needs, so the bottom of
99+the backlog is pushed out of the list.
100+
101+Two things are unpinned, because the terminal backend is not in a jolt-native
102+release yet: `libjolttui.so` comes out of a jolt-native checkout's target
103+directory (`JOLT_NATIVE=…`, or beside this tree) for `just tui`, and the flake
104+carries a second `jolt-native-tui` input at the rev that has it — its own input
105+rather than a bump, so the window half stays on the release the rest of the
106+tree names. Both become one pin when it ships.
95 107
96 ## Signing in108 ## Signing in
97 109
modified src/frq/app.jolt +63 -35
@@ -1297,8 +1297,25 @@
12971297 :settings [:vbox {:key :screen-settings} [settings-screen]]
12981298 [:vbox {:key :screen-chats} [chats-screen]])))
12991299
1300-(defn -main [& _]
1301- ;; Before the window: the settings, the rooms this client has been in, and a
1300+(defn start!
1301+ "Everything a launch does before the loop starts, for whichever backend is
1302+ about to run it.
1303+
1304+ This used to be the first half of `-main`, and a second entry point copied it
1305+ — which is how frq.tui came up on a client that had restored nothing, was
1306+ signed into nothing and was connected to nothing: an empty buffer with nobody
1307+ in it, which reads as a broken screen rather than as a client that was never
1308+ told to start.
1309+
1310+ The timers are handed in because they belong to the backend: `after!` and
1311+ `every!` are how anything gets onto the loop thread, and the terminal has its
1312+ own pair. So are the two things only a window has — `title!` renames one, and
1313+ `measure!` reports its size — and either may be nil where there is none.
1314+
1315+ `av?` is the media plane. It wants a window: a call paints frames into a
1316+ texture, and there is no texture in a terminal."
1317+ [{:keys [after! every! title! measure! av?] :or {av? true}}]
1318+ ;; Before the loop: the settings, the rooms this client has been in, and a
13021319 ;; saved sign-in deciding which mode the connect screen opens in and what it
13031320 ;; says.
13041321 (s/restore-prefs!)
@@ -1307,51 +1324,62 @@
13071324 ;; And then it connects on its own. A remembered account has already said
13081325 ;; what it wants; making it say so again at every launch is a click that
13091326 ;; carries no information. It is a timer rather than a call here so the
1310- ;; window is up first the connect screen with its status is what the
1327+ ;; screen is up first the connect screen with its status is what the
13111328 ;; user should be looking at while this happens, and if it fails, the
13121329 ;; error lands somewhere visible.
1313- (vidya/after! 150 s/connect!))
1314- ;; Calls arrive rather than being asked for, so the media plane is drained
1315- ;; every frame whether or not one is up the drain costs a single integer
1316- ;; read when it is not. It has to be a timer: `frame-rgba!` and everything
1317- ;; else that touches a node belongs to the loop thread, and this is glimmer's
1318- ;; way of getting onto it.
1319- (av/init-logging!)
1320- ;; Who to tell when a call ends under us rather than at our asking. Set here
1321- ;; rather than in frq.av because sending a TAGMSG needs the connection, and
1322- ;; that belongs to the state layer.
1323- (reset! av/on-dropped s/announce-leave!)
1324- (vidya/after! 0 av/install-pump!)
1325- ;; The window's width, into a ratom, a few times a second. Polled rather than
1330+ (after! 150 s/connect!))
1331+ (when av?
1332+ ;; Calls arrive rather than being asked for, so the media plane is drained
1333+ ;; every frame whether or not one is up the drain costs a single integer
1334+ ;; read when it is not. It has to be a timer: `frame-rgba!` and everything
1335+ ;; else that touches a node belongs to the loop thread, and this is
1336+ ;; glimmer's way of getting onto it.
1337+ (av/init-logging!)
1338+ ;; Who to tell when a call ends under us rather than at our asking. Set
1339+ ;; here rather than in frq.av because sending a TAGMSG needs the
1340+ ;; connection, and that belongs to the state layer.
1341+ (reset! av/on-dropped s/announce-leave!)
1342+ (after! 0 av/install-pump!))
1343+ ;; The surface's size, into a ratom, a few times a second. Polled rather than
13261344 ;; delivered: the backend reports a size by writing it onto the window node,
13271345 ;; and only what a component derefs re-renders so the layout follows a drag
1328- ;; of the window's edge without every frame touching the tree.
1329- ;;
1330- ;; The height comes off the same tick, from `screen-size` rather than a
1331- ;; second call: it is the window's content size, and the pictures in the
1332- ;; conversation are sized against it.
1333- (vidya/every! 200 #(let [w (vidya/window-width)
1334- h (long (second (vidya/screen-size)))]
1335- (when (not= w @s/window-width)
1336- (reset! s/window-width w))
1337- (when (not= h @s/window-height)
1338- (reset! s/window-height h))))
1346+ ;; of the edge without every frame touching the tree.
1347+ (when measure!
1348+ (measure!)
1349+ (every! 200 measure!))
13391350 ;; The nick in the window title, so a second window of this client is told
13401351 ;; apart from the first by the one thing that differs and so the answer to
13411352 ;; "who am I here?" is on screen without opening Settings.
13421353 ;;
1343- ;; Polled, like the width above and for the same reason: the title belongs to
1354+ ;; Polled, like the size above and for the same reason: the title belongs to
13441355 ;; the window rather than to the tree, so no render puts it there, and the
13451356 ;; nick is only settled once a connection has been made. Twice a second is
13461357 ;; far more often than a nick changes, and a string compare is what a tick
13471358 ;; costs when it has not.
13481359 ;; Seeded with the title `run` opens the window under, so the first tick of
13491360 ;; a launch that has nobody signed in yet sets nothing.
1350- (let [shown (atom "frq")]
1351- (vidya/every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick))
1352- (str "frq — " @s/form-nick)
1353- "frq")]
1354- (when (not= title @shown)
1355- (reset! shown title)
1356- (vidya/set-title! title)))))
1361+ (when title!
1362+ (let [shown (atom "frq")]
1363+ (every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick))
1364+ (str "frq — " @s/form-nick)
1365+ "frq")]
1366+ (when (not= title @shown)
1367+ (reset! shown title)
1368+ (title! title))))))
1369+ nil)
1370+
1371+(defn -main [& _]
1372+ (start! {:after! vidya/after!
1373+ :every! vidya/every!
1374+ :title! vidya/set-title!
1375+ ;; The height comes off the same tick, from `screen-size` rather
1376+ ;; than a second call: it is the window's content size, and the
1377+ ;; pictures in the conversation are sized against it.
1378+ :measure! (fn []
1379+ (let [w (vidya/window-width)
1380+ h (long (second (vidya/screen-size)))]
1381+ (when (not= w @s/window-width)
1382+ (reset! s/window-width w))
1383+ (when (not= h @s/window-height)
1384+ (reset! s/window-height h))))})
13571385 (ui/run app :title "frq" :width 520 :height 860))
@@ -1297,8 +1297,25 @@
1297 :settings [:vbox {:key :screen-settings} [settings-screen]]1297 :settings [:vbox {:key :screen-settings} [settings-screen]]
1298 [:vbox {:key :screen-chats} [chats-screen]])))1298 [:vbox {:key :screen-chats} [chats-screen]])))
1299 1299
1300-(defn -main [& _]1300+(defn start!
1301- ;; Before the window: the settings, the rooms this client has been in, and a1301+ "Everything a launch does before the loop starts, for whichever backend is
1302+ about to run it.
1303+
1304+ This used to be the first half of `-main`, and a second entry point copied it
1305+ — which is how frq.tui came up on a client that had restored nothing, was
1306+ signed into nothing and was connected to nothing: an empty buffer with nobody
1307+ in it, which reads as a broken screen rather than as a client that was never
1308+ told to start.
1309+
1310+ The timers are handed in because they belong to the backend: `after!` and
1311+ `every!` are how anything gets onto the loop thread, and the terminal has its
1312+ own pair. So are the two things only a window has — `title!` renames one, and
1313+ `measure!` reports its size — and either may be nil where there is none.
1314+
1315+ `av?` is the media plane. It wants a window: a call paints frames into a
1316+ texture, and there is no texture in a terminal."
1317+ [{:keys [after! every! title! measure! av?] :or {av? true}}]
1318+ ;; Before the loop: the settings, the rooms this client has been in, and a
1302 ;; saved sign-in deciding which mode the connect screen opens in and what it1319 ;; saved sign-in deciding which mode the connect screen opens in and what it
1303 ;; says.1320 ;; says.
1304 (s/restore-prefs!)1321 (s/restore-prefs!)
@@ -1307,51 +1324,62 @@
1307 ;; And then it connects on its own. A remembered account has already said1324 ;; And then it connects on its own. A remembered account has already said
1308 ;; what it wants; making it say so again at every launch is a click that1325 ;; what it wants; making it say so again at every launch is a click that
1309 ;; carries no information. It is a timer rather than a call here so the1326 ;; carries no information. It is a timer rather than a call here so the
1310- ;; window is up first the connect screen with its status is what the1327+ ;; screen is up first the connect screen with its status is what the
1311 ;; user should be looking at while this happens, and if it fails, the1328 ;; user should be looking at while this happens, and if it fails, the
1312 ;; error lands somewhere visible.1329 ;; error lands somewhere visible.
1313- (vidya/after! 150 s/connect!))1330+ (after! 150 s/connect!))
1314- ;; Calls arrive rather than being asked for, so the media plane is drained1331+ (when av?
1315- ;; every frame whether or not one is up the drain costs a single integer1332+ ;; Calls arrive rather than being asked for, so the media plane is drained
1316- ;; read when it is not. It has to be a timer: `frame-rgba!` and everything1333+ ;; every frame whether or not one is up the drain costs a single integer
1317- ;; else that touches a node belongs to the loop thread, and this is glimmer's1334+ ;; read when it is not. It has to be a timer: `frame-rgba!` and everything
1318- ;; way of getting onto it.1335+ ;; else that touches a node belongs to the loop thread, and this is
1319- (av/init-logging!)1336+ ;; glimmer's way of getting onto it.
1320- ;; Who to tell when a call ends under us rather than at our asking. Set here1337+ (av/init-logging!)
1321- ;; rather than in frq.av because sending a TAGMSG needs the connection, and1338+ ;; Who to tell when a call ends under us rather than at our asking. Set
1322- ;; that belongs to the state layer.1339+ ;; here rather than in frq.av because sending a TAGMSG needs the
1323- (reset! av/on-dropped s/announce-leave!)1340+ ;; connection, and that belongs to the state layer.
1324- (vidya/after! 0 av/install-pump!)1341+ (reset! av/on-dropped s/announce-leave!)
1325- ;; The window's width, into a ratom, a few times a second. Polled rather than1342+ (after! 0 av/install-pump!))
1343+ ;; The surface's size, into a ratom, a few times a second. Polled rather than
1326 ;; delivered: the backend reports a size by writing it onto the window node,1344 ;; delivered: the backend reports a size by writing it onto the window node,
1327 ;; and only what a component derefs re-renders so the layout follows a drag1345 ;; and only what a component derefs re-renders so the layout follows a drag
1328- ;; of the window's edge without every frame touching the tree.1346+ ;; of the edge without every frame touching the tree.
1329- ;;1347+ (when measure!
1330- ;; The height comes off the same tick, from `screen-size` rather than a1348+ (measure!)
1331- ;; second call: it is the window's content size, and the pictures in the1349+ (every! 200 measure!))
1332- ;; conversation are sized against it.
1333- (vidya/every! 200 #(let [w (vidya/window-width)
1334- h (long (second (vidya/screen-size)))]
1335- (when (not= w @s/window-width)
1336- (reset! s/window-width w))
1337- (when (not= h @s/window-height)
1338- (reset! s/window-height h))))
1339 ;; The nick in the window title, so a second window of this client is told1350 ;; The nick in the window title, so a second window of this client is told
1340 ;; apart from the first by the one thing that differs and so the answer to1351 ;; apart from the first by the one thing that differs and so the answer to
1341 ;; "who am I here?" is on screen without opening Settings.1352 ;; "who am I here?" is on screen without opening Settings.
1342 ;;1353 ;;
1343- ;; Polled, like the width above and for the same reason: the title belongs to1354+ ;; Polled, like the size above and for the same reason: the title belongs to
1344 ;; the window rather than to the tree, so no render puts it there, and the1355 ;; the window rather than to the tree, so no render puts it there, and the
1345 ;; nick is only settled once a connection has been made. Twice a second is1356 ;; nick is only settled once a connection has been made. Twice a second is
1346 ;; far more often than a nick changes, and a string compare is what a tick1357 ;; far more often than a nick changes, and a string compare is what a tick
1347 ;; costs when it has not.1358 ;; costs when it has not.
1348 ;; Seeded with the title `run` opens the window under, so the first tick of1359 ;; Seeded with the title `run` opens the window under, so the first tick of
1349 ;; a launch that has nobody signed in yet sets nothing.1360 ;; a launch that has nobody signed in yet sets nothing.
1350- (let [shown (atom "frq")]1361+ (when title!
1351- (vidya/every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick))1362+ (let [shown (atom "frq")]
1352- (str "frq — " @s/form-nick)1363+ (every! 500 #(let [title (if (and (s/connected?) (seq @s/form-nick))
1353- "frq")]1364+ (str "frq — " @s/form-nick)
1354- (when (not= title @shown)1365+ "frq")]
1355- (reset! shown title)1366+ (when (not= title @shown)
1356- (vidya/set-title! title)))))1367+ (reset! shown title)
1368+ (title! title))))))
1369+ nil)
1370+
1371+(defn -main [& _]
1372+ (start! {:after! vidya/after!
1373+ :every! vidya/every!
1374+ :title! vidya/set-title!
1375+ ;; The height comes off the same tick, from `screen-size` rather
1376+ ;; than a second call: it is the window's content size, and the
1377+ ;; pictures in the conversation are sized against it.
1378+ :measure! (fn []
1379+ (let [w (vidya/window-width)
1380+ h (long (second (vidya/screen-size)))]
1381+ (when (not= w @s/window-width)
1382+ (reset! s/window-width w))
1383+ (when (not= h @s/window-height)
1384+ (reset! s/window-height h))))})
1357 (ui/run app :title "frq" :width 520 :height 860))1385 (ui/run app :title "frq" :width 520 :height 860))
modified src/frq/tui.jolt +59 -29
@@ -13,6 +13,12 @@
1313 title, a window width, a picture chooser and with no window open every one
1414 of those is inert, which is the behaviour those calls already have to have.
1515
16+ It is the client, not a preview of it: `frq.app/start!` is what a launch
17+ does, and this hands it the terminal's timers instead of the window's. The
18+ saved sign-in is restored and connects itself, the rooms this client has been
19+ in come back, and `#test` is joined on arrival — the same session as the
20+ window, painted somewhere else.
21+
1622 Two ways to run it:
1723
1824 jolt -M:tui the real terminal, until Ctrl-Q
@@ -20,7 +26,10 @@
2026
2127 The headless one is `tui_headless` the same layout and the same painting
2228 with the writer taken off the end and is what a screenshot in a bug report
23- or a CI check should be."
29+ or a CI check should be. It is also the one that has nothing to show: a
30+ screenshot taken a moment after launch is of a client that has not finished
31+ connecting, so `--demo` fills a buffer of its own rather than waiting on a
32+ server for one."
2433 (:require [clojure.string :as str]
2534 [frq.app :as app]
2635 [frq.state :as s]
@@ -38,13 +47,13 @@
3847 (def ^:private points-per-cell 8)
3948
4049 (defn- seed!
41- "Put a conversation on screen for a client that is not connected to anything.
50+ "Put a conversation on screen without a server behind it `--demo`.
4251
43- A terminal session started for a look at the layout has no server behind it,
44- and an empty buffer says nothing about how the buffer looks. These go in
45- through `push-message!` rather than into the atom directly, so what is drawn
46- is a real buffer timestamps, day headings, the actor lookup, reply chips
47- and reactions all included."
52+ A screenshot is taken before a connection has been made, and an empty buffer
53+ says nothing about how a buffer looks. These go in through `push-message!`
54+ rather than into the atom directly, so what is drawn is a real buffer:
55+ timestamps, day headings, the actor lookup, reply chips and reactions all
56+ included."
4857 []
4958 (let [now (System/currentTimeMillis)
5059 minute 60000]
@@ -83,41 +92,62 @@
8392 (reset! s/window-height (* rows points-per-cell 2)))
8493 nil)
8594
95+(defn- start!
96+ "The launch, with the terminal's timers in place of the window's.
97+
98+ No title and no media plane: a terminal has no title bar to write a nick
99+ into, and a call paints frames into a texture there is no texture for here.
100+ Everything else — the saved settings, the rooms, the sign-in that connects
101+ itself — is the same startup the window runs."
102+ []
103+ (app/start! {:after! tui/after!
104+ :every! tui/every!
105+ :title! nil
106+ :av? false
107+ :measure! narrow!}))
108+
86109 (defn- run-headless!
87110 "Mount the app in a session of `cols` by `rows` with no terminal, let it
88111 settle, and print what was painted."
89- [cols rows]
112+ [cols rows demo? wait]
90113 ;; The size has to be known before the first render, or the layout reads the
91114 ;; zero it was left at. A timer runs on the loop thread, which is where
92115 ;; screen-size may be asked and the first one fires before the first paint.
93- (tui/after! 0 narrow!)
94- (tui/after! 120 (fn []
116+ (tui/after! 0 (fn []
117+ (narrow!)
118+ (start!)
119+ (when demo? (seed!))))
120+ ;; Long enough for whatever the screenshot is of. A client that restores a
121+ ;; sign-in spends the first seconds connecting, so the default is a picture
122+ ;; of the connect screen and `--wait=` is how you ask for one of the room.
123+ (tui/after! wait (fn []
95124 (println (tui/screen-str))
96125 (tui/quit!)))
97126 (ui/run app/app :headless [cols rows] :points-per-cell points-per-cell))
98127
99-(defn- run-terminal! []
100- (tui/after! 0 narrow!)
101- ;; The terminal is resized by dragging its edge, and only a poll notices:
102- ;; the backend reports a size, and only what a component derefs re-renders.
103- (tui/every! 200 narrow!)
128+(defn- run-terminal! [demo?]
129+ ;; On the loop thread, and before the first paint: `start!` sets timers of
130+ ;; its own, and a timer added from anywhere else is a node touched from the
131+ ;; wrong thread waiting to happen.
132+ (tui/after! 0 (fn []
133+ (narrow!)
134+ (start!)
135+ (when demo? (seed!))))
104136 (ui/run app/app :mouse true :points-per-cell points-per-cell))
105137
106138 (defn -main [& args]
107139 (let [args (set args)
108140 headless? (contains? args "--headless")
109- cols (if-let [n (first (keep #(when (str/starts-with? % "--cols=")
110- (subs % 7))
111- args))]
112- (Integer/parseInt n)
113- 100)
114- rows (if-let [n (first (keep #(when (str/starts-with? % "--rows=")
115- (subs % 7))
116- args))]
117- (Integer/parseInt n)
118- 36)]
119- (s/restore-prefs!)
120- (seed!)
141+ demo? (contains? args "--demo")
142+ num (fn [flag default]
143+ (if-let [n (first (keep #(when (str/starts-with? % flag)
144+ (subs % (count flag)))
145+ args))]
146+ (Integer/parseInt n)
147+ default))
148+ cols (num "--cols=" 100)
149+ rows (num "--rows=" 36)
150+ wait (num "--wait=" 400)]
121151 (if headless?
122- (run-headless! cols rows)
123- (run-terminal!))))
152+ (run-headless! cols rows demo? wait)
153+ (run-terminal! demo?))))
@@ -13,6 +13,12 @@
13 title, a window width, a picture chooser and with no window open every one13 title, a window width, a picture chooser and with no window open every one
14 of those is inert, which is the behaviour those calls already have to have.14 of those is inert, which is the behaviour those calls already have to have.
15 15
16+ It is the client, not a preview of it: `frq.app/start!` is what a launch
17+ does, and this hands it the terminal's timers instead of the window's. The
18+ saved sign-in is restored and connects itself, the rooms this client has been
19+ in come back, and `#test` is joined on arrival — the same session as the
20+ window, painted somewhere else.
21+
16 Two ways to run it:22 Two ways to run it:
17 23
18 jolt -M:tui the real terminal, until Ctrl-Q24 jolt -M:tui the real terminal, until Ctrl-Q
@@ -20,7 +26,10 @@
20 26
21 The headless one is `tui_headless` the same layout and the same painting27 The headless one is `tui_headless` the same layout and the same painting
22 with the writer taken off the end and is what a screenshot in a bug report28 with the writer taken off the end and is what a screenshot in a bug report
23- or a CI check should be."29+ or a CI check should be. It is also the one that has nothing to show: a
30+ screenshot taken a moment after launch is of a client that has not finished
31+ connecting, so `--demo` fills a buffer of its own rather than waiting on a
32+ server for one."
24 (:require [clojure.string :as str]33 (:require [clojure.string :as str]
25 [frq.app :as app]34 [frq.app :as app]
26 [frq.state :as s]35 [frq.state :as s]
@@ -38,13 +47,13 @@
38 (def ^:private points-per-cell 8)47 (def ^:private points-per-cell 8)
39 48
40 (defn- seed!49 (defn- seed!
41- "Put a conversation on screen for a client that is not connected to anything.50+ "Put a conversation on screen without a server behind it `--demo`.
42 51
43- A terminal session started for a look at the layout has no server behind it,52+ A screenshot is taken before a connection has been made, and an empty buffer
44- and an empty buffer says nothing about how the buffer looks. These go in53+ says nothing about how a buffer looks. These go in through `push-message!`
45- through `push-message!` rather than into the atom directly, so what is drawn54+ rather than into the atom directly, so what is drawn is a real buffer:
46- is a real buffer timestamps, day headings, the actor lookup, reply chips55+ timestamps, day headings, the actor lookup, reply chips and reactions all
47- and reactions all included."56+ included."
48 []57 []
49 (let [now (System/currentTimeMillis)58 (let [now (System/currentTimeMillis)
50 minute 60000]59 minute 60000]
@@ -83,41 +92,62 @@
83 (reset! s/window-height (* rows points-per-cell 2)))92 (reset! s/window-height (* rows points-per-cell 2)))
84 nil)93 nil)
85 94
95+(defn- start!
96+ "The launch, with the terminal's timers in place of the window's.
97+
98+ No title and no media plane: a terminal has no title bar to write a nick
99+ into, and a call paints frames into a texture there is no texture for here.
100+ Everything else — the saved settings, the rooms, the sign-in that connects
101+ itself — is the same startup the window runs."
102+ []
103+ (app/start! {:after! tui/after!
104+ :every! tui/every!
105+ :title! nil
106+ :av? false
107+ :measure! narrow!}))
108+
86 (defn- run-headless!109 (defn- run-headless!
87 "Mount the app in a session of `cols` by `rows` with no terminal, let it110 "Mount the app in a session of `cols` by `rows` with no terminal, let it
88 settle, and print what was painted."111 settle, and print what was painted."
89- [cols rows]112+ [cols rows demo? wait]
90 ;; The size has to be known before the first render, or the layout reads the113 ;; The size has to be known before the first render, or the layout reads the
91 ;; zero it was left at. A timer runs on the loop thread, which is where114 ;; zero it was left at. A timer runs on the loop thread, which is where
92 ;; screen-size may be asked and the first one fires before the first paint.115 ;; screen-size may be asked and the first one fires before the first paint.
93- (tui/after! 0 narrow!)116+ (tui/after! 0 (fn []
94- (tui/after! 120 (fn []117+ (narrow!)
118+ (start!)
119+ (when demo? (seed!))))
120+ ;; Long enough for whatever the screenshot is of. A client that restores a
121+ ;; sign-in spends the first seconds connecting, so the default is a picture
122+ ;; of the connect screen and `--wait=` is how you ask for one of the room.
123+ (tui/after! wait (fn []
95 (println (tui/screen-str))124 (println (tui/screen-str))
96 (tui/quit!)))125 (tui/quit!)))
97 (ui/run app/app :headless [cols rows] :points-per-cell points-per-cell))126 (ui/run app/app :headless [cols rows] :points-per-cell points-per-cell))
98 127
99-(defn- run-terminal! []128+(defn- run-terminal! [demo?]
100- (tui/after! 0 narrow!)129+ ;; On the loop thread, and before the first paint: `start!` sets timers of
101- ;; The terminal is resized by dragging its edge, and only a poll notices:130+ ;; its own, and a timer added from anywhere else is a node touched from the
102- ;; the backend reports a size, and only what a component derefs re-renders.131+ ;; wrong thread waiting to happen.
103- (tui/every! 200 narrow!)132+ (tui/after! 0 (fn []
133+ (narrow!)
134+ (start!)
135+ (when demo? (seed!))))
104 (ui/run app/app :mouse true :points-per-cell points-per-cell))136 (ui/run app/app :mouse true :points-per-cell points-per-cell))
105 137
106 (defn -main [& args]138 (defn -main [& args]
107 (let [args (set args)139 (let [args (set args)
108 headless? (contains? args "--headless")140 headless? (contains? args "--headless")
109- cols (if-let [n (first (keep #(when (str/starts-with? % "--cols=")141+ demo? (contains? args "--demo")
110- (subs % 7))142+ num (fn [flag default]
111- args))]143+ (if-let [n (first (keep #(when (str/starts-with? % flag)
112- (Integer/parseInt n)144+ (subs % (count flag)))
113- 100)145+ args))]
114- rows (if-let [n (first (keep #(when (str/starts-with? % "--rows=")146+ (Integer/parseInt n)
115- (subs % 7))147+ default))
116- args))]148+ cols (num "--cols=" 100)
117- (Integer/parseInt n)149+ rows (num "--rows=" 36)
118- 36)]150+ wait (num "--wait=" 400)]
119- (s/restore-prefs!)
120- (seed!)
121 (if headless?151 (if headless?
122- (run-headless! cols rows)152+ (run-headless! cols rows demo? wait)
123- (run-terminal!))))153+ (run-terminal! demo?))))