OXYGENHUB
◆ v0.9.19 oxygenhub / templates / proposal.html
29.3 KiB · HTML · last changed Resolve a proposal's conflicts on the web: the hub brings the target into the source bookmark, as oxy merge would, and opens the resolver; the proposal follows the resolution (closes #337) 8 days ago source blame history raw
de505df5 Andy McCall ↶ 1
{% extends "base.html" %}
2
{% import "repo_tabs.html" as m %}
38833ed7 Andy McCall ↶ 3
{% block title %}Proposal #{{ p.number }} {{ p.title }} · {{ repo.owner }}/{{ repo.name }}{% endblock %}
de505df5 Andy McCall ↶ 4
{% block bodyattrs %} data-repo="{{ repo.url }}"{% endblock %}
5
{% block tabs %}{% call m::tabs(repo, "proposals") %}{% endblock %}
a1b98695 Andy McCall ↶ 6
{% block mainclass %}{% if tab != "conversation" %} class="wide"{% endif %}{% endblock %}
de505df5 Andy McCall ↶ 7
{% block content %}
ffb67a81 Andy McCall 8
{% include "repo_backdrop.html" %}
9
<div data-backdrop-avoid>
4f37afcc Andy McCall ↶ 10
<div class="head"><h1>{{ p.title }}{% if can_edit %} <a class="head-pencil" href="#edit" title="Edit title, target and description" aria-label="Edit title, target and description"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M11.01 1.31a1.75 1.75 0 0 1 2.48 0l1.2 1.2a1.75 1.75 0 0 1 0 2.48L5.1 14.6a1.75 1.75 0 0 1-.8.46l-3.13.88a.75.75 0 0 1-.92-.92l.88-3.13a1.75 1.75 0 0 1 .46-.8Zm1.42 1.06a.25.25 0 0 0-.36 0L10.5 3.94 12.06 5.5l1.57-1.57a.25.25 0 0 0 0-.36ZM11 6.56 9.44 5 3.15 11.3a.25.25 0 0 0-.06.11l-.55 1.95 1.95-.55a.25.25 0 0 0 .11-.06Z"/></svg></a>{% endif %} <small class="px kind-proposal">proposal #{{ p.number }}</small>
de505df5 Andy McCall ↶ 11
  <small>
12
    <span class="tag{% if p.state == "landed" %} head{% else if p.state == "closed" %} bad{% endif %}">{{ p.state }}</span>
13
    {{ p.author }} wants <span class="lbl lbl-bm">{{ source_label }}</span> into <a class="lbl lbl-bm" href="{{ repo.url }}/tree/{{ p.target }}">{{ p.target }}</a>
14
    · {{ changes.len()|plural("change", "changes") }} · opened <time title="{{ p.created_ms|when_i }}">{{ p.created_ms|ago_i }}</time>
15
  </small>
16
</h1></div>
38833ed7 Andy McCall ↶ 17
{% if !closes.is_empty() %}<p class="chain" aria-label="The chain of work">{% for c in closes %}<a class="chain-link kind-issue st-{{ c.state }}" href="{{ c.url }}" title="{{ c.title }}"><span class="chain-kind">issue #{{ c.number }}</span> {{ c.title }} <span class="chain-state">{{ c.state }}</span></a>{% if !loop.last %} <span class="chain-and">+</span> {% endif %}{% endfor %}
18
<span class="chain-arrow">→</span> <span class="chain-link kind-proposal st-{{ p.state }}"><span class="chain-kind">proposal #{{ p.number }}</span> <span class="chain-state">{{ p.state }}</span></span>
19
<span class="chain-arrow">→</span> <a class="lbl lbl-bm" href="{{ repo.url }}/tree/{{ p.target }}">{{ p.target }}</a>{% if p.state == "landed" %} <span class="chain-state st-landed">landed</span>{% endif %}</p>{% endif %}
de505df5 Andy McCall ↶ 20
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
21
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
217814c2 Andy McCall ↶ 22
{% if conflicted %}<div class="strip bad">Landing would carry {{ conflicts.len()|plural("conflict", "conflicts") }}. Any collaborator can push a resolution change to the source; the proposal follows it. <a href="?tab=files#conflicts">See them</a>{% if can_resolve %}<form class="inline" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/resolve"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"> <button type="submit" class="small" title="Brings {{ p.target }} into {{ p.source_bookmark }} here, as oxy merge would, then opens the resolver on the first conflicted file">RESOLVE ON THE WEB</button></form>{% endif %}</div>{% endif %}
de505df5 Andy McCall ↶ 23
{% if landed_already && p.state != "landed" %}<div class="strip warn">Every change is already in {{ p.target }}.</div>{% endif %}
668b2ad7 Andy McCall ↶ 24
{% if p.state == "landed" && !since_landing.is_empty() %}<div class="strip warn"><code>{{ p.source_bookmark }}</code> has {{ since_landing.len()|plural("change", "changes") }} since this landed, not on <code>{{ p.target }}</code>: {% for c in since_landing %}<a href="{{ repo.url }}/change/{{ c.hash }}"><code>{{ c.hash|short }}</code></a> {{ c.title }}{% if !loop.last %}, {% endif %}{% endfor %}. <a href="{{ repo.url }}/proposals/new?source={{ p.source_bookmark }}&amp;target={{ p.target }}">Open a new proposal</a> to take {% if since_landing.len() == 1 %}it{% else %}them{% endif %} there.</div>{% endif %}
de505df5 Andy McCall ↶ 25
<div class="bar">
26
  <span class="modes">
