nandi/frqpublic Fork 0
478a726
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.

Make the overview a second conversation, and give a jump somewhere to land

The strip under a conversation holds a hundred lines now and scrolls, which
is a horizontal split rather than a caption: two children of a column that
both fill it are two halves of it, each keeping its own scroll. A terminal
keeps the fixed handful it had — its rows are cells, and two scrolling panes
in a screen that is already a conversation, a compose bar and a tab bar
leaves neither half worth reading.

Which hundred is a turn to each room until they run out. The newest hundred
outright would be the strip reporting on whichever room is busiest, and that
is the room you can already see; a turn each means the room that said one
thing all day is in the first handful, beside the room that has said a
hundred. Not the room being read, which is on the screen already and in
full. Newest at the top, which is the other way round from a conversation
and right for the same reason a conversation is the way it is: what you came
for is what has just happened. Days are ruled off as they are in the
backlog, and needed more here — these lines come from rooms last spoken in
at their own times, so ten rows can cross a day twice.

Pressing a room chip goes to that line rather than to the room's end, and
the way back to where you were is in the strip's heading. It comes and goes
in a slot that holds its height either way, so the lines under it do not
move when it appears. The line itself is read rather than pressed: it was a
link for a moment, which made half the rows accent-coloured and half plain,
since a line the server has echoed back has an id to aim at and one this
client has just sent does not.

A jump crossing a room is given longer to land and longer to stay marked:
the screen has to be built before there is anything to scroll, and what the
reader arrives at is a conversation they were not in a moment ago.

