Use plain Bulma themes when possible

This commit is contained in:
2026-03-03 18:02:04 +00:00
parent 8e0be6ca89
commit 34ee49410d
4 changed files with 136 additions and 55 deletions

View File

@@ -150,9 +150,11 @@
{% if variant.warn_verbatim_plan %} {% if variant.warn_verbatim_plan %}
<tr> <tr>
<td colspan="9"> <td colspan="9">
<article class="command-variant-warning"> <article class="message is-warning is-light">
<div class="message-body is-size-7 has-text-dark">
<strong>Warning:</strong> <strong>{{ variant.variant_label }}</strong> is in <code>verbatim</code> mode with plan fanout enabled. <strong>Warning:</strong> <strong>{{ variant.variant_label }}</strong> is in <code>verbatim</code> mode with plan fanout enabled.
Recipients will get raw transcript-style output. Recipients will get raw transcript-style output.
</div>
</article> </article>
</td> </td>
</tr> </tr>
@@ -188,15 +190,17 @@
<h4 class="title is-7" style="margin-top: 0.95rem;">Effective Destinations</h4> <h4 class="title is-7" style="margin-top: 0.95rem;">Effective Destinations</h4>
{% if profile.enabled_egress_bindings %} {% if profile.enabled_egress_bindings %}
<ul class="command-destination-list is-size-7"> <div class="content is-size-7" style="margin-bottom: 0.4rem;">
{% for row in profile.enabled_egress_bindings %} {% for row in profile.enabled_egress_bindings %}
<li class="command-destination-item"> <p style="margin-bottom: 0.3rem;">
<span class="tag is-link is-light is-rounded is-small">{{ row.service }}</span> <span class="tag is-link is-light is-rounded is-small">{{ row.service }}</span>
<code>{{ row.channel_identifier }}</code> <code>{{ row.channel_identifier }}</code>
</li> </p>
{% endfor %} {% endfor %}
</ul> </div>
<p class="command-destination-summary">{{ profile.enabled_egress_bindings|length }} enabled egress destination{{ profile.enabled_egress_bindings|length|pluralize }}.</p> <article class="notification is-info is-light is-size-7" style="padding: 0.5rem 0.7rem;">
{{ profile.enabled_egress_bindings|length }} enabled egress destination{{ profile.enabled_egress_bindings|length|pluralize }}.
</article>
{% else %} {% else %}
<article class="notification is-warning is-light is-size-7">No enabled egress destinations. Plan fanout will show sent:0.</article> <article class="notification is-warning is-light is-size-7">No enabled egress destinations. Plan fanout will show sent:0.</article>
{% endif %} {% endif %}
@@ -441,51 +445,5 @@
.command-order-capsule-form + .command-order-capsule-form .command-order-btn { .command-order-capsule-form + .command-order-capsule-form .command-order-btn {
border-top: 1px solid #dbdbdb; border-top: 1px solid #dbdbdb;
} }
.command-variant-warning {
border: 1px solid rgba(171, 109, 17, 0.45);
background: linear-gradient(180deg, rgba(255, 246, 226, 0.98), rgba(255, 238, 204, 0.95));
color: #6e450e;
border-radius: 8px;
padding: 0.48rem 0.62rem;
font-size: 0.78rem;
line-height: 1.35;
}
.command-variant-warning strong {
color: #3f2a09;
}
.command-variant-warning code {
color: #5b3a0c;
background: rgba(255, 255, 255, 0.55);
}
.command-destination-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.28rem;
}
.command-destination-item {
display: inline-flex;
align-items: center;
gap: 0.42rem;
background: rgba(244, 248, 255, 0.85);
border: 1px solid rgba(58, 103, 165, 0.2);
border-radius: 7px;
padding: 0.26rem 0.38rem;
width: fit-content;
max-width: 100%;
}
.command-destination-summary {
margin-top: 0.44rem;
display: inline-flex;
align-items: center;
border-radius: 999px;
background: rgba(239, 247, 255, 0.95);
border: 1px solid rgba(58, 103, 165, 0.25);
padding: 0.16rem 0.52rem;
font-size: 0.73rem;
color: #284d7c;
}
</style> </style>
{% endblock %} {% endblock %}

View File

@@ -25,7 +25,19 @@
<div class="has-text-grey"><code>{{ row.actor_identifier }}</code></div> <div class="has-text-grey"><code>{{ row.actor_identifier }}</code></div>
{% endif %} {% endif %}
</td> </td>
<td><code>{{ row.payload }}</code></td> <td>
{% if row.payload_view.summary_items %}
<div class="tags" style="margin-bottom: 0.35rem;">
{% for item in row.payload_view.summary_items %}
<span class="tag task-ui-badge"><strong>{{ item.0 }}</strong>: {{ item.1 }}</span>
{% endfor %}
</div>
{% endif %}
<details>
<summary class="is-size-7 has-text-link" style="cursor:pointer;">View payload JSON</summary>
<pre class="task-event-payload">{{ row.payload_view.pretty_text }}</pre>
</details>
</td>
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="4">No events.</td></tr> <tr><td colspan="4">No events.</td></tr>
@@ -47,4 +59,23 @@
</table> </table>
</article> </article>
</div></section> </div></section>
<style>
.task-event-payload {
margin-top: 0.35rem;
padding: 0.6rem;
border-radius: 8px;
border: 1px solid rgba(127, 127, 127, 0.25);
background: rgba(245, 245, 245, 0.75);
color: #1f1f1f;
max-width: 72ch;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
[data-theme="dark"] .task-event-payload {
background: rgba(35, 35, 35, 0.75);
color: #f5f5f5;
border-color: rgba(200, 200, 200, 0.35);
}
</style>
{% endblock %} {% endblock %}

View File

@@ -7,6 +7,7 @@ from core.models import (
ChatSession, ChatSession,
ChatTaskSource, ChatTaskSource,
DerivedTask, DerivedTask,
DerivedTaskEvent,
Message, Message,
Person, Person,
PersonIdentifier, PersonIdentifier,
@@ -154,3 +155,38 @@ class TasksPagesManagementTests(TestCase):
response = self.client.get(reverse("tasks_hub")) response = self.client.get(reverse("tasks_hub"))
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.assertContains(response, "Scope Person") self.assertContains(response, "Scope Person")
def test_task_detail_renders_payload_summary_and_json(self):
project = TaskProject.objects.create(user=self.user, name="Payload Test")
session = ChatSession.objects.create(user=self.user, identifier=self.pid_signal)
origin = Message.objects.create(
user=self.user,
session=session,
ts=1_700_000_000_000,
text="origin",
sender_uuid="+15551230000",
custom_author="OTHER",
source_service="signal",
source_chat_id="+15551230000",
)
task = DerivedTask.objects.create(
user=self.user,
project=project,
title="Payload detail",
source_service="signal",
source_channel="+15551230000",
origin_message=origin,
reference_code="42",
status_snapshot="open",
)
DerivedTaskEvent.objects.create(
task=task,
event_type="reaction_captured",
payload={"source": "signal", "emoji": "❤️", "reason": "heart_reaction"},
)
response = self.client.get(reverse("tasks_task", kwargs={"task_id": str(task.id)}))
self.assertEqual(200, response.status_code)
self.assertContains(response, "View payload JSON")
self.assertContains(response, "<strong>source</strong>: signal", html=True)
self.assertContains(response, "&quot;emoji&quot;: &quot;❤️&quot;")

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
from urllib.parse import urlencode from urllib.parse import urlencode
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
@@ -174,6 +175,60 @@ def _service_label(service: str) -> str:
return labels.get(key, key.title() if key else "Unknown") return labels.get(key, key.title() if key else "Unknown")
def _format_task_event_payload(raw_payload):
payload = raw_payload
if payload is None:
payload = {}
if isinstance(payload, str):
text = payload.strip()
if not text:
return {
"summary_items": [],
"pretty_text": "{}",
"is_mapping": False,
}
try:
parsed = json.loads(text)
payload = parsed
except Exception:
return {
"summary_items": [("text", text[:140])],
"pretty_text": text,
"is_mapping": False,
}
if isinstance(payload, dict):
summary = []
preferred = ("source", "reason", "reaction", "emoji", "presence", "last_seen_ts")
for key in preferred:
if key in payload:
summary.append((key, str(payload.get(key))))
if not summary:
for key in list(payload.keys())[:4]:
summary.append((str(key), str(payload.get(key))))
pretty = json.dumps(payload, indent=2, ensure_ascii=False, sort_keys=True)
return {
"summary_items": summary,
"pretty_text": pretty,
"is_mapping": True,
}
if isinstance(payload, (list, tuple)):
pretty = json.dumps(list(payload), indent=2, ensure_ascii=False)
return {
"summary_items": [("items", str(len(payload)))],
"pretty_text": pretty,
"is_mapping": False,
}
text = str(payload)
return {
"summary_items": [("value", text[:140])],
"pretty_text": text,
"is_mapping": False,
}
def _creator_label_for_message(user, service: str, message) -> str: def _creator_label_for_message(user, service: str, message) -> str:
msg = message msg = message
if msg is None: if msg is None:
@@ -800,6 +855,7 @@ class TaskDetail(LoginRequiredMixin, View):
raw_actor = str(getattr(row, "actor_identifier", "") or "").strip() raw_actor = str(getattr(row, "actor_identifier", "") or "").strip()
if raw_actor: if raw_actor:
row.actor_display = raw_actor row.actor_display = raw_actor
row.payload_view = _format_task_event_payload(getattr(row, "payload", None))
task.creator_label = _creator_label_for_message( task.creator_label = _creator_label_for_message(
request.user, request.user,
str(getattr(task, "source_service", "") or "").strip().lower(), str(getattr(task, "source_service", "") or "").strip().lower(),