gabriel / musehub public
issue_detail.html html
597 lines 28.3 KB
Raw
sha256:f99af7b1a7f36c4d537d1c630d4b71fc39222b1255f82e930929e2fc89015e11 fix: relax browse_repo perf budget to 500ms — 200ms was too… Sonnet 4.6 99 days ago
1 {% extends "musehub/base.html" %}
2 {% from "musehub/macros/label.html" import label_chip %}
3
4 {%- set state_cls = "open" if issue.state == "open" else "closed" %}
5
6 {% block title %}#{{ issue.number }} · {{ issue.title }} — {{ owner }}/{{ repo_slug }}{% endblock %}
7
8 {% block breadcrumb %}
9 <a href="/{{ owner }}">{{ owner }}</a> /
10 <a href="{{ base_url }}">{{ repo_slug }}</a> /
11 <a href="{{ base_url }}/issues">issues</a> / #{{ issue.number }}
12 {% endblock %}
13
14 {% block repo_nav %}{% include "musehub/partials/repo_nav.html" %}{% endblock %}
15
16 {% block page_json %}{"page": "issue-detail"}{% endblock %}
17
18 {# Full-bleed hero layout — same pattern as proposal_detail #}
19 {% block container_extra_class %} page-container{% endblock %}
20
21 {% block content %}
22 <div class="isd-page">
23
24 {# ── Hero ─────────────────────────────────────────────────────────────────── #}
25 <div class="isd-hero isd-hero--{{ state_cls }}">
26 <div class="isd-hero-glow isd-hero-glow--{{ state_cls }}"></div>
27 <div class="isd-hero-inner">
28
29 {# Eyebrow: left = state + number + type + labels | right = filed by + time #}
30 <div class="isd-eyebrow">
31 <div class="isd-eyebrow-left">
32 <span class="isd-state-badge isd-state-badge--{{ state_cls }}">
33 {%- if issue.state == "open" %}
34 {{ icon("dot", 10) }}
35 Open
36 {%- else %}
37 {{ icon("check", 10) }}
38 Closed
39 {%- endif %}
40 </span>
41 <span class="isd-issue-num">#{{ issue.number }}</span>
42 {%- if issue_type %}
43 <span class="isd-type-pill" style="color:{{ issue_type[2] }};border-color:{{ issue_type[2] }}33;background:{{ issue_type[2] }}18">
44 {%- if issue_type[0] == 'bug' %}
45 {{ icon("bug", 11) }}
46 {%- elif issue_type[0] == 'feature' %}
47 {{ icon("star", 11) }}
48 {%- elif issue_type[0] == 'enhancement' %}
49 {{ icon("chevrons-up", 11) }}
50 {%- elif issue_type[0] == 'question' %}
51 {{ icon("help-circle", 11) }}
52 {%- elif issue_type[0] == 'documentation' %}
53 {{ icon("book", 11) }}
54 {%- elif issue_type[0] == 'performance' %}
55 {{ icon("zap", 11) }}
56 {%- elif issue_type[0] == 'discussion' %}
57 {{ icon("message-square", 11) }}
58 {%- endif %}
59 {{ issue_type[1] }}
60 </span>
61 {%- endif %}
62 {%- for lbl in display_labels %}
63 {%- set lbl_obj = labels_data | selectattr('name', 'equalto', lbl) | first | default({'color': '#8b949e'}) %}
64 {{ label_chip(lbl, lbl_obj.color, href=base_url ~ '/issues?label=' ~ (lbl | urlencode)) }}
65 {%- endfor %}
66 </div>
67
68 <div class="isd-eyebrow-right">
69 <span class="isd-eyebrow-filed-label">filed by</span>
70 <img src="/avatars/handle/{{ issue.author | urlencode }}.svg" class="isd-eyebrow-sigil" width="20" height="20" alt="" aria-hidden="true">
71 <a href="/{{ issue.author }}" class="isd-actor-link">{{ issue.author or "unknown" }}</a>
72 <span class="isd-identity-badge isd-identity-badge--{{ author_identity_type }}">{{ author_identity_type }}</span>
73 <span class="isd-eyebrow-sep">·</span>
74 <span class="isd-eyebrow-time">{{ issue.created_at | fmtrelative }}</span>
75 {%- if issue.closed_at %}
76 <span class="isd-eyebrow-sep">·</span>
77 <span class="isd-eyebrow-time">closed {{ issue.closed_at | fmtrelative }}</span>
78 {%- endif %}
79 {%- if issue.assignee and issue.assignee != issue.author %}
80 <span class="isd-eyebrow-sep">·</span>
81 <span class="isd-eyebrow-filed-label">assigned to</span>
82 <a href="/{{ issue.assignee }}" class="isd-actor-link">{{ issue.assignee }}</a>
83 {%- endif %}
84 </div>
85 </div>
86
87 <h1 class="isd-title">{{ issue.title }}</h1>
88
89 </div>
90
91 {# Stats strip — anchors | blast | churn | proposals #}
92 {%- set _blast_total = namespace(v=0) %}
93 {%- set _churn30_total = namespace(v=0) %}
94 {%- for _ai in anchor_intel.values() %}
95 {%- if _ai.blast is defined and _ai.blast is not none %}
96 {%- set _blast_total.v = _blast_total.v + (_ai.blast | int) %}
97 {%- endif %}
98 {%- if _ai.churn_30d is defined and _ai.churn_30d is not none %}
99 {%- set _churn30_total.v = _churn30_total.v + (_ai.churn_30d | int) %}
100 {%- endif %}
101 {%- endfor %}
102 <div class="isd-stats">
103 <div class="isd-stat">
104 <span class="isd-stat-val{% if symbol_anchors %} isd-stat-val--accent{% endif %}">{{ symbol_anchors | length | fmtnum }}</span>
105 <span class="isd-stat-label">Anchors</span>
106 </div>
107 <div class="isd-stat">
108 {%- if anchor_intel %}
109 <span class="isd-stat-val isd-stat-val--hot">{{ _blast_total.v | fmtnum }}</span>
110 {%- else %}
111 <span class="isd-stat-val isd-stat-val--dim">—</span>
112 {%- endif %}
113 <span class="isd-stat-label">Blast radius</span>
114 </div>
115 <div class="isd-stat">
116 {%- if anchor_intel %}
117 <span class="isd-stat-val isd-stat-val--hot">{{ _churn30_total.v | fmtnum }}</span>
118 {%- else %}
119 <span class="isd-stat-val isd-stat-val--dim">—</span>
120 {%- endif %}
121 <span class="isd-stat-label">Churn 30d</span>
122 </div>
123 <div class="isd-stat">
124 <span class="isd-stat-val{% if linked_proposals %} isd-stat-val--accent{% endif %}">{{ linked_proposals | length | fmtnum }}</span>
125 <span class="isd-stat-label">Proposals</span>
126 </div>
127 </div>
128 </div>
129
130 {# ── Body ──────────────────────────────────────────────────────────────────── #}
131 <div class="isd-wrap">
132 <div class="isd-layout">
133
134 {# ═══ Main column (65%) ══════════════════════════════════════════════════ #}
135 {#
136 Card order — designed to guide a developer from the familiar to the new:
137 1. Description — what the issue is (familiar)
138 2. Activity — the conversation thread (familiar)
139 3. Commits — linked commits (bridge: devs know commits)
140 4. Symbols — pinned functions / classes (new Muse concept)
141 5. Intelligence — blast radius, churn, gravity (full Muse payoff)
142 #}
143 <div class="isd-main">
144
145 {# 1. Issue description — what is this issue? #}
146 <div class="isd-body-card">
147 <div class="isd-body-bd">
148 {%- if issue.body %}
149 <div class="isd-body-content">{{ issue.body | markdown | safe }}</div>
150 {%- else %}
151 <div class="isd-body-empty">No description provided.</div>
152 {%- endif %}
153 </div>
154 </div>
155
156 {# 2. Activity timeline — the conversation (familiar from any issue tracker) #}
157 <div class="isd-timeline">
158 <div class="isd-timeline-hd">
159 <span class="isd-timeline-icon">◎</span>
160 <span class="isd-timeline-title">Activity</span>
161 {%- set comment_count = timeline | selectattr('kind', 'equalto', 'comment') | list | length %}
162 {%- if comment_count %}<span class="isd-panel-count">{{ comment_count | fmtnum }}</span>{%- endif %}
163 </div>
164 <div id="issue-comments" class="isd-timeline-bd">
165 {% include "musehub/fragments/issue_comments.html" %}
166 </div>
167 </div>
168
169 {# 3. Symbol anchors — Muse-native: pin the exact functions / classes in scope #}
170 {%- if symbol_anchors %}
171 <div class="isd-panel isd-panel--anchors">
172 <div class="isd-panel-hd">
173 <span class="isd-panel-icon">◈</span>
174 <span class="isd-panel-title">Symbol Anchors</span>
175 <span class="isd-panel-count">{{ symbol_anchors | length | fmtnum }}</span>
176 </div>
177 <div class="isd-panel-bd isd-panel-bd--flush">
178 {%- for anchor in symbol_anchors %}
179 {%- set ar = parse_symbol_anchor(anchor) %}
180 {%- set site = site_base_url() %}
181 {%- set blob_href = ar.blob_url(site, owner, repo_slug) %}
182 {%- set ai = anchor_intel.get(anchor, {}) %}
183 <a href="{{ blob_href }}" class="isd-anchor{% if ar.is_cross_repo %} isd-anchor--cross-repo{% endif %}">
184 <span class="isd-anchor-addr">
185 {%- if ar.is_cross_repo %}<span class="isd-anchor-repo">{{ ar.owner }}/{{ ar.repo }}</span><span class="isd-anchor-sep">::</span>{%- endif %}
186 <span class="isd-anchor-file">{{ ar.file_path }}</span>
187 {%- if ar.symbol_name %}<span class="isd-anchor-sep">::</span><span class="isd-anchor-sym">{{ ar.symbol_name }}</span>{%- endif %}
188 {%- if ar.ref %}<span class="isd-anchor-ref">@{{ ar.ref }}</span>{%- endif %}
189 </span>
190 <span class="isd-anchor-stats">
191 {%- set blast = ai.blast if ai.blast is defined and ai.blast is not none else none %}
192 {%- set churn = ai.churn_30d if ai.churn_30d is defined and ai.churn_30d is not none else none %}
193 {%- if blast is not none %}<span class="isd-anchor-stat" title="Blast radius">⬡ {{ blast | fmtnum }}</span>{%- endif %}
194 {%- if churn is not none %}<span class="isd-anchor-stat" title="Churn last 30d">↻ {{ churn | fmtnum }}</span>{%- endif %}
195 {%- if ai.last_author is defined and ai.last_author %}<span class="isd-anchor-author">{{ ai.last_author }}</span>{%- endif %}
196 </span>
197 <span class="isd-anchor-arrow">↗</span>
198 </a>
199 {%- endfor %}
200 </div>
201 </div>
202 {%- endif %}
203
204 {# 4. Intelligence — Muse singularity mode: blast radius, churn velocity, gravity, sparklines #}
205 {%- set _first_ai = namespace(v=none) %}
206 {%- for anchor in symbol_anchors %}
207 {%- set _ai = anchor_intel.get(anchor, {}) if anchor_intel else {} %}
208 {%- if _ai and _first_ai.v is none %}{%- set _first_ai.v = _ai %}{%- endif %}
209 {%- endfor %}
210 {%- if _first_ai.v %}
211 {%- set ai = _first_ai.v %}
212 {%- set grav_raw = ai.gravity if ai.gravity is defined else 0 %}
213 {%- set grav_pct = (grav_raw * 100) | round(2) %}
214 {%- set grav_cls = 'high' if grav_pct >= 5 else ('mid' if grav_pct >= 1 else 'low') %}
215 {%- set blast_val = ai.blast if ai.blast is defined and ai.blast is not none else 0 %}
216 {%- set bd = ai.blast_direct if ai.blast_direct is defined else 0 %}
217 {%- set bc = ai.blast_cross if ai.blast_cross is defined else 0 %}
218 {%- set churn_val = ai.churn_90d if ai.churn_90d is defined else (ai.churn if ai.churn is defined else 0) %}
219 {%- set author_val = ai.author_count if ai.author_count is defined else 0 %}
220 <div class="isd-panel isd-panel--intel">
221 <div class="isd-panel-hd">
222 <span class="isd-panel-icon">⬡</span>
223 <span class="isd-panel-title">Intelligence</span>
224 </div>
225 <div class="isd-panel-bd">
226
227 {# ── Blast radius ── #}
228 <div class="isd-ic-section isd-ic-section--blast">
229 <div class="isd-ic-section-label">Blast radius</div>
230 <div class="isd-ic-blast-nums">
231 <span class="isd-ic-blast-num isd-ic-blast-num--direct">{{ bd | fmtnum }}</span>
232 <span class="isd-ic-blast-sep">direct dependents</span>
233 <span class="isd-ic-blast-dot">·</span>
234 <span class="isd-ic-blast-num isd-ic-blast-num--cross">{{ bc | fmtnum }}</span>
235 <span class="isd-ic-blast-sep">transitive</span>
236 </div>
237 <div class="isd-ic-grav-wrap">
238 <div class="isd-ic-grav-track">
239 <div class="isd-ic-grav-fill isd-ic-grav-fill--{{ grav_cls }}" style="width:{{ [grav_pct, 100] | min }}%"></div>
240 </div>
241 <span class="isd-ic-grav-label isd-ic-grav-label--{{ grav_cls }}">gravity: {{ grav_pct }}% of codebase</span>
242 </div>
243 {%- if ai.blast_top is defined and ai.blast_top %}
244 <div class="isd-ic-co-syms">
245 <span class="isd-ic-co-label">top callers:</span>
246 {%- for co_path in ai.blast_top %}
247 <span class="isd-ic-co-sym" title="{{ co_path }}">{{ co_path.split('/')[-1] }}</span>
248 {%- endfor %}
249 {%- if blast_val > (ai.blast_top | length) %}
250 <span class="isd-ic-co-sym isd-ic-co-sym--more">+{{ (blast_val - (ai.blast_top | length)) | fmtnum }}</span>
251 {%- endif %}
252 </div>
253 {%- endif %}
254 </div>
255
256 {# ── Churn ── #}
257 <div class="isd-ic-section isd-ic-section--churn">
258 <div class="isd-ic-section-label">Churn (last 90 days)</div>
259 <div class="isd-ic-churn-line">
260 <strong class="isd-ic-churn-n">{{ churn_val | fmtnum }}</strong>
261 <span class="isd-ic-churn-txt">modification{{ 's' if churn_val != 1 }}</span>
262 {%- if author_val > 0 %}
263 <span class="isd-ic-churn-dot">·</span>
264 <strong class="isd-ic-churn-n">{{ author_val | fmtnum }}</strong>
265 <span class="isd-ic-churn-txt">author{{ 's' if author_val != 1 }}</span>
266 {%- endif %}
267 {%- if ai.last_changed is defined and ai.last_changed %}
268 <span class="isd-ic-churn-dot">·</span>
269 <span class="isd-ic-churn-txt">last touched</span>
270 <span class="isd-ic-churn-ts">{{ ai.last_changed | fmtdate }}</span>
271 {%- if ai.last_author is defined and ai.last_author %}
272 <strong class="isd-ic-churn-author">{{ ai.last_author }}</strong>
273 {%- endif %}
274 {%- endif %}
275 </div>
276 {%- if ai.weekly is defined and ai.weekly %}
277 {%- set _sm = namespace(v=1) %}
278 {%- for w in ai.weekly %}{%- if w > _sm.v %}{%- set _sm.v = w %}{%- endif %}{%- endfor %}
279 <div class="isd-ic-sparkline" title="Weekly changes — 12 weeks, newest on left">
280 {%- for w in ai.weekly %}
281 {%- if w > 0 %}
282 <span class="isd-ic-spark-bar"
283 style="height:{{ [((w / _sm.v) * 44) | round | int, 4] | max }}px"
284 title="Week -{{ loop.index0 }}: {{ w }} change{{ 's' if w != 1 }}"></span>
285 {%- else %}
286 <span class="isd-ic-spark-gap"></span>
287 {%- endif %}
288 {%- endfor %}
289 </div>
290 {%- endif %}
291 </div>
292
293 </div>
294 </div>
295 {%- endif %}
296
297 </div>{# /isd-main #}
298
299 {# ═══ Sidebar (35%) ══════════════════════════════════════════════════════ #}
300 <aside class="isd-sidebar">
301
302 {# Assignee #}
303 <div class="isd-sb-card">
304 <div class="isd-sb-hd">Assignee</div>
305 <div class="isd-sb-bd">
306 {%- if issue.assignee %}
307 <div class="isd-assignee">
308 <img src="/avatars/handle/{{ issue.assignee | urlencode }}.svg" class="isd-assignee-sigil" width="32" height="32" alt="" aria-hidden="true">
309 <div class="isd-assignee-info">
310 <a href="/{{ issue.assignee }}" class="isd-actor-link">{{ issue.assignee }}</a>
311 <span class="isd-identity-badge isd-identity-badge--{{ assignee_identity_type }}">{{ assignee_identity_type }}</span>
312 </div>
313 </div>
314 {%- else %}
315 <span class="isd-muted">○ unassigned</span>
316 {%- endif %}
317 </div>
318 </div>
319
320 {# Linked proposals — Signals 1, 2, 3: commit graph, symbol overlap, branch reachability #}
321 {%- if linked_proposals %}
322 {%- set _reasons = linked_proposals | map(attribute='match_reason') | list | unique | sort | list %}
323 {%- if _reasons == ["commit_graph"] %}
324 {%- set _label = "via commit graph" %}
325 {%- set _tip = "Matched by VCS commit graph ancestry (merged)" %}
326 {%- elif _reasons == ["branch_reachability"] %}
327 {%- set _label = "in-flight" %}
328 {%- set _tip = "Commit reachable from from_branch but not yet in to_branch" %}
329 {%- elif _reasons == ["symbol_overlap"] %}
330 {%- set _label = "via symbol overlap" %}
331 {%- set _tip = "Matched by symbol address overlap" %}
332 {%- elif "commit_graph" in _reasons and "branch_reachability" in _reasons %}
333 {%- set _label = "via commit graph + in-flight" %}
334 {%- set _tip = "Some proposals merged (commit graph), others in-flight (branch reachability)" %}
335 {%- else %}
336 {%- set _label = "via Muse graph" %}
337 {%- set _tip = "Matched by VCS commit graph, branch reachability, or symbol overlap" %}
338 {%- endif %}
339 <div class="isd-sb-card">
340 <div class="isd-sb-hd">
341 Linked Proposals
342 <span class="isd-lp-signal-badge" title="{{ _tip }}">{{ _label }}</span>
343 </div>
344 <div class="isd-sb-bd isd-sb-bd--flush">
345 {%- for lp in linked_proposals %}
346 <a href="{{ base_url }}/proposals/{{ lp.proposal_id }}" class="isd-sb-proposal-row">
347 <span class="isd-lp-state isd-lp-state--{{ lp.state }}">
348 {%- if lp.state == "merged" %}●{%- elif lp.state == "open" %}○{%- else %}✕{%- endif %}
349 </span>
350 <span class="isd-lp-ref">#{{ lp.proposal_number }}</span>
351 <span class="isd-lp-title">{{ lp.title | truncate(34, True, '…') }}</span>
352 {%- if lp.match_reason == "branch_reachability" %}
353 <span class="isd-lp-sym-dot isd-lp-sym-dot--inflight" title="In-flight: commit reachable from branch, not yet merged">⬆</span>
354 {%- elif lp.match_reason == "symbol_overlap" %}
355 <span class="isd-lp-sym-dot" title="Matched via symbol overlap">◆</span>
356 {%- endif %}
357 </a>
358 {%- endfor %}
359 </div>
360 </div>
361 {%- endif %}
362
363 {# Release — Muse-native VCS graph release tracking #}
364 <div class="isd-sb-card isd-sb-card--release">
365 <div class="isd-sb-hd">
366 Release
367 {%- if release_context.all_landed %}<span class="isd-rel-status-chip isd-rel-status-chip--landed">landed</span>
368 {%- elif release_context.any_landed %}<span class="isd-rel-status-chip isd-rel-status-chip--partial">partial</span>
369 {%- elif not release_context.no_commits %}<span class="isd-rel-status-chip isd-rel-status-chip--pending">unreleased</span>
370 {%- endif %}
371 </div>
372 <div class="isd-sb-bd isd-release-bd">
373 {%- if release_context.no_commits %}
374 <span class="isd-release-none">no commits linked to this issue</span>
375 {%- else %}
376 {%- for ac in release_context.anchor_commits %}
377 <div class="isd-rel-commit {% if ac.landed_in %}isd-rel-commit--landed{% else %}isd-rel-commit--pending{% endif %}">
378 <div class="isd-rel-commit-top">
379 <a href="{{ base_url }}/commits/{{ ac.full }}" class="isd-rel-hash">{{ ac.short }}</a>
380 {%- if ac.landed_in %}
381 <span class="isd-rel-landed-in">
382 {%- for rel in ac.landed_in %}
383 <a href="{{ base_url }}/releases/{{ rel.tag }}" class="isd-rel-ver isd-rel-ver--landed">{{ rel.tag }}</a>
384 {%- endfor %}
385 </span>
386 {%- else %}
387 <span class="isd-rel-on-branch">{{ ac.branch or 'dev' }}</span>
388 {%- endif %}
389 </div>
390 {%- if ac.message %}
391 <div class="isd-rel-msg">"{{ ac.message }}"</div>
392 {%- endif %}
393 {%- if ac.author or ac.date %}
394 <div class="isd-rel-meta">
395 {%- if ac.author %}<span class="isd-rel-author">{{ ac.author }}</span>{%- endif %}
396 {%- if ac.author and ac.date %}<span class="isd-rel-sep">·</span>{%- endif %}
397 {%- if ac.date %}<span class="isd-rel-date">{{ ac.date | fmtdate }}</span>{%- endif %}
398 </div>
399 {%- endif %}
400 </div>
401 {%- endfor %}
402 {%- if not release_context.all_landed and release_context.next_tag %}
403 <div class="isd-rel-next">
404 <span class="isd-rel-next-label">next release</span>
405 <span class="isd-rel-next-tag">{{ release_context.next_tag }}</span>
406 <span class="isd-rel-next-hint">proposed</span>
407 </div>
408 {%- endif %}
409 {%- endif %}
410 </div>
411 </div>
412
413 {# Act via CLI / MCP / REST — tabbed #}
414 <div class="isd-sb-card isd-sb-card--cli" x-data="tabSwitcher">
415 <div class="isd-sb-hd isd-sb-hd--tabs">
416 <button class="isd-act-tab" :class="cliClass" @click="setCliTab">CLI</button>
417 <button class="isd-act-tab" :class="mcpClass" @click="setMcpTab">MCP</button>
418 <button class="isd-act-tab" :class="restClass" @click="setRestTab">REST</button>
419 </div>
420
421 {# ── CLI pane ───────────────────────────────────────────────────────── #}
422 <div class="isd-sb-bd" x-show="showCli">
423
424 <div class="isd-cli-label">create</div>
425 <div class="isd-snippet-wrap">
426 <pre class="isd-cli-snippet"><code class="language-bash">muse hub issue create \
427 --title "..." \
428 --body "..." \
429 --label bug \
430 --anchor path/to/file.py::Symbol \
431 --commit-anchor &lt;sha&gt; \
432 --repo {{ owner }}/{{ repo_slug }}</code></pre>
433 </div>
434
435 <div class="isd-cli-label">read</div>
436 <div class="isd-snippet-wrap">
437 <pre class="isd-cli-snippet"><code class="language-bash">muse hub issue get {{ issue.number }} --json
438 muse hub issue list --state open --json</code></pre>
439 </div>
440
441 <div class="isd-cli-label">update</div>
442 <div class="isd-snippet-wrap">
443 <pre class="isd-cli-snippet"><code class="language-bash">muse hub issue edit {{ issue.number }} \
444 {%- if issue.symbol_anchors %}
445 {%- for anchor in issue.symbol_anchors %}
446 --anchor {{ anchor }} \
447 {%- endfor %}
448 {%- else %}
449 --anchor path/to/file.py::Symbol \
450 {%- endif %}
451 {%- if issue.commit_anchors %}
452 {%- for ca in issue.commit_anchors %}
453 --commit-anchor {{ ca }} \
454 {%- endfor %}
455 {%- endif %}
456 --repo {{ owner }}/{{ repo_slug }}</code></pre>
457 </div>
458
459 <div class="isd-cli-label">comment</div>
460 <div class="isd-snippet-wrap">
461 <pre class="isd-cli-snippet"><code class="language-bash">muse hub issue comment {{ issue.number }} \
462 --body "Fixed in &lt;sha&gt;" \
463 --repo {{ owner }}/{{ repo_slug }}</code></pre>
464 </div>
465
466 <div class="isd-cli-label">{% if issue.state == 'open' %}close{% else %}reopen{% endif %}</div>
467 <div class="isd-snippet-wrap">
468 <pre class="isd-cli-snippet"><code class="language-bash">muse hub issue {% if issue.state == 'open' %}close{% else %}reopen{% endif %} {{ issue.number }} \
469 --repo {{ owner }}/{{ repo_slug }}</code></pre>
470 </div>
471 </div>
472
473 {# ── MCP pane ───────────────────────────────────────────────────────── #}
474 <div class="isd-sb-bd" x-show="showMcp" x-cloak>
475
476 <div class="isd-cli-label">create</div>
477 <div class="isd-snippet-wrap">
478 <pre class="isd-cli-snippet"><code class="language-javascript">create_issue({
479 repo_id: "{{ repo_id }}",
480 title: "...",
481 body: "...",
482 labels: ["bug"],
483 symbol_anchors: [
484 "path/to/file.py::Symbol"
485 ],
486 commit_anchors: ["&lt;sha&gt;"]
487 })</code></pre>
488 </div>
489
490 <div class="isd-cli-label">read</div>
491 <div class="isd-snippet-wrap">
492 <pre class="isd-cli-snippet"><code class="language-javascript">get_issue({
493 repo_id: "{{ repo_id }}",
494 issue_number: {{ issue.number }}
495 })
496
497 list_issues({
498 repo_id: "{{ repo_id }}",
499 state: "open"
500 })</code></pre>
501 </div>
502
503 <div class="isd-cli-label">update</div>
504 <div class="isd-snippet-wrap">
505 <pre class="isd-cli-snippet"><code class="language-javascript">edit_issue({
506 repo_id: "{{ repo_id }}",
507 issue_number: {{ issue.number }},
508 {%- if issue.symbol_anchors %}
509 symbol_anchors: [
510 {%- for anchor in issue.symbol_anchors %}
511 "{{ anchor }}"{{ "," if not loop.last }}
512 {%- endfor %}
513 ]
514 {%- else %}
515 symbol_anchors: ["path/to/file.py::Symbol"]
516 {%- endif %}
517 })</code></pre>
518 </div>
519
520 <div class="isd-cli-label">comment</div>
521 <div class="isd-snippet-wrap">
522 <pre class="isd-cli-snippet"><code class="language-javascript">create_issue_comment({
523 repo_id: "{{ repo_id }}",
524 issue_number: {{ issue.number }},
525 body: "..."
526 })</code></pre>
527 </div>
528
529 <div class="isd-cli-label">{% if issue.state == 'open' %}close{% else %}reopen{% endif %}</div>
530 <div class="isd-snippet-wrap">
531 <pre class="isd-cli-snippet"><code class="language-javascript">{% if issue.state == 'open' %}close{% else %}reopen{% endif %}_issue({
532 repo_id: "{{ repo_id }}",
533 issue_number: {{ issue.number }}
534 })</code></pre>
535 </div>
536 </div>
537
538 {# ── REST pane ──────────────────────────────────────────────────────── #}
539 <div class="isd-sb-bd" x-show="showRest" x-cloak>
540
541 <div class="isd-cli-label">create</div>
542 <div class="isd-snippet-wrap">
543 <pre class="isd-cli-snippet"><code class="language-bash">curl -X POST \
544 http://localhost:10003/api/repos/{{ repo_id }}/issues \
545 -H "Content-Type: application/json" \
546 -H "Authorization: MSign handle=\"...\" ts=... sig=\"...\"" \
547 -d '{
548 "title": "...",
549 "body": "...",
550 "labels": ["bug"],
551 "symbol_anchors": ["path/to/file.py::Symbol"]
552 }'</code></pre>
553 </div>
554
555 <div class="isd-cli-label">read</div>
556 <div class="isd-snippet-wrap">
557 <pre class="isd-cli-snippet"><code class="language-bash"># get one issue
558 curl http://localhost:10003/api/repos/{{ repo_id }}/issues/{{ issue.number }}
559
560 # list open issues
561 curl "http://localhost:10003/api/repos/{{ repo_id }}/issues?state=open"</code></pre>
562 </div>
563
564 <div class="isd-cli-label">update</div>
565 <div class="isd-snippet-wrap">
566 <pre class="isd-cli-snippet"><code class="language-bash">curl -X PATCH \
567 http://localhost:10003/api/repos/{{ repo_id }}/issues/{{ issue.number }} \
568 -H "Content-Type: application/json" \
569 -H "Authorization: MSign handle=\"...\" ts=... sig=\"...\"" \
570 -d '{"title": "...", "body": "..."}'</code></pre>
571 </div>
572
573 <div class="isd-cli-label">comment</div>
574 <div class="isd-snippet-wrap">
575 <pre class="isd-cli-snippet"><code class="language-bash">curl -X POST \
576 http://localhost:10003/api/repos/{{ repo_id }}/issues/{{ issue.number }}/comments \
577 -H "Content-Type: application/json" \
578 -H "Authorization: MSign handle=\"...\" ts=... sig=\"...\"" \
579 -d '{"body": "Fixed in &lt;sha&gt;"}'</code></pre>
580 </div>
581
582 <div class="isd-cli-label">{% if issue.state == 'open' %}close{% else %}reopen{% endif %}</div>
583 <div class="isd-snippet-wrap">
584 <pre class="isd-cli-snippet"><code class="language-bash">curl -X POST \
585 http://localhost:10003/api/repos/{{ repo_id }}/issues/{{ issue.number }}/{% if issue.state == 'open' %}close{% else %}reopen{% endif %} \
586 -H "Authorization: MSign handle=\"...\" ts=... sig=\"...\""</code></pre>
587 </div>
588 </div>
589 </div>
590
591 </aside>
592
593 </div>
594 </div>
595
596 </div>
597 {% endblock %}
File History 2 commits
sha256:f99af7b1a7f36c4d537d1c630d4b71fc39222b1255f82e930929e2fc89015e11 fix: relax browse_repo perf budget to 500ms — 200ms was too… Sonnet 4.6 99 days ago
sha256:763eb2cb8675073b84c19345b27586d2ed939a9aee97c5479b69f502f1a70eff fix(tests): update test suite to match current implementation Sonnet 4.6 patch 121 days ago