27
    <a href="?tab=conversation"{% if tab == "conversation" %} class="on"{% endif %}>conversation ({{ comments.len() + reviews.len() }})</a>
28
    <a href="?tab=changes"{% if tab == "changes" %} class="on"{% endif %}>changes ({{ changes.len() }})</a>
29
    <a href="?tab=files"{% if tab == "files" %} class="on"{% endif %}>files ({{ files.len() }})</a>
30
  </span>
31
  <span class="small mute">projected state <code title="{{ state_hash }}">{{ state_hash|short }}</code> · {{ heads.len()|plural("head", "heads") }} · <a href="{{ repo.url }}/tree/{% if let Some(h) = heads.first() %}{{ h }}{% endif %}">browse</a> · <a href="{{ api_url }}">json</a></span>
32
</div>
33
34
{% if tab == "changes" %}
35
{% if changes.is_empty() %}<p class="empty">Nothing to add: the target already has everything.</p>{% endif %}
36
{% for c in changes %}{% call m::change_row(repo, c) %}{% endfor %}
37
38
{% else if tab == "files" %}
39
{% if !conflicts.is_empty() %}
40
<section class="panel" id="conflicts"><h2>Conflicts the landing would carry</h2>
41
{% for c in conflicts %}<p><strong>{{ c.path }}</strong> <span class="tag bad">{{ c.kind }}</span></p>{% if let Some(t) = c.marked %}<pre class="marked">{{ t }}</pre>{% endif %}{% endfor %}
42
</section>
43
{% endif %}
9df855f2 Andy McCall ↶ 44
{% if !files.is_empty() %}{% call m::diff_bar(ignore_ws, "tab=files&") %}{% endif %}
de505df5 Andy McCall ↶ 45
{% for f in files %}
9df855f2 Andy McCall ↶ 46
<details class="file-diff" id="{{ f.anchor }}"{% if f.lines.len() <= 400 %} open{% endif %}>
47
  <summary><span class="kind kind-{{ f.kind }}">{{ f.kind }}</span> <strong>{{ f.path }}</strong> <span class="counts"><span class="add">+{{ f.added }}</span> <span class="del">−{{ f.removed }}</span></span></summary>
de505df5 Andy McCall ↶ 48
  {% if f.lines.is_empty() %}<p class="empty">No line changes.</p>{% else %}
49
  <table class="code diff">
50
  {% for l in f.lines %}
51
    <tr class="{{ l.kind.class() }}">
52
      <td class="n">{% if let Some(n) = l.old_no %}{{ n }}{% endif %}</td>
53
      <td class="n">{% if let Some(n) = l.new_no %}{{ n }}{% endif %}</td>
54
      <td class="s">{{ l.kind.sign() }}</td>
9df855f2 Andy McCall ↶ 55
      <td class="t">{% call m::line_text(l) %}{% if let Some(n) = l.new_no %}{% if shell.user.is_some() && p.is_open() %}<details class="linecomment"><summary title="Comment on this line">+</summary>
de505df5 Andy McCall ↶ 56
        <form method="post" action="{{ repo.url }}/proposal/{{ p.number }}/comment"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="path" value="{{ f.path }}"><input type="hidden" name="line" value="{{ n }}"><textarea name="body" rows="3" required placeholder="Anchored to this line's atom; it follows the line through rebases."></textarea><button type="submit">COMMENT</button></form></details>{% endif %}{% endif %}</td>
57
    </tr>
58
    {% if let Some(n) = l.new_no %}{% if let Some(idxs) = placed.get(&crate::web::proposals::key(&f.path, n)) %}
59
    <tr class="ctx"><td colspan="4" class="thread">
60
      {% for i in idxs %}{% let c = comments[*i] %}
