Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a984bd0
feat: initial addition of tray icon
nmggithub Mar 12, 2026
64538f2
fix: fix formatting issues
nmggithub Mar 12, 2026
f9112f0
refactor: move entire tray implementation `tray.ts`
nmggithub Mar 12, 2026
beb08f3
fix: fix inverted boolean logic
nmggithub Mar 12, 2026
9549570
fix: don't duplicate tray configuration work
nmggithub Mar 12, 2026
fcd4f00
fix: use consistent export syntax in `tray.ts`
nmggithub Mar 12, 2026
3441ef1
feat: set up tray enablement ipc
nmggithub Mar 12, 2026
82bce0e
feat: add setting to toggle tray icon
nmggithub Mar 12, 2026
1a4f31c
feat: add thread menu items
nmggithub Mar 12, 2026
6169a61
fix: format files
nmggithub Mar 12, 2026
867d8f8
refactor: rename `useTray` hook to `useTrayEnabled`
nmggithub Mar 13, 2026
2d63b25
refactor: make tray state setter hook and move from update pattern to…
nmggithub Mar 13, 2026
14786ba
fix: remove leftover `console.log`
nmggithub Mar 13, 2026
0c2ca96
fix: format files as needed
nmggithub Mar 13, 2026
b6de8bd
fix: use `toSorted` instead of `sorted`
nmggithub Mar 15, 2026
e5d7fd3
fix: increase thread name truncation length in tray menu
nmggithub Mar 15, 2026
01156de
fix: remove old todo
nmggithub Mar 15, 2026
a5f2731
fix: address linter concerns
nmggithub Mar 15, 2026
7fa0ec2
fix: address additional linter concerns
nmggithub Mar 15, 2026
a0c944d
fix: actually fail early on non-macOS platforms
nmggithub Mar 15, 2026
10a9876
fix: create main window from tray if it doesn't exist
nmggithub Mar 15, 2026
9d0d67f
fix: don't rely on old thread state
nmggithub Mar 15, 2026
053e7f6
fix: add state updater to `useTrayState`
nmggithub Mar 15, 2026
8c1cd5b
fix: actually create main window on thread click from tray
nmggithub Mar 15, 2026
4ab4f1b
feat: allow for tray menu to re-open main window
nmggithub Mar 15, 2026
a1bc2ed
style: re-order hooks
nmggithub Mar 15, 2026
f388328
feat: add new thread in project action from tray
nmggithub Mar 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"dependencies": {
"effect": "catalog:",
"electron": "40.6.0",
"electron-updater": "^6.6.2"
"electron-updater": "^6.6.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@t3tools/contracts": "workspace:*",
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/src/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,7 @@ import {
reduceDesktopUpdateStateOnUpdateAvailable,
} from "./updateMachine";
import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch";
import { setupTrayIpcHandlers, setTrayEnabled, setReadyToHandleTrayMessages } from "./tray";

fixPath();

Expand DownExpand Up@@ -1330,13 +1331,16 @@ async function bootstrap(): Promise<void> {
writeDesktopLogHeader("bootstrap backend start requested");
mainWindow = createWindow();
writeDesktopLogHeader("bootstrap main window created");
setupTrayIpcHandlers();
writeDesktopLogHeader("bootstrap tray ipc handlers registered");
}

app.on("before-quit", () => {
isQuitting = true;
writeDesktopLogHeader("before-quit received");
clearUpdatePollTimer();
stopBackend();
setTrayEnabled(false);
restoreStdIoCapture?.();
});

Expand DownExpand Up@@ -1366,6 +1370,7 @@ app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
setReadyToHandleTrayMessages(false);
});

if (process.platform !== "win32") {
Expand All@@ -1389,3 +1394,10 @@ if (process.platform !== "win32") {
app.quit();
});
}

export function getMainWindow(createIfNeeded: boolean = false): BrowserWindow | null {
if (createIfNeeded && mainWindow === null) {
mainWindow = createWindow();
}
return mainWindow;
}
23 changes: 22 additions & 1 deletion apps/desktop/src/preload.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from "electron";
import type { DesktopBridge } from "@t3tools/contracts";
import type { DesktopBridge, DesktopTrayState } from "@t3tools/contracts";

const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
const CONFIRM_CHANNEL = "desktop:confirm";
Expand All@@ -11,6 +11,11 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state";
const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download";
const UPDATE_INSTALL_CHANNEL = "desktop:update-install";
const SET_TRAY_ENABLED_CHANNEL = "desktop:set-tray-enabled";
const GET_TRAY_STATE_CHANNEL = "desktop:get-tray-state";
const SET_TRAY_STATE_CHANNEL = "desktop:set-tray-state";
const SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL = "desktop:set-ready-to-handle-tray-messages";
const TRAY_MESSAGE_CHANNEL = "desktop:tray-message";
const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null;

contextBridge.exposeInMainWorld("desktopBridge", {
Expand DownExpand Up@@ -45,4 +50,20 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.removeListener(UPDATE_STATE_CHANNEL, wrappedListener);
};
},
setTrayEnabled: (enabled: boolean) => ipcRenderer.invoke(SET_TRAY_ENABLED_CHANNEL, enabled),
getTrayState: () => ipcRenderer.invoke(GET_TRAY_STATE_CHANNEL),
setTrayState: (state: DesktopTrayState) => ipcRenderer.invoke(SET_TRAY_STATE_CHANNEL, state),
setReadyToHandleTrayMessages: (ready: boolean) =>
ipcRenderer.invoke(SET_READY_TO_HANDLE_TRAY_MESSAGES_CHANNEL, ready),
onTrayMessage: (listener) => {
const wrappedListener = (_event: Electron.IpcRendererEvent, message: unknown) => {
if (typeof message !== "object" || message === null) return;
listener(message as Parameters<typeof listener>[0]);
};

ipcRenderer.on(TRAY_MESSAGE_CHANNEL, wrappedListener);
return () => {
ipcRenderer.removeListener(TRAY_MESSAGE_CHANNEL, wrappedListener);
};
},
} satisfies DesktopBridge);
Loading