adventure: let a player back out from the web, not only in Matrix
Three verbs to match gogobee's: call off an expedition, turn back out of somebody else's party, send the pet sitter home. Which one this page offers is derived here rather than pushed — leadership is already legible in the party seats and the sitter's standing is already in the babysit offer, so nothing new crosses the wire. Two things running it turned up that no test would have. An applied abandon left "Pull out of the run" sitting under a verdict saying the expedition was over, so an applied verb now also hides the other verbs it just made untrue. And a party member was being offered that same button in the first place, beside the one that actually works — Pete knows from the seat it just read that gogobee would refuse it, so it is withheld. Also: heal the Matrix handle onto push rows stored before the column existed, on its own endpoint rather than through the subscribe upsert, which resets both watermarks and would have silenced the digest for anybody who reads the site regularly. And stack the board row below sm — four flex columns that wrapped to six lines on a phone, pre-existing.
This commit is contained in:
@@ -33,7 +33,9 @@
|
||||
rejected_busy: "couldn't, you're already out there",
|
||||
rejected_insufficient_funds: "couldn't cover it",
|
||||
rejected_zone_locked: "couldn't, that zone isn't open to you",
|
||||
rejected_nothing_to_resume: "couldn't, there's nothing waiting for you"
|
||||
rejected_nothing_to_resume: "couldn't, there's nothing waiting for you",
|
||||
rejected_is_leader: "couldn't, you're the one leading it",
|
||||
rejected_nothing_to_cancel: "couldn't, no sitter is engaged"
|
||||
};
|
||||
|
||||
// syncOffers keeps the panel's own copy from outliving the truth. Watching it
|
||||
@@ -45,6 +47,29 @@
|
||||
// puts the button back, and that asymmetry is the point: a refusal is often
|
||||
// about a stale page, and taking away the retry would leave them nothing to do
|
||||
// about it.
|
||||
// INVALIDATES is what an applied verb makes untrue about the OTHER verbs on
|
||||
// the page. Hiding only the offer that was taken is not enough, and watching it
|
||||
// run is what showed why: after "Call the whole thing off" landed, the panel
|
||||
// went on offering "Pull out of the run" — directly under a verdict saying the
|
||||
// expedition had been abandoned. That is the same lie W5a fixed for the bout,
|
||||
// in a new place.
|
||||
//
|
||||
// The one asymmetry worth keeping: an applied EXTRACT does not hide the
|
||||
// abandon. An extracted run is still the owner's to close — that is exactly
|
||||
// what the abandon verb is for from town — so taking the button away there
|
||||
// would remove the next thing they might legitimately want.
|
||||
var INVALIDATES = {
|
||||
expedition_abandon: ['extract', 'expedition_leave'],
|
||||
expedition_leave: ['extract', 'expedition_abandon'],
|
||||
extract: ['expedition_leave']
|
||||
};
|
||||
|
||||
function hideOffer(action) {
|
||||
var btn = document.querySelector('.adv-action-btn[data-action="' + action + '"]');
|
||||
if (!btn) return;
|
||||
(btn.closest('[data-offer]') || btn).classList.add('hidden');
|
||||
}
|
||||
|
||||
function syncOffers(orders) {
|
||||
var newest = {};
|
||||
orders.forEach(function (o) { if (!(o.action in newest)) newest[o.action] = o; });
|
||||
@@ -56,6 +81,7 @@
|
||||
var offer = btn.closest('[data-offer]') || btn;
|
||||
if (o.status === 'applied') {
|
||||
offer.classList.add('hidden');
|
||||
(INVALIDATES[action] || []).forEach(hideOffer);
|
||||
return;
|
||||
}
|
||||
// All three halves of the restore matter, and each is easy to forget.
|
||||
@@ -80,7 +106,10 @@
|
||||
siege_join: 'Join the defence',
|
||||
expedition_start: 'Set out',
|
||||
expedition_resume: 'Go back in',
|
||||
babysit: 'Hire the sitter'
|
||||
babysit: 'Hire the sitter',
|
||||
expedition_abandon: 'Call it off',
|
||||
expedition_leave: 'Turn back',
|
||||
babysit_cancel: 'Send the sitter home'
|
||||
};
|
||||
|
||||
// The owner's euro balance as of the render, for the money confirms. Absent on
|
||||
@@ -246,7 +275,19 @@
|
||||
row.className = 'mt-2 flex gap-1.5';
|
||||
var yes = document.createElement('button');
|
||||
yes.type = 'button';
|
||||
yes.className = 'rounded-full bg-theme-adventure text-white px-3 py-1 font-semibold';
|
||||
// A destructive verb gets the red the mischief storefront already uses for
|
||||
// "this is the one that does something to somebody". "Call the whole thing
|
||||
// off" throws away a whole party's day and was raising a confirm identical to
|
||||
// the one for hiring a pet sitter — the two most different decisions on the
|
||||
// page, in the same purple.
|
||||
//
|
||||
// Red rather than the button's own --warn, and that is not a style
|
||||
// preference: --warn is a dark amber in every light theme and a LIGHT amber
|
||||
// in the dark one (it is the only dark card), so white on it is unreadable in
|
||||
// exactly the theme this was first tried in. Seen, not reasoned about.
|
||||
yes.className = btn.getAttribute('data-confirm-tone') === 'warn'
|
||||
? 'rounded-full bg-red-500 text-white px-3 py-1 font-semibold'
|
||||
: 'rounded-full bg-theme-adventure text-white px-3 py-1 font-semibold';
|
||||
yes.textContent = (btn.getAttribute('data-confirm-label') || 'Yes, do it') +
|
||||
(cost > 0 ? ' · €' + euroFmt(cost) : '');
|
||||
yes.addEventListener('click', function () { boxEl.remove(); placeOrder(btn); });
|
||||
|
||||
@@ -50,6 +50,33 @@
|
||||
});
|
||||
}
|
||||
|
||||
// A subscription stored before the server learned to record the Matrix handle
|
||||
// can never match an owner-scoped adventure alert, and nothing re-subscribes on
|
||||
// its own — subscribe() only runs on a click. So an existing subscription gets
|
||||
// its handle topped up once, silently, from the page it is already on.
|
||||
//
|
||||
// Once per endpoint, not once per load: the marker is the endpoint itself, so a
|
||||
// rotated subscription heals again and a browser that has already done it never
|
||||
// asks twice. The server's update is a no-op on an already-healed row, so a lost
|
||||
// marker costs one wasted request and nothing else.
|
||||
var HEAL_KEY = "pete.pushHeal.v1";
|
||||
|
||||
function healLocalpart(sub) {
|
||||
if (!sub || !sub.endpoint) return;
|
||||
try {
|
||||
if (localStorage.getItem(HEAL_KEY) === sub.endpoint) return;
|
||||
} catch (e) { /* private mode: heal every load rather than never */ }
|
||||
fetch("/api/push/heal", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ endpoint: sub.endpoint }),
|
||||
credentials: "same-origin",
|
||||
}).then(function (res) {
|
||||
if (!res.ok) return;
|
||||
try { localStorage.setItem(HEAL_KEY, sub.endpoint); } catch (e) {}
|
||||
}).catch(function () { /* transient — the next load will do */ });
|
||||
}
|
||||
|
||||
function unsubscribe() {
|
||||
return currentSub().then(function (sub) {
|
||||
if (!sub) return;
|
||||
@@ -153,6 +180,7 @@
|
||||
}
|
||||
currentSub().then(function (sub) {
|
||||
paint(!!sub, sub ? "You'll get a nudge when new stories land." : "Get a nudge when new stories land.");
|
||||
healLocalpart(sub);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user