61
      <div class="comment{% if c.row.resolved %} resolved{% endif %}"><span class="who">{{ c.row.author }}</span> <time class="mute" title="{{ c.row.created_ms|when_i }}">{{ c.row.created_ms|ago_i }}</time>{% if c.row.resolved %} <span class="tag">resolved</span>{% endif %}
62
        {% if shell.user.is_some() %}<form class="tiny" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/comment/{{ c.row.id }}/resolve"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button>{% if c.row.resolved %}unresolve{% else %}resolve{% endif %}</button></form>{% endif %}
63
        <div class="body">{{ c.body_html|safe }}</div></div>
64
      {% endfor %}
65
    </td></tr>
66
    {% endif %}{% endif %}
67
  {% endfor %}
68
  </table>
69
  {% endif %}
9df855f2 Andy McCall ↶ 70
</details>
de505df5 Andy McCall ↶ 71
{% endfor %}
72
{% if !outdated.is_empty() %}
73
<section class="panel"><h2>Comments on lines that are gone</h2>
74
{% for i in outdated %}{% let c = comments[*i] %}<div class="comment"><span class="who">{{ c.row.author }}</span> on <code>{{ c.row.path }}</code> <time class="mute">{{ c.row.created_ms|ago_i }}</time><div class="body">{{ c.body_html|safe }}</div></div>{% endfor %}
75
</section>
76
{% endif %}
77
78
{% else %}
9198231c Andy McCall ↶ 79
<div class="side-layout conversation">
a1b98695 Andy McCall ↶ 80
<div class="timeline">
81
  <div class="tl-item">
82
    <img class="tl-avatar" src="/avatar/{{ p.author }}" alt="" width="36" height="36">
83
    <div class="tl-card">
4f37afcc Andy McCall ↶ 84
      <div class="tl-head" id="description"><span><a href="/{{ p.author }}"><strong>{{ p.author }}</strong></a> opened this proposal <a class="tl-when" href="#description"><time title="{{ p.created_ms|when_i }}">{{ p.created_ms|ago_i }}</time></a></span>
85
        <details class="menu tl-menu"><summary class="dots" title="More" aria-label="More options">···</summary>
86
          <div class="menu-pop menu-actions">
87
            <button type="button" class="menu-action" data-copy-link="#description">Copy link</button>
88
            <button type="button" class="menu-action" data-copy-text="{{ p.body }}">Copy Markdown</button>
89
            {% if can_edit %}<hr><a class="menu-action" href="#edit">Edit</a>{% endif %}
90
          </div></details></div>
91
      <div class="tl-view">{% if p.body.is_empty() %}<p class="tl-empty mute">No description.</p>{% else %}<article class="doc tl-doc">{{ body_html|safe }}</article>{% endif %}</div>
92
      {% if can_edit %}
7d2eb590 Andy McCall ↶ 93
      <form class="tl-editform{% if draft.is_some() %} open{% endif %}" id="edit" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/edit">
4f37afcc Andy McCall ↶ 94
        <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
95
        <div class="edit-row"><label for="etitle">Title</label><input type="text" id="etitle" name="title" value="{{ p.title }}"></div>
96
        <div class="edit-row"><label for="etarget">Into bookmark</label><input type="text" id="etarget" name="target" value="{{ p.target }}"></div>
7d2eb590 Andy McCall ↶ 97
        <textarea id="ebody" name="body" class="md" rows="10" aria-label="Description (Markdown)">{% if let Some(d) = draft %}{{ d }}{% else %}{{ p.body }}{% endif %}</textarea>
98
        {% if draft.is_some() %}<p class="small mute">Drafted by an assistant; nothing is saved until you update the proposal.</p>{% endif %}
119fd17d Andy McCall ↶ 99
        <div class="tl-actions"><a class="go" href="#description">Cancel</a> {% for a in assistants %}<button type="submit" class="agent-btn" formaction="{{ repo.url }}/proposal/{{ p.number }}/draft" data-stream="{{ repo.url }}/proposal/{{ p.number }}/draft/stream" formnovalidate name="assistant" value="{{ a }}" title="{{ a }}, an AI assistant, drafts the description from the changes; you edit it before saving"><img src="/avatar/agent/{{ a }}" alt="" width="16" height="16"> Draft with {{ a }}</button> {% endfor %}<button type="submit" class="primary-btn">Update proposal</button></div>
4f37afcc Andy McCall ↶ 100
      </form>
101
      {% endif %}
a1b98695 Andy McCall ↶ 102
    </div>
103
  </div>
b5a0a69b Andy McCall ↶ 104
  <div class="tl-live" data-live="{{ live_topic }}" data-fragment="timeline"><!--frag:timeline-->
a1b98695 Andy McCall ↶ 105
  {% for t in timeline %}
106
  {% if t.kind == "comment" %}{% let c = comments[t.index] %}
