fix(trips): remaining publish-toggle review findings

- resolveTripChild now asserts the resolved page uses the trip template, so a
  non-trip direct child of /trips could never be toggled through this endpoint
  (P3 adversarial).
- apiSend gains an optional timeoutMs (AbortController); trip-publish passes 10s
  so a hung toggle can't leave the switch stuck aria-busy. post-form omits it,
  keeping media uploads unbounded (P2 reliability).
- trips.html.twig reuses trip.html.twig's one-line active-trip slug match
  instead of a bespoke 3-branch OR (P2 maintainability).
- Draft-badge amber is now a --color-draft-accent token shared by the trip and
  journal badges instead of a twice-hardcoded #E0A458 literal (P3).

Rebuilt js/trip-publish.js and js/post/post-form.js (shared api-utils change).
PHP lint clean; trip-publish suite 10/10; post suite unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
This commit is contained in:
2026-07-08 17:31:40 +02:00
co-authored by Claude Opus 4.8
parent 25cee53718
commit 37b669424b
8 changed files with 85 additions and 65 deletions
+15 -2
View File
@@ -12,12 +12,25 @@
// generic failure. `okStatuses` lists extra codes to accept as success (e.g.
// 204 no-content, or 404 already-gone for an idempotent DELETE). Cookies
// auto-included.
export function apiSend(url, opts, okStatuses) {
return fetch(url, Object.assign({ credentials: 'include' }, opts)).then(function (r) {
//
// `timeoutMs` is OPTIONAL: when set, a hung request is aborted after that many
// ms so the caller's pending state can't stick forever (the abort rejects like
// any network error → generic error copy). Omit it (post-form's media uploads)
// to keep the old unbounded behaviour — a large upload must not be timed out.
export function apiSend(url, opts, okStatuses, timeoutMs) {
var controller = timeoutMs ? new AbortController() : null;
var timer = controller ? setTimeout(function () { controller.abort(); }, timeoutMs) : null;
var fetchOpts = Object.assign({ credentials: 'include' }, opts);
if (controller) fetchOpts.signal = controller.signal;
return fetch(url, fetchOpts).then(function (r) {
if (timer) clearTimeout(timer);
if (r.ok || (okStatuses && okStatuses.indexOf(r.status) !== -1)) return r;
var e = new Error('HTTP ' + r.status);
e.status = r.status;
throw e;
}, function (err) {
if (timer) clearTimeout(timer);
throw err; // abort (no .status) or network error → generic fallback copy
});
}
+2 -1
View File
@@ -19,6 +19,7 @@
import { apiSend, apiErrorMsg } from './api-utils.js';
var TOAST_TIMEOUT_MS = 5000;
var PUBLISH_TIMEOUT_MS = 10000; // abort a hung toggle so the switch never sticks (R13)
var toastTimer = null;
// One visible, page-level polite toast (R15). Modelled on the feed-actions.js
@@ -103,7 +104,7 @@ function onToggle(btn) {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ published: next })
}).then(function () {
}, null, PUBLISH_TIMEOUT_MS).then(function () {
// Success: flip the switch + Draft badge in place, no reload (R14).
setPublishedUI(btn, next);
setPending(btn, false);