Skip to content

fix(console): make the new Console banner responsive and snoozeable - #3193

Merged
HarshMN2345 merged 5 commits into
mainfrom
fix-new-console-banner-tablet
Sep 2, 2026
Merged

fix(console): make the new Console banner responsive and snoozeable#3193
HarshMN2345 merged 5 commits into
mainfrom
fix-new-console-banner-tablet

Conversation

@HarshMN2345

Copy link
Copy Markdown
Member

Three fixes found while testing the merged banner (#3192) on production.

1. Broken layout at tablet widths

The row switched to a column at isSmallViewport (max-width: 767px), but Button's .is-full-width-mobile applies at max-width: 768px. At exactly 768px the layout stayed a horizontal row and the button took the full line, crushing the copy into a ~90px column seven lines tall.

width before after
768px banner height 182px 96px
768px text column 90px 497px
768px wrap 7 lines 1 line

Direction now keys off isTabletViewport, and fullWidthMobile is dropped so the button sizes to its content.

newDevUpgradePro.svelte has the identical pairing (isSmallViewport + fullWidthMobile) and the same 768px break. Not touched here to keep this scoped, but worth a follow-up.

2. Dismissal was permanent

Closing the banner wrote a localStorage key that nothing ever cleared, so a single click on the X removed the message for the whole migration. For a rollout that runs months, one stray click shouldn't be a permanent opt-out.

Now uses the existing hideNotification helper with a one week cool-off and exponential backoff, and shouldShowNotification to gate registration. Someone who keeps dismissing it sees it less and less; someone who mis-clicks gets it back in a week.

(Note: newDevUpgradePro already stores a timestamp on dismiss and never reads it — the intent was there, unfinished.)

3. Resize regression from #3192

AlertStack owns the shell offset for alerts inside it, but only re-measured via ResizeObserver and afterNavigate. GradientBanner used to compensate with its own svelte:window on:resize, which #3192 correctly stopped running when a stack is in charge — removing the safety net.

Measured, resizing 1440 → 375 without reload:

before after
banner height 119px 119px
header.top 55.99px (stale) 118.79px
overlaps navbar yes no

Added the resize listener to AlertStack, which is now the right owner. This also covers HeaderAlert, which has the same gap.

Test plan

  • bun run format / lint / check — clean
  • Verified at 375, 768, 1024, 1440 — no overlap, offset tracks banner height
  • Verified resize in both directions without reload
  • Verified dismissal snoozes rather than permanently hides

Three fixes found while testing on production.

Responsive: the layout switched to a column at isSmallViewport (max-width
767px) but Button's .is-full-width-mobile applies at max-width 768px. At
exactly 768px the row stayed horizontal while the button took the full
line, crushing the copy into a ~90px column seven lines tall. Switched the
direction to isTabletViewport and dropped fullWidthMobile so the button
sizes to its content. 768px goes from 182px tall to 96px.

Dismissal: closing the banner wrote a localStorage key that was never
cleared, so one click removed the message for the entire migration. Now
uses hideNotification with a one week cool-off and exponential backoff, and
shouldShowNotification to gate registration. Repeated dismissals still fade
it out, but a stray click no longer opts the user out permanently.

Resize: AlertStack owns the shell offset for alerts inside it but only
re-measured via ResizeObserver and afterNavigate. GradientBanner used to
compensate with its own window resize handler, which no longer runs when a
stack is in charge, so resizing left the header overlapping the banner.
Added the resize listener to AlertStack, which also covers HeaderAlert.
@appwrite

appwrite Bot commented Sep 2, 2026

Copy link
Copy Markdown

Console (appwrite/console)

Project ID: 688b7bf400350cbd60e9

Sites (1)
Site Status Logs Preview QR
 console-stage
688b7cf6003b1842c9dc
Ready Ready View Logs Preview URL QR Code

Tip

Global CDN and DDoS protection come free with every Sites deployment

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes the new Console banner responsive and snoozeable while moving viewport-resize offset recalculation into the shared alert stack.

  • Aligns the banner layout with the tablet breakpoint and keeps its action content-sized.
  • Replaces permanent local dismissal with the existing account-preference backoff mechanism.
  • Recalculates alert-stack shell offsets when the viewport resizes.
  • Pins Browserslist to a patched release and updates its transitive lockfile entries.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/lib/components/newConsoleBanner.svelte Updates responsive presentation and uses the existing account-preference notification helper for snoozed dismissal.
src/lib/layout/alertStack.svelte Re-measures the shared alert-stack offset whenever viewport resizing can change alert height.
src/routes/(console)/+layout.svelte Registers the banner according to its time-based account preference rather than a permanent localStorage marker.
package.json Adds a Browserslist override requiring the patched release range.
bun.lock Resolves Browserslist and its browser-data dependencies consistently with the manifest override.

Reviews (5): Last reviewed commit: "revert: drop the local snooze fallback a..." | Re-trigger Greptile

Comment thread src/lib/components/newConsoleBanner.svelte Outdated
bun audit --audit-level high started failing on every branch after two
advisories were published against browserslist <=4.28.6, reached through
svelte-preprocess and @sentry/sveltekit. Neither is a direct dependency, so
this pins it in the existing overrides block alongside the other
transitive security pins. Resolves to 4.28.8.

GHSA-c83g-rgw3-j3cx, GHSA-73wf-gq98-2v4g
hideNotification fired updatePrefs without returning or catching it, so a
rejected write was unobservable: the banner hid locally, nothing was
recorded, and it returned on the next load with the backoff count lost.

updateNotificationPrefs and hideNotification now return the promise. This
is backwards compatible, the four existing callers ignore the return value
and keep their fire-and-forget behaviour.

The banner awaits it and, if the write is rejected, falls back to a local
snooze for a week so the dismissal is never silently dropped. Account prefs
stay the source of truth: they carry the backoff count and follow the user
across devices, and the local key is cleared as soon as a write succeeds.
Registration now goes through canShowNewConsoleBanner, which checks both.

Adds notifications.test.ts covering the returned promise, the rejection
path, the recorded expiry, and the backoff counter.
The local fallback recorded a flat one week with no hide count, so a run of
rejected preference writes restarted the sequence every time instead of
continuing it. That contradicted the behaviour described one line above it.

Extracts the fallback into localSnooze.ts, which reproduces the same curve
as notifications.ts: coolOffPeriod * factor ** (hideCount - 1). Repeated
failures now escalate 1w, 2w, 4w, 8w, and a successful write still clears
the local copy so account prefs remain the source of truth.

Reads and writes are guarded, so private mode or a corrupt value fails
towards showing the banner rather than hiding it forever.

Adds localSnooze.test.ts covering the escalation, the formula parity with
account prefs, the expiry boundary, and the corrupt-value case.
@HarshMN2345

HarshMN2345 commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

@greptile ## Fixed — the fallback now escalates on the same curve

Correct catch, and it was a real inconsistency: the comment directly above said "a week at first, doubling each time", while the fallback wrote a flat week with no hide count. A run of rejected writes restarted the sequence every time instead of continuing it.

Extracted into src/lib/helpers/localSnooze.ts, reproducing the formula from notifications.ts exactly:

expiry = now + coolOffPeriod * 3600000 * factor ** (hideCount - 1)

Repeated failures now escalate 1w → 2w → 4w → 8w, and a successful write still clears the local copy, so account prefs remain the source of truth and continue to carry the cross-device count.

Reads and writes are wrapped, so private mode or a corrupt value fails towards showing the banner rather than hiding it forever — the safer direction for a dismissable promo.

Proof

src/lib/helpers/localSnooze.test.ts:

✓ starts at the base cool-off
✓ doubles on each repeat instead of restarting at the base period   → [1, 2, 4, 8] weeks
✓ matches the account-prefs formula, coolOff * factor ** (hideCount - 1)
✓ holds until the deadline, then releases
✓ never hides the notification for a missing or corrupt value

Full CI chain

step result
bun audit --audit-level high exit 0
bun run check 0 errors
bun run lint 0 errors
bun run test:unit 284/284 (275 baseline, +9 new)
bun run build success

Comment on lines +55 to +63
clearLocalSnooze(SNOOZE_FALLBACK_KEY);
} catch {
// Prefs rejected the write, so the snooze would be lost on the next load. Hold it in
// this browser instead, escalating on the same curve so repeated failures continue
// the sequence rather than restarting at a week. Cleared once a write succeeds.
writeLocalSnooze(
SNOOZE_FALLBACK_KEY,
nextLocalSnooze(
readLocalSnooze(SNOOZE_FALLBACK_KEY),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Backoff history resets across stores

When successful and rejected preference writes occur in the same dismissal history, each path derives its count from only one store and clears or ignores the other. This resets part of the exponential-backoff sequence; for example, after three successful dismissals, a rejected fourth dismissal snoozes the banner for one week instead of eight weeks, causing it to reappear seven weeks early.

Knowledge Base Used: Console application shell

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/components/newConsoleBanner.svelte
Line: 55-63

Comment:
**Backoff history resets across stores**

When successful and rejected preference writes occur in the same dismissal history, each path derives its count from only one store and clears or ignores the other. This resets part of the exponential-backoff sequence; for example, after three successful dismissals, a rejected fourth dismissal snoozes the banner for one week instead of eight weeks, causing it to reappear seven weeks early.

**Knowledge Base Used:** [Console application shell](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/console/-/docs/console-application-shell.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

The fallback and its tests were about 210 of the 247 lines in this branch,
all to handle the case where an account prefs write is rejected. For a
dismissable promo the cost of not handling it is that the banner shows
again on the next load, and the user closes it again.

Reverting leaves dismissal identical to the four other hideNotification
call sites: fire and forget, account prefs as the only store. No second
source of truth, so no way for two counters to disagree.

Keeps the three fixes this branch is actually for: the 768px breakpoint
mismatch, the AlertStack resize listener, and the browserslist pin.
@appwrite appwrite deleted a comment from greptile-apps Bot Sep 2, 2026
@HarshMN2345
HarshMN2345 merged commit 46f82c6 into main Sep 2, 2026
4 checks passed
@HarshMN2345
HarshMN2345 deleted the fix-new-console-banner-tablet branch September 2, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants