You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(client): scrollable watch, and a gateway address nobody has to copy
## Summary
### Why?
Two things made a watch awkward to actually use.
**A big table could only be trimmed.** The previous change stopped a frame taller than the window from repainting the screen, by dropping settled rows and saying how many it had dropped. That keeps the redraw honest but it is still a table you cannot read: the rows are there, and the only reason they are not on screen is that the renderer had to choose. What a reader wants is what `top` gives them — the whole table, and a way to move through it.
**The gateway address had to be copied by hand, and went stale.** Compose publishes a fresh random port on every start, so a `GATEWAY_ADDR` noted from an earlier run points at a port that no longer exists, and every demo command fails with a connection refused that says nothing about why. That is not a hypothetical: it is the most common way these commands fail.
### What?
**A watch of a queue is now a scrollable full-screen view.** It takes the alternate buffer while it runs, reads keys in raw mode, and gives the screen back untouched afterwards:
| Key | |
|---|---|
| `↑` `↓` / `k` `j` | one row |
| `PgUp` `PgDn` / `Space` | one screen |
| `g` `G` | first row, last row |
| `q` | stop watching |
It follows the end of the table by default, so rows and stages appear without anyone touching it; scrolling up holds the reader's place, and scrolling back to the bottom resumes following. There is no dedicated key for that, because being at the end is what following is.
The full-screen view also removes the class of bug the trimming worked around, rather than managing it: the alternate buffer never scrolls, so each frame is painted from the top and there is no previous frame to find. The trimming path remains for the case where it is still needed — a terminal on stdout but not on stdin, where there is a screen to draw on but nobody to press a key.
The finished table is printed into the restored screen whole, however tall it is. Nothing is drawn over it, so a long one scrolls, which is what a reader of a completed run wants.
No new dependency: `golang.org/x/term` was already in use for the window size and provides raw mode too.
**Work recorded after a request settles says so.** A build for a speculation path nobody needed any more can finish after its batch has landed and be recorded against every request in it — seen in a fifty-request run, where two rows carried a `building`/`built` pair timestamped 350ms after `landed`. Rendering those like any other event read as a landed change building itself afterwards, so a terminal status marks them `[after: …]`. The orchestrator does cancel unwanted builds, but only ones still running when it next polls, and the fake runner finishes instantly — so this is mostly a demo artifact that a real runner would usually cancel instead.
**`make land`, `land-status`, `land-list`, `land-watch` and `demo-requests` find the gateway themselves**, by asking Docker for the running stack's published port. `GATEWAY_ADDR` is now an override for reaching a gateway the Makefile did not start, and a stack that is not running produces a sentence saying so rather than a refused connection. The resolution is done inside each recipe rather than as a `$(shell ...)` assignment, which would shell out to Docker on every `make help`.
**`demo-requests` also takes the provider from the running stack.** The two have to agree, and nothing enforced it: a stack started with `PROVIDER=git` and a `make demo-requests` that was not told so mints fake changes pointing at no repository, which the git merger rejects as commits it cannot find. Observed as fifty consecutive failures reading `not available from remote origin`, with nothing in the error to say the provider was the problem. The stack knows which one it has — it is mounted at `/etc/submitqueue` — so a run given no provider of its own asks it, and one given a provider that disagrees says so before it starts rather than after fifty rejections.
Finding the port is not enough on its own, because `?=` treats a variable exported in the shell as already set — so anyone who ran the `export GATEWAY_ADDR=…` the quickstart used to recommend keeps a dead port forever and never reaches the discovery at all. That is the failure this was meant to remove, so when the address came from the environment and a local stack is running somewhere else, both are named and `unset GATEWAY_ADDR` is suggested. An address given on the command line is deliberate and passes without comment.
## Test Plan
- ✅ drove the view through a pty with real keystrokes — `G`, two up-arrows, `PgUp`, `q` — and read the positions back out of the footer: `40-40 → 39-40 → 38-40 → 36-40 of 40`, then a clean exit
- ✅ the alternate screen is entered once and left once in every run captured, so the terminal is never left on it
- ✅ after `q`, all 40 rows are printed into the restored screen; after a settled 25-request run, all 25 are, with nothing hidden
- ✅ `make demo-requests` and `make land-list` with no `GATEWAY_ADDR` set at all; with it set explicitly; and with no stack running, which now says `No gateway found: 'submitqueue' is not running`
- ✅ reproduced the provider mismatch that prompted the detection — a `PROVIDER=git` stack with a plain `make demo-requests` — and confirmed it now creates git changes and lands them, while an explicit `PROVIDER=fake` against that stack warns before starting
- ✅ reproduced the stale-export case that prompted the guard — an exported address pointing at a port from an earlier stack, with a live stack elsewhere — and confirmed it names both and suggests unsetting, while the same address given on the command line stays silent
- ✅ redirected output still produces a plain log: `make demo-requests LAND=false` and piped runs take neither the screen nor the keyboard
- ✅ new tests for the parts that are not a terminal: every key and escape sequence including one split across reads, and the scroll arithmetic — bounds, paging, and that scrolling up releases follow while rows arriving do not move a view that has scrolled away
- ✅ `make test` (105 targets), `make lint`, `make gazelle`
Two behaviours worth knowing. A bare `Escape` is not acted on until another key follows, and swallows it — the alternative is misreading an arrow whose bytes arrive in separate reads, which is worse and intermittent. And with tall wrapped rows in a short window, moving up one row can land back at the bottom, because the number of rows that fit changes with their height.
Copy file name to clipboardExpand all lines: doc/howto/DEVELOPMENT.md
+3-7Lines changed: 3 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,15 +60,11 @@ docker ps
60
60
# 2. Start the full stack
61
61
make local-submitqueue-start
62
62
63
-
# 3. Read the gateway's port (Compose publishes a random one)
64
-
make local-submitqueue-ps
65
-
export GATEWAY_ADDR=localhost:<gateway port>
66
-
67
-
# 4. Create changes, enqueue them, and watch them land
63
+
# 3. Create changes, enqueue them, and watch them land
68
64
make demo-requests
69
65
70
-
#5. Stop services
71
-
make local-stop
66
+
#4. Stop services
67
+
make local-submitqueue-stop
72
68
```
73
69
74
70
[QUICKSTART.md](QUICKSTART.md) walks through the same run in detail, and on to `PROVIDER=git`, which lands real commits into a repository on disk — still with no credential.
Copy file name to clipboardExpand all lines: doc/howto/QUICKSTART.md
+18-14Lines changed: 18 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,13 +28,7 @@ Compose publishes each service on a **random** host port so several stacks can r
28
28
Gateway gRPC port: 58537
29
29
```
30
30
31
-
Export it, because every command below needs it:
32
-
33
-
```bash
34
-
export GATEWAY_ADDR=localhost:58537
35
-
```
36
-
37
-
Leaving it unset does not fall back to anything useful — the client's default is `localhost:8081`, the `go run` port rather than the compose one.
31
+
You do not have to note it down. Every command below finds the running stack's port for itself, which matters because Compose picks a fresh one on every start — a number copied from an earlier run is the most common reason a demo command cannot connect. Set `GATEWAY_ADDR=host:port` only to reach a gateway this Makefile did not start.
38
32
39
33
## Put traffic through it
40
34
@@ -118,6 +112,17 @@ Eight builds means the batch was speculating down eight paths at once, and `wait
118
112
119
113
`land-watch` fixes its set when it starts and exits non-zero if any request in that set finishes anywhere other than `landed`, which makes it usable from a script. A request accepted after the watch begins is not picked up: a watch that grew as the queue did would never finish.
120
114
115
+
Watching more requests than the window holds takes over the screen while it runs, the way `top` does, so the table can be scrolled rather than trimmed:
116
+
117
+
| Key ||
118
+
|---|---|
119
+
|`↑``↓` or `k``j`| one row |
120
+
|`PgUp``PgDn` or `Space`| one screen |
121
+
|`g``G`| first row, last row |
122
+
|`q`| stop watching |
123
+
124
+
The view follows the end of the table by default, so new rows and new stages appear without touching it. Scrolling up holds your place; scrolling back to the bottom starts following again. The screen you had is restored on exit and the finished table is printed into it whole, so nothing is lost with the view — and when output is redirected, none of this happens at all and the run stays a plain log.
125
+
121
126
A listing of a busy queue is mostly `speculating` rows, since that is where a request spends most of its active life — waiting on the build its batch was admitted for.
122
127
123
128
Under the hood these are `client list` and `client watch`, which take a queue and reach any gateway:
@@ -178,13 +183,14 @@ Gateway gRPC port: 55295
178
183
Merge target: /tmp/sq-sandbox/sandbox.git
179
184
```
180
185
181
-
Then the same command as before, with the same `PROVIDER`:
186
+
Then the same command as before, unchanged:
182
187
183
188
```bash
184
-
export GATEWAY_ADDR=localhost:55295
185
-
PROVIDER=git make demo-requests
189
+
make demo-requests
186
190
```
187
191
192
+
`demo-requests` creates changes for whichever provider the running stack was started with, so there is nothing to repeat and nothing to keep in sync. The two must agree — a fake change points at no repository, so a stack running the git merger rejects every one of them as a commit it cannot find — and rather than leaving that to memory, a run with no `PROVIDER` of its own asks the stack which one it has. Passing one that disagrees still works, and says so before it starts.
193
+
188
194
Now `demo-requests` pushes real branches with real commits, and landing them is a real cherry-pick and push. Look at the repository itself:
189
195
190
196
```bash
@@ -200,13 +206,11 @@ b5d86d6 seed the sandbox
200
206
201
207
The commits are there, and they are not the ones that were pushed: `SQUASH_REBASE` replays each change onto the target rather than merging it, which is why the queue can keep the trunk linear.
202
208
203
-
**`PROVIDER` has to match on both commands.** It selects what the stack merges with *and* what `demo-requests` creates; pointing fake changes at a stack wired to git means asking the merger to fetch a ref that was never pushed.
204
-
205
209
One property worth seeing, because it is the thing a submit queue exists for. A stack lands as a single push, so no reader ever observes it half-applied:
206
210
207
211
```bash
208
212
git -C /tmp/sq-sandbox/sandbox.git reflog show refs/heads/main | wc -l
209
-
PROVIDER=git make demo-requests COUNT=3 STACKED=true
213
+
make demo-requests COUNT=3 STACKED=true
210
214
git -C /tmp/sq-sandbox/sandbox.git reflog show refs/heads/main | wc -l
211
215
```
212
216
@@ -257,7 +261,7 @@ PROVIDER=github make local-submitqueue-start
257
261
258
262
The token is required rather than defaulted: a stack that silently falls back to the fake integrations reports changes as landed without having gone near the provider, which is a much worse way to find out.
259
263
260
-
From here everything is as before — `PROVIDER=github make demo-requests` opens real pull requests, enqueues them and watches them land.
264
+
From here everything is as before — `make demo-requests` opens real pull requests, enqueues them and watches them land, having picked up from the running stack that this one is GitHub.
0 commit comments