intent/templates/issues/detail.html
Alec Perkins 5811e72a0c Add issues and comment threads (Phase 3)
Issues: per-project numbers, statuses (open, accepted, rejected,
withdrawn, duplicate), feature links, edits, and a timeline of comments
and events. From an issue, collaborators create or update a feature
with the revision tied to the issue and an option to accept it.

Comments: general and line-anchored threads on features, one
discussion thread per issue, edit history, tombstone deletes, resolve,
and promoting a feature thread to an issue. Line threads attach to
top-level Markdown blocks and go outdated only when the text changes.

HTMX swaps threads in place with focus management; every action also
works as a plain form post. Markdown headings are re-levelled so user
text never skips heading levels on the page.

The example flow from concept.md (steps 1-7) is covered end to end in
tests/test_example_flow.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:18:45 -04:00

114 lines
5.2 KiB
HTML

{% extends "projects/base.html" %}
{% block page_title %}{{ issue.key }} {{ issue.title }}{% endblock %}
{% block project_content %}
<h1><span class="muted">{{ issue.key }}</span> {{ issue.title }}</h1>
<p>
<strong>{{ issue.get_status_display }}</strong>{% if issue.duplicate_of %} duplicate of <a href="{% url 'issues:detail' org_slug=project.org.slug project_slug=project.slug number=issue.duplicate_of.number %}">{{ issue.duplicate_of.key }} {{ issue.duplicate_of.title }}</a>{% endif %}.
Opened by {{ issue.author.username|default:"deleted user" }}
on <time datetime="{{ issue.created_at|date:'c' }}">{{ issue.created_at|date:"j M Y" }}</time>.
{% if promoted_url %}Started as <a href="{{ promoted_url }}">a comment thread on {{ promoted_from.feature.key }}</a>.{% endif %}
</p>
{% if can_edit_issue %}
<p><a href="{% url 'issues:edit' org_slug=project.org.slug project_slug=project.slug number=issue.number %}">Edit issue</a></p>
{% endif %}
<section aria-labelledby="body-heading">
<h2 id="body-heading" class="visually-hidden">Description</h2>
<div class="prose">
{% if issue.body %}{{ body }}{% else %}<p class="muted">No description.</p>{% endif %}
</div>
</section>
<section id="features" aria-labelledby="features-heading">
<h2 id="features-heading">Features</h2>
{% if features %}
<ul>
{% for feature in features %}
<li>
<a href="{% url 'features:detail' org_slug=project.org.slug project_slug=project.slug feature_key=feature.key %}">{{ feature.key }} {{ feature.title }}</a>
{% if access.can_edit %}
<form method="post" action="{% url 'issues:features' org_slug=project.org.slug project_slug=project.slug number=issue.number %}" class="inline">
{% csrf_token %}
<input type="hidden" name="action" value="unlink">
<input type="hidden" name="feature" value="{{ feature.pk }}">
<button type="submit">Unlink <span class="visually-hidden">{{ feature.key }}</span></button>
</form>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Not linked to any feature.</p>
{% endif %}
{% if access.can_edit %}
{% if link_form.fields.feature.queryset.exists %}
<form method="post" action="{% url 'issues:features' org_slug=project.org.slug project_slug=project.slug number=issue.number %}">
{% csrf_token %}
<input type="hidden" name="action" value="link">
{{ link_form }}
<button type="submit">Link feature</button>
</form>
{% endif %}
<p>Change features for this issue. The revision is tied to {{ issue.key }}{% if issue.is_open %}, and you can accept the issue as you save{% endif %}.</p>
<ul>
<li><a href="{% url 'features:create' org_slug=project.org.slug project_slug=project.slug %}?issue={{ issue.number }}">Create a feature from {{ issue.key }}</a></li>
{% for feature in features %}
<li><a href="{% url 'features:edit' org_slug=project.org.slug project_slug=project.slug feature_key=feature.key %}?issue={{ issue.number }}">Update {{ feature.key }} for {{ issue.key }}</a></li>
{% endfor %}
</ul>
{% endif %}
</section>
{% if duplicates %}
<section aria-labelledby="duplicates-heading">
<h2 id="duplicates-heading">Duplicates</h2>
<ul>
{% for dup in duplicates %}
<li><a href="{% url 'issues:detail' org_slug=project.org.slug project_slug=project.slug number=dup.number %}">{{ dup.key }} {{ dup.title }}</a></li>
{% endfor %}
</ul>
</section>
{% endif %}
{% if access.can_edit or can_withdraw %}
{% url 'issues:status' org_slug=project.org.slug project_slug=project.slug number=issue.number as status_url %}
<section id="decide" aria-labelledby="decide-heading">
<h2 id="decide-heading">Decision</h2>
{% if issue.is_open %}
<form method="post" action="{{ status_url }}">
{% csrf_token %}
{% if access.can_edit %}
<button type="submit" name="status" value="accepted">Accept</button>
<button type="submit" name="status" value="rejected">Reject</button>
{% endif %}
{% if can_withdraw %}
<button type="submit" name="status" value="withdrawn">Withdraw</button>
{% endif %}
</form>
{% if access.can_edit %}
<form method="post" action="{{ status_url }}">
{% csrf_token %}
<input type="hidden" name="status" value="duplicate">
{{ duplicate_form }}
<button type="submit">Mark as duplicate</button>
</form>
{% endif %}
{% elif access.can_edit or issue.status == "withdrawn" %}
<form method="post" action="{{ status_url }}">
{% csrf_token %}
<button type="submit" name="status" value="open">Reopen</button>
</form>
{% else %}
<p class="muted">This issue is closed.</p>
{% endif %}
</section>
{% endif %}
<section aria-labelledby="discussion-heading">
<h2 id="discussion-heading">Discussion</h2>
{% include "issues/_timeline.html" %}
</section>
{% endblock %}