107
  <div class="tl-item">
108
    <img class="tl-avatar" src="/avatar/{{ c.row.author }}" alt="" width="36" height="36">
109
    <div class="tl-card">
ffb67a81 Andy McCall 110
      <div class="tl-head"><a href="/{{ c.row.author }}"><strong>{{ c.row.author }}</strong></a>{% if !c.row.via.is_empty() %} <span class="mute" aria-hidden="true">↳</span> <span class="tag" title="An AI assistant wrote this for {{ c.row.author }}, who asked it">agent</span> <img class="avatar xs" src="/avatar/agent/{{ c.row.via }}" alt="" width="16" height="16"> <strong>{{ c.row.via }}</strong>{% endif %} commented <time title="{{ c.row.created_ms|when_i }}">{{ c.row.created_ms|ago_i }}</time></div>
a1b98695 Andy McCall ↶ 111
      <div class="tl-text">{{ c.body_html|safe }}</div>
112
    </div>
113
  </div>
114
  {% else if t.kind == "review" %}{% let r = reviews[t.index] %}
115
  <div class="tl-item review-{{ r.row.verdict }}">
116
    <img class="tl-avatar" src="/avatar/{{ r.row.author }}" alt="" width="36" height="36">
117
    <div class="tl-card">
206c1fc1 Andy McCall ↶ 118
      <div class="tl-head"><a href="/{{ r.row.author }}"><strong>{{ r.row.author }}</strong></a>{% if !r.row.via.is_empty() %} <span class="mute" aria-hidden="true">↳</span> <span class="tag" title="An AI assistant answered for {{ r.row.author }}, who asked it">agent</span> <img class="avatar xs" src="/avatar/agent/{{ r.row.via }}" alt="" width="16" height="16"> <strong>{{ r.row.via }}</strong>{% endif %} {% if r.row.verdict == "approve" %}{% if r.sealed %}<span class="sig sig-valid">◈</span> approved and sealed{% else %}<span class="sig sig-unsigned">○</span> approved, seal pending{% endif %}{% else if r.row.verdict == "changes" %}<span class="sig sig-invalid">✘</span> requested changes{% else %}reviewed{% endif %} <time title="{{ r.row.created_ms|when_i }}">{{ r.row.created_ms|ago_i }}</time></div>
a1b98695 Andy McCall ↶ 119
      {% if !r.row.body.is_empty() %}<div class="tl-text">{{ r.body_html|safe }}</div>{% endif %}
120
    </div>
121
  </div>
122
  {% else %}{% let e = events[t.index] %}
6ae3fa0a Andy McCall ↶ 123
  <p class="tl-event"><span class="tl-dot"></span><img class="tl-mini" src="/avatar/{{ e.actor }}" alt="" width="18" height="18"> <strong>{{ e.actor }}</strong> {{ e.event }}{% if e.event.ends_with("by change") && !e.detail.is_empty() %}{% let hash = e.detail.split(' ').next().unwrap_or("") %} <a href="{{ repo.url }}/change/{{ hash }}"><code>{{ hash }}</code></a>{{ e.detail[hash.len()..] }}{% else if !e.detail.is_empty() %}: <span title="{{ e.detail }}">{{ e.detail|state_label }}</span>{% endif %} <time class="mute" title="{{ e.at_ms|when_i }}">{{ e.at_ms|ago_i }}</time></p>
a1b98695 Andy McCall ↶ 124
  {% endif %}
de505df5 Andy McCall ↶ 125
  {% endfor %}
4f37afcc Andy McCall ↶ 126
  {% if p.state == "landed" %}
127
  <div class="tl-item tl-outcome landed">
128
    <span class="tl-badge" aria-hidden="true"><svg viewBox="0 0 16 16" width="18" height="18"><path fill="currentColor" d="M5.45 5.15A3.5 3.5 0 0 0 8.75 7.5h1.94a2.25 2.25 0 1 1 0 1.5H8.75a5 5 0 0 1-3.5-1.43v3.04a2.25 2.25 0 1 1-1.5 0V5.39a2.25 2.25 0 1 1 1.7-.24ZM4.5 2.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25-4.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/></svg></span>
129
    <div class="tl-card outcome-card">
130
      <div><strong>Proposal landed</strong>
668b2ad7 Andy McCall ↶ 131
      <p class="small">{% if !p.landed_by.is_empty() %}<a href="/{{ p.landed_by }}">{{ p.landed_by }}</a> landed{% else %}Landed{% endif %}{% if !changes.is_empty() %} {{ changes.len()|plural("change", "changes") }}{% endif %} into <a class="lbl lbl-bm" href="{{ repo.url }}/tree/{{ p.target }}">{{ p.target }}</a>{% if p.landed_ms > 0 %} <time title="{{ p.landed_ms|when_i }}">{{ p.landed_ms|ago_i }}</time>{% endif %}.{% if let Some(b) = deletable_source %} The <code>{{ b }}</code> bookmark can be safely deleted.{% endif %}</p></div>
