OverviewFilesCommitsIssuesDocsReleasesPeers

favela-surface commits / ad48d73

0.0.60: the banner mirrors chooseBundle, instead of guessing at it
My earlier fix overshot. Requiring `running` hid real updates on fresh installs; assuming ANY staged bundle beats the asset produced a notice that could not be dismissed — a restart button for a bundle chooseBundle will never pick. chooseBundle prefers the ASSET when the app was installed more recently than the staged bundle was built. The banner now applies the same comparison, so it says nothing when the answer is 'the app is already newer'. Pinned both directions, plus the undated fallback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ad48d73d38a4f8a1124a3ace39b8345019ddefb2
by plankatron <fleeky@gmail.com> · 2026-09-10T18:54:29.000Z

Changed files (3) +32 -2

lib/pages.js +16 -1
……@@ -53,7 +53,22 @@ export function withTheme (html, theme) {
5353 */
5454export function updateWaiting (u) {
5555 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
5772}
5873
5974export function withUpdateBanner (html, update) {
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-surface",
3 "version": "0.0.59",
3 "version": "0.0.60",
44 "description": "The favela renderer \u2014 server-side HTML over the daemon's socket. No storage, no keys, no swarm.",
55 "type": "module",
66 "main": "index.js",
test/pages.test.js +15 -0
……@@ -692,3 +692,18 @@ test('⛔ status reports the daemon\'s own last OTA line, not a verdict about it
692692test('an unlinked node says so, because nothing else it does will work', (t) => {
693693 t.ok(status({ me: { feed: 'a'.repeat(64), linked: false } }).includes('not linked'))
694694})
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.
699test('★ 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})