| … | … | @@ -53,7 +53,22 @@ export function withTheme (html, theme) { |
| 53 | 53 | */ |
| 54 | 54 | export function updateWaiting (u) { |
| 55 | 55 | if (!u || !u.staged) return false |
| 56 | return u.running ? u.staged !== u.running : true | |
| 56 | if (u.running) return u.staged !== u.running | |
| 57 | // ⛔ `running` NULL MEANS WE BOOTED THE APK'S OWN BUNDLE — and that does NOT automatically make a | |
| 58 | // staged bundle an update. `chooseBundle` prefers the ASSET when the app was installed more | |
| 59 | // recently than the staged bundle was built, so a staged-but-older bundle will never be applied | |
| 60 | // and a banner offering to restart for it is a button that does nothing, for ever. | |
| 61 | // | |
| 62 | // ⚠ This is the correction to my own earlier fix. Requiring `running` hid real updates on fresh | |
| 63 | // installs; assuming any staged bundle beats the asset produced a notice that could not be | |
| 64 | // dismissed. The honest rule is the one chooseBundle uses: compare BUILD TIMES, and say nothing | |
| 65 | // when the answer is "the app is already newer". | |
| 66 | const staged = Number(u.stagedBuiltAt) || 0 | |
| 67 | const shipped = Number(u.builtAt) || 0 | |
| 68 | if (staged && shipped) return staged > shipped | |
| 69 | // Neither side can be dated: fall back to announcing it, since a staged bundle with no ordering | |
| 70 | // is what chooseBundle also treats as a candidate. | |
| 71 | return true | |
| 57 | 72 | } |
| 58 | 73 | |
| 59 | 74 | export function withUpdateBanner (html, update) { |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "favela-surface", |
| 3 | "version": "0.0.59", | |
| 3 | "version": "0.0.60", | |
| 4 | 4 | "description": "The favela renderer \u2014 server-side HTML over the daemon's socket. No storage, no keys, no swarm.", |
| 5 | 5 | "type": "module", |
| 6 | 6 | "main": "index.js", |
| … | … | @@ -692,3 +692,18 @@ test('⛔ status reports the daemon\'s own last OTA line, not a verdict about it |
| 692 | 692 | test('an unlinked node says so, because nothing else it does will work', (t) => { |
| 693 | 693 | t.ok(status({ me: { feed: 'a'.repeat(64), linked: false } }).includes('not linked')) |
| 694 | 694 | }) |
| 695 | ||
| 696 | // ⛔ The banner must mirror what `chooseBundle` will ACTUALLY do. Requiring `running` hid real | |
| 697 | // updates on fresh installs; assuming any staged bundle wins produced a notice that could not be | |
| 698 | // dismissed — a restart button for a bundle that will never be chosen. | |
| 699 | test('★ a staged bundle OLDER than the installed app is not an update', (t) => { | |
| 700 | const page = '<html lang="en"><body><h1>x</h1></body></html>' | |
| 701 | // The app was installed after the staged bundle was built — chooseBundle prefers the asset. | |
| 702 | t.absent(withUpdateBanner(page, { running: null, staged: 'bbbb', stagedBuiltAt: 100, builtAt: 200 }).includes('update-bar'), | |
| 703 | 'the app is already newer, so there is nothing to restart for') | |
| 704 | t.ok(withUpdateBanner(page, { running: null, staged: 'bbbb', stagedBuiltAt: 300, builtAt: 200 }).includes('update-bar'), | |
| 705 | 'a staged bundle built AFTER the app still counts') | |
| 706 | // ⚠ No ordering available on either side: announce it, matching chooseBundle's own fallback. | |
| 707 | t.ok(withUpdateBanner(page, { running: null, staged: 'bbbb' }).includes('update-bar'), | |
| 708 | 'undated on both sides stays a candidate') | |
| 709 | }) |