16626c0d Andy McCall ↶ 132
      {% if let Some(b) = deletable_source %}<form method="post" action="{{ repo.url }}/bookmarks/delete"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="bookmark" value="{{ b }}"><button type="submit" data-busy="Deleting">Delete bookmark</button></form>{% endif %}
4f37afcc Andy McCall ↶ 133
    </div>
134
  </div>
135
  {% else if p.state == "closed" %}
136
  <div class="tl-item tl-outcome closed">
137
    <span class="tl-badge" aria-hidden="true">✕</span>
138
    <div class="tl-card outcome-card"><div><strong>Proposal closed</strong><p class="small">It was not landed{% if can_edit %}; reopen it below to carry on{% endif %}.</p></div></div>
139
  </div>
140
  {% endif %}
b5a0a69b Andy McCall ↶ 141
  {% for a in pending %}<p class="tl-event tl-pending" role="status"><span class="tl-dot"></span><img class="tl-mini" src="/avatar/agent/{{ a }}" alt="" width="18" height="18"> <strong>{{ a }}</strong> is answering… <span class="mute small">this updates by itself</span></p>{% endfor %}
142
  <!--/frag:timeline--></div>
a1b98695 Andy McCall ↶ 143
  {% if let Some(me) = shell.user %}
4f37afcc Andy McCall ↶ 144
  {% if can_edit && (p.state == "draft" || p.is_open() || p.state == "closed") %}<form id="pstate" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/state" hidden><input type="hidden" name="_csrf" value="{{ shell.csrf }}"></form>{% endif %}
a1b98695 Andy McCall ↶ 145
  <div class="tl-item">
146
    <img class="tl-avatar" src="/avatar/{{ me }}" alt="" width="36" height="36">
4f37afcc Andy McCall ↶ 147
    <div class="tl-card tl-compose">
a1b98695 Andy McCall ↶ 148
      <form method="post" action="{{ repo.url }}/proposal/{{ p.number }}/comment">
149
        <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
4f37afcc Andy McCall ↶ 150
        <p class="compose-title">Add a comment</p>
151
        <textarea id="body" name="body" class="md" rows="4" required aria-label="Comment (Markdown)"></textarea>
152
        <div class="compose-actions">
153
          {% if can_edit %}
154
          {% if p.state == "draft" %}<button type="submit" form="pstate" name="action" value="ready" formnovalidate>Ready for review</button>{% endif %}
155
          {% if p.state == "open" %}<button type="submit" form="pstate" name="action" value="draft" formnovalidate>Back to draft</button>{% endif %}
16626c0d Andy McCall ↶ 156
          {% if p.is_open() %}<button type="submit" form="pstate" name="action" value="close" class="danger-btn" formnovalidate data-busy="Closing">Close proposal</button>{% endif %}
157
          {% if p.state == "closed" %}<button type="submit" form="pstate" name="action" value="reopen" formnovalidate data-busy="Reopening">Reopen proposal</button>{% endif %}
4f37afcc Andy McCall ↶ 158
          {% endif %}
159
          <button type="submit" class="primary-btn">Comment</button>
160
        </div>
a1b98695 Andy McCall ↶ 161
      </form>
162
    </div>
163
  </div>
de505df5 Andy McCall ↶ 164
  {% if p.is_open() %}
4f37afcc Andy McCall ↶ 165
  <div class="tl-item">
166
    <span class="tl-badge review" aria-hidden="true">◈</span>
167
    <form class="tl-card tl-compose" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/review">
a1b98695 Andy McCall ↶ 168
      <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
4f37afcc Andy McCall ↶ 169
      <p class="compose-title">Review this proposal</p>
170
      <textarea id="rbody" name="body" rows="2" placeholder="Leave a summary (optional)" aria-label="Review summary"></textarea>
171
      <div class="compose-actions">
172
        <div class="segmented" role="radiogroup" aria-label="Verdict">
173
          <label><input type="radio" name="verdict" value="comment" checked><span>Comment</span></label>
174
          <label><input type="radio" name="verdict" value="approve"><span>Approve</span></label>
175
          <label><input type="radio" name="verdict" value="changes"><span>Request changes</span></label>
176
        </div>
16626c0d Andy McCall ↶ 177
        <button type="submit" class="primary-btn" data-busy="Submitting">Submit review</button>
4f37afcc Andy McCall ↶ 178
      </div>
