Break long values on the webhook page and event log at phone width (closes #391)
check / check (push) Successful in 3m38s

A name, ID or content type with no place to break ran past its row and
was cut off by the card at 390 pixels, and a long webhook name made the
webhook page scroll sideways. The elements holding such values now
carry wrap-anywhere, so a value breaks only where it must; controls,
statuses and times never do, so a row too wide for a phone still runs
past the edge. The event log's title row wraps like the webhook page's.

The browser test gains a 390-pixel check of both pages: neither scrolls
sideways and nothing shown runs past the page's or its card's edge. Its
seeded webhook name and newer event's content type are long enough to
fail without the change.

Model: opus-5-5
This commit is contained in:
2026-10-03 04:39:37 +00:00
parent 935e18c6f1
commit d5bdef9b56
8 changed files with 96 additions and 20 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
an event's own page. Spans only, since a button may hold no div. -->
<span class="flex flex-1 flex-wrap items-center justify-between gap-3">
<span class="flex flex-wrap items-center gap-3">
<span class="text-sm text-gray-700">{{.Target.DisplayName}}</span>
<span class="text-sm text-gray-700 wrap-anywhere">{{.Target.DisplayName}}</span>
{{if .Replay}}<span class="text-xs text-gray-500">replay</span>{{end}}
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">{{with .Paused}}waiting: target paused after repeated failures, next try no earlier than {{.Until}} ({{.Relative}}){{else}}{{.Status}}{{end}}</span>
</span>
+2 -2
View File
@@ -6,9 +6,9 @@
entrypoint; the request it copies did. -->
<div class="space-y-2 text-xs">
{{if .ResubmittedFrom}}
<p class="text-gray-500">The request it copies arrived at <span class="text-gray-900">{{.Entrypoint}}</span></p>
<p class="text-gray-500">The request it copies arrived at <span class="text-gray-900 wrap-anywhere">{{.Entrypoint}}</span></p>
{{else}}
<p class="text-gray-500">Arrived at <span class="text-gray-900">{{.Entrypoint}}</span></p>
<p class="text-gray-500">Arrived at <span class="text-gray-900 wrap-anywhere">{{.Entrypoint}}</span></p>
{{end}}
{{if .HeadersCut}}
<p class="text-gray-500">The request headers are larger than the event log shows. <a href="{{.Body.EventURL}}" class="btn-small">Show the request headers</a></p>
+8 -5
View File
@@ -6,15 +6,18 @@
<!-- 108rem, half again the 72rem (max-w-6xl) of the webhook list, the
event log, the navbar and the footer, so an entrypoint URL fits on
one line. An inline style, because the committed tailwind.css has
no class this wide. -->
no class this wide. wrap-anywhere goes only on names and
descriptions: a row too wide for a phone must still run past the
edge, where the browser test sees it, rather than break its
controls mid-word. -->
<div class="mx-auto px-6 py-8" style="max-width: 108rem">
<div class="mb-6">
<a href="/hooks" class="btn-small">&larr; Back to webhooks</a>
<div class="flex flex-wrap justify-between items-center gap-2 mt-2">
<div>
<h1 class="text-2xl font-medium text-gray-900">{{.Webhook.Name}}</h1>
<h1 class="text-2xl font-medium text-gray-900 wrap-anywhere">{{.Webhook.Name}}</h1>
{{if .Webhook.Description}}
<p class="text-sm text-gray-500 mt-1">{{.Webhook.Description}}</p>
<p class="text-sm text-gray-500 mt-1 wrap-anywhere">{{.Webhook.Description}}</p>
{{end}}
</div>
<div class="flex gap-2">
@@ -60,7 +63,7 @@
{{range .Entrypoints}}
<div class="p-4" x-data="collapsible">
<div class="flex flex-wrap items-center justify-between gap-2 mb-1">
<span x-show="closed" class="text-sm font-medium text-gray-900">{{if .Description}}{{.Description}}{{else}}Entrypoint{{end}}</span>
<span x-show="closed" class="text-sm font-medium text-gray-900 wrap-anywhere">{{if .Description}}{{.Description}}{{else}}Entrypoint{{end}}</span>
<!-- Edit shows this form in place of the
description and hides until it closes, and
Cancel resets what was typed. With
@@ -249,7 +252,7 @@
{{range .Targets}}
<div class="p-4">
<div class="flex flex-wrap items-center justify-between gap-2 mb-1">
<span class="text-sm font-medium text-gray-900">{{.Name}}</span>
<span class="text-sm font-medium text-gray-900 wrap-anywhere">{{.Name}}</span>
<div class="flex flex-wrap items-center gap-2">
<span class="badge-info">{{if eq .Type "database"}}archive{{else}}{{.Type}}{{end}}</span>
{{if .Active}}
+10 -5
View File
@@ -3,10 +3,15 @@
{{define "title"}}Full Event Log - {{.Webhook.Name}} - Webhooker{{end}}
{{define "content"}}
<!-- wrap-anywhere goes only on names, IDs and content types: a row too
wide for a phone must still run past the edge, where the browser
test sees it, rather than break its statuses, times or controls
mid-word. So a target's name in an event's row, which shares its
element with the delivery's status, goes without. -->
<div class="max-w-6xl mx-auto px-6 py-8">
<div class="mb-6">
<a href="/hook/{{.Webhook.ID}}" class="btn-small">&larr; Back to {{.Webhook.Name}}</a>
<div class="flex justify-between items-center mt-2">
<a href="/hook/{{.Webhook.ID}}" class="btn-small wrap-anywhere">&larr; Back to {{.Webhook.Name}}</a>
<div class="flex flex-wrap justify-between items-center gap-2 mt-2">
<h1 class="text-2xl font-medium text-gray-900">Full Event Log</h1>
<!-- Under a filter, this counts the events the filter lists. -->
<span class="text-sm text-gray-500">{{if gt .TotalEvents (len .Events)}}{{len .Events}} most recent of {{.TotalEvents}} events{{else}}{{.TotalEvents}}{{if not .Show}} total{{end}} event{{if ne .TotalEvents 1}}s{{end}}{{end}}{{if eq .Show "failed"}} with a failed delivery{{else if eq .Show "pending"}} with a delivery pending or retrying{{end}}</span>
@@ -28,8 +33,8 @@
<div role="button" tabindex="0" class="btn-small w-full flex flex-wrap justify-between gap-2" :aria-expanded="open" @mousedown="cancelPendingToggle" @click="toggleUnlessSelecting" @keydown.enter.prevent="toggle" @keydown.space.prevent="toggle">
<span class="flex flex-wrap items-center gap-3">
<span class="badge-info">{{.Method}}</span>
<span class="text-sm font-mono text-gray-700">{{.ID}}</span>
<span class="text-sm text-gray-500">{{.ContentType}}</span>
<span class="text-sm font-mono text-gray-700 wrap-anywhere">{{.ID}}</span>
<span class="text-sm text-gray-500 wrap-anywhere">{{.ContentType}}</span>
{{if .ResubmittedFrom}}
<span class="text-xs text-gray-500" title="This event is a copy of {{.ResubmittedFromID}}">resubmitted copy</span>
{{end}}
@@ -54,7 +59,7 @@
<div x-show="open" x-cloak class="mt-3 p-3 bg-gray-50 rounded-md">
<div class="mb-3 flex flex-wrap items-center justify-between gap-2">
<div class="text-xs text-gray-500">
{{if .ResubmittedFrom}}Resubmitted from event <a href="/hook/{{$.Webhook.ID}}/events/{{.ResubmittedFromID}}" class="btn-small font-mono">{{.ResubmittedFromID}}</a>.{{end}}
{{if .ResubmittedFrom}}Resubmitted from event <a href="/hook/{{$.Webhook.ID}}/events/{{.ResubmittedFromID}}" class="btn-small font-mono wrap-anywhere">{{.ResubmittedFromID}}</a>.{{end}}
{{if .ResubmitCount}}Resubmitted as {{.ResubmitCount}} new event{{if ne .ResubmitCount 1}}s{{end}}.{{end}}
</div>
<form method="POST" action="/hook/{{$.Webhook.ID}}/events/{{.ID}}/resubmit" class="inline">