diff --git a/.github/actions/build-extension/action.yml b/.github/actions/build-extension/action.yml
index c5368033..6cd68c9e 100644
--- a/.github/actions/build-extension/action.yml
+++ b/.github/actions/build-extension/action.yml
@@ -69,7 +69,7 @@ runs:
else
BASE="betterseqtaplus-${VERSION}"
fi
- (cd dist/chrome && zip -r "../${BASE}-chrome.zip" .)
- (cd dist/firefox && zip -r "../${BASE}-firefox.zip" .)
+ tar -a -cf "dist/${BASE}-chrome.zip" -C dist/chrome .
+ tar -a -cf "dist/${BASE}-firefox.zip" -C dist/firefox .
echo "chrome_zip=dist/${BASE}-chrome.zip" >> "$GITHUB_OUTPUT"
echo "firefox_zip=dist/${BASE}-firefox.zip" >> "$GITHUB_OUTPUT"
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 5974de2c..31e7769c 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -1,11 +1,18 @@
# Nightly release workflow — updates the same "nightly" release with fresh builds from main.
# Runs only on BetterSEQTA/BetterSEQTA-Plus. Uses the default GITHUB_TOKEN.
+#
+# Scheduled at midnight Australia/Adelaide (ACST/ACDT). GitHub cron is UTC-only, so we
+# trigger at 13:30 and 14:30 UTC and only proceed when Adelaide local time is 00:00.
+# Builds on windows-latest (matches local dev; Linux nightly builds were failing on Vite/Svelte).
name: Nightly Release
on:
schedule:
- - cron: "0 3 * * *"
+ # 13:30 UTC = 00:00 Adelaide during daylight saving (ACDT, UTC+10:30)
+ - cron: "30 13 * * *"
+ # 14:30 UTC = 00:00 Adelaide during standard time (ACST, UTC+9:30)
+ - cron: "30 14 * * *"
workflow_dispatch:
permissions:
@@ -16,16 +23,37 @@ env:
jobs:
nightly:
- runs-on: ubuntu-latest
+ runs-on: windows-latest
+ defaults:
+ run:
+ shell: bash
steps:
+ - name: Check Adelaide midnight
+ id: time_check
+ run: |
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+ echo "proceed=true" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ TZ=Australia/Adelaide
+ if [ "$(date +%H%M)" = "0000" ]; then
+ echo "proceed=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "proceed=false" >> "$GITHUB_OUTPUT"
+ echo "Skipping: not midnight Australia/Adelaide ($(date +%Y-%m-%d %H:%M %Z))"
+ fi
+
- uses: actions/checkout@v4
+ if: steps.time_check.outputs.proceed == 'true'
- name: Set build date
id: build_date
- run: echo "date=$(date -u +'%Y-%m-%d')" >> "$GITHUB_OUTPUT"
+ if: steps.time_check.outputs.proceed == 'true'
+ run: echo "date=$(TZ=Australia/Adelaide date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT"
- name: Build extension
id: build
+ if: steps.time_check.outputs.proceed == 'true'
uses: ./.github/actions/build-extension
with:
gh_release_update_check: "true"
@@ -34,6 +62,7 @@ jobs:
release_repo: ${{ github.repository }}
- name: Ensure nightly release exists
+ if: steps.time_check.outputs.proceed == 'true'
run: |
TITLE="Nightly (${{ steps.build_date.outputs.date }})"
if ! gh release view "${{ env.NIGHTLY_TAG }}" 2>/dev/null; then
@@ -46,6 +75,7 @@ jobs:
fi
- name: Upload nightly assets
+ if: steps.time_check.outputs.proceed == 'true'
run: |
gh release upload "${{ env.NIGHTLY_TAG }}" \
--clobber \
diff --git a/src/plugins/built-in/notificationCollector/index.ts b/src/plugins/built-in/notificationCollector/index.ts
index 8af0127c..67d49910 100644
--- a/src/plugins/built-in/notificationCollector/index.ts
+++ b/src/plugins/built-in/notificationCollector/index.ts
@@ -12,6 +12,7 @@ import {
injectArchivedForUser,
mountArchivedNotificationInjection,
} from "./injectArchivedNotifications";
+import styles from "./styles.css?inline";
const notificationCollectorSettings = {
saveLocally: booleanSetting({
@@ -46,6 +47,10 @@ const notificationCollectorPlugin: Plugin<
return () => {};
}
+ const styleEl = document.createElement("style");
+ styleEl.textContent = styles;
+ document.head.appendChild(styleEl);
+
await api.storage.loaded;
await api.settings.loaded;
@@ -208,6 +213,7 @@ const notificationCollectorPlugin: Plugin<
teardownInjection();
document.removeEventListener("visibilitychange", handleVisibilityChange);
pageChangeUnregister.unregister();
+ styleEl.remove();
};
},
};
diff --git a/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts b/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts
index c83b4507..57785e15 100644
--- a/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts
+++ b/src/plugins/built-in/notificationCollector/injectArchivedNotifications.ts
@@ -10,6 +10,12 @@ import {
const LIST_SELECTOR = '[class*="notifications__list___"]';
const ITEMS_SELECTOR = '[class*="notifications__items___"]';
+const ITEM_SELECTOR = '[class*="notifications__item___"]';
+const BACKED_UP_CLASS = "bsplus-notification-backed-up";
+const BACKUP_BADGE_CLASS = "bsplus-notification-backup-badge";
+
+const BACKUP_CHECK_SVG =
+ '';
function notificationTimestamp(item: Record): number {
const ms = new Date(String(item.timestamp ?? 0)).getTime();
@@ -61,9 +67,33 @@ async function tryInjectArchived(archive: ArchiveMap): Promise {
async function injectWithRetries(archive: ArchiveMap, attempts = 10) {
for (let i = 0; i < attempts; i++) {
const done = await tryInjectArchived(archive);
- if (done) return;
+ if (done) break;
await delay(120);
}
+ applyBackupBadges(archive);
+}
+
+export function applyBackupBadges(archive: ArchiveMap) {
+ const backedUpIds = new Set(Object.keys(archive));
+
+ for (const itemEl of document.querySelectorAll(ITEM_SELECTOR)) {
+ const id = itemEl.getAttribute("data-id");
+ if (!id) continue;
+
+ if (backedUpIds.has(id)) {
+ itemEl.classList.add(BACKED_UP_CLASS);
+ if (!itemEl.querySelector(`.${BACKUP_BADGE_CLASS}`)) {
+ const badge = document.createElement("span");
+ badge.className = BACKUP_BADGE_CLASS;
+ badge.title = "Saved locally";
+ badge.innerHTML = BACKUP_CHECK_SVG;
+ itemEl.appendChild(badge);
+ }
+ } else {
+ itemEl.classList.remove(BACKED_UP_CLASS);
+ itemEl.querySelector(`.${BACKUP_BADGE_CLASS}`)?.remove();
+ }
+ }
}
export function mountArchivedNotificationInjection(
diff --git a/src/plugins/built-in/notificationCollector/styles.css b/src/plugins/built-in/notificationCollector/styles.css
new file mode 100644
index 00000000..109605b3
--- /dev/null
+++ b/src/plugins/built-in/notificationCollector/styles.css
@@ -0,0 +1,25 @@
+[class*="notifications__item___"].bsplus-notification-backed-up {
+ position: relative;
+}
+
+.bsplus-notification-backup-badge {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--better-main, #22c55e);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+ z-index: 2;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+}
+
+.bsplus-notification-backup-badge svg {
+ width: 9px;
+ height: 9px;
+ color: #fff;
+}