mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
chore: bump version to 0.1494
This commit is contained in:
@@ -26,7 +26,7 @@ When user says "COM":
|
|||||||
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
|
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
|
||||||
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
||||||
|
|
||||||
**Version**: 0.1493 (must match `package.json` for npm publish)
|
**Version**: 0.1494 (must match `package.json` for npm publish)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1493",
|
"version": "0.1494",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
+68
-30
@@ -1237,6 +1237,9 @@ class ClaudemanApp {
|
|||||||
this._inputQueueMaxBytes = 64 * 1024; // 64KB cap per session
|
this._inputQueueMaxBytes = 64 * 1024; // 64KB cap per session
|
||||||
this._connectionStatus = 'connected';
|
this._connectionStatus = 'connected';
|
||||||
|
|
||||||
|
// Sequential input send chain — ensures keystroke ordering across async fetches
|
||||||
|
this._inputSendChain = Promise.resolve();
|
||||||
|
|
||||||
// Accessibility: Focus trap for modals
|
// Accessibility: Focus trap for modals
|
||||||
this.activeFocusTrap = null;
|
this.activeFocusTrap = null;
|
||||||
|
|
||||||
@@ -1538,38 +1541,24 @@ class ClaudemanApp {
|
|||||||
// Handle keyboard input with batching for rapid keystrokes
|
// Handle keyboard input with batching for rapid keystrokes
|
||||||
this._pendingInput = '';
|
this._pendingInput = '';
|
||||||
this._inputFlushTimeout = null;
|
this._inputFlushTimeout = null;
|
||||||
this._inputFlushDelay = 16; // Flush at 60fps max
|
this._inputFlushDelay = 8; // Flush at ~120fps for snappy input
|
||||||
|
|
||||||
|
const flushInput = () => {
|
||||||
|
// Clear timeout immediately so new keystrokes can schedule fresh flushes.
|
||||||
|
// Previously this was set AFTER the await fetch(), which meant the stale
|
||||||
|
// timer ID blocked new keystrokes from scheduling — serializing all input
|
||||||
|
// behind the previous fetch's full network round-trip.
|
||||||
|
this._inputFlushTimeout = null;
|
||||||
|
|
||||||
const flushInput = async () => {
|
|
||||||
if (this._pendingInput && this.activeSessionId) {
|
if (this._pendingInput && this.activeSessionId) {
|
||||||
const input = this._pendingInput;
|
const input = this._pendingInput;
|
||||||
const sessionId = this.activeSessionId;
|
const sessionId = this.activeSessionId;
|
||||||
this._pendingInput = '';
|
this._pendingInput = '';
|
||||||
|
|
||||||
// Queue immediately if offline
|
// Fire-and-forget: don't block the flush cycle waiting for network.
|
||||||
if (!this.isOnline || this._connectionStatus === 'disconnected') {
|
// This prevents keystroke serialization behind fetch RTT.
|
||||||
this._enqueueInput(sessionId, input);
|
this._sendInputAsync(sessionId, input);
|
||||||
this._inputFlushTimeout = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await fetch(`/api/sessions/${sessionId}/input`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ input })
|
|
||||||
});
|
|
||||||
if (!resp.ok) {
|
|
||||||
this._enqueueInput(sessionId, input);
|
|
||||||
} else {
|
|
||||||
// Clear pending hooks when user sends input (they've addressed the prompt)
|
|
||||||
this.clearPendingHooks(sessionId);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this._enqueueInput(sessionId, input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this._inputFlushTimeout = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.terminal.onData((data) => {
|
this.terminal.onData((data) => {
|
||||||
@@ -1592,7 +1581,7 @@ class ClaudemanApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch regular input at 60fps
|
// Batch regular input at high frequency for responsiveness
|
||||||
if (!this._inputFlushTimeout) {
|
if (!this._inputFlushTimeout) {
|
||||||
this._inputFlushTimeout = setTimeout(flushInput, this._inputFlushDelay);
|
this._inputFlushTimeout = setTimeout(flushInput, this._inputFlushDelay);
|
||||||
}
|
}
|
||||||
@@ -2976,6 +2965,37 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send input to server without blocking the keystroke flush cycle.
|
||||||
|
* Uses a sequential promise chain to preserve character ordering
|
||||||
|
* across concurrent async fetches.
|
||||||
|
*/
|
||||||
|
_sendInputAsync(sessionId, input) {
|
||||||
|
// Queue immediately if offline
|
||||||
|
if (!this.isOnline || this._connectionStatus === 'disconnected') {
|
||||||
|
this._enqueueInput(sessionId, input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain sends sequentially to preserve keystroke ordering
|
||||||
|
this._inputSendChain = this._inputSendChain.then(async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`/api/sessions/${sessionId}/input`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ input })
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
this._enqueueInput(sessionId, input);
|
||||||
|
} else {
|
||||||
|
this.clearPendingHooks(sessionId);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this._enqueueInput(sessionId, input);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_enqueueInput(sessionId, input) {
|
_enqueueInput(sessionId, input) {
|
||||||
const existing = this._inputQueue.get(sessionId) || '';
|
const existing = this._inputQueue.get(sessionId) || '';
|
||||||
let combined = existing + input;
|
let combined = existing + input;
|
||||||
@@ -3757,10 +3777,16 @@ class ClaudemanApp {
|
|||||||
|
|
||||||
// Green fade overlay — Web Animations API for reliable GPU compositing.
|
// Green fade overlay — Web Animations API for reliable GPU compositing.
|
||||||
// (CSS keyframe animations were invisible: black-on-black had zero contrast.)
|
// (CSS keyframe animations were invisible: black-on-black had zero contrast.)
|
||||||
|
const fadeCfg = this.loadAppSettingsFromStorage();
|
||||||
|
const fadeEnabled = fadeCfg.tabFadeEnabled ?? true;
|
||||||
|
const fadeInDur = fadeCfg.tabFadeInDuration ?? 250;
|
||||||
|
const fadeInEase = fadeCfg.tabFadeInEasing || 'ease-in';
|
||||||
|
const fadeOutDur = fadeCfg.tabFadeOutDuration ?? 120;
|
||||||
|
const fadeOutEase = fadeCfg.tabFadeOutEasing || 'ease-in';
|
||||||
const main = document.querySelector('.main');
|
const main = document.querySelector('.main');
|
||||||
let fade = null;
|
let fade = null;
|
||||||
const fadeOutPromise = new Promise(resolve => {
|
const fadeOutPromise = new Promise(resolve => {
|
||||||
if (main) {
|
if (main && fadeEnabled) {
|
||||||
const prev = main.querySelector('.tab-switch-fade');
|
const prev = main.querySelector('.tab-switch-fade');
|
||||||
if (prev) prev.remove();
|
if (prev) prev.remove();
|
||||||
fade = document.createElement('div');
|
fade = document.createElement('div');
|
||||||
@@ -3773,10 +3799,10 @@ class ClaudemanApp {
|
|||||||
// Web Animations API — runs on compositor thread, bypasses containment issues
|
// Web Animations API — runs on compositor thread, bypasses containment issues
|
||||||
const anim = fade.animate(
|
const anim = fade.animate(
|
||||||
[{ opacity: 0 }, { opacity: 1 }],
|
[{ opacity: 0 }, { opacity: 1 }],
|
||||||
{ duration: 120, easing: 'ease-out', fill: 'forwards' }
|
{ duration: fadeInDur, easing: fadeInEase, fill: 'forwards' }
|
||||||
);
|
);
|
||||||
anim.finished.then(resolve).catch(resolve);
|
anim.finished.then(resolve).catch(resolve);
|
||||||
setTimeout(resolve, 350); // safety fallback
|
setTimeout(resolve, fadeInDur + 100); // safety fallback
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -3920,7 +3946,7 @@ class ClaudemanApp {
|
|||||||
if (fade) {
|
if (fade) {
|
||||||
const revealAnim = fade.animate(
|
const revealAnim = fade.animate(
|
||||||
[{ opacity: 1 }, { opacity: 0 }],
|
[{ opacity: 1 }, { opacity: 0 }],
|
||||||
{ duration: 120, easing: 'ease-in', fill: 'forwards' }
|
{ duration: fadeOutDur, easing: fadeOutEase, fill: 'forwards' }
|
||||||
);
|
);
|
||||||
revealAnim.finished.then(() => fade.remove()).catch(() => fade.remove());
|
revealAnim.finished.then(() => fade.remove()).catch(() => fade.remove());
|
||||||
}
|
}
|
||||||
@@ -8986,6 +9012,12 @@ class ClaudemanApp {
|
|||||||
document.getElementById('appSettingsSubagentTracking').checked = settings.subagentTrackingEnabled ?? true;
|
document.getElementById('appSettingsSubagentTracking').checked = settings.subagentTrackingEnabled ?? true;
|
||||||
document.getElementById('appSettingsSubagentActiveTabOnly').checked = settings.subagentActiveTabOnly ?? true;
|
document.getElementById('appSettingsSubagentActiveTabOnly').checked = settings.subagentActiveTabOnly ?? true;
|
||||||
document.getElementById('appSettingsImageWatcherEnabled').checked = settings.imageWatcherEnabled ?? false;
|
document.getElementById('appSettingsImageWatcherEnabled').checked = settings.imageWatcherEnabled ?? false;
|
||||||
|
// Tab switch animation settings
|
||||||
|
document.getElementById('appSettingsTabFadeEnabled').checked = settings.tabFadeEnabled ?? true;
|
||||||
|
document.getElementById('appSettingsTabFadeInDuration').value = settings.tabFadeInDuration ?? 250;
|
||||||
|
document.getElementById('appSettingsTabFadeInEasing').value = settings.tabFadeInEasing || 'ease-in';
|
||||||
|
document.getElementById('appSettingsTabFadeOutDuration').value = settings.tabFadeOutDuration ?? 120;
|
||||||
|
document.getElementById('appSettingsTabFadeOutEasing').value = settings.tabFadeOutEasing || 'ease-in';
|
||||||
// Claude CLI settings
|
// Claude CLI settings
|
||||||
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
|
const claudeModeSelect = document.getElementById('appSettingsClaudeMode');
|
||||||
const allowedToolsRow = document.getElementById('allowedToolsRow');
|
const allowedToolsRow = document.getElementById('allowedToolsRow');
|
||||||
@@ -9111,6 +9143,12 @@ class ClaudemanApp {
|
|||||||
subagentTrackingEnabled: document.getElementById('appSettingsSubagentTracking').checked,
|
subagentTrackingEnabled: document.getElementById('appSettingsSubagentTracking').checked,
|
||||||
subagentActiveTabOnly: document.getElementById('appSettingsSubagentActiveTabOnly').checked,
|
subagentActiveTabOnly: document.getElementById('appSettingsSubagentActiveTabOnly').checked,
|
||||||
imageWatcherEnabled: document.getElementById('appSettingsImageWatcherEnabled').checked,
|
imageWatcherEnabled: document.getElementById('appSettingsImageWatcherEnabled').checked,
|
||||||
|
// Tab switch animation settings
|
||||||
|
tabFadeEnabled: document.getElementById('appSettingsTabFadeEnabled').checked,
|
||||||
|
tabFadeInDuration: parseInt(document.getElementById('appSettingsTabFadeInDuration').value) || 250,
|
||||||
|
tabFadeInEasing: document.getElementById('appSettingsTabFadeInEasing').value || 'ease-in',
|
||||||
|
tabFadeOutDuration: parseInt(document.getElementById('appSettingsTabFadeOutDuration').value) || 120,
|
||||||
|
tabFadeOutEasing: document.getElementById('appSettingsTabFadeOutEasing').value || 'ease-in',
|
||||||
// Claude CLI settings
|
// Claude CLI settings
|
||||||
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
||||||
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
|
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
|
||||||
|
|||||||
@@ -726,6 +726,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-tabs">
|
<div class="modal-tabs">
|
||||||
<button class="modal-tab-btn active" data-tab="settings-display">Display</button>
|
<button class="modal-tab-btn active" data-tab="settings-display">Display</button>
|
||||||
|
<button class="modal-tab-btn" data-tab="settings-tab-switch">Tab-Switch</button>
|
||||||
<button class="modal-tab-btn" data-tab="settings-claude">Claude CLI</button>
|
<button class="modal-tab-btn" data-tab="settings-claude">Claude CLI</button>
|
||||||
<button class="modal-tab-btn" data-tab="settings-models">Models</button>
|
<button class="modal-tab-btn" data-tab="settings-models">Models</button>
|
||||||
<button class="modal-tab-btn" data-tab="settings-paths">Paths</button>
|
<button class="modal-tab-btn" data-tab="settings-paths">Paths</button>
|
||||||
@@ -825,6 +826,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Tab-Switch Tab -->
|
||||||
|
<div class="modal-tab-content hidden" id="settings-tab-switch">
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="settings-section-header">Tab Switch Animation</div>
|
||||||
|
<div class="settings-item" title="Green fade overlay when switching session tabs">
|
||||||
|
<span class="settings-item-label">Enable</span>
|
||||||
|
<label class="switch switch-sm">
|
||||||
|
<input type="checkbox" id="appSettingsTabFadeEnabled" checked>
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="settings-item" title="Fade-in duration in milliseconds">
|
||||||
|
<span class="settings-item-label">Fade In</span>
|
||||||
|
<div class="settings-item-actions">
|
||||||
|
<input type="number" id="appSettingsTabFadeInDuration" class="settings-item-input" value="250" min="50" max="1000" step="10">
|
||||||
|
<span style="font-size: 0.65rem; color: var(--text-muted);">ms</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-item" title="Fade-in easing curve">
|
||||||
|
<span class="settings-item-label">Fade In Easing</span>
|
||||||
|
<select id="appSettingsTabFadeInEasing" class="settings-item-input" style="width: 100px; padding: 0.25rem 0.4rem; font-size: 0.7rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 3px; color: var(--text);">
|
||||||
|
<option value="linear">linear</option>
|
||||||
|
<option value="ease">ease</option>
|
||||||
|
<option value="ease-in">ease-in</option>
|
||||||
|
<option value="ease-out">ease-out</option>
|
||||||
|
<option value="ease-in-out">ease-in-out</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="settings-item" title="Fade-out (reveal) duration in milliseconds">
|
||||||
|
<span class="settings-item-label">Fade Out</span>
|
||||||
|
<div class="settings-item-actions">
|
||||||
|
<input type="number" id="appSettingsTabFadeOutDuration" class="settings-item-input" value="120" min="50" max="1000" step="10">
|
||||||
|
<span style="font-size: 0.65rem; color: var(--text-muted);">ms</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-item" title="Fade-out easing curve">
|
||||||
|
<span class="settings-item-label">Fade Out Easing</span>
|
||||||
|
<select id="appSettingsTabFadeOutEasing" class="settings-item-input" style="width: 100px; padding: 0.25rem 0.4rem; font-size: 0.7rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 3px; color: var(--text);">
|
||||||
|
<option value="linear">linear</option>
|
||||||
|
<option value="ease">ease</option>
|
||||||
|
<option value="ease-in">ease-in</option>
|
||||||
|
<option value="ease-out">ease-out</option>
|
||||||
|
<option value="ease-in-out">ease-in-out</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Claude CLI Tab -->
|
<!-- Claude CLI Tab -->
|
||||||
<div class="modal-tab-content hidden" id="settings-claude">
|
<div class="modal-tab-content hidden" id="settings-claude">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
|||||||
@@ -2765,6 +2765,15 @@ body {
|
|||||||
|
|
||||||
.modal-content.modal-lg {
|
.modal-content.modal-lg {
|
||||||
max-width: 520px;
|
max-width: 520px;
|
||||||
|
max-height: 85vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content.modal-lg > .modal-body {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Case Picker - Base Styles */
|
/* Mobile Case Picker - Base Styles */
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const browser = await chromium.launch({ headless: true });
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
console.log('1. Loading page...');
|
||||||
|
await page.goto('http://localhost:3000', { waitUntil: 'domcontentloaded', timeout: 10000 });
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
|
||||||
|
console.log('2. Opening App Settings...');
|
||||||
|
await page.locator('button[title="App Settings"]').click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
console.log(` Modal active: ${(await page.locator('#appSettingsModal.active').count()) > 0}`);
|
||||||
|
|
||||||
|
console.log('3. Clicking Display tab...');
|
||||||
|
await page.locator('[data-tab="settings-display"]').click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
|
// Scroll the modal body to the bottom to reveal all settings
|
||||||
|
await page.locator('#settings-display').evaluate(el => el.scrollTop = el.scrollHeight);
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
console.log('4. Tab Switch Animation section...');
|
||||||
|
const header = page.locator('.settings-section-header:text("Tab Switch Animation")');
|
||||||
|
console.log(` Header visible: ${await header.isVisible()}`);
|
||||||
|
|
||||||
|
console.log('5. Checking controls...');
|
||||||
|
const toggleLabel = page.locator('#appSettingsTabFadeEnabled').locator('xpath=ancestor::label');
|
||||||
|
console.log(` ✓ Enable toggle: visible=${await toggleLabel.isVisible()}`);
|
||||||
|
for (const id of ['appSettingsTabFadeInDuration','appSettingsTabFadeInEasing','appSettingsTabFadeOutDuration','appSettingsTabFadeOutEasing']) {
|
||||||
|
console.log(` ✓ #${id}: visible=${await page.locator('#' + id).isVisible()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('6. Defaults...');
|
||||||
|
const enabled = await page.locator('#appSettingsTabFadeEnabled').isChecked();
|
||||||
|
const inDur = await page.locator('#appSettingsTabFadeInDuration').inputValue();
|
||||||
|
const inEase = await page.locator('#appSettingsTabFadeInEasing').inputValue();
|
||||||
|
const outDur = await page.locator('#appSettingsTabFadeOutDuration').inputValue();
|
||||||
|
const outEase = await page.locator('#appSettingsTabFadeOutEasing').inputValue();
|
||||||
|
console.log(` enabled=${enabled}, inDur=${inDur}, inEase=${inEase}, outDur=${outDur}, outEase=${outEase}`);
|
||||||
|
|
||||||
|
console.log('7. Modifying settings...');
|
||||||
|
// Click the toggle label (switch slider) instead of the hidden checkbox
|
||||||
|
await toggleLabel.click();
|
||||||
|
const nowChecked = await page.locator('#appSettingsTabFadeEnabled').isChecked();
|
||||||
|
console.log(` Toggle clicked, now checked=${nowChecked}`);
|
||||||
|
|
||||||
|
await page.locator('#appSettingsTabFadeInDuration').fill('500');
|
||||||
|
await page.locator('#appSettingsTabFadeInEasing').selectOption('linear');
|
||||||
|
await page.locator('#appSettingsTabFadeOutDuration').fill('200');
|
||||||
|
await page.locator('#appSettingsTabFadeOutEasing').selectOption('ease-out');
|
||||||
|
console.log(' All values changed');
|
||||||
|
|
||||||
|
// Save — scroll back up to find the save button in the footer
|
||||||
|
const saveBtn = page.locator('#appSettingsModal .modal-footer button.btn-primary');
|
||||||
|
await saveBtn.scrollIntoViewIfNeeded();
|
||||||
|
await saveBtn.click();
|
||||||
|
console.log(' Saved');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
|
// Re-open to verify persistence
|
||||||
|
console.log('8. Persistence check...');
|
||||||
|
await page.locator('button[title="App Settings"]').click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await page.locator('[data-tab="settings-display"]').click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
// Scroll down again
|
||||||
|
await page.locator('#settings-display').evaluate(el => el.scrollTop = el.scrollHeight);
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
const p = {
|
||||||
|
enabled: await page.locator('#appSettingsTabFadeEnabled').isChecked(),
|
||||||
|
inDur: await page.locator('#appSettingsTabFadeInDuration').inputValue(),
|
||||||
|
inEase: await page.locator('#appSettingsTabFadeInEasing').inputValue(),
|
||||||
|
outDur: await page.locator('#appSettingsTabFadeOutDuration').inputValue(),
|
||||||
|
outEase: await page.locator('#appSettingsTabFadeOutEasing').inputValue(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const checks = [
|
||||||
|
[!p.enabled, `enabled=${p.enabled} (expect false)`],
|
||||||
|
[p.inDur === '500', `inDur=${p.inDur} (expect 500)`],
|
||||||
|
[p.inEase === 'linear', `inEase=${p.inEase} (expect linear)`],
|
||||||
|
[p.outDur === '200', `outDur=${p.outDur} (expect 200)`],
|
||||||
|
[p.outEase === 'ease-out', `outEase=${p.outEase} (expect ease-out)`],
|
||||||
|
];
|
||||||
|
|
||||||
|
let allPass = true;
|
||||||
|
for (const [ok, msg] of checks) {
|
||||||
|
console.log(` ${ok ? '✓' : '✗'} ${msg}`);
|
||||||
|
if (!ok) allPass = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n${allPass ? 'ALL TESTS PASSED ✓' : 'SOME TESTS FAILED ✗'}`);
|
||||||
|
await browser.close();
|
||||||
|
process.exit(allPass ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => { console.error('CRASH:', err.message); process.exit(1); });
|
||||||
Reference in New Issue
Block a user