chore: bump version to 0.1439

This commit is contained in:
arkon
2026-01-30 17:50:02 +01:00
parent 5b6dd078f1
commit b520ce44e5
23 changed files with 225 additions and 36 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ When user says "COM":
1. Increment version in BOTH `package.json` AND `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`
**Version**: 0.1438 (must match `package.json`)
**Version**: 0.1439 (must match `package.json`)
## Project Overview
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claudeman",
"version": "0.1438",
"version": "0.1439",
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module",
"main": "dist/index.js",
+12
View File
@@ -338,6 +338,9 @@ export class Session extends EventEmitter {
// Image watcher setting (per-session toggle)
private _imageWatcherEnabled: boolean = true;
// Flicker filter setting (per-session toggle, applied on frontend)
private _flickerFilterEnabled: boolean = false;
// Timer tracking for cleanup (prevents memory leaks)
private _autoCompactTimer: NodeJS.Timeout | null = null;
private _autoClearTimer: NodeJS.Timeout | null = null;
@@ -694,6 +697,14 @@ export class Session extends EventEmitter {
this._imageWatcherEnabled = enabled;
}
get flickerFilterEnabled(): boolean {
return this._flickerFilterEnabled;
}
set flickerFilterEnabled(enabled: boolean) {
this._flickerFilterEnabled = enabled;
}
isIdle(): boolean {
return this._status === 'idle';
}
@@ -734,6 +745,7 @@ export class Session extends EventEmitter {
niceEnabled: this._niceConfig.enabled,
niceValue: this._niceConfig.niceValue,
color: this._color,
flickerFilterEnabled: this._flickerFilterEnabled,
};
}
+2
View File
@@ -180,6 +180,8 @@ export interface SessionState {
niceValue?: number;
/** User-assigned color for visual differentiation */
color?: SessionColor;
/** Flicker filter enabled (buffers output after screen clears) */
flickerFilterEnabled?: boolean;
}
// ========== Global Stats Types ==========
+132 -2
View File
@@ -631,6 +631,7 @@ class ClaudemanApp {
// Once set, never recalculated. Persisted to localStorage and server.
this.subagentParentMap = new Map();
this.ralphStatePanelCollapsed = true; // Default to collapsed
this.ralphClosedSessions = new Set(); // Sessions where user explicitly closed Ralph panel
// Plan subagent windows (visible agents during plan generation)
this.planSubagents = new Map(); // Map<agentId, { type, model, status, startTime, element, relativePos }>
@@ -667,6 +668,11 @@ class ClaudemanApp {
this._wasAtBottomBeforeWrite = true; // Default to true for sticky scroll
this.syncWaitTimeout = null; // Timeout for incomplete sync blocks
// Flicker filter state (buffers output after screen clears)
this.flickerFilterBuffer = '';
this.flickerFilterActive = false;
this.flickerFilterTimeout = null;
// Render debouncing
this.renderSessionTabsTimeout = null;
this.renderRalphStatePanelTimeout = null;
@@ -1053,6 +1059,44 @@ class ClaudemanApp {
this._wasAtBottomBeforeWrite = this.isTerminalAtBottom();
}
// Check if flicker filter is enabled for current session
const session = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
const flickerFilterEnabled = session?.flickerFilterEnabled ?? false;
// Flicker filter: detect screen clear patterns and buffer output
if (flickerFilterEnabled) {
// Detect Ink's screen clear patterns:
// - ESC[2J (clear entire screen)
// - ESC[H ESC[J (cursor home + clear to end)
// - ESC[?25l ESC[H (hide cursor + home - common Ink pattern)
const hasScreenClear = data.includes('\x1b[2J') ||
data.includes('\x1b[H\x1b[J') ||
(data.includes('\x1b[H') && data.includes('\x1b[?25l'));
if (hasScreenClear) {
// Screen clear detected - activate flicker filter
this.flickerFilterActive = true;
this.flickerFilterBuffer += data;
// Clear any existing timeout and set a new one
if (this.flickerFilterTimeout) {
clearTimeout(this.flickerFilterTimeout);
}
this.flickerFilterTimeout = setTimeout(() => {
this.flickerFilterTimeout = null;
this.flushFlickerBuffer();
}, SYNC_WAIT_TIMEOUT_MS); // 50ms buffer window
return; // Don't process normally, wait for buffer window
}
if (this.flickerFilterActive) {
// Filter is active, accumulate data
this.flickerFilterBuffer += data;
return; // Continue buffering
}
}
// Accumulate raw data (may contain DEC 2026 markers)
this.pendingWrites += data;
@@ -1090,6 +1134,28 @@ class ClaudemanApp {
}
}
/**
* Flush the flicker filter buffer to the terminal.
* Called after the buffer window expires.
*/
flushFlickerBuffer() {
if (!this.flickerFilterBuffer) return;
// Transfer buffered data to normal pending writes
this.pendingWrites += this.flickerFilterBuffer;
this.flickerFilterBuffer = '';
this.flickerFilterActive = false;
// Trigger a normal flush
if (!this.writeFrameScheduled) {
this.writeFrameScheduled = true;
requestAnimationFrame(() => {
this.flushPendingWrites();
this.writeFrameScheduled = false;
});
}
}
/**
* Flush pending writes to terminal, processing DEC 2026 sync markers.
* Strips markers and writes content atomically within a single frame.
@@ -1366,6 +1432,7 @@ class ClaudemanApp {
}
this.terminalBuffers.delete(data.id);
this.ralphStates.delete(data.id); // Clean up ralph state for this session
this.ralphClosedSessions.delete(data.id); // Clean up closed tracking for this session
this.projectInsights.delete(data.id); // Clean up project insights for this session
this.closeSessionLogViewerWindows(data.id); // Close log viewer windows for this session
this.closeSessionImagePopups(data.id); // Close image popup windows for this session
@@ -1803,16 +1870,22 @@ class ClaudemanApp {
// Ralph loop/todo events
addListener('session:ralphLoopUpdate', (e) => {
const data = JSON.parse(e.data);
// Skip if user explicitly closed this session's Ralph panel
if (this.ralphClosedSessions.has(data.sessionId)) return;
this.updateRalphState(data.sessionId, { loop: data.state });
});
addListener('session:ralphTodoUpdate', (e) => {
const data = JSON.parse(e.data);
// Skip if user explicitly closed this session's Ralph panel
if (this.ralphClosedSessions.has(data.sessionId)) return;
this.updateRalphState(data.sessionId, { todos: data.todos });
});
addListener('session:ralphCompletionDetected', (e) => {
const data = JSON.parse(e.data);
// Skip if user explicitly closed this session's Ralph panel
if (this.ralphClosedSessions.has(data.sessionId)) return;
// Prevent duplicate notifications for the same completion
const completionKey = `${data.sessionId}:${data.phrase}`;
if (this._shownCompletions?.has(completionKey)) {
@@ -1846,11 +1919,15 @@ class ClaudemanApp {
// RALPH_STATUS block and circuit breaker events
addListener('session:ralphStatusUpdate', (e) => {
const data = JSON.parse(e.data);
// Skip if user explicitly closed this session's Ralph panel
if (this.ralphClosedSessions.has(data.sessionId)) return;
this.updateRalphState(data.sessionId, { statusBlock: data.block });
});
addListener('session:circuitBreakerUpdate', (e) => {
const data = JSON.parse(e.data);
// Skip if user explicitly closed this session's Ralph panel
if (this.ralphClosedSessions.has(data.sessionId)) return;
this.updateRalphState(data.sessionId, { circuitBreaker: data.status });
// Notify if circuit breaker opens
if (data.status.state === 'OPEN') {
@@ -2222,6 +2299,13 @@ class ClaudemanApp {
clearTimeout(timer);
}
this.idleTimers.clear();
// Clear flicker filter state
if (this.flickerFilterTimeout) {
clearTimeout(this.flickerFilterTimeout);
this.flickerFilterTimeout = null;
}
this.flickerFilterBuffer = '';
this.flickerFilterActive = false;
// Clear pending hooks
this.pendingHooks.clear();
// Clear tab alerts
@@ -2250,8 +2334,8 @@ class ClaudemanApp {
}
data.sessions.forEach(s => {
this.sessions.set(s.id, s);
// Load ralph state from session data
if (s.ralphLoop || s.ralphTodos) {
// Load ralph state from session data (only if not explicitly closed by user)
if ((s.ralphLoop || s.ralphTodos) && !this.ralphClosedSessions.has(s.id)) {
this.ralphStates.set(s.id, {
loop: s.ralphLoop || null,
todos: s.ralphTodos || []
@@ -2842,6 +2926,14 @@ class ClaudemanApp {
async selectSession(sessionId) {
if (this.activeSessionId === sessionId) return;
// Clean up flicker filter state when switching sessions
if (this.flickerFilterTimeout) {
clearTimeout(this.flickerFilterTimeout);
this.flickerFilterTimeout = null;
}
this.flickerFilterBuffer = '';
this.flickerFilterActive = false;
this.activeSessionId = sessionId;
this.hideWelcome();
// Clear idle hooks on view, but keep action hooks until user interacts
@@ -5754,6 +5846,9 @@ class ClaudemanApp {
const sessionId = data.sessionId;
// Clear from closed set in case user previously closed Ralph for this session
this.ralphClosedSessions.delete(sessionId);
// Step 2: Configure Ralph tracker for this session
await fetch(`/api/sessions/${sessionId}/ralph-config`, {
method: 'POST',
@@ -6932,6 +7027,7 @@ class ClaudemanApp {
document.getElementById('modalAutoClearEnabled').checked = session.autoClearEnabled ?? false;
document.getElementById('modalAutoClearThreshold').value = session.autoClearThreshold ?? 140000;
document.getElementById('modalImageWatcherEnabled').checked = session.imageWatcherEnabled ?? true;
document.getElementById('modalFlickerFilterEnabled').checked = session.flickerFilterEnabled ?? false;
// Initialize color picker with current session color
const currentColor = session.color || 'default';
@@ -7010,6 +7106,26 @@ class ClaudemanApp {
}
}
async toggleFlickerFilter() {
if (!this.editingSessionId) return;
const enabled = document.getElementById('modalFlickerFilterEnabled').checked;
try {
await fetch(`/api/sessions/${this.editingSessionId}/flicker-filter`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled })
});
// Update local session state
const session = this.sessions.get(this.editingSessionId);
if (session) {
session.flickerFilterEnabled = enabled;
}
this.showToast(`Flicker filter ${enabled ? 'enabled' : 'disabled'}`, 'success');
} catch (err) {
this.showToast('Failed to toggle flicker filter', 'error');
}
}
async autoSaveRespawnConfig() {
if (!this.editingSessionId) return;
const config = {
@@ -7715,6 +7831,11 @@ class ClaudemanApp {
const config = this.getRalphConfig();
// If user is enabling Ralph, clear from closed set
if (config.enabled) {
this.ralphClosedSessions.delete(this.editingSessionId);
}
try {
const res = await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`, {
method: 'POST',
@@ -9045,6 +9166,9 @@ class ClaudemanApp {
async closeRalphTracker() {
if (!this.activeSessionId) return;
// Mark this session as explicitly closed - will stay hidden until user re-enables
this.ralphClosedSessions.add(this.activeSessionId);
// Disable tracker via API
await fetch(`/api/sessions/${this.activeSessionId}/ralph-config`, {
method: 'POST',
@@ -9343,6 +9467,12 @@ class ClaudemanApp {
if (!panel) return;
// If user explicitly closed this session's Ralph panel, keep it hidden
if (this.ralphClosedSessions.has(this.activeSessionId)) {
panel.style.display = 'none';
return;
}
const state = this.ralphStates.get(this.activeSessionId);
// Check if there's anything to show
+10
View File
@@ -583,6 +583,16 @@
</label>
<span class="form-hint">Auto-popup new images (screenshots, generated files) in this session's directory</span>
</div>
<div class="form-section-header">Performance</div>
<div class="form-row form-row-switch">
<label>Flicker Filter</label>
<label class="switch">
<input type="checkbox" id="modalFlickerFilterEnabled" onchange="app.toggleFlickerFilter()">
<span class="slider"></span>
</label>
<span class="form-hint">Buffers output after screen clears to reduce Ink UI flickering (adds ~50ms latency)</span>
</div>
</div><!-- End context-tab -->
<!-- Ralph Wiggum Tab Content -->
+39 -4
View File
@@ -280,6 +280,7 @@ export class WebServer extends EventEmitter {
private store = getStore();
private port: number;
private https: boolean;
private testMode: boolean;
private screenManager: ScreenManager;
// Terminal batching for performance
private terminalBatches: Map<string, string> = new Map();
@@ -325,10 +326,11 @@ export class WebServer extends EventEmitter {
error: (error: Error, sessionId?: string) => void;
} | null = null;
constructor(port: number = 3000, https: boolean = false) {
constructor(port: number = 3000, https: boolean = false, testMode: boolean = false) {
super();
this.port = port;
this.https = https;
this.testMode = testMode;
if (https) {
const { key, cert } = getOrCreateSelfSignedCert();
@@ -1879,6 +1881,32 @@ export class WebServer extends EventEmitter {
};
});
// Toggle flicker filter for a session
this.app.post('/api/sessions/:id/flicker-filter', async (req) => {
const { id } = req.params as { id: string };
const body = req.body as { enabled: boolean };
const session = this.sessions.get(id);
if (!session) {
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found');
}
if (body.enabled === undefined) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'enabled field is required');
}
session.flickerFilterEnabled = body.enabled;
this.persistSessionState(session);
this.broadcast('session:updated', this.getSessionStateWithRespawn(session));
return {
success: true,
data: {
flickerFilterEnabled: body.enabled,
},
};
});
// Quick run (create session, run prompt, return result, then cleanup)
this.app.post('/api/run', async (req) => {
// Prevent unbounded session creation
@@ -4414,7 +4442,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Restore screen sessions BEFORE accepting connections
// This prevents race conditions where clients connect before state is ready
await this.restoreScreenSessions();
// CRITICAL: Skip in test mode to prevent tests from picking up user sessions
if (!this.testMode) {
await this.restoreScreenSessions();
}
// Clean up stale sessions from state file that don't have active screens
this.cleanupStaleSessions();
@@ -4587,6 +4618,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
niceValue: savedState.niceValue,
});
}
// Flicker filter (frontend-applied but persisted)
if (savedState.flickerFilterEnabled !== undefined) {
session.flickerFilterEnabled = savedState.flickerFilterEnabled;
}
// Respawn controller
if (savedState.respawnEnabled && savedState.respawnConfig) {
try {
@@ -4751,8 +4786,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
}
}
export async function startWebServer(port: number = 3000, https: boolean = false): Promise<WebServer> {
const server = new WebServer(port, https);
export async function startWebServer(port: number = 3000, https: boolean = false, testMode: boolean = false): Promise<WebServer> {
const server = new WebServer(port, https, testMode);
await server.start();
return server;
}
+1 -1
View File
@@ -171,7 +171,7 @@ describe('Agent-Browser Enhanced Tests', () => {
closeBrowser();
// Start web server
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
+1 -1
View File
@@ -135,7 +135,7 @@ describe('Browser Framework Comparison Benchmark', () => {
// Start web servers for each framework
for (const [name, port] of Object.entries(PORTS)) {
const server = new WebServer(port);
const server = new WebServer(port, false, true);
await server.start();
servers.push(server);
console.log(`✅ Started server for ${name} on port ${port}`);
+4 -4
View File
@@ -92,7 +92,7 @@ describe('Browser E2E Tests', () => {
beforeAll(async () => {
closeBrowser();
server = new WebServer(TEST_PORT_BROWSER);
server = new WebServer(TEST_PORT_BROWSER, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
@@ -211,7 +211,7 @@ describe('SSE Events API', () => {
let createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT_SSE);
server = new WebServer(TEST_PORT_SSE, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
}, 30000);
@@ -280,7 +280,7 @@ describe('Hook Events API', () => {
let createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT_HOOK);
server = new WebServer(TEST_PORT_HOOK, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
}, 30000);
@@ -345,7 +345,7 @@ describe('Ralph API', () => {
let createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT_RALPH);
server = new WebServer(TEST_PORT_RALPH, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
}, 30000);
+1 -1
View File
@@ -32,7 +32,7 @@ let setupComplete = false;
describe('Playwright Direct API Tests', () => {
beforeAll(async () => {
// Start web server
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
+1 -1
View File
@@ -34,7 +34,7 @@ describe('Puppeteer/Chrome Tests', () => {
beforeAll(async () => {
// Start web server
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
+1 -1
View File
@@ -17,7 +17,7 @@ export interface ServerFixture {
* @returns ServerFixture with server instance and connection info
*/
export async function createServerFixture(port: number): Promise<ServerFixture> {
const server = new WebServer(port);
const server = new WebServer(port, false, true);
await server.start();
// Wait for server to be fully ready
+3 -3
View File
@@ -13,7 +13,7 @@ describe('Edge Cases and Error Handling', () => {
const createdCases: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
@@ -261,7 +261,7 @@ describe('Concurrent Session Handling', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1);
server = new WebServer(TEST_PORT + 1, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
});
@@ -362,7 +362,7 @@ describe('API Request Validation', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT + 2);
server = new WebServer(TEST_PORT + 2, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 2}`;
});
+1 -1
View File
@@ -93,7 +93,7 @@ describe('File Link Click Tests', () => {
testLogFile = join(testDir, 'test.log');
writeFileSync(testLogFile, '=== Test Log Started ===\n');
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
await new Promise(r => setTimeout(r, 1000));
+2 -2
View File
@@ -200,7 +200,7 @@ describe('Hook Event API', () => {
let testSessionId: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
@@ -382,7 +382,7 @@ describe('Hook Data Sanitization', () => {
let testSessionId: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1); // Port 3131
server = new WebServer(TEST_PORT + 1, false, true); // Port 3131
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
+2 -2
View File
@@ -18,7 +18,7 @@ describe('Integration Flows', () => {
const createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
@@ -278,7 +278,7 @@ describe('SSE Event Flow', () => {
const createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1);
server = new WebServer(TEST_PORT + 1, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
});
+3 -3
View File
@@ -13,7 +13,7 @@ describe('Quick Start API', () => {
const createdCases: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
@@ -147,7 +147,7 @@ describe('Session Management', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1);
server = new WebServer(TEST_PORT + 1, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
});
@@ -204,7 +204,7 @@ describe('Case Management', () => {
const createdCases: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT + 2);
server = new WebServer(TEST_PORT + 2, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 2}`;
});
+1 -1
View File
@@ -27,7 +27,7 @@ describe('Ralph Integration Tests', () => {
const createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
+2 -2
View File
@@ -9,7 +9,7 @@ describe('Scheduled Runs API', () => {
const createdRuns: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
@@ -181,7 +181,7 @@ describe('Quick Run API', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1);
server = new WebServer(TEST_PORT + 1, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
});
+2 -2
View File
@@ -19,7 +19,7 @@ describe('Session Cleanup', () => {
const createdSessions: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
@@ -186,7 +186,7 @@ describe('Resource Management', () => {
const createdCases: string[] = [];
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1);
server = new WebServer(TEST_PORT + 1, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
});
+1 -1
View File
@@ -8,7 +8,7 @@ describe('Interactive Session Lifecycle', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
+2 -2
View File
@@ -37,7 +37,7 @@ describe('SSE Events', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT);
server = new WebServer(TEST_PORT, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT}`;
});
@@ -180,7 +180,7 @@ describe('SSE Event Types', () => {
let baseUrl: string;
beforeAll(async () => {
server = new WebServer(TEST_PORT + 1);
server = new WebServer(TEST_PORT + 1, false, true);
await server.start();
baseUrl = `http://localhost:${TEST_PORT + 1}`;
});