Skip to content

Issue 78 Notification Bell Empty

Ed Mozley edited this page Aug 17, 2026 · 1 revision

The notification bell opened to nothing (issue #78)

The bell sat in the header showing a red 3. Clicking it gave you an empty panel. On any other page the same bell listed the same three notifications correctly.

Reported in issue #78 by tjedelhauser.

Fixed in 46f96b53, released as update #1106.

The bell itself is described in Notifications.


1. What you saw

Where Badge Panel
Home page 3 βœ… empty ❌
Tasks, Tickets, Assets, everywhere else 3 βœ… three notifications βœ…

The reporter's description was exactly right: "when i open freeitsm on the Mainpage, i see the Notification Bell, but they didn't show me entries… when i go to another menu, for example Tasks, then i see the entries."

Two details make this harder to read than it looks:

  • The badge was correct. It said 3, and there really were 3. So the bell was clearly getting something from the server.
  • Nothing was logged, and nothing looked broken. No error banner, no failed request in the network tab β€” because, as it turns out, there was no request at all.

The combination reads as "you have no new notifications" rather than as a fault, which is why it took an outside report to notice.


2. The mechanism

The bell is drawn by the shared header, so it appears on every page without any module knowing about it. This is what runs when you click it:

async function open() {
    panel.classList.add('open');
    list.innerHTML = '…' + esc(window.t('common.notifications.loading')) + '…';   // line 762
    try {
        const d = await (await fetch(API + 'get_notifications.php')).json();      // line 764
        if (d.success) { render(d.notifications || []); paintBadge(d.unread); }
    } catch (e) {
        list.innerHTML = '…' + esc(window.t('common.notifications.load_failed')); // line 767
    }
}

window.t is the translation lookup, defined by assets/js/i18n.js. Every page in the application loads that script.

Except the home page, which never did.

So window.t did not exist there, and line 762 β€” which asks for the word "Loading…" before doing anything else β€” threw TypeError: window.t is not a function.

Why that made it silent rather than noisy

Look at where line 762 sits: one line above the try.

  • The fetch on line 764 was never reached. The request for your notifications was never sent.
  • The catch on line 767 was never entered either, because the failure happened before the block it guards. The message written specifically to say "couldn't load your notifications" could not fire, because the thing it was there to report broke one line too early.

And the badge kept working throughout, because the count is fetched by a different function that never touches a translation:

async function poll() {
    const d = await (await fetch(API + 'get_notifications.php?count_only=1')).json();
    if (d.success) paintBadge(d.unread);       // no window.t anywhere
}

Hence the exact symptom: a correct count over an empty list.

The general lesson: a try only protects the lines inside it. Setup work placed immediately above β€” building a string, reading a global, resolving a label β€” is outside the net, and a failure there takes out the error handling along with the feature. If a line can throw, it belongs inside the block that reports it.


3. It was three pages, not one

The reported page was the obvious one. Checking every page in the application for a bell without the translation script found two more with the identical fault:

Page What was wrong
index.php (home) Never loaded i18n.js at all β€” the page reported
system/index.php Prepared $translationNamespaces = ['common', 'system'] and then never emitted it, nor loaded the script. The wiring was started and not finished, so the variable sat there unused
problem-management/index.php No translation setup at all. The module's own text is English-only, but the shared header it draws is not

The System one is worth dwelling on: everything looked present. The namespaces were declared, the intent was visible in the code, and the one line that actually ships it to the browser was missing. Nothing about reading that file says "unfinished".


4. The fix

The home page was already translated on the server β€” its title and footer come from t('common.home.browser_title') and t('common.home.footer'). It was never a page that had no need of translation; it was a page that had only half its translation wired. So the fix is to ship the browser half too, on all three pages:

$translationNamespaces = ['common'];
<script>window.translations = <?php echo json_encode(I18n::exportForJs($translationNamespaces), …); ?>;</script>
<script src="assets/js/i18n.js?v=2"></script>

That also puts the home page in line to be translated in the browser, which it never has been.

A related weakness, deliberately left alone

Two other components call the same function defensively:

// assets/js/calendar.js and assets/js/war-room.js
return (typeof window.t === 'function') ? window.t(key, params) : key;

The bell β€” the one component deliberately rendered on every page β€” calls it bare. Adding the same guard would mean a page that forgets the script in future degrades to untranslated English rather than silently doing nothing. That is a separate change and is not in this fix.


5. πŸ“ The files involved

🟒 The fault

File Role
includes/waffle-menu.php renderNotificationBell() β€” the bell, its markup and its script. open() is where the throw happened
assets/js/i18n.js Defines window.t. Absent from three pages

πŸ”΅ The three pages fixed

File Role
index.php The home page β€” the one reported. Gained the export and the script
system/index.php Namespaces were already declared; gained the two lines that ship them
problem-management/index.php Gained the i18n include, I18n::initFromSession(), the namespaces and the script

6. How it was verified

Every page was driven in a real browser with fetch instrumented, so the question "was a request made?" is answered by observation rather than inference.

Page Before After
Home 0 requests, threw window.t is not a function 1 request, 3 items
System not yet tested 1 request, 3 items
Problem Management not yet tested 1 request, 3 items
Tasks (control, never broken) 1 request, 3 items 1 request, 3 items

The control matters: Tasks was untouched by the fix and had to keep behaving identically, or the change would have been doing something other than what it claimed.

An application-wide scan for "renders the bell, does not load i18n.js" was run before and after, and returns nothing now.


7. What this means for you

  • The bell works on every page, including the home page. Nothing needs configuring.
  • If your badge shows a number and the panel is empty, that is this bug β€” update.
  • Nothing was ever lost. The notifications existed the whole time and were readable from any other page; only that one panel could not fetch them.

Related pages

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally