diff --git a/com.pdma.notion-timer.sdPlugin/bin/plugin.js b/com.pdma.notion-timer.sdPlugin/bin/plugin.js
index 2ada462..ec3f8d8 100644
--- a/com.pdma.notion-timer.sdPlugin/bin/plugin.js
+++ b/com.pdma.notion-timer.sdPlugin/bin/plugin.js
@@ -6438,7 +6438,7 @@ async function stopTimer(token, entryId) {
}
// src/plugin.ts
-var CURRENT_VERSION = "1.0.14";
+var CURRENT_VERSION = "1.0.15";
var GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/master";
var SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4=
@@ -6492,6 +6492,19 @@ async function getGlobal() {
const stored = await plugin_default.settings.getGlobalSettings();
return { ...stored, ...HARDCODED };
}
+var memRunningEntryId = void 0;
+async function getRunningEntryId() {
+ if (memRunningEntryId === void 0) {
+ const stored = await plugin_default.settings.getGlobalSettings();
+ memRunningEntryId = stored.runningEntryId ?? null;
+ }
+ return memRunningEntryId;
+}
+async function setRunningEntry(entryId) {
+ memRunningEntryId = entryId;
+ const stored = await plugin_default.settings.getGlobalSettings();
+ await plugin_default.settings.setGlobalSettings({ ...stored, runningEntryId: entryId });
+}
function isConfigured(g) {
return !!(g.notionToken && g.userId);
}
@@ -6501,13 +6514,22 @@ function buttonTitle(projectName) {
var TimerToggle = class extends SingletonAction {
settingsCache = /* @__PURE__ */ new Map();
async onWillAppear(ev) {
- this.settingsCache.set(ev.action.id, ev.payload.settings);
const { activeEntryId, projectName } = ev.payload.settings;
const title = buttonTitle(projectName || "");
- if (activeEntryId) {
- await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]);
- } else {
+ const running = await getRunningEntryId();
+ const isRunning = !!activeEntryId && activeEntryId === running;
+ if (activeEntryId && !isRunning) {
+ const cleared = { ...ev.payload.settings, activeEntryId: null };
+ await ev.action.setSettings(cleared);
+ this.settingsCache.set(ev.action.id, cleared);
await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]);
+ } else {
+ this.settingsCache.set(ev.action.id, ev.payload.settings);
+ if (isRunning) {
+ await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]);
+ } else {
+ await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]);
+ }
}
}
async onPropertyInspectorDidAppear(ev) {
@@ -6551,18 +6573,21 @@ var TimerToggle = class extends SingletonAction {
this.settingsCache.set(ev.action.id, stopped);
await ev.action.setState(0);
await ev.action.setTitle(title);
- await ev.action.showOk();
+ await setRunningEntry(null);
} else {
- for (const other of this.actions) {
- if (other.id === ev.action.id) continue;
- const otherSettings = this.settingsCache.get(other.id);
- if (otherSettings?.activeEntryId) {
- await stopTimer(global.notionToken, otherSettings.activeEntryId);
- const stopped = { ...otherSettings, activeEntryId: null };
- await other.setSettings(stopped);
- this.settingsCache.set(other.id, stopped);
- await other.setState(0);
- await other.setTitle(buttonTitle(otherSettings.projectName || ""));
+ const prevEntryId = await getRunningEntryId();
+ if (prevEntryId) {
+ await stopTimer(global.notionToken, prevEntryId);
+ for (const other of this.actions) {
+ if (other.id === ev.action.id) continue;
+ const otherSettings = this.settingsCache.get(other.id);
+ if (otherSettings?.activeEntryId === prevEntryId) {
+ const stopped = { ...otherSettings, activeEntryId: null };
+ await other.setSettings(stopped);
+ this.settingsCache.set(other.id, stopped);
+ await other.setState(0);
+ await other.setTitle(buttonTitle(otherSettings.projectName || ""));
+ }
}
}
const entryId = await startTimer(
@@ -6575,9 +6600,9 @@ var TimerToggle = class extends SingletonAction {
const started = { ...ev.payload.settings, activeEntryId: entryId };
await ev.action.setSettings(started);
this.settingsCache.set(ev.action.id, started);
+ await setRunningEntry(entryId);
await ev.action.setState(1);
await ev.action.setTitle(`\u23F1 ${title}`);
- await ev.action.showOk();
}
} catch (err) {
plugin_default.logger.error("Timer toggle failed:", err);
diff --git a/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig b/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig
index c074d10..99f1a51 100644
Binary files a/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig and b/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig differ
diff --git a/com.pdma.notion-timer.sdPlugin/imgs/idle.png b/com.pdma.notion-timer.sdPlugin/imgs/idle.png
new file mode 100644
index 0000000..58ad4aa
Binary files /dev/null and b/com.pdma.notion-timer.sdPlugin/imgs/idle.png differ
diff --git a/com.pdma.notion-timer.sdPlugin/imgs/idle.svg b/com.pdma.notion-timer.sdPlugin/imgs/idle.svg
deleted file mode 100644
index bfa20b7..0000000
--- a/com.pdma.notion-timer.sdPlugin/imgs/idle.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/com.pdma.notion-timer.sdPlugin/imgs/running.png b/com.pdma.notion-timer.sdPlugin/imgs/running.png
new file mode 100644
index 0000000..8c20666
Binary files /dev/null and b/com.pdma.notion-timer.sdPlugin/imgs/running.png differ
diff --git a/com.pdma.notion-timer.sdPlugin/imgs/running.svg b/com.pdma.notion-timer.sdPlugin/imgs/running.svg
deleted file mode 100644
index 1b8feeb..0000000
--- a/com.pdma.notion-timer.sdPlugin/imgs/running.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/notion-timer.streamDeckPlugin b/notion-timer.streamDeckPlugin
index 915a2f1..36aadc8 100644
Binary files a/notion-timer.streamDeckPlugin and b/notion-timer.streamDeckPlugin differ
diff --git a/src/plugin.ts b/src/plugin.ts
index a224aa4..4c0599b 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -1,4 +1,4 @@
-const CURRENT_VERSION = "1.0.14";
+const CURRENT_VERSION = "1.0.15";
const GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/master";
const SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4=
@@ -66,6 +66,7 @@ interface GlobalSettings {
timingDbId: string;
projectsDbId: string;
userId: string;
+ runningEntryId?: string | null;
}
interface TimerSettings {
@@ -84,6 +85,23 @@ async function getGlobal(): Promise {
return { ...stored, ...HARDCODED };
}
+// In-memory cache so onWillAppear can check running state without an async round-trip
+let memRunningEntryId: string | null | undefined = undefined; // undefined = not yet loaded
+
+async function getRunningEntryId(): Promise {
+ if (memRunningEntryId === undefined) {
+ const stored = await streamDeck.settings.getGlobalSettings();
+ memRunningEntryId = stored.runningEntryId ?? null;
+ }
+ return memRunningEntryId;
+}
+
+async function setRunningEntry(entryId: string | null): Promise {
+ memRunningEntryId = entryId;
+ const stored = await streamDeck.settings.getGlobalSettings();
+ await streamDeck.settings.setGlobalSettings({ ...stored, runningEntryId: entryId });
+}
+
function isConfigured(g: GlobalSettings): boolean {
return !!(g.notionToken && g.userId);
}
@@ -98,13 +116,26 @@ class TimerToggle extends SingletonAction {
private settingsCache = new Map();
async onWillAppear(ev: WillAppearEvent): Promise {
- this.settingsCache.set(ev.action.id, ev.payload.settings);
const { activeEntryId, projectName } = ev.payload.settings;
const title = buttonTitle(projectName || "");
- if (activeEntryId) {
- await Promise.all([ev.action.setState(1), ev.action.setTitle(`⏱ ${title}`)]);
- } else {
+
+ // Use in-memory cache to determine correct state before rendering — no flash
+ const running = await getRunningEntryId();
+ const isRunning = !!activeEntryId && activeEntryId === running;
+
+ if (activeEntryId && !isRunning) {
+ // Self-heal: this button thinks it's running but it's not — clear it
+ const cleared = { ...ev.payload.settings, activeEntryId: null };
+ await ev.action.setSettings(cleared);
+ this.settingsCache.set(ev.action.id, cleared);
await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]);
+ } else {
+ this.settingsCache.set(ev.action.id, ev.payload.settings);
+ if (isRunning) {
+ await Promise.all([ev.action.setState(1), ev.action.setTitle(`⏱ ${title}`)]);
+ } else {
+ await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]);
+ }
}
}
@@ -153,19 +184,23 @@ class TimerToggle extends SingletonAction {
this.settingsCache.set(ev.action.id, stopped);
await ev.action.setState(0);
await ev.action.setTitle(title);
- await ev.action.showOk();
+ await setRunningEntry(null);
} else {
- // Stop any other running timer first
- for (const other of this.actions) {
- if (other.id === ev.action.id) continue;
- const otherSettings = this.settingsCache.get(other.id);
- if (otherSettings?.activeEntryId) {
- await stopTimer(global.notionToken, otherSettings.activeEntryId);
- const stopped = { ...otherSettings, activeEntryId: null };
- await other.setSettings(stopped);
- this.settingsCache.set(other.id, stopped);
- await other.setState(0);
- await other.setTitle(buttonTitle(otherSettings.projectName || ""));
+ const prevEntryId = await getRunningEntryId();
+
+ // Stop previous timer
+ if (prevEntryId) {
+ await stopTimer(global.notionToken, prevEntryId);
+ for (const other of this.actions) {
+ if (other.id === ev.action.id) continue;
+ const otherSettings = this.settingsCache.get(other.id);
+ if (otherSettings?.activeEntryId === prevEntryId) {
+ const stopped = { ...otherSettings, activeEntryId: null };
+ await other.setSettings(stopped);
+ this.settingsCache.set(other.id, stopped);
+ await other.setState(0);
+ await other.setTitle(buttonTitle(otherSettings.projectName || ""));
+ }
}
}
@@ -179,9 +214,9 @@ class TimerToggle extends SingletonAction {
const started = { ...ev.payload.settings, activeEntryId: entryId };
await ev.action.setSettings(started);
this.settingsCache.set(ev.action.id, started);
+ await setRunningEntry(entryId);
await ev.action.setState(1);
await ev.action.setTitle(`⏱ ${title}`);
- await ev.action.showOk();
}
} catch (err) {
streamDeck.logger.error("Timer toggle failed:", err);
diff --git a/version.json b/version.json
index e9ea986..b0b51e1 100644
--- a/version.json
+++ b/version.json
@@ -1 +1 @@
-{ "version": "1.0.14" }
+{ "version": "1.0.15" }