-
Notifications
You must be signed in to change notification settings - Fork 15
Issue 78 Notification Bell Empty
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.
| 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.
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.
Look at where line 762 sits: one line above the try.
- The
fetchon line 764 was never reached. The request for your notifications was never sent. - The
catchon 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
tryonly 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.
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".
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.
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.
| 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 |
| 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 |
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.
- 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.
- Bugs resolved β the index of write-ups like this one
- Notifications β what the bell shows and the rules that keep it quiet
-
Internationalisation (i18n) β
window.t, the namespace export, and how a page wires up the browser half
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)