diff --git a/PRIVACY.md b/PRIVACY.md
index 0bda008..087dfc2 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -1,6 +1,6 @@
# Privacy Policy
-**Last updated: March 18, 2026**
+**Last updated: June 4, 2026**
## Overview
@@ -14,7 +14,8 @@ Plane Alert collects and stores the following data locally on your device:
- **Alert settings** — the alerts you configure (e.g. registrations, flight numbers, aircraft types).
- **Notification preferences** — your notification and sound settings.
- **Caught aircraft** — ICAO hex addresses of aircraft you have marked as caught.
-- **Notification history** — a local log of triggered alerts (callsign, timestamp, flight details).
+- **Notification history** — a local log of triggered alerts (callsign, timestamp, flight details). This log is stored only on your current device and is intentionally excluded from backup exports, as it reflects activity specific to your device and is not meaningful to restore.
+- **Statistics** — all-time counters derived from triggered notifications: total notification count, date of first detection, aircraft type counts, and airline prefix counts. These are stored locally and can be reset at any time via the History tab.
## How Your Data Is Used
@@ -24,6 +25,12 @@ All data is used solely to provide the core functionality of the extension: poll
All data is stored **locally on your device**, so nothing is shared with the developer. With one exception: your approximate location and radius are sent to the airplanes.live API with each poll to retrieve nearby aircraft.
+## Backup & Export
+
+The backup export (Settings → Backup) includes: alerts, location, radius, units, notification preferences, sound settings, startup tab, caught aircraft, and statistics.
+
+**Notification history is excluded from backups by design.** It is an ephemeral device-local log and carries no value when restored to a different device or after a reinstall.
+
## Third-Party Services
Plane Alert uses the [airplanes.live](https://airplanes.live) API to retrieve real-time aircraft data. Your approximate location (latitude, longitude, radius) is sent to this API with each poll. Please refer to the [airplanes.live](https://airplanes.live) website for their own privacy practices.
@@ -34,7 +41,7 @@ We do not sell, trade, or share any of your data with third parties.
## Data Deletion
-You can delete all locally stored data at any time by removing the extension from Chrome, or by using the "Export backup" feature and clearing your settings manually.
+You can delete all locally stored data at any time by removing the extension from Chrome, or by using the "Export backup" feature and clearing your settings manually. Notification history and statistics can be cleared independently via the History tab.
## Changes to This Policy
diff --git a/README.md b/README.md
index f95b183..4b3bf1b 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
A Chrome extension that notifies you when a tracked aircraft enters your radius — powered by the [airplanes.live](https://airplanes.live) API.
-
+


@@ -15,7 +15,7 @@ A Chrome extension that notifies you when a tracked aircraft enters your radius
- **Catch aircraft** — mark an aircraft as caught directly from the notification or the Live tab detail panel. Caught aircraft won't trigger future notifications. Manage your caught list in the History tab.
- **Alert sound** — choose from Ping, Radar, Alert or Chime with adjustable volume (soft, medium, loud)
- **Live tab** — see all aircraft in your radius in real time, with sorting and filtering options
-- **Detail dropdown** — click any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading
+- **Detail dropdown** — click any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading. Use the 🔔/🔕 bell buttons to toggle alerts for that registration or type directly from the dropdown.
- **Notification history** — a log of every triggered alert with callsign, time and flight details
- **Settings** — organised into collapsible cards: Location, Filters, Units, Notifications, Startup tab, and Backup & Test
- **Startup tab** — choose which tab opens on launch, or always return to the last tab you were on
@@ -60,18 +60,32 @@ Plane Alert polls the airplanes.live API in the background every minute. When an
---
+## Data & privacy
+
+All data is stored locally on your device. The only external call is to the airplanes.live API, which receives your approximate location and radius with each poll.
+
+**What is included in a backup export:** alerts, location, radius, units, notification preferences, sound settings, startup tab, caught aircraft, and statistics.
+
+**What is intentionally excluded from backups:** notification history (`notifHistory`). This log is ephemeral by design — it reflects what happened on your device and is not meaningful to restore on another machine or after a reinstall.
+
+See [PRIVACY.md](PRIVACY.md) for the full privacy policy.
+
+---
+
## File structure
```
plane-alert/
├── manifest.json
├── background.js — background service worker, polling & notifications
+├── shared.js — shared match logic (used by background & popup)
├── offscreen.html — offscreen document for audio playback
├── offscreen.js — Web Audio API sound engine
├── icons/
│ ├── icon16.png
│ ├── icon48.png
-│ └── icon128.png
+│ ├── icon128.png
+│ └── airplanes-live-logo.png
└── popup/
├── popup.html — extension UI skeleton
├── popup.css — all styles
@@ -104,4 +118,4 @@ This project is licensed under the **GNU General Public License v3.0**.
You are free to view, modify and redistribute the source code, but any derivative work must also be open source under the same license.
-**Publishing this extension or any derivative to a browser extension store (Chrome, Firefox, Edge, etc.) requires explicit written permission from the author.** See the [LICENSE](LICENSE) file for full details.
+**Publishing this extension or any derivative to a browser extension store (Chrome, Firefox, Edge, etc.) requires explicit written permission from the author.** See the [LICENSE](LICENSE) file for full details.
\ No newline at end of file
diff --git a/background.js b/background.js
index bb73f21..5d12427 100644
--- a/background.js
+++ b/background.js
@@ -1,4 +1,4 @@
-// background.js — runs in the background and polls the API
+// background.js v1.1.0 — runs in the background and polls the API
// Depends on shared.js (loaded via manifest.json background.scripts)
// ─── OFFSCREEN DOCUMENT ────────────────────────────────────────────────────
@@ -72,6 +72,40 @@ chrome.runtime.onMessage.addListener((msg) => {
}
});
+// ─── STATISTIEKEN BIJHOUDEN ────────────────────────────────────────────────
+
+async function recordStats(ac) {
+ const keys = ['statsTotalCount', 'statsFirstDetection', 'statsTypeCounts', 'statsAirlineCounts'];
+ const data = await chrome.storage.local.get(keys);
+
+ const totalCount = (data.statsTotalCount || 0) + 1;
+ const firstDetect = data.statsFirstDetection || new Date().toISOString();
+ const typeCounts = data.statsTypeCounts || {};
+ const airlineCounts = data.statsAirlineCounts || {};
+
+ // Type tellen (alleen als bekend)
+ if (ac.t) {
+ const t = ac.t.toUpperCase().trim();
+ typeCounts[t] = (typeCounts[t] || 0) + 1;
+ }
+
+ // Airline tellen: eerste 3 letters van vluchtcode, alleen als vlucht minstens 4 tekens heeft
+ if (ac.flight && ac.flight.trim().length >= 4) {
+ const prefix = ac.flight.trim().toUpperCase().substring(0, 3);
+ // Alleen letters (geen cijfers-prefix zoals militairen of privé)
+ if (/^[A-Z]{3}$/.test(prefix)) {
+ airlineCounts[prefix] = (airlineCounts[prefix] || 0) + 1;
+ }
+ }
+
+ await chrome.storage.local.set({
+ statsTotalCount: totalCount,
+ statsFirstDetection: firstDetect,
+ statsTypeCounts: typeCounts,
+ statsAirlineCounts: airlineCounts
+ });
+}
+
async function pollAircraft() {
const { enabled = true } = await chrome.storage.local.get('enabled');
if (!enabled) return;
@@ -106,7 +140,6 @@ async function pollAircraft() {
for (const ac of aircraft) {
if (ac.hex && caughtAircraft.includes(ac.hex)) continue;
- // matchesAlert komt uit shared.js
const matchingAlert = config.alerts.find(alert => alert.active && matchesAlert(ac, alert));
if (!matchingAlert) continue;
@@ -119,6 +152,9 @@ async function pollAircraft() {
inRange[key] = true;
await chrome.storage.local.set({ inRange });
+ // Statistieken bijhouden voor elke nieuwe match
+ await recordStats(ac);
+
const { notificationsEnabled = true, notifShow = {} } =
await chrome.storage.local.get(['notificationsEnabled', 'notifShow']);
const show = Object.assign({ reg: false, type: true, alt: true, speed: true, route: true, dir: true }, notifShow);
diff --git a/popup/alerts.js b/popup/alerts.js
index 40413d0..a478543 100644
--- a/popup/alerts.js
+++ b/popup/alerts.js
@@ -1,4 +1,4 @@
-// alerts.js — injecteert Alerts tab HTML en beheert alert logica
+// alerts.js v1.1.0 — injecteert Alerts tab HTML en beheert alert logica
// ─── HTML INJECTIE ──────────────────────────────────────────────────────────
@@ -108,6 +108,7 @@ function setupAlertsEvents() {
document.getElementById('alertValue').value = '';
document.getElementById('alertNote').value = '';
renderAlerts(alerts);
+ showSaved('Alert added');
});
}
@@ -145,7 +146,7 @@ async function renderAlerts(alerts) {
list.querySelectorAll('.alert-note').forEach(el => {
el.addEventListener('click', (e) => {
e.stopPropagation();
- if (el.querySelector('input')) return; // al in edit mode
+ if (el.querySelector('input')) return;
const id = el.dataset.id;
const currentNote = el.textContent.trim() === '+ add note' ? '' : el.textContent.trim();
diff --git a/popup/history.js b/popup/history.js
index d3ae6ef..516e1aa 100644
--- a/popup/history.js
+++ b/popup/history.js
@@ -1,9 +1,25 @@
-// history.js — injecteert History tab HTML en beheert notificatiegeschiedenis
+// history.js v1.2.0 — injecteert History tab HTML en beheert notificatiegeschiedenis
// ─── HTML INJECTIE ──────────────────────────────────────────────────────────
function initHistoryTab() {
document.getElementById('tab-history').innerHTML = `
+
+
+
Matching onlyHide aircraft that don't match any active alert
Airborne onlyTemporarily hides ground traffic in the Live tab only. Does not affect notifications.
Min. altitudeOnly show aircraft above a set altitude
-
Detail dropdownClick any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading. Use 🎯 Catch to mark it as caught — it won't trigger notifications again. Click again to collapse.
+
Detail dropdownClick any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading. Click again to collapse.
+
🔔 / 🔕 buttonsEach detail dropdown shows a bell button next to the registration and aircraft type. 🔔 means an alert is active for that value — click to remove it. 🔕 means no alert exists — click to add one instantly without going to the Alerts tab.
+
🎯 CatchMark an aircraft as caught from the detail dropdown. It won't trigger notifications again until you release it.
@@ -84,12 +86,12 @@
RadiusChoose 25, 50 or 100 km (or nm in imperial mode), or set a custom value with the slider
Hide ground trafficGlobal setting — suppresses both notifications and Live tab entries for aircraft on the ground
-
UnitsSwitch between metric (km · m · km/h) and imperial (nm · ft · kts). Affects the Live tab, detail panel, and notifications.
+
UnitsSwitch between metric (km · m · km/h) and imperial (nm · ft · kts). Affects the Live tab, detail dropdown, and notifications.
NotificationsToggle desktop notifications on or off. Polling continues either way.
SoundChoose an alert sound and volume. A preview plays when you select a sound.
ContentChoose which details appear in each notification: aircraft type, altitude, speed, route and/or direction
Startup tabChoose which tab opens when you launch the extension. Set it to a fixed tab or to Last used to always return to where you left off.
-
BackupExport all settings (alerts, location, radius, notification preferences) to a JSON file. Import restores everything at once. Old alerts-only backups are still supported.
+
BackupExport all settings (alerts, location, radius, notification preferences) to a JSON file. Import restores everything at once. Old alerts-only backups are still supported. Note: notification history is not included in exports.
diff --git a/popup/popup.js b/popup/popup.js
index 86ea3bf..2a96783 100644
--- a/popup/popup.js
+++ b/popup/popup.js
@@ -1,4 +1,16 @@
-// popup.js — init, tab switching, help overlay, master toggle, status
+// popup.js v1.1.0 — init, tab switching, help overlay, master toggle, status
+
+// ─── TOAST (gedeeld door alle popup-scripts) ───────────────────────────────
+
+let toastTimer = null;
+function showSaved(label = 'Saved') {
+ const toast = document.getElementById('savedToast');
+ if (!toast) return;
+ toast.textContent = `✓ ${label}`;
+ toast.classList.add('visible');
+ clearTimeout(toastTimer);
+ toastTimer = setTimeout(() => toast.classList.remove('visible'), 1800);
+}
// ─── TAB SWITCHING ─────────────────────────────────────────────────────────
diff --git a/popup/settings.js b/popup/settings.js
index 716126a..6e8f5d8 100644
--- a/popup/settings.js
+++ b/popup/settings.js
@@ -1,4 +1,5 @@
-// settings.js — injecteert Settings tab HTML en beheert alle instellingen
+// settings.js v1.2.0 — injecteert Settings tab HTML en beheert alle instellingen
+// showSaved() is beschikbaar via popup.js
// ─── HTML INJECTIE ──────────────────────────────────────────────────────────
@@ -34,6 +35,21 @@ function initSettingsTab() {
+
+
+
+
+
+
+
+
+
Metric: km · m · km/h · Imperial: nm · ft · kts
+
+
+
-
-
-
-
-
-
-
-
-
Metric: km · m · km/h · Imperial: nm · ft · kts
-
-
-
-
+
Desktop notifications
Polling continues when off
-
Sound
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Content
+
+
Content
-
-
✈️ PH-BXA (B744) spotted!
-
8500m · 850 km/h · AMS→JFK · from the west
+
+
+
+
✈️ PH-BXA (B744) spotted!
+
8500m · 850 km/h · AMS→JFK · from the west
+
@@ -127,6 +118,31 @@ function initSettingsTab() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -148,37 +164,25 @@ function initSettingsTab() {
-
+
Exports all settings including alerts, location and notifications.
-
+
-
`;
setupSettingsEvents();
}
-// ─── SAVED TOAST ───────────────────────────────────────────────────────────
-
-let toastTimer = null;
-function showSaved(label = 'Saved') {
- const toast = document.getElementById('savedToast');
- toast.textContent = `✓ ${label}`;
- toast.classList.add('visible');
- clearTimeout(toastTimer);
- toastTimer = setTimeout(() => toast.classList.remove('visible'), 1800);
-}
-
// ─── LOCATIE ───────────────────────────────────────────────────────────────
function updateCoordDisplay(lat, lon) {
@@ -327,7 +331,6 @@ function setupSettingsEvents() {
// ─── INSTELLINGEN HERLADEN (na backup import) ──────────────────────────────
async function loadSettings() {
- // Onthoud welke kaarten open waren voor we de HTML herinjekten
const openCards = [];
document.querySelectorAll('.settings-card-body').forEach(body => {
if (body.style.display !== 'none') openCards.push(body.id);
@@ -341,7 +344,6 @@ async function loadSettings() {
initRadiusButtons(radius, units);
await initSettings();
- // Herstel open kaarten
openCards.forEach(id => {
const body = document.getElementById(id);
if (body) {
@@ -517,7 +519,8 @@ async function initSettings() {
'hideGround', 'notificationsEnabled', 'notifShow',
'alertSound', 'alertVolume',
'startupTab', 'lastTab',
- 'caughtAircraft', 'caughtAircraftLabels'
+ 'caughtAircraft', 'caughtAircraftLabels',
+ 'statsTotalCount', 'statsFirstDetection', 'statsTypeCounts', 'statsAirlineCounts'
];
document.getElementById('btnExportAlerts').addEventListener('click', async () => {