179
      <p class="compose-note">An approval counts once you seal the changes from a client: <code>oxy seal --proposal {{ shell.base_url }}{{ repo.url }}/proposal/{{ p.number }}</code></p>
a1b98695 Andy McCall ↶ 180
    </form>
4f37afcc Andy McCall ↶ 181
  </div>
182
  {% endif %}
a1b98695 Andy McCall ↶ 183
  {% endif %}
de505df5 Andy McCall ↶ 184
</div>
8e96499f Andy McCall ↶ 185
<aside class="side side-sections">
b5a0a69b Andy McCall ↶ 186
  <section class="side-sec" data-live="{{ live_topic }} {{ repo_topic }}" data-fragment="landing"><!--frag:landing-->
8e96499f Andy McCall ↶ 187
    <div class="side-head"><h3>Landing</h3></div>
de505df5 Andy McCall ↶ 188
  {% if p.state == "landed" %}<p><span class="sig sig-valid">✔</span> landed{% if !p.landed_state.is_empty() %} at <code>{{ p.landed_state|short }}</code>{% endif %}</p>
0b20f1ec Andy McCall ↶ 189
  {% if !p.landed_by.is_empty() %}<p class="small mute">by <a href="/{{ p.landed_by }}">{{ p.landed_by }}</a>, {{ p.landed_via }}, {{ p.landed_ms|ago }}</p>{% endif %}
de505df5 Andy McCall ↶ 190
  {% else if p.state == "closed" %}<p class="mute">closed</p>
191
  {% else %}
0b20f1ec Andy McCall ↶ 192
    {% if blockers.is_empty() %}<p><span class="sig sig-valid">✔</span> ready to land</p>
c7067c7b Andy McCall 193
    {% if requirements.required_approvals == 0 && approvals.is_empty() %}<p class="small warn-text">No approval yet, and this repository's <a href="{{ repo.url }}/settings/landing">landing rules</a> require none.</p>{% endif %}
0b20f1ec Andy McCall ↶ 194
    {% else %}<ul class="plain small">{% for b in blockers %}<li><span class="sig sig-invalid">✘</span> {{ b }}</li>{% endfor %}</ul>{% endif %}
de505df5 Andy McCall ↶ 195
    {% if can_land %}
a7aa76ac Andy McCall 196
      {% if hub_can_land %}<form method="post" action="{{ repo.url }}/proposal/{{ p.number }}/land"{% if approvals.is_empty() %} data-confirm="Land #{{ p.number }} without any approval?"{% endif %}><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" data-busy="LANDING">LAND</button></form>
de505df5 Andy McCall ↶ 197
      {% else %}<p class="small">This repository has a trust policy, so land from a client with your key:</p><p class="secret">oxy proposal land {{ shell.base_url }}{{ repo.url }}/proposal/{{ p.number }}</p>{% endif %}
198
    {% endif %}
86a69b53 Andy McCall ↶ 199
    {% if !p.auto_land_by.is_empty() %}<p class="small"><span class="sig sig-pending" aria-hidden="true">◷</span> lands when its checks pass (set by <a href="/{{ p.auto_land_by }}">{{ p.auto_land_by }}</a>)</p>
200
      {% if can_auto_land %}<form method="post" action="{{ repo.url }}/proposal/{{ p.number }}/auto-land"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="on" value="0"><button type="submit" class="small">don't land it by itself</button></form>{% endif %}
201
    {% else if can_auto_land && hub_can_land %}<form method="post" action="{{ repo.url }}/proposal/{{ p.number }}/auto-land"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="on" value="1"><button type="submit" title="It lands, as you, once its required checks pass and nothing else blocks it">LAND WHEN CHECKS PASS</button></form>{% endif %}
2a8b6216 Andy McCall ↶ 202
    {% if can_land_over_checks && hub_can_land %}<details class="land-over"><summary class="small">Land now, over the checks</summary>
203
      <form method="post" action="{{ repo.url }}/proposal/{{ p.number }}/land" data-confirm="Land #{{ p.number }} before its required checks pass?"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
204
        <label for="over-checks" class="small">Why it can't wait for them (shown on the proposal, and audited)</label>
205
        <input type="text" id="over-checks" name="over_checks" required maxlength="300" placeholder="the runners are down; this is the fix">
206
        <button type="submit" class="danger-btn" data-busy="LANDING">LAND OVER CHECKS</button>
207
      </form></details>{% endif %}
de505df5 Andy McCall ↶ 208
  {% endif %}
b5a0a69b Andy McCall ↶ 209
  <!--/frag:landing--></section>
