proctolog 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -3
- data/README.md +32 -18
- data/app/controllers/proctolog/application_controller.rb +2 -2
- data/app/controllers/proctolog/bursts_controller.rb +1 -1
- data/app/controllers/proctolog/choices_controller.rb +3 -1
- data/app/controllers/proctolog/decisions_controller.rb +1 -1
- data/app/controllers/proctolog/notes_controller.rb +2 -2
- data/app/controllers/proctolog/prompts_controller.rb +1 -1
- data/app/controllers/proctolog/pull_requests_controller.rb +2 -4
- data/app/controllers/proctolog/review_data_controller.rb +3 -1
- data/app/controllers/proctolog/shares_controller.rb +27 -0
- data/app/helpers/proctolog/decisions_helper.rb +6 -2
- data/app/helpers/proctolog/desk_helper.rb +48 -8
- data/app/helpers/proctolog/rounds_helper.rb +4 -2
- data/app/helpers/proctolog/shares_helper.rb +11 -0
- data/app/helpers/proctolog/standings_helper.rb +5 -4
- data/app/models/proctolog/burst.rb +17 -8
- data/app/models/proctolog/choices.rb +8 -0
- data/app/models/proctolog/doctor.rb +23 -12
- data/app/models/proctolog/github.rb +1 -1
- data/app/models/proctolog/localhost.rb +17 -0
- data/app/models/proctolog/marks.rb +1 -1
- data/app/models/proctolog/note.rb +4 -0
- data/app/models/proctolog/owner.rb +3 -0
- data/app/models/proctolog/pick.rb +12 -0
- data/app/models/proctolog/rounds.rb +37 -0
- data/app/models/proctolog/server.rb +1 -7
- data/app/models/proctolog/setup.rb +190 -0
- data/app/models/proctolog/share/ngrok.rb +120 -0
- data/app/models/proctolog/share/settings.rb +23 -0
- data/app/models/proctolog/share/tailscale.rb +103 -0
- data/app/models/proctolog/share.rb +101 -0
- data/app/models/proctolog/standing.rb +24 -7
- data/app/views/proctolog/bursts/_line.html.erb +2 -2
- data/app/views/proctolog/bursts/_side.html.erb +6 -5
- data/app/views/proctolog/bursts/_side_stop.html.erb +4 -4
- data/app/views/proctolog/notes/_card.html.erb +7 -3
- data/app/views/proctolog/open_pull_requests/_side.html.erb +4 -3
- data/app/views/proctolog/open_pull_requests/_start.html.erb +8 -0
- data/app/views/proctolog/open_pull_requests/index.html.erb +4 -5
- data/app/views/proctolog/open_pull_requests/start/_first_time.html.erb +3 -0
- data/app/views/proctolog/open_pull_requests/start/_nothing_open.html.erb +3 -0
- data/app/views/proctolog/open_pull_requests/start/_nothing_written.html.erb +3 -0
- data/app/views/proctolog/shares/_line.html.erb +5 -0
- data/app/views/proctolog/shares/show.html.erb +48 -0
- data/app/views/proctolog/shares/ways/_ngrok.html.erb +6 -0
- data/app/views/proctolog/shares/ways/_tailscale.html.erb +2 -0
- data/bin/proctolog +154 -27
- data/completions/_proctolog +31 -6
- data/config/application.rb +23 -4
- data/config/initializers/grouse.rb +1 -1
- data/config/routes.rb +3 -0
- data/docs/manual/bursts.md +7 -2
- data/docs/manual/deciding.md +4 -1
- data/docs/manual/handover.md +6 -0
- data/docs/manual/pr-format.md +1 -1
- data/docs/manual/reading.md +54 -27
- data/docs/manual/setup.md +59 -0
- data/docs/manual/sharing.md +77 -0
- data/docs/manual/updates.md +11 -4
- data/lib/proctolog/changelog/news.rb +67 -0
- data/lib/proctolog/changelog.rb +75 -0
- data/lib/proctolog/installation.rb +19 -0
- data/lib/proctolog/version.rb +1 -1
- data/public/asking.js +31 -4
- data/public/decisions.js +4 -4
- data/public/desk.js +10 -6
- data/public/keys.js +6 -0
- data/public/notes.js +164 -58
- data/public/pick.css +21 -0
- data/public/respond.css +6 -0
- data/public/respond.js +51 -7
- data/public/review-drafts.js +47 -9
- data/public/review-rounds.js +27 -3
- data/public/share.css +58 -0
- data/public/side.js +11 -20
- data/public/terminal.css +295 -34
- data/public/viewer.js +700 -173
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e70671fa7a287f7273aa19d34e110411abdaf6eaa53af119c538e6f9f558b83
|
|
4
|
+
data.tar.gz: 715e723cad15056e1c5e878e21f89895e10a8a970550a6eb84183e9ccfc2c84a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab6e65d6dffbb0cf8cfa2b1dd228879e3aae55f32f5118a77873c5e69fab28e840dfce9240cf44d88efc30e59a7cb4b8e271d800e987126e6c3514d60a718109
|
|
7
|
+
data.tar.gz: 38aa55282ff812f1eb8ff1fe22b6dbff1493f0debfe592c6b57f6e21a0cd6a2b7343ad86d45363202cfa3318070f9116a71039bffec371866a60c2cc8d1680f4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,117 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
What changed in each version of the `proctolog` gem, newest first
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
What changed in each version of the `proctolog` gem, newest first: what it
|
|
4
|
+
adds, what it changes and what it fixes. Versions are semantic
|
|
5
|
+
([docs/manual/updates.md](docs/manual/updates.md)); a release that asks
|
|
6
|
+
something of you says what under **You do**. Unreleased is what main has that
|
|
7
|
+
no gem has yet.
|
|
8
|
+
|
|
9
|
+
## 0.3.0
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `proctolog setup` is the one command to remember: it takes you from
|
|
14
|
+
wherever you are to an open desk, a step at a time. It links PRoctolog's
|
|
15
|
+
command, completion and skills, then goes through doctor's checks in the
|
|
16
|
+
order you meet them, each saying why it matters, checked again when you
|
|
17
|
+
press Enter, and stamped done or left for later. Outside a checkout, it
|
|
18
|
+
asks for one. Its sharing step says what Tailscale and ngrok each give, and
|
|
19
|
+
where to get them. Of anyone else's, it installs nothing
|
|
20
|
+
([docs/manual/setup.md](docs/manual/setup.md)). A bare `proctolog`, and
|
|
21
|
+
`serve` outside a checkout, point to it.
|
|
22
|
+
- `proctolog update` says what's new since the version you had, from this
|
|
23
|
+
changelog: each change by its first sentence, under Added, Changed and
|
|
24
|
+
Fixed, and in full what you must do. It starts with the update after this
|
|
25
|
+
one, as the version you leave is the one that runs it.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `proctolog install` is another name for `setup`: it links as before, then
|
|
30
|
+
takes you through the rest. After an update it only links, and says what's
|
|
31
|
+
new.
|
|
32
|
+
- `doctor` says how to install Chrome when it is missing.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- `proctolog update` said it pulled a checkout forward, and to restart the
|
|
37
|
+
desk, when origin had nothing newer. It says the checkout is the latest now,
|
|
38
|
+
and changes nothing.
|
|
39
|
+
- On macOS 27 the desk took every port on this Mac for answering: Try it
|
|
40
|
+
showed the app up and never started it, `doctor` said port 4040 was taken,
|
|
41
|
+
and `proctolog share start` shared a desk that was not there.
|
|
42
|
+
|
|
43
|
+
## 0.2.0
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- The desk is at <http://proctolog.localhost:4040>, a name of its own that a
|
|
48
|
+
browser's history finds from `procto`; `localhost:4040` still works.
|
|
49
|
+
- Share the desk with your phone or another computer, through Tailscale (on
|
|
50
|
+
your tailnet, which the sharing page names, as one account can be in
|
|
51
|
+
several) or ngrok (behind its sign-in with Google, for your address): from
|
|
52
|
+
the foot of the pane at the left, with `proctolog share`, or with
|
|
53
|
+
`proctolog serve --share`. `proctolog share status` and `doctor` say what
|
|
54
|
+
each one needs. Neither is installed for you.
|
|
55
|
+
- "Start here" heads the page of open pull requests until you have a burst of
|
|
56
|
+
your own, and whenever there is nothing to decide: how many are written for
|
|
57
|
+
PRoctolog and how to open one, how to have them rewritten when none is, or
|
|
58
|
+
how to have some written when nothing is open.
|
|
59
|
+
- **back to you** on a line of the Route, and `↩` on it in the pane, once the
|
|
60
|
+
agent's answers have come back for you to confirm or answer, or it
|
|
61
|
+
addressed a note of yours. The `↩` follows as you go, without the page
|
|
62
|
+
loading again.
|
|
63
|
+
- A panel beside the pictures of a change, in the viewer: the five ways to
|
|
64
|
+
look at them, each a button with its key, and the changed regions, counted
|
|
65
|
+
and numbered. `n` and `p`, or a press on a number, focus on a region and
|
|
66
|
+
keep your zoom; `z` goes near it and `0` back to the whole. The panel shows
|
|
67
|
+
the region as the before has it and as the after does, and Enter, or a
|
|
68
|
+
press on either, writes a note on it, in any view.
|
|
69
|
+
- Shift B sets blink going by itself, or stops it.
|
|
70
|
+
- Once the viewer shows only part of a picture, a map in its corner shows the
|
|
71
|
+
whole, with the part on the stage framed; a press on the map goes there.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- Decisions are beads, on the Route and in the pane at the left: a full-size
|
|
76
|
+
bead for a blocking decision, a small one for a non-blocking, solid once
|
|
77
|
+
reviewed, hollow while it waits. They take the place of the counts and of
|
|
78
|
+
`x`, `~` and `-`, and in the pane follow your choices as you make them.
|
|
79
|
+
- `N` opens the next pull request that asks for you: one with a blocking
|
|
80
|
+
decision left, or something back from the agent.
|
|
81
|
+
- With no burst open, the pane lists your bursts with how many decisions of
|
|
82
|
+
each grade wait, rather than how far each one is.
|
|
83
|
+
- In the viewer, a view's key again goes back to side by side. Changed regions
|
|
84
|
+
are outlined dashed and a note's box framed solid, so the two are told apart
|
|
85
|
+
at a glance.
|
|
86
|
+
- A note on a picture is saved where you are: the page doesn't load again or
|
|
87
|
+
move, zoomed in or not, and closing the viewer draws the notes there where
|
|
88
|
+
the page stands, rather than coming back at the last one.
|
|
89
|
+
- `bin/release` refuses a patch that adds something, and says when lines are
|
|
90
|
+
still left under Unreleased.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- "Review alone" on a pull request in no stack saved nothing, and said the
|
|
95
|
+
page was older than the desk's last start.
|
|
96
|
+
- "Ask Claude now" on a pull request nobody has opened yet sent the session to
|
|
97
|
+
GitHub, which has nothing of it. It names the branch, its base and the file
|
|
98
|
+
its description is kept in now, and has the session read it with git.
|
|
99
|
+
- The note button on selected text sat away from the selection on a burst's
|
|
100
|
+
pages.
|
|
101
|
+
- A note of yours that an agent answered stayed open in the marks, for the
|
|
102
|
+
page's Notes and for an agent reading them. It follows its answer now:
|
|
103
|
+
addressed once the answer is in, confirmed once you confirm it, open again
|
|
104
|
+
once you answer back, and its card points you to Returned work.
|
|
105
|
+
- `bin/release` and the Release workflow read the changelog whatever the
|
|
106
|
+
locale: a `⌘` or `↩` in it stopped them where none was set.
|
|
107
|
+
|
|
108
|
+
**You do**: nothing, unless you want to share the desk: install Tailscale or
|
|
109
|
+
ngrok, as [docs/manual/sharing.md](docs/manual/sharing.md) says.
|
|
6
110
|
|
|
7
111
|
## 0.1.1
|
|
8
112
|
|
|
113
|
+
### Changed
|
|
114
|
+
|
|
9
115
|
- The Try it rail follows the reading column and its keys rather than the far
|
|
10
116
|
edge of the window, and grows to 20rem on wide screens; its outline scrolls
|
|
11
117
|
on its own, leaving the starter room.
|
|
@@ -18,6 +124,8 @@ that asks something of you says what under **You do**.
|
|
|
18
124
|
|
|
19
125
|
The first one published as a gem.
|
|
20
126
|
|
|
127
|
+
### Added
|
|
128
|
+
|
|
21
129
|
- `rvx proctolog install`, or
|
|
22
130
|
`gem install proctolog` and `proctolog install`, in place of cloning the
|
|
23
131
|
repository. Under rv it runs on a Ruby 4 of its own, whatever Ruby the shell
|
data/README.md
CHANGED
|
@@ -12,16 +12,16 @@ it works.
|
|
|
12
12
|
|
|
13
13
|
## The short version
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
whose PRs you review:
|
|
15
|
+
With [rv](https://rv.dev), from a checkout of the repo whose PRs you review:
|
|
17
16
|
|
|
18
17
|
```sh
|
|
19
|
-
rvx proctolog install
|
|
20
18
|
cd path/to/your-repo
|
|
21
|
-
proctolog
|
|
22
|
-
proctolog serve # then open http://localhost:4040
|
|
19
|
+
rvx proctolog setup # a step at a time, to http://proctolog.localhost:4040
|
|
23
20
|
```
|
|
24
21
|
|
|
22
|
+
`setup` is the one command to remember: it links PRoctolog's own pieces, says
|
|
23
|
+
what to do about the rest, and opens the desk. `install` is another name for it.
|
|
24
|
+
|
|
25
25
|
Pick a PR, a stack, or a few ticked ones, and press `?` once you're in. When
|
|
26
26
|
your agent opens PRs, tell it to "write these PRs for PRoctolog", so they come
|
|
27
27
|
with decisions to review. Everything below is the long version.
|
|
@@ -41,11 +41,10 @@ brew install diffle imagemagick ffmpeg
|
|
|
41
41
|
With rv, whatever Ruby your shell or your repo runs:
|
|
42
42
|
|
|
43
43
|
```sh
|
|
44
|
-
rvx proctolog
|
|
45
|
-
proctolog doctor
|
|
44
|
+
rvx proctolog setup
|
|
46
45
|
```
|
|
47
46
|
|
|
48
|
-
rv gives PRoctolog a Ruby 4 and gems of its own, and `
|
|
47
|
+
rv gives PRoctolog a Ruby 4 and gems of its own, and `setup` puts a
|
|
49
48
|
`proctolog` in `~/.local/bin` that runs it through `rvx`. The apps you review
|
|
50
49
|
keep their own Ruby: what the desk starts for them never sees PRoctolog's.
|
|
51
50
|
rv installs from gem.coop, which mirrors RubyGems.
|
|
@@ -54,20 +53,24 @@ With Ruby 4.0 already in your shell:
|
|
|
54
53
|
|
|
55
54
|
```sh
|
|
56
55
|
gem install proctolog
|
|
57
|
-
proctolog
|
|
58
|
-
proctolog doctor
|
|
56
|
+
proctolog setup
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
Either way `
|
|
62
|
-
and the agent skills (below) into `~/.claude/skills` and `~/.agents/skills`.
|
|
63
|
-
|
|
59
|
+
Either way `setup` first links the zsh completion into `~/.zsh/completions`
|
|
60
|
+
and the agent skills (below) into `~/.claude/skills` and `~/.agents/skills`.
|
|
61
|
+
Then it takes the rest a step at a time: gh, the checkout, a first burst, what
|
|
62
|
+
turns features on, sharing the desk with your phone. Each step says why it
|
|
63
|
+
matters, checks again once you've acted, and is stamped done or left for
|
|
64
|
+
later; the last one opens the desk ([setting up](docs/manual/setup.md)). Run it
|
|
65
|
+
again whenever: what's done is stamped and passed. `doctor` asks the same
|
|
66
|
+
things all at once, a line each with the fix: `!!` for what the desk can't do
|
|
64
67
|
without, `--` for what only turns a feature off.
|
|
65
68
|
|
|
66
69
|
When a newer PRoctolog is out, the desk says so, and `proctolog update` takes
|
|
67
70
|
it ([versions and updates](docs/manual/updates.md)).
|
|
68
71
|
|
|
69
72
|
Working on PRoctolog itself? Run it from a checkout instead: clone it, then
|
|
70
|
-
`bundle install &&
|
|
73
|
+
`bundle install && bin/proctolog setup`, which also puts a `proctolog` that runs
|
|
71
74
|
the checkout in `~/.local/bin`. Keep the checkout where it is, since
|
|
72
75
|
everything links back to it. The desk tells a checkout of a newer release
|
|
73
76
|
too, and `proctolog update` pulls it forward.
|
|
@@ -79,7 +82,7 @@ cd path/to/your-repo
|
|
|
79
82
|
proctolog serve
|
|
80
83
|
```
|
|
81
84
|
|
|
82
|
-
Open <http://localhost:4040>. It lists the repo's open PRs: GitHub's stacks
|
|
85
|
+
Open <http://proctolog.localhost:4040>. It lists the repo's open PRs: GitHub's stacks
|
|
83
86
|
whole, then the rest, yours first. Press "Review alone" on one, "Review the
|
|
84
87
|
stack" on a stack, or tick a few and review them together. That's all the setup
|
|
85
88
|
there is.
|
|
@@ -104,7 +107,7 @@ Your review is saved as you go, on your Mac under `~/.local/share/proctolog/`.
|
|
|
104
107
|
|
|
105
108
|
PRoctolog finds decisions under a `## Decisions for the owner` heading, one
|
|
106
109
|
checkbox line each. The `proctolog-pr-writing` skill teaches your agent the
|
|
107
|
-
format, and `proctolog
|
|
110
|
+
format, and `proctolog setup` already put it where the agent looks. Tell it to
|
|
108
111
|
"write these PRs for PRoctolog", or "rewrite this PR's description for
|
|
109
112
|
PRoctolog" for one that's open. Check a description before publishing it:
|
|
110
113
|
|
|
@@ -147,7 +150,7 @@ bundle config set --local with grouse && bundle install
|
|
|
147
150
|
|
|
148
151
|
Then every page of the desk carries Grouse's overlay, at the bottom right: say or type what's wrong with the page you're
|
|
149
152
|
on, and the note keeps the page, the templates that drew it, what you pointed
|
|
150
|
-
at and, if you ask, a screenshot. Read them at <http://localhost:4040/grouse>.
|
|
153
|
+
at and, if you ask, a screenshot. Read them at <http://proctolog.localhost:4040/grouse>.
|
|
151
154
|
An agent working on the desk reads them through Grouse's export API;
|
|
152
155
|
[AGENTS.md](AGENTS.md) tells it how. They're kept under
|
|
153
156
|
`~/.local/share/proctolog/grouse_notes`, one store for every repo you review.
|
|
@@ -156,15 +159,26 @@ A voice note is written out by ElevenLabs, and every note written up as an
|
|
|
156
159
|
issue by Anthropic, with keys the desk reads from 1Password when it serves:
|
|
157
160
|
`config/keys.yml` holds the references, never the keys, and `op` (1Password's
|
|
158
161
|
CLI, signed in) reads them. Point them at your own items there if yours live
|
|
159
|
-
elsewhere.
|
|
162
|
+
elsewhere. A trial desk, on marks of its own (`PROCTOLOG_MARKS`), reads none,
|
|
163
|
+
so an agent trying a change never has 1Password ask you to unlock it.
|
|
160
164
|
|
|
161
165
|
Without a key a voice note arrives "audio only", with a recording that plays,
|
|
162
166
|
and a note's page says "No structurer set up". `bin/rails grouse:doctor`, in the
|
|
163
167
|
desk's checkout, says what Grouse is missing.
|
|
164
168
|
|
|
169
|
+
## From your phone
|
|
170
|
+
|
|
171
|
+
`proctolog share` puts the desk where your other devices reach it and nobody
|
|
172
|
+
else does: through Tailscale, at your Mac's name on your tailnet, or through
|
|
173
|
+
ngrok, behind its sign-in with Google for your own address. The pane at the
|
|
174
|
+
left says whether it's shared, and opens the page that shares it. Neither is
|
|
175
|
+
installed for you; `proctolog share status` says what each one needs. More in
|
|
176
|
+
[the address, and sharing it](docs/manual/sharing.md).
|
|
177
|
+
|
|
165
178
|
## More
|
|
166
179
|
|
|
167
180
|
- [Bursts, the route and where things live](docs/manual/bursts.md)
|
|
181
|
+
- [The address, and sharing it](docs/manual/sharing.md)
|
|
168
182
|
- [How the desk runs your app](docs/manual/app.md)
|
|
169
183
|
- [Reading a PR by keyboard](docs/manual/reading.md)
|
|
170
184
|
- [Deciding](docs/manual/deciding.md)
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Proctolog
|
|
4
4
|
class ApplicationController < ActionController::Base
|
|
5
|
-
rescue_from(Github::Unreachable) { render plain: "GitHub did not answer.\n\n#{it.message}", status: :service_unavailable }
|
|
5
|
+
rescue_from(Github::Unreachable) { render plain: "GitHub did not answer.\n\n#{it.message}\n\nIf gh is not set up on this Mac yet, proctolog setup takes you through it.", status: :service_unavailable }
|
|
6
6
|
rescue_from(ActionController::InvalidAuthenticityToken) do
|
|
7
|
-
redirect_back_or_to root_path, alert: "
|
|
7
|
+
redirect_back_or_to root_path, alert: "The desk did not know this page any more, so nothing was saved. Do it once more."
|
|
8
8
|
end
|
|
9
9
|
rescue_from(Burst::Missing) { render plain: it.message, status: :not_found }
|
|
10
10
|
# A button's press comes back to the page it was pressed on, with the
|
|
@@ -12,7 +12,7 @@ module Proctolog
|
|
|
12
12
|
@route = @burst.route(fresh: params.key?(:fresh))
|
|
13
13
|
return redirect_to(burst_path(@burst)) if params.key?(:fresh)
|
|
14
14
|
|
|
15
|
-
@taken_in = @burst.
|
|
15
|
+
@taken_in = @burst.take_in
|
|
16
16
|
@standings = @burst.standings(@route)
|
|
17
17
|
@notices = @burst.notices
|
|
18
18
|
@rules = @burst.rules.select { it.within?(focus) }
|
|
@@ -12,11 +12,13 @@ module Proctolog
|
|
|
12
12
|
def show = render(json: {document: choices.document})
|
|
13
13
|
|
|
14
14
|
# A desk from before bursts had their name reads `review`, so both are
|
|
15
|
-
# written: one rolled back can still read what this one saved.
|
|
15
|
+
# written: one rolled back can still read what this one saved. What the
|
|
16
|
+
# owner said of an agent's answers moves the notes they answer at once.
|
|
16
17
|
def update
|
|
17
18
|
incoming = JSON.parse(request.raw_post)
|
|
18
19
|
mine?(incoming) or raise KeyError
|
|
19
20
|
choices.keep(incoming.merge("burst" => @burst.slug, "review" => @burst.slug))
|
|
21
|
+
@burst.take_in
|
|
20
22
|
render json: {revision: choices.revision}
|
|
21
23
|
end
|
|
22
24
|
|
|
@@ -13,7 +13,7 @@ module Proctolog
|
|
|
13
13
|
return redirect_to(burst_decisions_path(@burst, show: params[:show]))
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
@burst.
|
|
16
|
+
@burst.take_in
|
|
17
17
|
# Listed and counted within the focus; the stars and ticks it sends are
|
|
18
18
|
# found among the whole burst's.
|
|
19
19
|
@decisions = @burst.decisions(pages:, focus:)
|
|
@@ -10,8 +10,8 @@ module Proctolog
|
|
|
10
10
|
|
|
11
11
|
before_action { @burst = Burst.find(params.expect(:burst_id)) }
|
|
12
12
|
|
|
13
|
-
# A note
|
|
14
|
-
# answer
|
|
13
|
+
# A note on a picture, on the page or in the viewer, is saved without
|
|
14
|
+
# leaving either: the answer names the note and where the page shows it.
|
|
15
15
|
def create
|
|
16
16
|
note = marks.pin(kind: one_of(Note::KINDS, params.expect(:kind)), text: params.expect(:text).strip, place:)
|
|
17
17
|
respond_to do |format|
|
|
@@ -11,13 +11,11 @@ module Proctolog
|
|
|
11
11
|
@route = @burst.route(fresh: params.key?(:fresh))
|
|
12
12
|
return redirect_to(burst_pull_request_path(@burst, params.expect(:number))) if params.key?(:fresh)
|
|
13
13
|
|
|
14
|
-
@burst.
|
|
14
|
+
@burst.take_in
|
|
15
15
|
@stop = @route.at(PullRequest.number(params.expect(:number))) or return render(plain: "##{params[:number]} is not part of #{@burst.name}.", status: :not_found)
|
|
16
16
|
standings = @burst.standings(@route)
|
|
17
17
|
@standing = standings.fetch(@stop)
|
|
18
|
-
|
|
19
|
-
# wait for one gesture, and do not hold up deciding.
|
|
20
|
-
@next_to_review = focus.round_from(@stop).find { standings.fetch(it).progress.blocking_left.positive? }
|
|
18
|
+
@next_to_review = focus.round_from(@stop).find { standings.fetch(it).asks_for_owner? }
|
|
21
19
|
@since = @burst.since(@stop) if @standing.changed?
|
|
22
20
|
@scenarios = @burst.scenarios.select { it.pr == @stop.number }
|
|
23
21
|
@rendering = Rendering.new(@burst.rendering(@stop.number), notes: @standing.marks.notes, pages: pages, linked: @stop.description.linked)
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Proctolog
|
|
4
4
|
# What the pages' script needs of the burst's review rounds, built from the
|
|
5
|
-
# whole burst; with `pr`, that pull request's descriptions too.
|
|
5
|
+
# whole burst; with `pr`, that pull request's descriptions too. Answers an
|
|
6
|
+
# agent wrote since are taken in first, as a page's load takes them in.
|
|
6
7
|
class ReviewDataController < ApplicationController
|
|
7
8
|
def show
|
|
8
9
|
@burst = Burst.find(params.expect(:burst_id))
|
|
10
|
+
@burst.take_in
|
|
9
11
|
render json: helpers.round_data(@burst, pr: params[:pr]&.then { PullRequest.number(it) })
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proctolog
|
|
4
|
+
# The page that shares the desk with the owner's other devices, through
|
|
5
|
+
# whichever of Tailscale and ngrok this Mac has, and stops it (Share). A
|
|
6
|
+
# press comes back to it, with the sentence; `back` is the page it was
|
|
7
|
+
# opened from.
|
|
8
|
+
class SharesController < ApplicationController
|
|
9
|
+
before_action { @back = params[:back].to_s.then { (it.start_with?("/") && !it.start_with?("//")) ? it : root_path } }
|
|
10
|
+
rescue_from(Share::Refused) { redirect_to share_path(back: @back), alert: it.message }
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@share = Share.at(Desk.port)
|
|
14
|
+
@ways = Share.ways
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
share = Share.open(Desk.port, via: params[:via].presence, allow: params[:allow]&.split(/[\s,]+/))
|
|
19
|
+
redirect_to share_path(back: @back), notice: share.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def destroy
|
|
23
|
+
Share.at(Desk.port)&.close
|
|
24
|
+
redirect_to share_path(back: @back), notice: "The desk is only on this Mac again."
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -9,7 +9,11 @@ module Proctolog
|
|
|
9
9
|
# focus is never taken for one whose decision is gone.
|
|
10
10
|
# What the inventory holds changed with the desk: a page built from an
|
|
11
11
|
# older one would read its decisions wrong.
|
|
12
|
-
INVENTORY =
|
|
12
|
+
INVENTORY = 4
|
|
13
|
+
|
|
14
|
+
# Where the description of a pull request nobody has opened yet is kept,
|
|
15
|
+
# which an agent reads in place of GitHub's.
|
|
16
|
+
def unopened_file(stop) = (Unopened.find(stop.number)&.file&.to_s if stop.pull_request.unopened?)
|
|
13
17
|
|
|
14
18
|
# The pictures' files and pixel signatures are looked up at each page,
|
|
15
19
|
# not kept with the rest: a picture fetched since the reading is known.
|
|
@@ -20,7 +24,7 @@ module Proctolog
|
|
|
20
24
|
|
|
21
25
|
def draft_decision(box)
|
|
22
26
|
{id: box.to_param, pr: box.stop.number, title: box.stop.title, url: box.stop.url, branch: box.stop.pull_request.head,
|
|
23
|
-
head: box.stop.head_oid, description: box.stop.description.digest, position: box.position, lead: box.lead,
|
|
27
|
+
base: box.stop.pull_request.base, file: unopened_file(box.stop), head: box.stop.head_oid, description: box.stop.description.digest, position: box.position, lead: box.lead,
|
|
24
28
|
text: box.decision.text, key: box.decision.key, ticked: box.decision.ticked, protected: @burst.refusal(box.stop.number).present?,
|
|
25
29
|
grade: box.decision.grade, theme: box.decision.theme_name, pictures: box.decision.pictures.map(&:to_h)}
|
|
26
30
|
end
|
|
@@ -13,17 +13,48 @@ module Proctolog
|
|
|
13
13
|
time.strftime((time.to_date == Date.current) ? "at %H:%M" : "on %-d %b at %H:%M")
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
# A pull request's decisions as beads, the way deciding draws them: a
|
|
17
|
+
# bead to each blocking decision and a smaller one to each non-blocking,
|
|
18
|
+
# solid once reviewed and hollow while it waits. Each grade is labelled
|
|
19
|
+
# in words, which the pane's card shows. The page's script draws the same
|
|
20
|
+
# as choices are made (beadsOf in review-drafts.js).
|
|
21
|
+
def decision_beads(progress)
|
|
22
|
+
return unless progress.any?
|
|
23
|
+
|
|
24
|
+
tag.span(class: "beads") do
|
|
25
|
+
safe_join(progress.grades.select { it.total.positive? }.map do |grade|
|
|
26
|
+
tag.span(class: grade.name, role: "img", aria: {label: reviewed_in_words(grade)}) do
|
|
27
|
+
safe_join(Array.new(grade.total) { tag.span(class: ["bead", ("reviewed" if it < grade.reviewed)]) })
|
|
28
|
+
end
|
|
29
|
+
end)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A whole burst has too many decisions for a bead each: a hollow bead of
|
|
34
|
+
# each grade that waits, and how many of it.
|
|
35
|
+
def decisions_left(progress)
|
|
36
|
+
waiting = progress.grades.select { it.left.positive? }
|
|
37
|
+
return if waiting.empty?
|
|
38
|
+
|
|
39
|
+
tag.span(class: "beads") do
|
|
40
|
+
safe_join(waiting.map do |grade|
|
|
41
|
+
tag.span(class: [grade.name, "count"], role: "img", aria: {label: "#{pluralize(grade.left, "#{grade.name} decision")} to review"}) do
|
|
42
|
+
tag.span(class: "bead") + grade.left.to_s
|
|
43
|
+
end
|
|
44
|
+
end)
|
|
23
45
|
end
|
|
24
46
|
end
|
|
25
47
|
|
|
26
|
-
|
|
48
|
+
# The pane's mark for a pull request the agent's answers came back on,
|
|
49
|
+
# labelled with what came back (Standing#for_the_owner). The page's
|
|
50
|
+
# script marks the pane again from the same words as answers are judged.
|
|
51
|
+
def side_mark(came_back)
|
|
52
|
+
return tag.span(class: "mark", data: {side_mark: ""}) if came_back.empty?
|
|
53
|
+
|
|
54
|
+
tag.span("↩", class: "mark", data: {side_mark: ""}, role: "img", aria: {label: back_in_words(came_back)})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def back_in_words(came_back) = "back to you: #{came_back.to_sentence}"
|
|
27
58
|
|
|
28
59
|
# The whole burst in one line: "6 of 439 decisions reviewed" over a bar.
|
|
29
60
|
# Focused on a part, what is left in it against the burst: "120 / 380
|
|
@@ -57,5 +88,14 @@ module Proctolog
|
|
|
57
88
|
private
|
|
58
89
|
|
|
59
90
|
def keycaps(pressed) = safe_join(pressed.split.map { tag.kbd(it) })
|
|
91
|
+
|
|
92
|
+
# "1 of 4 blocking decisions reviewed".
|
|
93
|
+
def reviewed_in_words(grade)
|
|
94
|
+
decisions = "#{grade.name} decision".pluralize(grade.total)
|
|
95
|
+
if grade.left.zero? then (grade.total == 1) ? "its one #{decisions} reviewed" : "all #{grade.total} #{decisions} reviewed"
|
|
96
|
+
elsif grade.reviewed.zero? then "none of #{grade.total} #{decisions} reviewed yet"
|
|
97
|
+
else "#{grade.reviewed} of #{grade.total} #{decisions} reviewed"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
60
100
|
end
|
|
61
101
|
end
|
|
@@ -7,13 +7,15 @@ module Proctolog
|
|
|
7
7
|
# which a page takes in as its own so that it knows the answer to a pass
|
|
8
8
|
# an agent took itself. On a pull request's page, its description now
|
|
9
9
|
# and as each handover the desk kept found it, by batch, so the page can
|
|
10
|
-
# say what changed since.
|
|
10
|
+
# say what changed since. `back` names the pull requests something came
|
|
11
|
+
# back on, in the words of the pane's mark.
|
|
11
12
|
def round_data(burst, pr: nil)
|
|
12
13
|
route = burst.route
|
|
13
14
|
rounds = Rounds.new(burst, route:)
|
|
14
15
|
responses = ResponseFiles.new(burst)
|
|
16
|
+
back = burst.standings(route).filter_map { |stop, standing| [stop.number, back_in_words(standing.for_the_owner)] if standing.back_to_owner? }.to_h
|
|
15
17
|
{requests: rounds.requests, batches: Handover.kept(burst).map(&:batch), reports: responses.all, errors: responses.errors,
|
|
16
|
-
responseFolder: responses.folder.to_s, heads: rounds.heads,
|
|
18
|
+
responseFolder: responses.folder.to_s, heads: rounds.heads, back:,
|
|
17
19
|
**(route.at(pr)&.then { |stop| {descriptions: descriptions_of(burst, stop)} } || {})}
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Proctolog
|
|
4
|
+
module SharesHelper
|
|
5
|
+
# A way's sentence, with the addresses in it to follow: where to download
|
|
6
|
+
# it, or where to turn something on.
|
|
7
|
+
def linked_sentence(sentence)
|
|
8
|
+
safe_join(sentence.split(%r{(https?://[^\s,]*[^\s,.])}).map { it.start_with?("http") ? link_to(it, it, target: "_blank", rel: "noopener") : it })
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -34,11 +34,12 @@ module Proctolog
|
|
|
34
34
|
safe_join([safe_join(parts, ", "), "."])
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# What a line of the route says at its right: whose the pull request is
|
|
38
|
-
#
|
|
37
|
+
# What a line of the route says at its right: whose the pull request is,
|
|
38
|
+
# "back to you" when the owner's turn is what came back from the agent,
|
|
39
|
+
# then its decisions as beads.
|
|
39
40
|
def beside_a_line(standing)
|
|
40
|
-
whose = {agent: "with the agent", settled: "settled", waiting: "waiting"}
|
|
41
|
-
safe_join([whose, (
|
|
41
|
+
whose = {owner: (tag.strong("back to you") if standing.back_to_owner?), agent: "with the agent", settled: "settled", waiting: "waiting"}.fetch(standing.turn)
|
|
42
|
+
safe_join([whose, decision_beads(standing.progress)].compact, " ")
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def verdict_of(condition) = VERDICTS.fetch(condition.met)
|
|
@@ -124,14 +124,9 @@ module Proctolog
|
|
|
124
124
|
def decisions(pages: {}, focus: route) = Decisions.new(route: focus, marks: method(:marks), renderings: method(:rendering), named: @waiting, pages:)
|
|
125
125
|
|
|
126
126
|
# Where every stop of the route stands, in its order: each layer knows the
|
|
127
|
-
# standing of the one it sits on.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
route.each_with_object({}) do |stop, standings|
|
|
131
|
-
standings[stop] = Standing.new(stop:, marks: marks(stop.number), progress: progress.fetch(stop.number), under: route.under(stop)&.then { standings[it] },
|
|
132
|
-
above: route.above(stop), scenarios: scenarios.select { it.pr == stop.number }.map(&:name), named: @before_it_goes.fetch(stop.number, []))
|
|
133
|
-
end
|
|
134
|
-
end
|
|
127
|
+
# standing of the one it sits on. Worked out once for a reading of a
|
|
128
|
+
# page, which asks for it from the page and from its pane.
|
|
129
|
+
def standings(route) = (@standings ||= {}.compare_by_identity)[route] ||= standings_on(route)
|
|
135
130
|
|
|
136
131
|
# What moved in a pull request since the owner's "good to go", to show
|
|
137
132
|
# when its standing says something did.
|
|
@@ -143,6 +138,11 @@ module Proctolog
|
|
|
143
138
|
|
|
144
139
|
def reports = Reports.new(self)
|
|
145
140
|
|
|
141
|
+
# What came back from agents, taken into the marks: a report moves the
|
|
142
|
+
# note it names, and a handover's answers move theirs as the owner judges
|
|
143
|
+
# them (Rounds#move_notes). Answers the notes the reports moved.
|
|
144
|
+
def take_in = reports.take_in.tap { Rounds.new(self, route:).move_notes }
|
|
145
|
+
|
|
146
146
|
# A description's picture as a file on this Mac, once the desk has shown it.
|
|
147
147
|
def picture_file(asset) = (Project.current.pictures.glob("#{asset}.*").first if Picture.asset?(asset.to_s))
|
|
148
148
|
|
|
@@ -246,6 +246,15 @@ module Proctolog
|
|
|
246
246
|
|
|
247
247
|
private
|
|
248
248
|
|
|
249
|
+
def standings_on(route)
|
|
250
|
+
progress, scenarios, returned = progress(route), scenarios_on(route), Rounds.new(self, route:).returned
|
|
251
|
+
route.each_with_object({}) do |stop, standings|
|
|
252
|
+
standings[stop] = Standing.new(stop:, marks: marks(stop.number), progress: progress.fetch(stop.number),
|
|
253
|
+
returned: returned.fetch(stop.number, Rounds::Returned.new), under: route.under(stop)&.then { standings[it] },
|
|
254
|
+
above: route.above(stop), scenarios: scenarios.select { it.pr == stop.number }.map(&:name), named: @before_it_goes.fetch(stop.number, []))
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
249
258
|
# The pictures its descriptions show, each by its id and its kind, as GitHub's HTML addresses them.
|
|
250
259
|
def pictures_of(route) = route.flat_map { Rendering.pictures(rendering(it.number)) }
|
|
251
260
|
|
|
@@ -9,6 +9,12 @@ module Proctolog
|
|
|
9
9
|
# A writing older than the one kept: another page moved on since.
|
|
10
10
|
Stale = Class.new(StandardError)
|
|
11
11
|
|
|
12
|
+
# One grade's share of a pull request's decisions: "blocking" or
|
|
13
|
+
# "non-blocking", how many it has, and how many of them wait.
|
|
14
|
+
Grade = Data.define(:name, :total, :left) do
|
|
15
|
+
def reviewed = total - left
|
|
16
|
+
end
|
|
17
|
+
|
|
12
18
|
# How far one pull request's decisions are: a decision is reviewed once
|
|
13
19
|
# the owner chose on its present words, or it is ticked on GitHub. Its
|
|
14
20
|
# blocking decisions are counted apart: those left of them are what
|
|
@@ -31,6 +37,8 @@ module Proctolog
|
|
|
31
37
|
def non_blocking = total - blocking
|
|
32
38
|
def non_blocking_left = left - blocking_left
|
|
33
39
|
|
|
40
|
+
def grades = [Grade.new(name: "blocking", total: blocking, left: blocking_left), Grade.new(name: "non-blocking", total: non_blocking, left: non_blocking_left)]
|
|
41
|
+
|
|
34
42
|
def +(other) = with(**members.to_h { [it, public_send(it) + other.public_send(it)] })
|
|
35
43
|
end
|
|
36
44
|
|