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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Proctolog
|
|
7
|
+
# The desk reached from away from this Mac, by its owner alone: from their
|
|
8
|
+
# phone, or a computer elsewhere, through whichever of Tailscale or ngrok
|
|
9
|
+
# this Mac has. Tailscale lets in the devices of the owner's own tailnet
|
|
10
|
+
# and puts nothing on the internet; ngrok puts the desk there behind its
|
|
11
|
+
# sign-in with Google, for the addresses the owner allows.
|
|
12
|
+
#
|
|
13
|
+
# A share is of the desk on one port, kept as a file in the cache by that
|
|
14
|
+
# port. It stays open until it is closed, across the desk's restarts, and
|
|
15
|
+
# while its file says so the desk answers to the share's name as to its own.
|
|
16
|
+
# `who` is whom it lets in, as it was opened.
|
|
17
|
+
class Share < Data.define(:port, :way, :address, :who, :process)
|
|
18
|
+
Refused = Class.new(StandardError)
|
|
19
|
+
|
|
20
|
+
def initialize(port:, way:, address:, who: way.who, process: nil) = super
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
# The ways this Mac could share the desk, Tailscale first: it puts
|
|
24
|
+
# nothing on the internet.
|
|
25
|
+
def ways(path: ENV.fetch("PATH", "")) = [Tailscale.new(path:), Ngrok.new(path:)]
|
|
26
|
+
|
|
27
|
+
# The share of the desk on port, while its way still carries it. One
|
|
28
|
+
# whose way let it go (ngrok ended with the Mac's restart, say) is
|
|
29
|
+
# forgotten.
|
|
30
|
+
def at(port, ways: self.ways)
|
|
31
|
+
kept(port, ways:)&.then do |share|
|
|
32
|
+
next share if share.way.carrying?(share)
|
|
33
|
+
|
|
34
|
+
file(port).delete
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# As its file says, without asking its way: for every page of a burst,
|
|
40
|
+
# which says whether the desk is shared.
|
|
41
|
+
def kept(port, ways: self.ways)
|
|
42
|
+
record = recorded(port) or return
|
|
43
|
+
way = ways.find { it.to_param == record["way"] } or return
|
|
44
|
+
|
|
45
|
+
new(port:, way:, address: record["address"], who: record.fetch("who") { way.who }, process: record["process"])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Shares the desk on port through the way named, which is remembered,
|
|
49
|
+
# or the one last named while it is ready, or the first one ready.
|
|
50
|
+
# `allow` names whom ngrok lets in from now on. A share already open is
|
|
51
|
+
# only answered. A trial desk, on marks of its own, is never shared: it
|
|
52
|
+
# tries a change on this Mac, and its settings are the owner's.
|
|
53
|
+
def open(port, via: nil, allow: nil, ways: self.ways)
|
|
54
|
+
raise Refused, "A trial desk is not shared: it is for trying a change on this Mac." if Desk.trial?
|
|
55
|
+
|
|
56
|
+
Settings.current.with(allow:).save if allow
|
|
57
|
+
at(port, ways:) || chosen(via, ways).open(port).tap do
|
|
58
|
+
it.keep
|
|
59
|
+
Settings.current.with(via:).save if via
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Whether a request came through the desk's share: by the name it was
|
|
64
|
+
# asked for. Read from the file alone, as every request from away asks.
|
|
65
|
+
def carries?(request, port: Desk.port) = recorded(port)&.then { URI(it["address"]).host.casecmp?(request.host) } || false
|
|
66
|
+
|
|
67
|
+
def folder = Desk.cache.join("shares")
|
|
68
|
+
|
|
69
|
+
def file(port) = folder.join("#{port}.yml")
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def recorded(port) = file(port).then { YAML.safe_load_file(it) if it.file? }
|
|
74
|
+
|
|
75
|
+
def chosen(via, ways)
|
|
76
|
+
if via
|
|
77
|
+
way = ways.find { it.to_param == via } or raise Refused, "The desk is shared through #{ways.map(&:to_param).to_sentence(last_word_connector: " or ", two_words_connector: " or ")}, not #{via}."
|
|
78
|
+
way.ready? ? way : raise(Refused, "#{way.unready}.")
|
|
79
|
+
else
|
|
80
|
+
remembered = Settings.current.via
|
|
81
|
+
ways.find { it.to_param == remembered && it.ready? } || ways.find(&:ready?) || raise(Refused, unshareable(ways))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def unshareable(ways) = "Neither #{ways.map(&:name).join(" nor ")} can share the desk yet. #{ways.map { "#{it.unready}." }.join(" ")}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def keep = file.tap { it.dirname.mkpath }.write({"way" => way.to_param, "address" => address, "who" => who, "process" => process}.compact.to_yaml)
|
|
89
|
+
|
|
90
|
+
def close
|
|
91
|
+
way.close(self)
|
|
92
|
+
file.delete if file.exist?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def host = URI(address).host
|
|
96
|
+
|
|
97
|
+
def file = self.class.file(port)
|
|
98
|
+
|
|
99
|
+
def to_s = "The desk is shared at #{address} through #{way.name}: #{who}."
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -19,24 +19,43 @@ module Proctolog
|
|
|
19
19
|
# under it. `scenarios` are the names of this pull request's scenarios, and
|
|
20
20
|
# `named` the preconditions the burst file names for it. `progress` is how
|
|
21
21
|
# far the owner's choices on its decisions are; without one, only the boxes
|
|
22
|
-
# ticked on GitHub count as reviewed.
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# ticked on GitHub count as reviewed. `returned` is what came back from an
|
|
23
|
+
# agent on it.
|
|
24
|
+
def initialize(stop:, marks:, progress: nil, returned: Rounds::Returned.new, under: nil, above: [], scenarios: [], named: [])
|
|
25
|
+
@stop, @marks, @returned, @under, @above, @scenarios, @named = stop, marks, returned, under, above, scenarios, named
|
|
25
26
|
@progress = progress || Choices::Progress.of(decisions.index_with(&:ticked))
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
# Whose move it is: :waiting on something outside, the :agent's, the
|
|
29
|
-
# :owner's, or nobody's because it is :settled.
|
|
30
|
+
# :owner's, or nobody's because it is :settled. What came back from the
|
|
31
|
+
# agent makes it the owner's, whatever else is still with the agent.
|
|
30
32
|
def turn
|
|
31
33
|
if marks.waiting_on then :waiting
|
|
34
|
+
elsif back_to_owner? then :owner
|
|
32
35
|
elsif for_the_agent.any? then :agent
|
|
33
|
-
elsif good_to_go? && !changed?
|
|
36
|
+
elsif good_to_go? && !changed? then :settled
|
|
34
37
|
else :owner
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
def settled? = turn == :settled
|
|
39
42
|
|
|
43
|
+
# What came back from the agent for the owner, in a few words each: the
|
|
44
|
+
# notes it addressed, its answers to confirm and its questions.
|
|
45
|
+
def for_the_owner
|
|
46
|
+
addressed = notes(:addressed).size
|
|
47
|
+
[*("#{count(addressed, "note")} addressed, to confirm" if addressed.positive?),
|
|
48
|
+
*("#{@returned.to_confirm} returned, to confirm" if @returned.to_confirm.positive?),
|
|
49
|
+
*("#{count(@returned.questions, "question")} for you" if @returned.questions.positive?)]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def back_to_owner? = for_the_owner.any?
|
|
53
|
+
|
|
54
|
+
# Whether it waits for the owner: a blocking decision left, or something
|
|
55
|
+
# back from the agent. The non-blocking wait for one gesture, and hold
|
|
56
|
+
# nothing up.
|
|
57
|
+
def asks_for_owner? = back_to_owner? || progress.blocking_left.positive?
|
|
58
|
+
|
|
40
59
|
def good_to_go? = marks.good_to_go.present?
|
|
41
60
|
|
|
42
61
|
# What moved since "good to go" was given: the code, the description, both
|
|
@@ -91,8 +110,6 @@ module Proctolog
|
|
|
91
110
|
*("#{count(stop.open_threads, "review thread")} unresolved" if stop.open_threads.positive?)]
|
|
92
111
|
end
|
|
93
112
|
|
|
94
|
-
def for_the_owner = notes(:addressed).any? ? ["#{count(notes(:addressed).size, "note")} addressed, to confirm"] : []
|
|
95
|
-
|
|
96
113
|
def notes(state) = marks.notes.select { it.state == state.to_s }
|
|
97
114
|
|
|
98
115
|
def count(number, noun) = "#{number} #{noun.pluralize(number)}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<%# locals: (burst:, stop:, standing:) %>
|
|
2
|
-
<%# One pull request of the route, in a line: its number, its title, and at the right whose it is when it is not the owner's, and
|
|
3
|
-
<%= tag.li id: "pr-#{stop.number}", data: {stop: "",
|
|
2
|
+
<%# One pull request of the route, in a line: its number, its title, and at the right whether something came back from the agent, or else whose it is when it is not the owner's, and its decisions as beads. The line the cursor is on opens out: its title in full, what else there is to know of it, and its keys beside it. %>
|
|
3
|
+
<%= tag.li id: "pr-#{stop.number}", data: {stop: "", asks: standing.asks_for_owner?, turn: standing.turn} do %>
|
|
4
4
|
<p class="what">
|
|
5
5
|
<%= link_to "##{stop.number}", burst_pull_request_path(burst, stop.number), class: "number" %>
|
|
6
6
|
<%= link_to stop.title, burst_pull_request_path(burst, stop.number), class: "title" %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%# locals: (burst:, at: nil) %>
|
|
2
|
-
<%# The pane at the left of every page of a burst: the plaque the desk is named for, where the desk's pages are, and the route with how far each pull request's decisions are, so the owner always sees where they stand. `at` is the pull request the page is on. Focused on a part, it lists that part, with the key back to the whole burst, and counts what is left in the part against the burst; a pull request opened from outside the part stands apart under it. A page that keeps choices rewrites the counts as they change. data-desk keeps it out of a page's reading order. %>
|
|
2
|
+
<%# The pane at the left of every page of a burst: the plaque the desk is named for, where the desk's pages are, and the route with how far each pull request's decisions are and what came back from the agent, so the owner always sees where they stand. `at` is the pull request the page is on. Focused on a part, it lists that part, with the key back to the whole burst, and counts what is left in the part against the burst; a pull request opened from outside the part stands apart under it. A page that keeps choices rewrites the counts as they change. data-desk keeps it out of a page's reading order. %>
|
|
3
3
|
<% route = burst.route %>
|
|
4
|
-
<%
|
|
4
|
+
<% standings = burst.standings(route) %>
|
|
5
5
|
<aside class="side" aria-label="The burst" data-desk>
|
|
6
6
|
<div class="head">
|
|
7
7
|
<%= link_to image_tag("/plaque.png", alt: "", class: "plaque"), burst_path(burst), tabindex: -1, aria: {hidden: true} %>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<p class="focus">Only <%= focus.part.in_words %> · <%= link_to url_for(part: nil), data: {key: "P"} do %><kbd>P</kbd> the whole burst<% end %></p>
|
|
20
20
|
<% end %>
|
|
21
21
|
<%= tag.p class: "overall", data: {side_overall: "", part_prs: (focus.map(&:number).join(" ") unless focus.whole?)} do %>
|
|
22
|
-
<%= side_gauge(
|
|
22
|
+
<%= side_gauge(standings.each_value.sum(Proctolog::Choices::NONE, &:progress), part: (focus.sum(Proctolog::Choices::NONE) { standings.fetch(it).progress } unless focus.whole?)) %>
|
|
23
23
|
<% end %>
|
|
24
24
|
<%# Only how many notices there are: they are read at the head of the route. %>
|
|
25
25
|
<% if (notices = burst.notices).any? %>
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
<h2><%= part_heading(part, route) %></h2>
|
|
31
31
|
<ol class="stops">
|
|
32
32
|
<% route.stops_of(part).each do |stop| %>
|
|
33
|
-
<%= render "proctolog/bursts/side_stop", burst:, stop:,
|
|
33
|
+
<%= render "proctolog/bursts/side_stop", burst:, stop:, standing: standings.fetch(stop), at: %>
|
|
34
34
|
<% end %>
|
|
35
35
|
</ol>
|
|
36
36
|
<% end %>
|
|
37
37
|
<% if (outside = route.at(at)) && !focus.include?(outside) %>
|
|
38
38
|
<h2>Outside this part</h2>
|
|
39
39
|
<ol class="stops">
|
|
40
|
-
<%= render "proctolog/bursts/side_stop", burst:, stop: outside,
|
|
40
|
+
<%= render "proctolog/bursts/side_stop", burst:, stop: outside, standing: standings.fetch(outside), at: %>
|
|
41
41
|
</ol>
|
|
42
42
|
<% end %>
|
|
43
43
|
|
|
@@ -45,5 +45,6 @@
|
|
|
45
45
|
<%= tag.div class: "read", data: {freshness: burst_freshness_path(burst), read_at: burst.read_at.iso8601} do %>
|
|
46
46
|
<p>GitHub read <%= tag.time read_at_in_words(burst.read_at), datetime: burst.read_at.iso8601 %> · <%= link_to "read again", burst_freshness_path(burst, again: 1, back: request.fullpath), class: "again", data: {key: "r"} %></p>
|
|
47
47
|
<p class="moved" data-moved hidden></p>
|
|
48
|
+
<%= render "proctolog/shares/line" %>
|
|
48
49
|
<% end %>
|
|
49
50
|
</aside>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<%# locals: (burst:, stop:,
|
|
2
|
-
<%# One pull request of the pane:
|
|
1
|
+
<%# locals: (burst:, stop:, standing:, at:) %>
|
|
2
|
+
<%# One pull request of the pane: ↩ when the agent's answers came back on it, its number, its title and its decisions as beads, marked when the page is about it. Held under the pointer, side.js says the same in words, with its title in full and the way to it on GitHub. %>
|
|
3
3
|
<%= tag.li data: {side_pr: stop.number, github: stop.url}, aria: {current: ("page" if stop.number == at)} do %>
|
|
4
4
|
<%= link_to burst_pull_request_path(burst, stop.number) do %>
|
|
5
|
-
|
|
5
|
+
<%= side_mark(standing.for_the_owner) %>
|
|
6
6
|
<span class="number"><%= stop.number %></span>
|
|
7
7
|
<span class="title"><%= stop.title %></span>
|
|
8
|
-
<span class="
|
|
8
|
+
<span class="decisions" data-side-decisions><%= decision_beads(standing.progress) %></span>
|
|
9
9
|
<% end %>
|
|
10
10
|
<% end %>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%# locals: (note:, burst:, stop:, where: nil, back: nil, attributes: {}) %>
|
|
2
|
-
<%# One note as a card: what it is, what was said, where it is pinned (`where`), the agent's reply, and the ways to move it along. A press of those comes back to `back` when the page that showed the card gave one. %>
|
|
2
|
+
<%# One note as a card: what it is, what was said, where it is pinned (`where`), the agent's reply, and the ways to move it along. A press of those comes back to `back` when the page that showed the card gave one. A note answered in a handover is confirmed or answered back under Returned work, which moves it (Rounds#move_notes), so it can only be withdrawn here. %>
|
|
3
3
|
<%= tag.li(**{class: "card", data: {kind: note.kind, state: note.state}}.deep_merge(attributes)) do %>
|
|
4
4
|
<p class="meta"><span class="kind"><%= note.kind %></span> <span class="state"><%= note.state %></span></p>
|
|
5
5
|
<p class="text"><%= with_mentions(note.text, burst:) %></p>
|
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
<% end %>
|
|
13
13
|
<div class="moves">
|
|
14
14
|
<% path = burst_pull_request_note_path(burst, stop.number, note.id) %>
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
<% if note.answered_in_a_handover? %>
|
|
16
|
+
<% if note.addressed? %><p class="judged">Confirm it, or answer back, under Returned work.</p><% end %>
|
|
17
|
+
<% else %>
|
|
18
|
+
<%= button_to "Confirm", path, method: :patch, params: {state: "confirmed", return: back}.compact, class: "quiet" if note.addressed? %>
|
|
19
|
+
<%= button_to "Reopen", path, method: :patch, params: {state: "open", return: back}.compact, class: "quiet" unless note.open? %>
|
|
20
|
+
<% end %>
|
|
17
21
|
<%= button_to "Withdraw", path, method: :patch, params: {state: "withdrawn", return: back}.compact, class: "quiet" unless note.withdrawn? %>
|
|
18
22
|
</div>
|
|
19
23
|
<% end %>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%# locals: (bursts:, read_at:) %>
|
|
2
|
-
<%# The pane at the left where no burst is open: the plaque, where the desk's pages are, and the bursts to open, the newest first, each with how
|
|
2
|
+
<%# The pane at the left where no burst is open: the plaque, where the desk's pages are, and the bursts to open, the newest first, each with how many of its decisions wait. Those put away stay off it, as on the page. %>
|
|
3
3
|
<aside class="side" aria-label="The desk" data-desk>
|
|
4
4
|
<div class="head">
|
|
5
5
|
<%= link_to image_tag("/plaque.png", alt: "", class: "plaque"), open_pull_requests_path, tabindex: -1, aria: {hidden: true} %>
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
<% progress = burst.progress(burst.route).each_value.sum(Proctolog::Choices::NONE) %>
|
|
16
16
|
<li>
|
|
17
17
|
<%= link_to burst_path(burst) do %>
|
|
18
|
-
<span class="mark"
|
|
18
|
+
<span class="mark"></span>
|
|
19
19
|
<span class="number"></span>
|
|
20
20
|
<span class="title"><%= burst.name %></span>
|
|
21
|
-
<span class="
|
|
21
|
+
<span class="decisions"><%= decisions_left(progress) %></span>
|
|
22
22
|
<% end %>
|
|
23
23
|
</li>
|
|
24
24
|
<% end %>
|
|
@@ -26,5 +26,6 @@
|
|
|
26
26
|
<% end %>
|
|
27
27
|
<div class="read">
|
|
28
28
|
<p>GitHub read <%= tag.time read_at_in_words(read_at), datetime: read_at.iso8601 %> · <%= link_to "read again", open_pull_requests_path(fresh: 1), class: "again" %></p>
|
|
29
|
+
<%= render "proctolog/shares/line" %>
|
|
29
30
|
</div>
|
|
30
31
|
</aside>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%# locals: (pick:, repository:, first_time:) %>
|
|
2
|
+
<%# At the head of the page, what to do here: for someone who has not reviewed here yet, or when nothing below has decisions to ask. %>
|
|
3
|
+
<% if (start = pick.start(first_time:)) %>
|
|
4
|
+
<section class="start" aria-labelledby="start">
|
|
5
|
+
<h2 id="start">Start here</h2>
|
|
6
|
+
<%= render "proctolog/open_pull_requests/start/#{start}", pick:, repository: %>
|
|
7
|
+
</section>
|
|
8
|
+
<% end %>
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
</header>
|
|
16
16
|
|
|
17
17
|
<main class="route pick">
|
|
18
|
+
<%= render "start", pick: @pick, repository: @repository, first_time: [*@bursts, *@put_away].none?(&:own?) %>
|
|
19
|
+
|
|
18
20
|
<% if @bursts.any? || @put_away.any? %>
|
|
19
21
|
<h2>Your bursts</h2>
|
|
20
22
|
<ol class="lines">
|
|
@@ -36,10 +38,6 @@
|
|
|
36
38
|
<% end %>
|
|
37
39
|
<% end %>
|
|
38
40
|
|
|
39
|
-
<% if @pick.empty? %>
|
|
40
|
-
<p class="lede">Nothing is open in <%= @repository %>.</p>
|
|
41
|
-
<% end %>
|
|
42
|
-
|
|
43
41
|
<% @pick.stacks.each do |stack| %>
|
|
44
42
|
<h2>Stack #<%= stack.number %>, <%= stack.pull_requests.size %> from the bottom up</h2>
|
|
45
43
|
<%= button_to "Review the stack", review_stack_path(stack.number), class: "quiet" %>
|
|
@@ -59,7 +57,8 @@
|
|
|
59
57
|
|
|
60
58
|
<% if (loose = @pick.loose).any? %>
|
|
61
59
|
<h2><%= @pick.stacks.any? ? "In no stack" : "Pull requests" %></h2>
|
|
62
|
-
|
|
60
|
+
<%# Its "Review alone" buttons post elsewhere, so the form carries the session's token, not one good for bursts_path alone. %>
|
|
61
|
+
<%= form_with url: bursts_path, method: :post, authenticity_token: form_authenticity_token do |form| %>
|
|
63
62
|
<ol class="lines">
|
|
64
63
|
<% loose.each do |pull_request| %>
|
|
65
64
|
<li>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<%# locals: (pick:, repository:) %>
|
|
2
|
+
<p><%= pluralize(pick.written.size, "pull request") %> below <%= (pick.written.size == 1) ? "is" : "are" %> written for PRoctolog: their facts count the decisions they ask you. Press <b>Review alone</b> on one, <b>Review the stack</b> for a whole stack, or tick a few to review them together.</p>
|
|
3
|
+
<p>Inside, <kbd>?</kbd> shows the keys.</p>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<%# locals: (pick:, repository:) %>
|
|
2
|
+
<p>Nothing is open in <%= repository %>, and no branch here waits to be opened.</p>
|
|
3
|
+
<p>The desk reviews pull requests whose descriptions list decisions for you. Ask your agent to “write these PRs for PRoctolog”: it opens them on GitHub, or leaves them on branches here for you to review first. Then <%= link_to "read GitHub again", open_pull_requests_path(fresh: 1) %>.</p>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<%# locals: (pick:, repository:) %>
|
|
2
|
+
<p>None of these is written for PRoctolog yet, so the desk has no decisions to ask you. Any of them still opens, to read with its pictures and code.</p>
|
|
3
|
+
<p>For decisions, ask your agent to “rewrite these PRs for PRoctolog”. <code>proctolog check <%= pick.loose.first&.number || pick.stacks.first&.top&.number %></code> says what a description still lacks.</p>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<%# At the foot of the pane at the left: whether the desk is shared, as its file says, and the page to share it or stop. %>
|
|
2
|
+
<% share = Proctolog::Share.kept(Proctolog::Desk.port) %>
|
|
3
|
+
<p class="share">
|
|
4
|
+
<%= share ? "Shared through #{share.way.name}" : "Only on this Mac" %> · <%= link_to (share ? "see it" : "share it"), share_path(back: request.fullpath), class: "again" %>
|
|
5
|
+
</p>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<% content_for :title, "Sharing the desk" %>
|
|
2
|
+
<% content_for :head do %>
|
|
3
|
+
<link rel="stylesheet" href="/share.css">
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<header class="burst">
|
|
7
|
+
<h1>Sharing the desk</h1>
|
|
8
|
+
<p class="lede">
|
|
9
|
+
Review from your phone, or a computer elsewhere, while this Mac keeps the desk. Only you get in.
|
|
10
|
+
<%= link_to "Back", @back %>
|
|
11
|
+
</p>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<main class="route share">
|
|
15
|
+
<% if @share %>
|
|
16
|
+
<p class="now">Shared at <%= link_to @share.address, @share.address %> through <%= @share.way.name %>: <%= @share.who %>.</p>
|
|
17
|
+
<p class="hint">It stays shared until you stop it, the desk running or not.<% if Proctolog::Share.carries?(request) %> This page came through it: stopping it closes this page too.<% end %></p>
|
|
18
|
+
<%= button_to "Stop sharing", share_path, method: :delete, params: {back: @back}, class: "quiet" %>
|
|
19
|
+
<% else %>
|
|
20
|
+
<p class="now">Only on this Mac, at <%= link_to Proctolog::Desk.address, Proctolog::Desk.address %>.</p>
|
|
21
|
+
|
|
22
|
+
<h2>Share it through</h2>
|
|
23
|
+
<div class="ways">
|
|
24
|
+
<% @ways.each do |way| %>
|
|
25
|
+
<section class="way" aria-label="<%= way.name %>">
|
|
26
|
+
<h3><%= way.name %></h3>
|
|
27
|
+
<% if way.ready? %>
|
|
28
|
+
<%= form_with url: share_path, method: :post do |form| %>
|
|
29
|
+
<%= form.hidden_field :via, value: way.to_param %>
|
|
30
|
+
<%= form.hidden_field :back, value: @back %>
|
|
31
|
+
<%= render "proctolog/shares/ways/#{way.to_param}", way:, form: %>
|
|
32
|
+
<%= form.button "Share through #{way.name}", class: "quiet" %>
|
|
33
|
+
<% end %>
|
|
34
|
+
<% else %>
|
|
35
|
+
<p class="facts"><%= linked_sentence(way.unready) %>.</p>
|
|
36
|
+
<% end %>
|
|
37
|
+
</section>
|
|
38
|
+
<% end %>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<h2>From a terminal</h2>
|
|
43
|
+
<p class="hint">
|
|
44
|
+
<code>proctolog share</code> shares it the way you named last, or the first one ready, and prints the address;
|
|
45
|
+
<code>proctolog share stop</code> stops it, and <code>proctolog share status</code> says what each way needs.
|
|
46
|
+
An agent can run them too, on a Mac you left working.
|
|
47
|
+
</p>
|
|
48
|
+
</main>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<%# locals: (way:, form:) %>
|
|
2
|
+
<p class="facts">At your ngrok account's address, on the internet, behind ngrok's sign-in with Google and a warning page of its own. Its free plan carries 1 GB a month, and every picture and film you open passes through it.</p>
|
|
3
|
+
<p class="allow">
|
|
4
|
+
<%= form.label :allow, "Who may sign in" %>
|
|
5
|
+
<%= form.text_field :allow, value: way.allowed.join(", "), spellcheck: false, autocomplete: "off" %>
|
|
6
|
+
</p>
|