And two pieces of furniture are gone, both of which were drawing a line
under something nobody was going to miss: the rule above the compose bar,
and the row held open under the backlog for a button that is usually not
there. The three banners under it share one wrapper now — each was an empty
row costing the column a gap of its own, which is two dozen points of
nothing between the strip and the compose bar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-11T21:47:28-07:00 Browse files
478a726 parent: 1f55e68
modified src/frq/app.clj +189 -72
@@ -260,6 +260,33 @@
260260 (str (subs text 0 (dec limit)) "…")
261261 text)))
262262
263+(defn- goto-message!
264+ "Show message `id`, in `channel`, and say which one it was.
265+
266+ Three things in a fixed order: be in the room, aim the scroll at the line,
267+ and mark it once it is there. `jump-to` comes off again as soon as the frame
268+ that scrolled has been painted — a scroll target that stays set pins the
269+ view and takes scrolling away from the reader — while the highlight outlives
270+ it, because arriving at a screenful of messages says nothing about which one
271+ was asked for.
272+
273+ `settle` is how long that first frame is given, and `linger` how long the
274+ mark stays. Within a room the scrolling frame is the next one and the line
275+ is somewhere the reader was already looking. Crossing a room is a slower
276+ and a stranger arrival — the screen has to be built before there is
277+ anything to scroll, and what it opens on is a conversation the reader was
278+ not in a moment ago — so a caller that crosses one asks for more of both."
279+ [channel id settle linger]
280+ (when (and channel (not= channel @s/current))
281+ (s/open-channel! channel))
282+ (reset! s/jump-to id)
283+ (reset! s/highlight id)
284+ (platform/after! settle (fn [] (reset! s/jump-to nil)))
285+ ;; The highlight only clears itself: a later jump elsewhere owns the
286+ ;; highlight from then on.
287+ (platform/after! linger (fn [] (when (= id @s/highlight)
288+ (reset! s/highlight nil)))))
289+
263290 (defn- reply-chip
264291 "What a message is replying to, above it, and a way back to it.
265292
@@ -273,18 +300,9 @@
273300 ;; action, and a filled pill above every answer was the loudest thing in
274301 ;; the column.
275302 [:link {:label (str " " (:from target) ": " (summarise target 48))
276- :on-click #(do (reset! s/jump-to id)
277- (reset! s/highlight id)
278- ;; Off again once the frame that scrolled has
279- ;; been painted, so the reader keeps the view.
280- (platform/after! 120 (fn [] (reset! s/jump-to nil)))
281- ;; The highlight stays long enough to be read,
282- ;; and only clears itself: a later jump elsewhere
283- ;; owns the highlight from then on.
284- (platform/after! 2000
285- (fn []
286- (when (= id @s/highlight)
287- (reset! s/highlight nil)))))}]
303+ ;; The line being answered is in the room already open, so the
304+ ;; next frame is the one that scrolls.
305+ :on-click #(goto-message! channel id 120 2000)}]
288306 ;; The message it answers is older than this buffer goes.
289307 [:dim-label {:label " replying to an earlier message"}])])
290308
@@ -780,11 +798,7 @@
780798
781799 (= :failed (:status pr))
782800 [:dim-label {:label "No Bluesky profile found"}])]
783- ;; What the pointer is holding open says how to stop having to hold it.
784- ;; Pinned, that line has nothing left to tell anyone.
785- [:vbox {:key :hint}
786- (when-not pinned?
787- [:dim-label {:label "Click the face to keep this open"}])]]
801+]
788802 ;; `slot` is where libcosmic puts a button: the two actions go to the
789803 ;; foot of the dialog, and anything else here would be another control
790804 ;; stacked in the body.
@@ -1103,21 +1117,29 @@
11031117 ;; what every other row under the message list has had to say for itself.
11041118 ;;
11051119 ;; Counted from the lines there actually are, not from the ceiling: the strip
1106-;; holds a few per room, so its height is the client's rooms, and reserving
1107-;; for a full one in a client with two channels in it would take a third of
1108-;; the window off the conversation to leave it empty.
1120+;; holds at most `overview-lines` of them there, and a room with two lines in
1121+;; it should not have a screenful reserved against it.
1122+;;
1123+;; The window reserves nothing for the overview. Its pane scrolls and fills,
1124+;; so it takes its half of the column by being in it — there is no number to
1125+;; subtract, and subtracting one would take the room twice.
11091126 (defn- overview-height []
1110- (* (chrome-scale) (+ 24 (* 20 (count (s/recent-everywhere))) 16)))
1127+ (* (chrome-scale)
1128+ (+ 24 (* 20 (min s/overview-lines (count (s/recent-everywhere)))) 16)))
11111129
11121130 (defn- below-messages []
1113- ;; 140 is that, counted: the gap under the row of columns, the jump button's
1114- ;; 34pt row, the separator and the two empty wrappers with a gap apiece, then
1115- ;; the compose row and the air under it — its own and the window's. Short by
1116- ;; any of it and the column runs past the bottom edge, which does not show as
1117- ;; a list that is too long: it shows as a compose bar sitting flat on the
1118- ;; bottom of the window with its margin cut off.
1131+ ;; 115 is that, counted: the gap under the row of columns, the jump button's
1132+ ;; 34pt row, the banners in their one wrapper with one gap, then the compose
1133+ ;; row and the air under it — its own and the window's. Short by any of it
1134+ ;; and the column runs past the bottom edge, which does not show as a list
1135+ ;; that is too long: it shows as a compose bar sitting flat on the bottom of
1136+ ;; the window with its margin cut off.
1137+ ;;
1138+ ;; It was 140 when a rule was drawn above the compose bar and the three
1139+ ;; banners each cost the column a gap of their own. The rule went, and they
1140+ ;; went into one wrapper: twenty-five points of nothing, given back.
11191141 (+ (* (chrome-scale)
1120- (+ 140
1142+ (+ 115
11211143 (if @s/replying-to 34 0)
11221144 (if @s/attachment 76 0)))
11231145 ;; The two extra rows the terminal's compose field wraps into, in points:
@@ -1136,7 +1158,7 @@
11361158 ;; anywhere else because this is the number the backlog is laid out
11371159 ;; against: without it the strip is drawn past the bottom of the window
11381160 ;; and takes the compose bar with it.
1139- (if @s/overview? (overview-height) 0)))
1161+ (if (and @terminal? @s/overview?) (overview-height) 0)))
11401162
11411163 (defn- messages-width
11421164 "How wide the message list may be with the people panel beside it.
@@ -1234,34 +1256,113 @@
12341256 conversation behind it."
12351257 [i m]
12361258 [:hbox {:key i :spacing 8 :wrap false}
1259+ ;; The room is the button, and pressing it goes to this line rather than to
1260+ ;; the room's end: the line is what was read here and what the press was
1261+ ;; about, and a jump that landed on the newest message instead would answer
1262+ ;; a question nobody asked from a strip that was showing the answer.
1263+ ;;
1264+ ;; Unless the line has no id to aim at — one this client has sent and the
1265+ ;; server has not echoed back yet — and then the room is all there is to
1266+ ;; offer.
1267+ ;;
1268+ ;; 600ms to land and five seconds marked: the room is a room away, so the
1269+ ;; screen it scrolls in has to be built before there is anything to scroll,
1270+ ;; and what the reader arrives at is a conversation they were not in a
1271+ ;; moment ago. The mark is the whole answer to "which of these was the line
1272+ ;; I pressed", and it has to still be there when they have finished
1273+ ;; recognising where they are.
12371274 [:button {:label (:channel m)
1238- :on-click #(s/open-channel! (:channel m))}]
1275+ :on-click (if (:id m)
1276+ #(do (s/leaving-for-overview!)
1277+ (goto-message! (:channel m) (:id m) 600 5000))
1278+ #(do (s/leaving-for-overview!)
1279+ (s/open-channel! (:channel m))))}]
1280+ ;; And the line itself, as something to read rather than to press. It was a
1281+ ;; link for a moment, which made the strip two things at once: a line the
1282+ ;; server has echoed back has an id to aim at and a line this client has
1283+ ;; just sent does not, so half the rows came out accent-coloured and half
1284+ ;; plain, down the same list. The chip beside it is the way to the message
1285+ ;; and is on every row either way.
12391286 [:vbox {:key :said}
12401287 [:dim-label {:label (str (when-let [at (:at m)] (str (clock/clock-time at) " "))
12411288 (:from m) ": "
12421289 (preview-line (:text m)))}]]])
12431290
12441291 (defn- overview-pane
1245- "Every room's last few lines in one list, oldest at the top.
1292+ "Every other room's recent lines in one list, newest at the top.
12461293
12471294 A strip under the conversation rather than a screen of its own: the question
12481295 it answers is anything happening anywhere else is one you ask while
12491296 reading something, and an answer you have to leave the room for is one you
12501297 stop asking for.
12511298
1252- It does not scroll. A fixed handful of lines is what makes the room it takes
1253- from the backlog a number this file can reserve; a scroll here would be a
1254- second thing on the screen sizing itself against the window, fighting the
1255- backlog above it for the same points."
1299+ In a window it scrolls, and the column it is in fills the height: two
1300+ children of a column that both fill it are two halves of it, which is the
1301+ horizontal split this is. The backlog above keeps its own scroll and its own
1302+ place in it, so reading down here does not move the conversation.
1303+
1304+ A terminal gets a handful of lines and no scroll. Its rows are cells rather
1305+ than points, and a second scrolling pane in a screen that is already a
1306+ conversation, a compose bar and a tab bar leaves neither half enough rows to
1307+ be worth reading — so there it stays the strip it was, and `below-messages`
1308+ reserves it by the row."
12561309 []
1257- (let [lines (s/recent-everywhere)]
1258- [:vbox {:key :overview :spacing 4 :margin-top 4}
1259- [:separator {}]
1260- [:dim-label {:label "Everywhere else"}]
1261- (if (seq lines)
1262- (for [[i m] (map-indexed vector lines)]
1263- ^{:key (str (:channel m) "-" (or (:id m) i))} [overview-row i m])
1264- [:dim-label {:label "Nothing has been said in any room yet."}])]))
1310+ (let [lines (s/recent-everywhere)
1311+ ;; The way back, and only while there is somewhere to go: the strip
1312+ ;; is the one thing here that moves you without your having chosen a
1313+ ;; room, so it is the one thing that owes you an undo. Gone once you
1314+ ;; are back in that room, where it would offer to take you where you
1315+ ;; already are.
1316+ back (let [room @s/overview-return]
1317+ (when (and room (not= room @s/current))
1318+ [:button {:label (str "← Back to " room)
1319+ :on-click s/overview-back!}]))
1320+ ;; The row keeps its height whether or not the way back is in it. A
1321+ ;; button is 34 points and a caption is not, so a heading that grew
1322+ ;; one when you jumped and lost it when you came back moved every
1323+ ;; line under it — the strip rendering two different ways depending
1324+ ;; on where you had been.
1325+ heading (fn []
1326+ [:hbox {:key :heading :spacing 8 :wrap false :align :center}
1327+ [:dim-label {:label "Everywhere else"}]
1328+ [:vbox {:key :back}
1329+ (or back [:spacer {:size @chrome-row}])]])
1330+ ;; The same heading on the same rule the conversation puts between two
1331+ ;; days, and needed more here than there: these lines come from rooms
1332+ ;; that were last spoken in at their own times, so a strip of them can
1333+ ;; cross a day twice in ten rows where a single room's backlog crosses
1334+ ;; one once a day. A clock alone would then be the only thing saying
1335+ ;; which, and `9:40` says nothing about how long ago it was.
1336+ rows (fn [ms]
1337+ (if (seq ms)
1338+ (mapcat
1339+ (fn [i m]
1340+ (let [prev (when (pos? i) (nth ms (dec i)))
1341+ day (some-> (:at m) clock/day)
1342+ new-day? (and day (not= day (some-> (:at prev) clock/day)))]
1343+ (cond-> []
1344+ new-day?
1345+ (conj (day-separator (str "overview-day-" i)
1346+ (clock/day-label (:at m))))
1347+ true
1348+ (conj ^{:key (str (:channel m) "-" (or (:id m) i))}
1349+ [overview-row i m]))))
1350+ (range (count ms))
1351+ ms)
1352+ [[:dim-label {:label "Nothing has been said in any other room yet."}]]))]
1353+ (if @terminal?
1354+ (into [:vbox {:key :overview :spacing 4 :margin-top 4}
1355+ [:separator {}]
1356+ [heading]]
1357+ (rows (take s/overview-lines lines)))
1358+ [:vbox {:key :overview :spacing 4 :margin-top 4 :fill-height true}
1359+ [:separator {}]
1360+ [heading]
1361+ ;; Named, so coming back to a conversation does not throw away where
1362+ ;; the reader had got to in here.
1363+ (into [:scroll {:scroll-key "overview-list" :orientation :vertical
1364+ :spacing 4}]
1365+ (rows lines))])))
12651366
12661367 (defn chat-screen []
12671368 (let [name @s/current
@@ -1366,18 +1467,19 @@
13661467 ;; compose bar sits in whether or not the panel was showing.
13671468 [:vbox {:key :people-pane}
13681469 (when show-users? [users-panel name])]]
1369- ;; Under the conversation and above the jump button: it is a second list
1370- ;; of messages, so it belongs with the first rather than down among the
1371- ;; compose bar's rows. In a wrapper of its own, since it comes and goes.
1372- [:vbox {:key :overview-pane}
1373- (when @s/overview? [overview-pane])]
1374- ;; Only while it is needed, and directly above the compose bar: the way
1375- ;; back to the present belongs next to the thing that puts you there.
1376- ;; The row keeps its height whether or not the button is in it, so the
1377- ;; compose bar below stays where the reader last saw it.
1470+ ;; Only while it is needed, and directly under the backlog: the way back
1471+ ;; to the present belongs next to the thing that puts you there, which is
1472+ ;; the conversation and not the overview.
1473+ ;;
1474+ ;; The row used to be held open by a spacer when the button was not in
1475+ ;; it, so that the compose bar did not move when it came and went. It sat
1476+ ;; under the backlog then, where the empty row read as the end of the
1477+ ;; conversation. Above the strip it reads as a hole between two lists,
1478+ ;; and there is no arguing a reader out of that — so the row is only
1479+ ;; there when the button is, and what moves is a strip below it moving by
1480+ ;; one row on the rare frame the reader has scrolled away from the end.
13781481 [:vbox {:key :jump}
1379- (if @s/at-present?
1380- [:spacer {:size 34}]
1482+ (when-not @s/at-present?
13811483 ;; With the key beside it where there is a key: a terminal is where a
13821484 ;; reader is least likely to reach for the mouse, and most likely to
13831485 ;; have paged up here with the keyboard in the first place.
@@ -1385,26 +1487,42 @@
13851487 "↓ Jump to present (Ctrl-End)"
13861488 "↓ Jump to present")
13871489 :on-click s/jump-to-present!}])]
1388- [:separator {}]
1389- ;; What the draft is answering, directly above where it is being typed.
1390- [:vbox {:key :replying}
1490+ ;; The strip, under the conversation and the way back to it: it is a
1491+ ;; second list of messages, so it belongs with the first rather than down
1492+ ;; among the compose bar's rows. In a wrapper of its own, since it comes
1493+ ;; and goes.
1494+ [:vbox {:key :overview-pane :fill-height (boolean (and @s/overview?
1495+ (not @terminal?)))}
1496+ (when @s/overview? [overview-pane])]
1497+ ;; No rule above the compose bar. The bar is already told apart from the
1498+ ;; conversation by what it is — a framed box and a button on a strip of
1499+ ;; air — and a line drawn over it as well was the app underlining the one
1500+ ;; edge nobody was going to miss.
1501+ ;; The three banners in one wrapper, at no spacing of its own. Each is a
1502+ ;; row that is usually empty, and an empty child still costs the column
1503+ ;; its gap — three of them stacked under the overview was two dozen
1504+ ;; points of nothing between the strip and the compose bar. Inside, they
1505+ ;; carry their own air only when they have something in them.
1506+ [:vbox {:key :banners :spacing 0}
1507+ ;; What the draft is answering, directly above where it is being typed.
1508+ [:vbox {:key :replying}
13911509 (when-let [target @s/replying-to]
13921510 [:hbox {:spacing 8}
13931511 [:dim-label {:label (str "↩ " (:from target) ": " (summarise target 36))}]
13941512 [:button {:label "✕" :on-click s/cancel-reply!}]])]
1395- ;; And, in the same place, that the box holds a rewrite rather than
1396- ;; something new: the text in it is a copy of a line already on screen,
1397- ;; and without this Send would look like it was about to say it twice.
1398- [:vbox {:key :editing}
1513+ ;; And, in the same place, that the box holds a rewrite rather than
1514+ ;; something new: the text in it is a copy of a line already on screen,
1515+ ;; and without this Send would look like it was about to say it twice.
1516+ [:vbox {:key :editing}
13991517 (when @s/editing
14001518 [:hbox {:spacing 8}
14011519 [:dim-label {:label "✏️ Editing your message"}]
14021520 [:button {:label "✕" :on-click s/cancel-edit!}]])]
1403- ;; The pasted picture, above the line it will go out with. Shown rather
1404- ;; than written into the draft: what is being sent is a picture, and a URL
1405- ;; dropped into the entry would be an unreadable line of text sitting in
1406- ;; the middle of whatever the reader was in the middle of typing.
1407- [:vbox {:key :attachment}
1521+ ;; The pasted picture, above the line it will go out with. Shown rather
1522+ ;; than written into the draft: what is being sent is a picture, and a URL
1523+ ;; dropped into the entry would be an unreadable line of text sitting in
1524+ ;; the middle of whatever the reader was in the middle of typing.
1525+ [:vbox {:key :attachment}
14081526 (when-let [att @s/attachment]
14091527 [:hbox {:spacing 8}
14101528 ;; Small: it is a reminder of what is attached, not the picture
@@ -1413,7 +1531,7 @@
14131531 [:dim-label {:label (if (= :uploading (:status att))
14141532 "Uploading…"
14151533 "Picture attached")}]
1416- [:button {:label "✕" :on-click s/clear-attachment!}]])]
1534+ [:button {:label "✕" :on-click s/clear-attachment!}]])]]
14171535 ;; The line and its buttons on the middle of the width rather than
14181536 ;; against its left edge: the entry asks for a fixed width, and on a
14191537 ;; window wider than that a left-aligned row leaves it stranded in the
@@ -1421,11 +1539,10 @@
14211539 ;; window and centring costs nothing.
14221540 ;; Equal air above and below, so the row sits on the middle of the strip
14231541 ;; between the separator and the bottom edge rather than flat against it.
1424- ;; Above it is four of the column's gaps: one after the separator and one
1425- ;; for each of the empty wrappers — the reply banner, the edit banner and
1426- ;; the attachment, which cost a gap apiece whether or not they have
1427- ;; anything in them. This margin plus the window's own is what answers
1428- ;; them underneath.
1542+ ;; Above it is one of the column's gaps, where it used to be four: the
1543+ ;; reply banner, the edit banner and the attachment share a wrapper now
1544+ ;; and cost one between them whether or not they have anything in them.
1545+ ;; This margin plus the window's own is what answers it underneath.
14291546 [:hbox {:spacing 8 :align :center :margin-bottom 12}
14301547 ;; narrow enough that Send keeps its place on a phone-width row
14311548 ;; A picture is pasted where everything else is typed: Ctrl+V. The field
@@ -260,6 +260,33 @@
260 (str (subs text 0 (dec limit)) "…")260 (str (subs text 0 (dec limit)) "…")
261 text)))261 text)))
262 262
263+(defn- goto-message!
264+ "Show message `id`, in `channel`, and say which one it was.
265+
266+ Three things in a fixed order: be in the room, aim the scroll at the line,
267+ and mark it once it is there. `jump-to` comes off again as soon as the frame
268+ that scrolled has been painted — a scroll target that stays set pins the
269+ view and takes scrolling away from the reader — while the highlight outlives
270+ it, because arriving at a screenful of messages says nothing about which one
271+ was asked for.
272+
273+ `settle` is how long that first frame is given, and `linger` how long the
274+ mark stays. Within a room the scrolling frame is the next one and the line
275+ is somewhere the reader was already looking. Crossing a room is a slower
276+ and a stranger arrival — the screen has to be built before there is
277+ anything to scroll, and what it opens on is a conversation the reader was
278+ not in a moment ago — so a caller that crosses one asks for more of both."
279+ [channel id settle linger]
280+ (when (and channel (not= channel @s/current))
281+ (s/open-channel! channel))
282+ (reset! s/jump-to id)
283+ (reset! s/highlight id)
284+ (platform/after! settle (fn [] (reset! s/jump-to nil)))
285+ ;; The highlight only clears itself: a later jump elsewhere owns the
286+ ;; highlight from then on.
287+ (platform/after! linger (fn [] (when (= id @s/highlight)
288+ (reset! s/highlight nil)))))
289+
263 (defn- reply-chip290 (defn- reply-chip
264 "What a message is replying to, above it, and a way back to it.291 "What a message is replying to, above it, and a way back to it.
265 292
@@ -273,18 +300,9 @@
273 ;; action, and a filled pill above every answer was the loudest thing in300 ;; action, and a filled pill above every answer was the loudest thing in
274 ;; the column.301 ;; the column.
275 [:link {:label (str " " (:from target) ": " (summarise target 48))302 [:link {:label (str " " (:from target) ": " (summarise target 48))
276- :on-click #(do (reset! s/jump-to id)303+ ;; The line being answered is in the room already open, so the
277- (reset! s/highlight id)304+ ;; next frame is the one that scrolls.
278- ;; Off again once the frame that scrolled has305+ :on-click #(goto-message! channel id 120 2000)}]
279- ;; been painted, so the reader keeps the view.
280- (platform/after! 120 (fn [] (reset! s/jump-to nil)))
281- ;; The highlight stays long enough to be read,
282- ;; and only clears itself: a later jump elsewhere
283- ;; owns the highlight from then on.
284- (platform/after! 2000
285- (fn []
286- (when (= id @s/highlight)
287- (reset! s/highlight nil)))))}]
288 ;; The message it answers is older than this buffer goes.306 ;; The message it answers is older than this buffer goes.
289 [:dim-label {:label " replying to an earlier message"}])])307 [:dim-label {:label " replying to an earlier message"}])])
290 308
@@ -780,11 +798,7 @@
780 798
781 (= :failed (:status pr))799 (= :failed (:status pr))
782 [:dim-label {:label "No Bluesky profile found"}])]800 [:dim-label {:label "No Bluesky profile found"}])]
783- ;; What the pointer is holding open says how to stop having to hold it.801+]
784- ;; Pinned, that line has nothing left to tell anyone.
785- [:vbox {:key :hint}
786- (when-not pinned?
787- [:dim-label {:label "Click the face to keep this open"}])]]
788 ;; `slot` is where libcosmic puts a button: the two actions go to the802 ;; `slot` is where libcosmic puts a button: the two actions go to the
789 ;; foot of the dialog, and anything else here would be another control803 ;; foot of the dialog, and anything else here would be another control
790 ;; stacked in the body.804 ;; stacked in the body.
@@ -1103,21 +1117,29 @@
1103 ;; what every other row under the message list has had to say for itself.1117 ;; what every other row under the message list has had to say for itself.
1104 ;;1118 ;;
1105 ;; Counted from the lines there actually are, not from the ceiling: the strip1119 ;; Counted from the lines there actually are, not from the ceiling: the strip
1106-;; holds a few per room, so its height is the client's rooms, and reserving1120+;; holds at most `overview-lines` of them there, and a room with two lines in
1107-;; for a full one in a client with two channels in it would take a third of1121+;; it should not have a screenful reserved against it.
1108-;; the window off the conversation to leave it empty.1122+;;
1123+;; The window reserves nothing for the overview. Its pane scrolls and fills,
1124+;; so it takes its half of the column by being in it — there is no number to
1125+;; subtract, and subtracting one would take the room twice.
1109 (defn- overview-height []1126 (defn- overview-height []
1110- (* (chrome-scale) (+ 24 (* 20 (count (s/recent-everywhere))) 16)))1127+ (* (chrome-scale)
1128+ (+ 24 (* 20 (min s/overview-lines (count (s/recent-everywhere)))) 16)))
1111 1129
1112 (defn- below-messages []1130 (defn- below-messages []
1113- ;; 140 is that, counted: the gap under the row of columns, the jump button's1131+ ;; 115 is that, counted: the gap under the row of columns, the jump button's
1114- ;; 34pt row, the separator and the two empty wrappers with a gap apiece, then1132+ ;; 34pt row, the banners in their one wrapper with one gap, then the compose
1115- ;; the compose row and the air under it — its own and the window's. Short by1133+ ;; row and the air under it — its own and the window's. Short by any of it
1116- ;; any of it and the column runs past the bottom edge, which does not show as1134+ ;; and the column runs past the bottom edge, which does not show as a list
1117- ;; a list that is too long: it shows as a compose bar sitting flat on the1135+ ;; that is too long: it shows as a compose bar sitting flat on the bottom of
1118- ;; bottom of the window with its margin cut off.1136+ ;; the window with its margin cut off.
1137+ ;;
1138+ ;; It was 140 when a rule was drawn above the compose bar and the three
1139+ ;; banners each cost the column a gap of their own. The rule went, and they
1140+ ;; went into one wrapper: twenty-five points of nothing, given back.
1119 (+ (* (chrome-scale)1141 (+ (* (chrome-scale)
1120- (+ 1401142+ (+ 115
1121 (if @s/replying-to 34 0)1143 (if @s/replying-to 34 0)
1122 (if @s/attachment 76 0)))1144 (if @s/attachment 76 0)))
1123 ;; The two extra rows the terminal's compose field wraps into, in points:1145 ;; The two extra rows the terminal's compose field wraps into, in points:
@@ -1136,7 +1158,7 @@
1136 ;; anywhere else because this is the number the backlog is laid out1158 ;; anywhere else because this is the number the backlog is laid out
1137 ;; against: without it the strip is drawn past the bottom of the window1159 ;; against: without it the strip is drawn past the bottom of the window
1138 ;; and takes the compose bar with it.1160 ;; and takes the compose bar with it.
1139- (if @s/overview? (overview-height) 0)))1161+ (if (and @terminal? @s/overview?) (overview-height) 0)))
1140 1162
1141 (defn- messages-width1163 (defn- messages-width
1142 "How wide the message list may be with the people panel beside it.1164 "How wide the message list may be with the people panel beside it.
@@ -1234,34 +1256,113 @@
1234 conversation behind it."1256 conversation behind it."
1235 [i m]1257 [i m]
1236 [:hbox {:key i :spacing 8 :wrap false}1258 [:hbox {:key i :spacing 8 :wrap false}
1259+ ;; The room is the button, and pressing it goes to this line rather than to
1260+ ;; the room's end: the line is what was read here and what the press was
1261+ ;; about, and a jump that landed on the newest message instead would answer
1262+ ;; a question nobody asked from a strip that was showing the answer.
1263+ ;;
1264+ ;; Unless the line has no id to aim at — one this client has sent and the
1265+ ;; server has not echoed back yet — and then the room is all there is to
1266+ ;; offer.
1267+ ;;
1268+ ;; 600ms to land and five seconds marked: the room is a room away, so the
1269+ ;; screen it scrolls in has to be built before there is anything to scroll,
1270+ ;; and what the reader arrives at is a conversation they were not in a
1271+ ;; moment ago. The mark is the whole answer to "which of these was the line
1272+ ;; I pressed", and it has to still be there when they have finished
1273+ ;; recognising where they are.
1237 [:button {:label (:channel m)1274 [:button {:label (:channel m)
1238- :on-click #(s/open-channel! (:channel m))}]1275+ :on-click (if (:id m)
1276+ #(do (s/leaving-for-overview!)
1277+ (goto-message! (:channel m) (:id m) 600 5000))
1278+ #(do (s/leaving-for-overview!)
1279+ (s/open-channel! (:channel m))))}]
1280+ ;; And the line itself, as something to read rather than to press. It was a
1281+ ;; link for a moment, which made the strip two things at once: a line the
1282+ ;; server has echoed back has an id to aim at and a line this client has
1283+ ;; just sent does not, so half the rows came out accent-coloured and half
1284+ ;; plain, down the same list. The chip beside it is the way to the message
1285+ ;; and is on every row either way.
1239 [:vbox {:key :said}1286 [:vbox {:key :said}
1240 [:dim-label {:label (str (when-let [at (:at m)] (str (clock/clock-time at) " "))1287 [:dim-label {:label (str (when-let [at (:at m)] (str (clock/clock-time at) " "))
1241 (:from m) ": "1288 (:from m) ": "
1242 (preview-line (:text m)))}]]])1289 (preview-line (:text m)))}]]])
1243 1290
1244 (defn- overview-pane1291 (defn- overview-pane
1245- "Every room's last few lines in one list, oldest at the top.1292+ "Every other room's recent lines in one list, newest at the top.
1246 1293
1247 A strip under the conversation rather than a screen of its own: the question1294 A strip under the conversation rather than a screen of its own: the question
1248 it answers is anything happening anywhere else is one you ask while1295 it answers is anything happening anywhere else is one you ask while
1249 reading something, and an answer you have to leave the room for is one you1296 reading something, and an answer you have to leave the room for is one you
1250 stop asking for.1297 stop asking for.
1251 1298
1252- It does not scroll. A fixed handful of lines is what makes the room it takes1299+ In a window it scrolls, and the column it is in fills the height: two
1253- from the backlog a number this file can reserve; a scroll here would be a1300+ children of a column that both fill it are two halves of it, which is the
1254- second thing on the screen sizing itself against the window, fighting the1301+ horizontal split this is. The backlog above keeps its own scroll and its own
1255- backlog above it for the same points."1302+ place in it, so reading down here does not move the conversation.
1303+
1304+ A terminal gets a handful of lines and no scroll. Its rows are cells rather
1305+ than points, and a second scrolling pane in a screen that is already a
1306+ conversation, a compose bar and a tab bar leaves neither half enough rows to
1307+ be worth reading — so there it stays the strip it was, and `below-messages`
1308+ reserves it by the row."
1256 []1309 []
1257- (let [lines (s/recent-everywhere)]1310+ (let [lines (s/recent-everywhere)
1258- [:vbox {:key :overview :spacing 4 :margin-top 4}1311+ ;; The way back, and only while there is somewhere to go: the strip
1259- [:separator {}]1312+ ;; is the one thing here that moves you without your having chosen a
1260- [:dim-label {:label "Everywhere else"}]1313+ ;; room, so it is the one thing that owes you an undo. Gone once you
1261- (if (seq lines)1314+ ;; are back in that room, where it would offer to take you where you
1262- (for [[i m] (map-indexed vector lines)]1315+ ;; already are.
1263- ^{:key (str (:channel m) "-" (or (:id m) i))} [overview-row i m])1316+ back (let [room @s/overview-return]
1264- [:dim-label {:label "Nothing has been said in any room yet."}])]))1317+ (when (and room (not= room @s/current))
1318+ [:button {:label (str "← Back to " room)
1319+ :on-click s/overview-back!}]))
1320+ ;; The row keeps its height whether or not the way back is in it. A
1321+ ;; button is 34 points and a caption is not, so a heading that grew
1322+ ;; one when you jumped and lost it when you came back moved every
1323+ ;; line under it — the strip rendering two different ways depending
1324+ ;; on where you had been.
1325+ heading (fn []
1326+ [:hbox {:key :heading :spacing 8 :wrap false :align :center}
1327+ [:dim-label {:label "Everywhere else"}]
1328+ [:vbox {:key :back}
1329+ (or back [:spacer {:size @chrome-row}])]])
1330+ ;; The same heading on the same rule the conversation puts between two
1331+ ;; days, and needed more here than there: these lines come from rooms
1332+ ;; that were last spoken in at their own times, so a strip of them can
1333+ ;; cross a day twice in ten rows where a single room's backlog crosses
1334+ ;; one once a day. A clock alone would then be the only thing saying
1335+ ;; which, and `9:40` says nothing about how long ago it was.
1336+ rows (fn [ms]
1337+ (if (seq ms)
1338+ (mapcat
1339+ (fn [i m]
1340+ (let [prev (when (pos? i) (nth ms (dec i)))
1341+ day (some-> (:at m) clock/day)
1342+ new-day? (and day (not= day (some-> (:at prev) clock/day)))]
1343+ (cond-> []
1344+ new-day?
1345+ (conj (day-separator (str "overview-day-" i)
1346+ (clock/day-label (:at m))))
1347+ true
1348+ (conj ^{:key (str (:channel m) "-" (or (:id m) i))}
1349+ [overview-row i m]))))
1350+ (range (count ms))
1351+ ms)
1352+ [[:dim-label {:label "Nothing has been said in any other room yet."}]]))]
1353+ (if @terminal?
1354+ (into [:vbox {:key :overview :spacing 4 :margin-top 4}
1355+ [:separator {}]
1356+ [heading]]
1357+ (rows (take s/overview-lines lines)))
1358+ [:vbox {:key :overview :spacing 4 :margin-top 4 :fill-height true}
1359+ [:separator {}]
1360+ [heading]
1361+ ;; Named, so coming back to a conversation does not throw away where
1362+ ;; the reader had got to in here.
1363+ (into [:scroll {:scroll-key "overview-list" :orientation :vertical
1364+ :spacing 4}]
1365+ (rows lines))])))
1265 1366
1266 (defn chat-screen []1367 (defn chat-screen []
1267 (let [name @s/current1368 (let [name @s/current
@@ -1366,18 +1467,19 @@
1366 ;; compose bar sits in whether or not the panel was showing.1467 ;; compose bar sits in whether or not the panel was showing.
1367 [:vbox {:key :people-pane}1468 [:vbox {:key :people-pane}
1368 (when show-users? [users-panel name])]]1469 (when show-users? [users-panel name])]]
1369- ;; Under the conversation and above the jump button: it is a second list1470+ ;; Only while it is needed, and directly under the backlog: the way back
1370- ;; of messages, so it belongs with the first rather than down among the1471+ ;; to the present belongs next to the thing that puts you there, which is
1371- ;; compose bar's rows. In a wrapper of its own, since it comes and goes.1472+ ;; the conversation and not the overview.
1372- [:vbox {:key :overview-pane}1473+ ;;
1373- (when @s/overview? [overview-pane])]1474+ ;; The row used to be held open by a spacer when the button was not in
1374- ;; Only while it is needed, and directly above the compose bar: the way1475+ ;; it, so that the compose bar did not move when it came and went. It sat
1375- ;; back to the present belongs next to the thing that puts you there.1476+ ;; under the backlog then, where the empty row read as the end of the
1376- ;; The row keeps its height whether or not the button is in it, so the1477+ ;; conversation. Above the strip it reads as a hole between two lists,
1377- ;; compose bar below stays where the reader last saw it.1478+ ;; and there is no arguing a reader out of that — so the row is only
1479+ ;; there when the button is, and what moves is a strip below it moving by
1480+ ;; one row on the rare frame the reader has scrolled away from the end.
1378 [:vbox {:key :jump}1481 [:vbox {:key :jump}
1379- (if @s/at-present?1482+ (when-not @s/at-present?
1380- [:spacer {:size 34}]
1381 ;; With the key beside it where there is a key: a terminal is where a1483 ;; With the key beside it where there is a key: a terminal is where a
1382 ;; reader is least likely to reach for the mouse, and most likely to1484 ;; reader is least likely to reach for the mouse, and most likely to
1383 ;; have paged up here with the keyboard in the first place.1485 ;; have paged up here with the keyboard in the first place.
@@ -1385,26 +1487,42 @@
1385 "↓ Jump to present (Ctrl-End)"1487 "↓ Jump to present (Ctrl-End)"
1386 "↓ Jump to present")1488 "↓ Jump to present")
1387 :on-click s/jump-to-present!}])]1489 :on-click s/jump-to-present!}])]
1388- [:separator {}]1490+ ;; The strip, under the conversation and the way back to it: it is a
1389- ;; What the draft is answering, directly above where it is being typed.1491+ ;; second list of messages, so it belongs with the first rather than down
1390- [:vbox {:key :replying}1492+ ;; among the compose bar's rows. In a wrapper of its own, since it comes
1493+ ;; and goes.
1494+ [:vbox {:key :overview-pane :fill-height (boolean (and @s/overview?
1495+ (not @terminal?)))}
1496+ (when @s/overview? [overview-pane])]
1497+ ;; No rule above the compose bar. The bar is already told apart from the
1498+ ;; conversation by what it is — a framed box and a button on a strip of
1499+ ;; air — and a line drawn over it as well was the app underlining the one
1500+ ;; edge nobody was going to miss.
1501+ ;; The three banners in one wrapper, at no spacing of its own. Each is a
1502+ ;; row that is usually empty, and an empty child still costs the column
1503+ ;; its gap — three of them stacked under the overview was two dozen
1504+ ;; points of nothing between the strip and the compose bar. Inside, they
1505+ ;; carry their own air only when they have something in them.
1506+ [:vbox {:key :banners :spacing 0}
1507+ ;; What the draft is answering, directly above where it is being typed.
1508+ [:vbox {:key :replying}
1391 (when-let [target @s/replying-to]1509 (when-let [target @s/replying-to]
1392 [:hbox {:spacing 8}1510 [:hbox {:spacing 8}
1393 [:dim-label {:label (str "↩ " (:from target) ": " (summarise target 36))}]1511 [:dim-label {:label (str "↩ " (:from target) ": " (summarise target 36))}]
1394 [:button {:label "✕" :on-click s/cancel-reply!}]])]1512 [:button {:label "✕" :on-click s/cancel-reply!}]])]
1395- ;; And, in the same place, that the box holds a rewrite rather than1513+ ;; And, in the same place, that the box holds a rewrite rather than
1396- ;; something new: the text in it is a copy of a line already on screen,1514+ ;; something new: the text in it is a copy of a line already on screen,
1397- ;; and without this Send would look like it was about to say it twice.1515+ ;; and without this Send would look like it was about to say it twice.
1398- [:vbox {:key :editing}1516+ [:vbox {:key :editing}
1399 (when @s/editing1517 (when @s/editing
1400 [:hbox {:spacing 8}1518 [:hbox {:spacing 8}
1401 [:dim-label {:label "✏️ Editing your message"}]1519 [:dim-label {:label "✏️ Editing your message"}]
1402 [:button {:label "✕" :on-click s/cancel-edit!}]])]1520 [:button {:label "✕" :on-click s/cancel-edit!}]])]
1403- ;; The pasted picture, above the line it will go out with. Shown rather1521+ ;; The pasted picture, above the line it will go out with. Shown rather
1404- ;; than written into the draft: what is being sent is a picture, and a URL1522+ ;; than written into the draft: what is being sent is a picture, and a URL
1405- ;; dropped into the entry would be an unreadable line of text sitting in1523+ ;; dropped into the entry would be an unreadable line of text sitting in
1406- ;; the middle of whatever the reader was in the middle of typing.1524+ ;; the middle of whatever the reader was in the middle of typing.
1407- [:vbox {:key :attachment}1525+ [:vbox {:key :attachment}
1408 (when-let [att @s/attachment]1526 (when-let [att @s/attachment]
1409 [:hbox {:spacing 8}1527 [:hbox {:spacing 8}
1410 ;; Small: it is a reminder of what is attached, not the picture1528 ;; Small: it is a reminder of what is attached, not the picture
@@ -1413,7 +1531,7 @@
1413 [:dim-label {:label (if (= :uploading (:status att))1531 [:dim-label {:label (if (= :uploading (:status att))
1414 "Uploading…"1532 "Uploading…"
1415 "Picture attached")}]1533 "Picture attached")}]
1416- [:button {:label "✕" :on-click s/clear-attachment!}]])]1534+ [:button {:label "✕" :on-click s/clear-attachment!}]])]]
1417 ;; The line and its buttons on the middle of the width rather than1535 ;; The line and its buttons on the middle of the width rather than
1418 ;; against its left edge: the entry asks for a fixed width, and on a1536 ;; against its left edge: the entry asks for a fixed width, and on a
1419 ;; window wider than that a left-aligned row leaves it stranded in the1537 ;; window wider than that a left-aligned row leaves it stranded in the
@@ -1421,11 +1539,10 @@
1421 ;; window and centring costs nothing.1539 ;; window and centring costs nothing.
1422 ;; Equal air above and below, so the row sits on the middle of the strip1540 ;; Equal air above and below, so the row sits on the middle of the strip
1423 ;; between the separator and the bottom edge rather than flat against it.1541 ;; between the separator and the bottom edge rather than flat against it.
1424- ;; Above it is four of the column's gaps: one after the separator and one1542+ ;; Above it is one of the column's gaps, where it used to be four: the
1425- ;; for each of the empty wrappers — the reply banner, the edit banner and1543+ ;; reply banner, the edit banner and the attachment share a wrapper now
1426- ;; the attachment, which cost a gap apiece whether or not they have1544+ ;; and cost one between them whether or not they have anything in them.
1427- ;; anything in them. This margin plus the window's own is what answers1545+ ;; This margin plus the window's own is what answers it underneath.
1428- ;; them underneath.
1429 [:hbox {:spacing 8 :align :center :margin-bottom 12}1546 [:hbox {:spacing 8 :align :center :margin-bottom 12}
1430 ;; narrow enough that Send keeps its place on a phone-width row1547 ;; narrow enough that Send keeps its place on a phone-width row
1431 ;; A picture is pasted where everything else is typed: Ctrl+V. The field1548 ;; A picture is pasted where everything else is typed: Ctrl+V. The field
modified src/frq/state.clj +70 -22
@@ -131,6 +131,32 @@
131131 (swap! overview? not)
132132 (save-prefs!))
133133
134+;; The room the reader was in when a line in the overview took them somewhere
135+;; else, or nil. The strip is the one place in the app that moves you without
136+;; you having asked to leave where you were — everything else is a room you
137+;; chose — so it is the one place that owes you the way back.
138+(defonce overview-return (atom nil))
139+
140+(declare open-channel!)
141+
142+(defn leaving-for-overview!
143+ "Remember where we are, because a line in the strip is about to take us out
144+ of it. Nothing to remember if there is nowhere to go back to."
145+ []
146+ (reset! overview-return @current))
147+
148+(defn overview-back!
149+ "Back to the room the strip took you out of.
150+
151+ The room, and not the place in it: the backlog's scroll is remembered under
152+ one name for every conversation — see `messages-scroll-key` — so what comes
153+ back is the room and whatever that one viewport is currently showing of it.
154+ A place of your own in every room is a bigger change than this button."
155+ []
156+ (when-let [room @overview-return]
157+ (reset! overview-return nil)
158+ (open-channel! room)))
159+
134160 ;; The window's content width in points, polled from the backend a few times a
135161 ;; second. The app is laid out for a phone-width window, and this is what lets
136162 ;; a wide one be more than a phone with margins: past `wide-width` the channel
@@ -1939,28 +1965,39 @@
19391965
19401966
19411967
1942-;; How many lines the overview takes from each room. Per room rather than
1943-;; across all of them: the strip is there to say what is happening everywhere,
1944-;; and one busy channel taking the whole of a shared budget is the strip
1945-;; answering about one room — which is the room you are probably already in.
1946-(def overview-per-channel 3)
1968+;; How many lines the overview holds in all.
1969+(def overview-limit 100)
1970+
1971+;; How many it shows without being scrolled — the terminal's whole answer,
1972+;; since a strip there is a handful of rows taken off a conversation that is
1973+;; measured in how many of those fit.
1974+(def overview-lines 8)
1975+
1976+(defn- round-robin
1977+ "The colls' firsts, then their seconds, and so on until they are spent.
19471978
1948-;; And a ceiling on the lot, because the rooms are not a fixed number: at three
1949-;; apiece, a client in twenty channels would hand the strip sixty rows and the
1950-;; conversation above it nothing. Past this the newest win, which is the same
1951-;; bargain every other list here makes.
1952-(def overview-limit 24)
1979+ This is how the overview stays about every room while still being a fixed
1980+ number of lines. Taking the newest hundred outright would be the strip
1981+ answering about whichever room is busiest — which is the one you can already
1982+ see. A turn each means a room that said one thing all day is in the first
1983+ handful, beside the room that has said a hundred."
1984+ [colls]
1985+ (lazy-seq
1986+ (let [colls (remove empty? colls)]
1987+ (when (seq colls)
1988+ (concat (map first colls)
1989+ (round-robin (map rest colls)))))))
19531990
19541991 (defn recent-everywhere
1955- "The last `overview-per-channel` lines from every buffer at once, oldest
1956- first, and at most `overview-limit` of them.
1992+ "The newest lines from every buffer at once, newest first, and at most
1993+ `overview-limit` of them — a turn to each room until they run out.
19571994
19581995 Each carries the room it was said in, since that is the one thing a line
19591996 taken out of its own conversation no longer says for itself.
19601997
1961- Bounded per room before the sort, so the cost is the number of rooms rather
1962- than the length of their backlogs: a channel with a week of history in it
1963- must not make this the most expensive thing on the screen.
1998+ Bounded per room before anything else, so the cost is the number of rooms
1999+ rather than the length of their backlogs: a channel with a week of history
2000+ in it must not make this the most expensive thing on the screen.
19642001
19652002 Joins, parts and the rest of the system's own chatter are left out. They are
19662003 the noise this strip would drown in: a room nobody has spoken in for a day
@@ -1972,13 +2009,24 @@
19722009 rooms you are not looking at."
19732010 []
19742011 (->> (dissoc @channels @current)
1975- (mapcat (fn [[name buffer]]
1976- (->> (:messages buffer)
1977- (remove :system?)
1978- (take-last overview-per-channel)
1979- (map #(assoc % :channel name)))))
1980- (sort-by #(or (:at %) 0))
1981- (take-last overview-limit)))
2012+ (map (fn [[name buffer]]
2013+ ;; Newest first, which is the order a turn each has to be taken
2014+ ;; in: the first round is every room's latest line.
2015+ (->> (:messages buffer)
2016+ (remove :system?)
2017+ (take-last overview-limit)
2018+ reverse
2019+ (map #(assoc % :channel name)))))
2020+ (remove empty?)
2021+ round-robin
2022+ (take overview-limit)
2023+ ;; Newest at the top, which is the other way round from a conversation
2024+ ;; and right for the same reason a conversation is the way it is: what
2025+ ;; you came to the strip for is what has just happened, and a list you
2026+ ;; have to scroll to the bottom of to find it is a list that answers
2027+ ;; last. The turn-taking above is about which lines are in it, not
2028+ ;; about where they sit.
2029+ (sort-by #(or (:at %) 0) >)))
19822030
19832031 (def last-preview rooms/last-preview)
19842032
@@ -131,6 +131,32 @@
131 (swap! overview? not)131 (swap! overview? not)
132 (save-prefs!))132 (save-prefs!))
133 133
134+;; The room the reader was in when a line in the overview took them somewhere
135+;; else, or nil. The strip is the one place in the app that moves you without
136+;; you having asked to leave where you were — everything else is a room you
137+;; chose — so it is the one place that owes you the way back.
138+(defonce overview-return (atom nil))
139+
140+(declare open-channel!)
141+
142+(defn leaving-for-overview!
143+ "Remember where we are, because a line in the strip is about to take us out
144+ of it. Nothing to remember if there is nowhere to go back to."
145+ []
146+ (reset! overview-return @current))
147+
148+(defn overview-back!
149+ "Back to the room the strip took you out of.
150+
151+ The room, and not the place in it: the backlog's scroll is remembered under
152+ one name for every conversation — see `messages-scroll-key` — so what comes
153+ back is the room and whatever that one viewport is currently showing of it.
154+ A place of your own in every room is a bigger change than this button."
155+ []
156+ (when-let [room @overview-return]
157+ (reset! overview-return nil)
158+ (open-channel! room)))
159+
134 ;; The window's content width in points, polled from the backend a few times a160 ;; The window's content width in points, polled from the backend a few times a
135 ;; second. The app is laid out for a phone-width window, and this is what lets161 ;; second. The app is laid out for a phone-width window, and this is what lets
136 ;; a wide one be more than a phone with margins: past `wide-width` the channel162 ;; a wide one be more than a phone with margins: past `wide-width` the channel
@@ -1939,28 +1965,39 @@
1939 1965
1940 1966
1941 1967
1942-;; How many lines the overview takes from each room. Per room rather than1968+;; How many lines the overview holds in all.
1943-;; across all of them: the strip is there to say what is happening everywhere,1969+(def overview-limit 100)
1944-;; and one busy channel taking the whole of a shared budget is the strip1970+
1945-;; answering about one room — which is the room you are probably already in.1971+;; How many it shows without being scrolled — the terminal's whole answer,
1946-(def overview-per-channel 3)1972+;; since a strip there is a handful of rows taken off a conversation that is
1973+;; measured in how many of those fit.
1974+(def overview-lines 8)
1975+
1976+(defn- round-robin
1977+ "The colls' firsts, then their seconds, and so on until they are spent.
1947 1978
1948-;; And a ceiling on the lot, because the rooms are not a fixed number: at three1979+ This is how the overview stays about every room while still being a fixed
1949-;; apiece, a client in twenty channels would hand the strip sixty rows and the1980+ number of lines. Taking the newest hundred outright would be the strip
1950-;; conversation above it nothing. Past this the newest win, which is the same1981+ answering about whichever room is busiest — which is the one you can already
1951-;; bargain every other list here makes.1982+ see. A turn each means a room that said one thing all day is in the first
1952-(def overview-limit 24)1983+ handful, beside the room that has said a hundred."
1984+ [colls]
1985+ (lazy-seq
1986+ (let [colls (remove empty? colls)]
1987+ (when (seq colls)
1988+ (concat (map first colls)
1989+ (round-robin (map rest colls)))))))
1953 1990
1954 (defn recent-everywhere1991 (defn recent-everywhere
1955- "The last `overview-per-channel` lines from every buffer at once, oldest1992+ "The newest lines from every buffer at once, newest first, and at most
1956- first, and at most `overview-limit` of them.1993+ `overview-limit` of them — a turn to each room until they run out.
1957 1994
1958 Each carries the room it was said in, since that is the one thing a line1995 Each carries the room it was said in, since that is the one thing a line
1959 taken out of its own conversation no longer says for itself.1996 taken out of its own conversation no longer says for itself.
1960 1997
1961- Bounded per room before the sort, so the cost is the number of rooms rather1998+ Bounded per room before anything else, so the cost is the number of rooms
1962- than the length of their backlogs: a channel with a week of history in it1999+ rather than the length of their backlogs: a channel with a week of history
1963- must not make this the most expensive thing on the screen.2000+ in it must not make this the most expensive thing on the screen.
1964 2001
1965 Joins, parts and the rest of the system's own chatter are left out. They are2002 Joins, parts and the rest of the system's own chatter are left out. They are
1966 the noise this strip would drown in: a room nobody has spoken in for a day2003 the noise this strip would drown in: a room nobody has spoken in for a day
@@ -1972,13 +2009,24 @@
1972 rooms you are not looking at."2009 rooms you are not looking at."
1973 []2010 []
1974 (->> (dissoc @channels @current)2011 (->> (dissoc @channels @current)
1975- (mapcat (fn [[name buffer]]2012+ (map (fn [[name buffer]]
1976- (->> (:messages buffer)2013+ ;; Newest first, which is the order a turn each has to be taken
1977- (remove :system?)2014+ ;; in: the first round is every room's latest line.
1978- (take-last overview-per-channel)2015+ (->> (:messages buffer)
1979- (map #(assoc % :channel name)))))2016+ (remove :system?)
1980- (sort-by #(or (:at %) 0))2017+ (take-last overview-limit)
1981- (take-last overview-limit)))2018+ reverse
2019+ (map #(assoc % :channel name)))))
2020+ (remove empty?)
2021+ round-robin
2022+ (take overview-limit)
2023+ ;; Newest at the top, which is the other way round from a conversation
2024+ ;; and right for the same reason a conversation is the way it is: what
2025+ ;; you came to the strip for is what has just happened, and a list you
2026+ ;; have to scroll to the bottom of to find it is a list that answers
2027+ ;; last. The turn-taking above is about which lines are in it, not
2028+ ;; about where they sit.
2029+ (sort-by #(or (:at %) 0) >)))
1982 2030
1983 (def last-preview rooms/last-preview)2031 (def last-preview rooms/last-preview)
1984 2032