mirror of
https://github.com/chmouel/liseur-sync.git
synced 2026-10-19 11:23:25 +00:00
fix: take down a catch-up question the reader has walked into
A page turn always closes the catch-up panel, but a resize, a font change or a reflow moves the reader without one. A question already on screen could therefore end up pointing at the page underneath it, with its buttons still live, after the reader had been carried onto that page. The panel is now closed as soon as the answer is recorded, and left up when the answer could not be recorded, so a question the reader can still answer themselves is never taken away from them.
This commit is contained in:
parent
371848bc2d
commit
e4c0b38c10
3 changed files with 123 additions and 4 deletions
|
|
@ -105,6 +105,16 @@ the way to that spot is still one button away.
|
|||
- Because the answer settles the remote position as the baseline, a
|
||||
later position from that device is still offered normally; only the
|
||||
one already on screen is spent.
|
||||
- A question can come to be about the page in the reader's hand after it
|
||||
has been drawn. Every page turn takes the panel down with it, but a
|
||||
resize, a font change or a reflow moves the reader without one, so an
|
||||
offer can end up pointing at the page underneath it with its buttons
|
||||
still live. The panel is taken down when the silent answer is
|
||||
recorded, and only then: taken down first, it would have to be put
|
||||
back whenever the page would not go, and putting it back runs the same
|
||||
check again, which flickers the question for as long as the network is
|
||||
down. An unanswerable question left standing is one the reader can
|
||||
answer themselves.
|
||||
|
||||
## Implementation and acceptance
|
||||
|
||||
|
|
@ -118,6 +128,8 @@ the way to that spot is still one button away.
|
|||
- [x] Browser coverage: a same-page `pull` is silent, durable and sends
|
||||
nothing new; a same-page `conflict` is answered on a page that is
|
||||
queued but undeliverable, and that page is still owed afterwards
|
||||
and still becomes the agreed one when it lands; a different page
|
||||
and a *near* page of the same number are both still asked; and a
|
||||
book opened cold at the other device's position asks nothing.
|
||||
and still becomes the agreed one when it lands; a question already
|
||||
on screen comes down when a relocate nobody asked for carries the
|
||||
reader onto the page it names; a different page and a *near* page
|
||||
of the same number are both still asked; and a book opened cold at
|
||||
the other device's position asks nothing.
|
||||
|
|
|
|||
|
|
@ -791,11 +791,26 @@ async function agreeSamePage(offer) {
|
|||
// silent answer this exists to prevent. A flush that settled its own
|
||||
// acknowledged page on the way is right and stands; only the other
|
||||
// device's position is left unanswered, for a later quiet moment.
|
||||
await keepHere(offer.op, () => {
|
||||
const answered = await keepHere(offer.op, () => {
|
||||
const seen = placeView();
|
||||
return catchup.shown() === offer && current(stamp) &&
|
||||
samePage(placeHere(here, seen), placeOf(offer.op, seen));
|
||||
});
|
||||
// Usually there is no panel to take down: the page is checked
|
||||
// before the question is drawn. But an offer already on screen can
|
||||
// come to match, when a resize or a reflow moves the reader onto
|
||||
// its page without a page turn, and a live refresh then re-enters
|
||||
// here. An answered question must not be left standing with its
|
||||
// buttons still live.
|
||||
//
|
||||
// It is taken down afterwards rather than first, and only when the
|
||||
// answer was really recorded. Hiding first would have to put the
|
||||
// panel back when the page would not go, and putting it back runs
|
||||
// this gate again, which would fail the same way: a question that
|
||||
// flickers for as long as the network is down. Left up, it is a
|
||||
// question the reader can answer themselves, which is the honest
|
||||
// state of it.
|
||||
if (answered === "kept") hideCatchup();
|
||||
} finally {
|
||||
if (agreeingSamePage === key) agreeingSamePage = null;
|
||||
}
|
||||
|
|
|
|||
92
internal/webui/testdata/readerbrowser.mjs
vendored
92
internal/webui/testdata/readerbrowser.mjs
vendored
|
|
@ -2119,6 +2119,98 @@ async function samePageGuard(evalIn, check, { pause, wait, visibility, position,
|
|||
check('and it can still be answered', await wait(
|
||||
"document.getElementById('reader-catchup').hidden"));
|
||||
|
||||
// ----------------------------------- walking onto the asked-about page
|
||||
//
|
||||
// A question already on screen when its reason goes away. Every page
|
||||
// turn takes the panel down with it, so the only way to arrive on the
|
||||
// asked-about page with the question still up is a relocate nobody
|
||||
// asked for: a resize, a font change, a reflow. The reader ends up
|
||||
// looking at the page a panel above them is offering to take them to,
|
||||
// with live buttons on it.
|
||||
//
|
||||
// `goTo` is that relocate: it moves the book without the interaction
|
||||
// a page turn carries, which is what a reflow amounts to. What brings
|
||||
// the reader back round to the question afterwards is the other
|
||||
// device speaking again, because coming back to the tab would take
|
||||
// the panel down by itself and prove nothing.
|
||||
const onOther = await settledPage();
|
||||
await evalIn("document.getElementById('reader-prev').click()");
|
||||
const behind = await settledPage(onOther);
|
||||
// The other device's page is visited, photographed and left: an echo
|
||||
// has to be built from a real locator to name an exact page, and it
|
||||
// has to be the newest position there is to be a question at all. So
|
||||
// the reader goes back to their own page and lets it land first, and
|
||||
// only then does the other device speak.
|
||||
await evalIn(`(() => {
|
||||
const loc = document.querySelector('readium-view').lastLocation;
|
||||
window.__liveSaved = {
|
||||
href: loc.locator.href, section: loc.sectionFraction, fraction: loc.fraction,
|
||||
};
|
||||
return true;
|
||||
})()`);
|
||||
await evalIn("document.getElementById('reader-next').click()");
|
||||
check('the reader returns to their own page',
|
||||
await settledPage(behind) === onOther &&
|
||||
await wait(`(${drained}).then(n => n === 0)`), await pageNow());
|
||||
// The same photographed page, said again under a new name and a new
|
||||
// anchor. Once to raise the question, once more to bring a refresh
|
||||
// round while it is still up.
|
||||
const echoSaved = (id) => evalIn(`(async () => {
|
||||
const saved = window.__liveSaved;
|
||||
const drift = Math.min(saved.fraction + 0.012, 1);
|
||||
window.__liveEchoes = (window.__liveEchoes || 0) + 1;
|
||||
const out = await window.__liveCall('v1/ops', 'POST', { ops: [{
|
||||
op_id: ${JSON.stringify('live-test-' + id)}, work_id: window.__liveWork,
|
||||
client_ts: new Date().toISOString(), progression: drift,
|
||||
locator: {
|
||||
href: saved.href,
|
||||
locations: {
|
||||
progression: saved.section, totalProgression: drift,
|
||||
fragments: ['epubcfi(/6/9997!/4/2/16/1:' + window.__liveEchoes + ')'],
|
||||
},
|
||||
text: {
|
||||
before: 'still a page behind, in ', highlight: 'another',
|
||||
after: ' room on another device',
|
||||
},
|
||||
},
|
||||
}] });
|
||||
return out.results[0].status;
|
||||
})()`);
|
||||
const said = await echoSaved('echo-walk');
|
||||
check('the other device is a page back', said === 'applied' &&
|
||||
await pageNow() === onOther, said + ' ' + behind + ' vs ' + await pageNow());
|
||||
await resume();
|
||||
check('a page the reader is not on is a question',
|
||||
await wait("!document.getElementById('reader-catchup').hidden"), await panelText());
|
||||
check('and it is named exactly, not guessed at',
|
||||
(await panelText()).includes(`Page ${behind}`) &&
|
||||
!/near page/i.test(await panelText()), await panelText());
|
||||
// The relocate nobody asked for. It is given the same locator the
|
||||
// echo carries, not a fraction: a fraction is the number the page
|
||||
// table and the reader disagree about, which is the whole reason an
|
||||
// interpolated page is only ever *near*.
|
||||
await evalIn(`(() => {
|
||||
const saved = window.__liveSaved;
|
||||
document.querySelector('readium-view').goTo({
|
||||
href: saved.href, type: 'application/xhtml+xml',
|
||||
locations: { progression: saved.section },
|
||||
}).catch(() => {});
|
||||
return true;
|
||||
})()`);
|
||||
check('the reader arrives on it without turning a page',
|
||||
await settledPage(onOther) === behind &&
|
||||
!await panelHidden(), await pageNow() + ' ' + await panelHidden());
|
||||
const reads = await evalIn('window.__liveReads');
|
||||
check('the other device says the same page again',
|
||||
await echoSaved('echo-walk-again') === 'applied' &&
|
||||
await wait(`window.__liveReads > ${reads}`));
|
||||
check('the question does not survive being walked into', await wait(
|
||||
"document.getElementById('reader-catchup').hidden"), await panelText());
|
||||
check('it went because it was answered, not merely taken down',
|
||||
await settlesTo('live-test-echo-walk'), await baseline());
|
||||
await resume();
|
||||
check('and it is not asked again', await panelHidden(), await panelText());
|
||||
|
||||
// ------------------------------------------- a page that is a guess
|
||||
//
|
||||
// The same page *number*, arrived at by arithmetic across a fraction
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue