Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Changelog

All notable changes to this project will be documented in this file.

## Unreleased

## 1.0.1 - 2026-02-15

### Added
- Theme system with `dark`, `light`, `auto`, and `frosted` modes, plus a new Theme setting.
- Per-step “Leave process running” option and terminal app selection (Windows Terminal or Command Prompt).
- Close-on-exit option to terminate last-launched processes.
- Windows app discovery for Steam, Epic Games, and installed apps.
- `LastLaunch` state and command to track last-launched processes.
- Kill & Wipe workflow with a toolbar action, selectable options, and optional immediate mode.
- Desktop shortcut creation for Kill & Wipe with startup flags (`--kill-and-wipe`, `--kill-and-wipe-immediate`).
- Post-logout greeting message after Kill & Wipe completes.

### Changed
- Terminal steps now display `WT`/`CMD` badges and show default labels when no command is provided.
- Close-on-switch ignores steps marked to keep running.
- Titlebar and overlay now use theme-aware colors.
- Kill & Wipe runs Windows-first process cleanup, temp clearing, browser data wiping, DNS flush, and optional logout.
1 change: 1 addition & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<button id="btn-startup" class="toolbar-btn" title="Startup Apps">&#9889;</button>
<button id="btn-processes" class="toolbar-btn" title="Running Processes">&#9654;</button>
<button id="btn-history" class="toolbar-btn" title="Launch History">&#128339;</button>
<button id="btn-kill-wipe" class="toolbar-btn danger" title="Kill &amp; Wipe">&#9760;</button>
</div>
<div class="toolbar-actions">
<button id="btn-settings" class="toolbar-btn" title="Settings">&#9881;</button>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ export function newStep(type = 'app') {

switch (type) {
case 'app':
return { ...base, target: '', check_running: true };
return { ...base, target: '', check_running: true, keep_open: false };
case 'terminal':
return { ...base, command: '', working_dir: '', keep_open: true };
return { ...base, terminal_app: 'windows-terminal', command: '', working_dir: '', keep_open: true };
case 'folder':
return { ...base, target: '' };
return { ...base, target: '', keep_open: false };
case 'url':
return { ...base, target: '' };
return { ...base, target: '', keep_open: false };
default:
return { ...base, target: '' };
return { ...base, target: '', keep_open: false };
}
}
128 changes: 123 additions & 5 deletions frontend/src/dialogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ export function showConfirm(title, message) {
});
}

// -- Info dialog --
export function showInfo(title, message) {
return new Promise((resolve) => {
showModal(`
<div class="modal-title">${escapeHtml(title)}</div>
<p style="color: var(--text-secondary); margin-bottom: 12px;">${escapeHtml(message || '')}</p>
<div class="modal-actions">
<button class="btn-primary" id="info-ok">OK</button>
</div>
`);
document.getElementById('info-ok').addEventListener('click', () => { hideModal(); resolve(true); });
});
}

// ── Profile editor ──
export function showProfileEditor(profile, isNew) {
return new Promise((resolve) => {
Expand Down Expand Up @@ -128,7 +142,7 @@ export function showProfileEditor(profile, isNew) {
export function showStepEditor(step, isNew) {
return new Promise((resolve) => {
const typeOptions = ['app', 'terminal', 'folder', 'url']
.map(t => `<option value="${t}" ${step.type === t ? 'selected' : ''}>${t === 'terminal' ? 'Terminal (CMD)' : t.charAt(0).toUpperCase() + t.slice(1)}</option>`)
.map(t => `<option value="${t}" ${step.type === t ? 'selected' : ''}>${t === 'terminal' ? 'Terminal' : t.charAt(0).toUpperCase() + t.slice(1)}</option>`)
.join('');

showModal(`
Expand Down Expand Up @@ -201,6 +215,10 @@ function renderStepFields(step) {
<input type="checkbox" id="se-check-running" ${step.check_running !== false ? 'checked' : ''}>
<label for="se-check-running">Skip if already running</label>
</div>
<div class="form-check">
<input type="checkbox" id="se-keep-open" ${step.keep_open === true ? 'checked' : ''}>
<label for="se-keep-open">Leave process running</label>
</div>
`;
document.getElementById('se-browse-file').addEventListener('click', async () => {
try {
Expand All @@ -221,8 +239,15 @@ function renderStepFields(step) {
case 'terminal':
container.innerHTML = `
<div class="form-group">
<label>Command</label>
<input type="text" id="se-command" value="${escapeAttr(step.command || '')}" placeholder="npm run dev">
<label>Terminal App</label>
<select id="se-terminal-app">
<option value="windows-terminal" ${step.terminal_app !== 'cmd' ? 'selected' : ''}>Windows Terminal</option>
<option value="cmd" ${step.terminal_app === 'cmd' ? 'selected' : ''}>Command Prompt (cmd)</option>
</select>
</div>
<div class="form-group">
<label>Command (optional)</label>
<input type="text" id="se-command" value="${escapeAttr(step.command || '')}" placeholder="Leave blank to open the terminal">
</div>
<div class="form-group">
<label>Working Directory</label>
Expand All @@ -233,7 +258,7 @@ function renderStepFields(step) {
</div>
<div class="form-check">
<input type="checkbox" id="se-keep-open" ${step.keep_open !== false ? 'checked' : ''}>
<label for="se-keep-open">Keep terminal open</label>
<label for="se-keep-open">Leave process running</label>
</div>
`;
document.getElementById('se-browse-dir').addEventListener('click', async () => {
Expand All @@ -253,6 +278,10 @@ function renderStepFields(step) {
<button class="browse-btn" id="se-browse-folder">Browse</button>
</div>
</div>
<div class="form-check">
<input type="checkbox" id="se-keep-open" ${step.keep_open === true ? 'checked' : ''}>
<label for="se-keep-open">Leave process running</label>
</div>
`;
document.getElementById('se-browse-folder').addEventListener('click', async () => {
try {
Expand All @@ -268,6 +297,10 @@ function renderStepFields(step) {
<label>URL</label>
<input type="text" id="se-target" value="${escapeAttr(step.target || '')}" placeholder="https://example.com">
</div>
<div class="form-check">
<input type="checkbox" id="se-keep-open" ${step.keep_open === true ? 'checked' : ''}>
<label for="se-keep-open">Leave process running</label>
</div>
`;
break;
}
Expand All @@ -279,22 +312,26 @@ function readStepFields(step) {
// Clean up fields from other types
delete step.target;
delete step.check_running;
delete step.terminal_app;
delete step.command;
delete step.working_dir;
delete step.keep_open;

switch (type) {
case 'app': {
const target = document.getElementById('se-target');
const checkRunning = document.getElementById('se-check-running');
const keepOpen = document.getElementById('se-keep-open');
step.target = target ? target.value.trim() : '';
step.check_running = checkRunning ? checkRunning.checked : true;
step.keep_open = keepOpen ? keepOpen.checked : false;
break;
}
case 'terminal': {
const terminalApp = document.getElementById('se-terminal-app');
const command = document.getElementById('se-command');
const workdir = document.getElementById('se-workdir');
const keepOpen = document.getElementById('se-keep-open');
step.terminal_app = terminalApp ? terminalApp.value : 'windows-terminal';
step.command = command ? command.value.trim() : '';
step.working_dir = workdir ? workdir.value.trim() : '';
step.keep_open = keepOpen ? keepOpen.checked : true;
Expand All @@ -303,7 +340,9 @@ function readStepFields(step) {
case 'folder':
case 'url': {
const target = document.getElementById('se-target');
const keepOpen = document.getElementById('se-keep-open');
step.target = target ? target.value.trim() : '';
step.keep_open = keepOpen ? keepOpen.checked : false;
break;
}
}
Expand All @@ -319,6 +358,15 @@ export function showSettings(settings) {
<label>Default launch delay (ms)</label>
<input type="number" id="set-delay" value="${settings.launch_delay_ms || 500}" min="0" step="100">
</div>
<div class="form-group">
<label>Theme</label>
<select id="set-theme">
<option value="auto" ${(settings.theme || 'dark') === 'auto' ? 'selected' : ''}>Auto (System)</option>
<option value="dark" ${(settings.theme || 'dark') === 'dark' ? 'selected' : ''}>Dark</option>
<option value="light" ${(settings.theme || 'dark') === 'light' ? 'selected' : ''}>Light</option>
<option value="frosted" ${(settings.theme || 'dark') === 'frosted' ? 'selected' : ''}>Frosted Glass</option>
</select>
</div>
<div class="form-check">
<input type="checkbox" id="set-minimized" ${settings.start_minimized ? 'checked' : ''}>
<label for="set-minimized">Start minimized</label>
Expand All @@ -331,6 +379,10 @@ export function showSettings(settings) {
<input type="checkbox" id="set-close-switch" ${settings.close_on_switch !== false ? 'checked' : ''}>
<label for="set-close-switch">Offer to close apps when switching profiles</label>
</div>
<div class="form-check">
<input type="checkbox" id="set-close-exit" ${settings.close_on_exit ? 'checked' : ''}>
<label for="set-close-exit">Close launched apps when exiting WorkSwitch</label>
</div>
<div class="form-check">
<input type="checkbox" id="set-autostart" ${settings.auto_start_with_windows ? 'checked' : ''}>
<label for="set-autostart">Launch with Windows</label>
Expand All @@ -354,9 +406,11 @@ export function showSettings(settings) {
const result = {
...settings,
launch_delay_ms: parseInt(document.getElementById('set-delay').value) || 500,
theme: document.getElementById('set-theme').value,
start_minimized: document.getElementById('set-minimized').checked,
minimize_to_tray: document.getElementById('set-tray').checked,
close_on_switch: document.getElementById('set-close-switch').checked,
close_on_exit: document.getElementById('set-close-exit').checked,
auto_start_with_windows: autoStart
};
hideModal();
Expand All @@ -365,6 +419,70 @@ export function showSettings(settings) {
});
}

// -- Kill & Wipe dialog --
export function showKillAndWipe(settings) {
return new Promise((resolve) => {
const s = settings || {};

showModal(`
<div class="modal-title">Kill &amp; Wipe</div>
<div class="warning-box">
This is destructive. It can close apps, delete cache, wipe browser data, and log you out.
</div>
<div class="form-check">
<input type="checkbox" id="kw-kill" ${s.kill_processes !== false ? 'checked' : ''}>
<label for="kw-kill">Kill running processes (excluding critical system ones)</label>
</div>
<div class="form-check">
<input type="checkbox" id="kw-temp" ${s.clear_temp !== false ? 'checked' : ''}>
<label for="kw-temp">Clear %TEMP% and Windows Temp</label>
</div>
<div class="form-check">
<input type="checkbox" id="kw-browsers" ${s.clear_browsers !== false ? 'checked' : ''}>
<label for="kw-browsers">Wipe browser cache, cookies, and history</label>
</div>
<div class="form-check">
<input type="checkbox" id="kw-dns" ${s.flush_dns !== false ? 'checked' : ''}>
<label for="kw-dns">Flush DNS cache</label>
</div>
<div class="form-check">
<input type="checkbox" id="kw-logout" ${s.logout !== false ? 'checked' : ''}>
<label for="kw-logout">Log out after completion</label>
</div>
<div class="form-check" style="margin-top:10px">
<input type="checkbox" id="kw-confirm" ${s.confirm_before !== false ? 'checked' : ''}>
<label for="kw-confirm">Show this dialog before running</label>
</div>
<div class="form-check">
<input type="checkbox" id="kw-shortcut">
<label for="kw-shortcut">Create desktop shortcut for Kill &amp; Wipe</label>
</div>
<div class="modal-actions">
<button class="btn-secondary" id="kw-cancel">Cancel</button>
<button class="btn-danger" id="kw-run">Run</button>
</div>
`);

document.getElementById('kw-cancel').addEventListener('click', () => { hideModal(); resolve(null); });
document.getElementById('kw-run').addEventListener('click', () => {
const result = {
settings: {
...s,
kill_processes: document.getElementById('kw-kill').checked,
clear_temp: document.getElementById('kw-temp').checked,
clear_browsers: document.getElementById('kw-browsers').checked,
flush_dns: document.getElementById('kw-dns').checked,
logout: document.getElementById('kw-logout').checked,
confirm_before: document.getElementById('kw-confirm').checked
},
create_shortcut: document.getElementById('kw-shortcut').checked
};
hideModal();
resolve(result);
});
});
}

// ── Close-on-switch dialog ──
export function showCloseOnSwitch(processes) {
return new Promise((resolve) => {
Expand Down
Loading