8e96499f Andy McCall ↶ 210
  {% if !closes.is_empty() %}<section class="side-sec"><div class="side-head"><h3>Closes</h3></div><ul class="plain small side-list">{% for c in closes %}<li><a href="{{ c.url }}" class="kind-issue">issue #{{ c.number }}</a> {{ c.title }} <span class="mute">{{ c.state }}</span></li>{% endfor %}</ul></section>{% endif %}
211
  <section class="side-sec">
212
    <div class="side-head"><h3>Reviewers</h3>
213
    {% if can_request %}<details class="menu"><summary class="gear" title="Request reviews" aria-label="Request reviews">{% include "gear.html" %}</summary>
214
      <form class="menu-pop" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/reviewers"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="action" value="set">
215
        <p class="menu-title">Request reviews</p>
216
        <input class="menu-filter" type="search" placeholder="Filter people" aria-label="Filter people" data-menu-filter>
217
        <div class="menu-list">{% for u in reviewable %}<label class="menu-item"><input type="checkbox" name="username" value="{{ u }}"{% if requested.contains(u) %} checked{% endif %}> <img class="avatar xs" src="/avatar/{{ u }}" alt="" loading="lazy"> <span class="menu-name">{{ u }}</span></label>{% endfor %}
218
        {% if reviewable.is_empty() %}<p class="menu-empty">Nobody else with access to this repository yet.</p>{% endif %}</div>
219
        <div class="menu-foot"><button type="submit" class="small primary-btn">Apply</button></div>
220
      </form></details>{% endif %}</div>
221
    {% if requested.is_empty() %}<p class="side-empty">No reviews requested</p>{% else %}<ul class="side-people">{% for r in requested %}<li><a href="/{{ r }}"><img class="avatar xs" src="/avatar/{{ r }}" alt="" loading="lazy"> {{ r }}</a> <span class="side-note-inline">awaiting review</span></li>{% endfor %}</ul>{% endif %}
ffb67a81 Andy McCall 222
    {% if !assistants.is_empty() %}<div class="side-assist">{% for a in assistants %}<form class="tiny inline" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/assistant"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="assistant" value="{{ a }}"><button type="submit" class="agent-btn" title="Ask {{ a }}, an AI assistant, to review; its answer is recorded as your review via {{ a }}"><img src="/avatar/agent/{{ a }}" alt="" width="16" height="16"> Ask {{ a }}</button> <button type="submit" class="agent-btn" name="task" value="summary" title="{{ a }}, an AI assistant, summarises the proposal; its summary is posted as your comment via {{ a }}"><img src="/avatar/agent/{{ a }}" alt="" width="16" height="16"> Summarise with {{ a }}</button></form>{% endfor %}</div>{% endif %}
8e96499f Andy McCall ↶ 223
  </section>
224
  <section class="side-sec">
225
    <div class="side-head"><h3>Labels</h3>
226
    {% if can_triage %}<details class="menu"><summary class="gear" title="Edit labels" aria-label="Edit labels">{% include "gear.html" %}</summary>
227
      <form class="menu-pop" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/labels"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
228
        <p class="menu-title">Apply labels to this proposal</p>
229
        {% if !all_labels.is_empty() %}<input class="menu-filter" type="search" placeholder="Filter labels" aria-label="Filter labels" data-menu-filter>{% endif %}
230
        <div class="menu-list">{% for l in all_labels %}<label class="menu-item"><input type="checkbox" name="label" value="{{ l.id }}"{% if labels.contains(l) %} checked{% endif %}> <span class="swatch" style="--c: {{ l.colour }}"></span> <span class="menu-name">{{ l.name }}{% if !l.description.is_empty() %}<small>{{ l.description }}</small>{% endif %}</span></label>{% endfor %}
231
        {% if all_labels.is_empty() %}<p class="menu-empty">This repository has no labels yet.</p>{% endif %}</div>
232
        <div class="menu-foot"><a class="small" href="{{ repo.url }}/labels">Edit labels</a> <button type="submit" class="small primary-btn">Apply</button></div>
233
      </form></details>{% endif %}</div>
234
    {% if labels.is_empty() %}<p class="side-empty">None yet</p>{% else %}<p class="side-labels">{% for l in labels %}<span class="label" style="--c: {{ l.colour }}" title="{{ l.description }}">{{ l.name }}</span> {% endfor %}</p>{% endif %}
235
  </section>
236
  <section class="side-sec">
9e50caed Andy McCall ↶ 237
    <div class="side-head"><h3>Projects</h3>
238
    {% if let Some(choices) = project_choices %}{% if !choices.is_empty() %}<details class="menu"><summary class="gear" title="Add to a project" aria-label="Add to a project">{% include "gear.html" %}</summary>
239
      <form class="menu-pop" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/project"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="action" value="add">
240
        <p class="menu-title">Add to a project</p>
241
        {% if choices.len() > 6 %}<input class="menu-filter" type="search" placeholder="Filter projects" aria-label="Filter projects" data-menu-filter>{% endif %}
242
        <div class="menu-list">{% for pr in choices %}<button type="submit" name="project" value="{{ pr.number }}" class="menu-item menu-pick"><span class="menu-name">{{ pr.title }}<small>{{ pr.owner }}</small></span></button>{% endfor %}</div>
243
      </form></details>{% endif %}{% endif %}</div>
991b5563 Andy McCall ↶ 244
  {% if projects.is_empty() %}<p class="side-empty">None yet</p>{% else %}<ul class="plain small side-list">{% for (pr, col) in projects %}<li><a href="/projects/{{ pr.owner }}/{{ pr.number }}">{{ pr.title }}</a> <span class="mute">· {{ col }}</span>{% if project_choices.is_some() %} <form class="inline" method="post" action="{{ repo.url }}/proposal/{{ p.number }}/project"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="project" value="{{ pr.number }}"><button name="action" value="remove" class="linkish" title="Take it off this project">remove</button></form>{% endif %}</li>{% endfor %}</ul>{% endif %}
9e50caed Andy McCall ↶ 245
  {% if let Some(choices) = project_choices %}{% if choices.is_empty() && projects.is_empty() %}<p class="side-note"><a href="/projects/{{ repo.owner }}">{{ repo.owner }}'s projects</a></p>{% endif %}{% endif %}
8e96499f Andy McCall ↶ 246
  </section>
a2ef53c5 Andy McCall ↶ 247
  <section class="side-sec" data-live="{{ live_topic }} {{ repo_topic }}" data-fragment="checks"><!--frag:checks-->
8e96499f Andy McCall ↶ 248
    <div class="side-head"><h3>Checks</h3></div>
b64e2537 Andy McCall ↶ 249
  <div id="checks">
8e96499f Andy McCall ↶ 250
  {% if checks.is_empty() %}<p class="side-empty">None reported on the projected state{% if !required_checks.is_empty() %}; required: {{ required_checks.join(", ") }}{% endif %}.</p>{% else %}
a2ef53c5 Andy McCall ↶ 251
  <ul class="plain">{% for c in checks %}<li><span class="sig{% if c.status == "passed" %} sig-valid{% else if c.status == "failed" %} sig-invalid{% else %} sig-unsigned{% endif %}">{% if c.status == "passed" %}✔{% else if c.status == "failed" %}✘{% else if c.status == "running" %}…{% else %}○{% endif %}</span> {% if c.url.is_empty() %}{{ c.name }}{% else %}<a href="{{ c.url }}">{{ c.name }}</a>{% endif %} <span class="mute small">{{ c.status }}{% if !c.summary.is_empty() %} · {{ c.summary|addresses }}{% endif %}{% if !c.seal.is_empty() %} · sealed{% endif %}</span></li>{% endfor %}</ul>{% endif %}
b64e2537 Andy McCall ↶ 252
  </div>
a2ef53c5 Andy McCall ↶ 253
  <!--/frag:checks--></section>
8e96499f Andy McCall ↶ 254
  <section class="side-sec">
255
    <div class="side-head"><h3>Approvals</h3></div>
256
  {% if approvals.is_empty() %}<p class="side-empty">None yet{% if requirements.required_approvals > 0 %}; {{ requirements.required_approvals }} sealed required{% endif %}.</p>{% else %}
de505df5 Andy McCall ↶ 257
  <ul class="plain">{% for a in approvals %}<li>{% if a.sealed %}<span class="sig sig-valid">◈</span>{% else %}<span class="sig sig-unsigned">○</span>{% endif %} <a href="/{{ a.account }}">{{ a.account }}</a>{% if !a.sealed %} <span class="mute small">seal pending</span>{% endif %}</li>{% endfor %}</ul>{% endif %}
8e96499f Andy McCall ↶ 258
  </section>
259
  {% if !owners.is_empty() %}<section class="side-sec"><div class="side-head"><h3>Code owners</h3></div><ul class="plain small">{% for o in owners %}<li>{{ o }}</li>{% endfor %}</ul></section>{% endif %}
260
  {% if !depends_on.is_empty() %}<section class="side-sec"><div class="side-head"><h3>Depends on</h3></div><ul class="plain small">{% for n in depends_on %}<li><a href="{{ repo.url }}/proposal/{{ n }}">#{{ n }}</a></li>{% endfor %}</ul></section>{% endif %}
de505df5 Andy McCall ↶ 261
</aside>
262
</div>
263
{% endif %}
ffb67a81 Andy McCall 264
</div>
de505df5 Andy McCall ↶ 265
{% endblock %}