Skip to content

Commit b79ad49

Browse files
committed
chore: version packages
1 parent 1821726 commit b79ad49

23 files changed

Lines changed: 3510 additions & 6 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# aicodeman
22

3+
## 0.3.0
4+
5+
### Minor Changes
6+
7+
- QR code authentication for tunnel access, 7-phase codebase refactor (route extraction, type domain modules, frontend module split, config consolidation, managed timers, test infrastructure), overlay rendering fixes, and security hardening
8+
39
## 0.2.9
410

511
### Patch Changes

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ When user says "COM":
5252
4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json`
5353
5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
5454
55-
**Version**: 0.2.9 (must match `package.json`)
55+
**Version**: 0.3.0 (must match `package.json`)
5656
5757
## Project Overview
5858

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aicodeman",
3-
"version": "0.2.9",
3+
"version": "0.3.0",
44
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
55
"type": "module",
66
"main": "dist/index.js",

src/web/routes/session-routes.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,7 @@ import type { SessionPort, EventPort, ConfigPort, InfraPort, AuthPort } from '..
4141
import { MAX_CONCURRENT_SESSIONS } from '../../config/map-limits.js';
4242
import { RunSummaryTracker } from '../../run-summary.js';
4343

44-
import {
45-
MAX_INPUT_LENGTH,
46-
MAX_SESSION_NAME_LENGTH,
47-
} from '../../config/terminal-limits.js';
44+
import { MAX_INPUT_LENGTH, MAX_SESSION_NAME_LENGTH } from '../../config/terminal-limits.js';
4845

4946
// Pre-compiled regex for terminal buffer cleaning (avoids per-request compilation)
5047
// eslint-disable-next-line no-control-regex

test-bg-deep.mjs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { chromium } from 'playwright';
2+
3+
const browser = await chromium.launch({ headless: true });
4+
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
5+
6+
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
7+
await page.waitForTimeout(6000);
8+
await page.locator('.session-tab').first().click();
9+
await page.waitForTimeout(4000);
10+
11+
// Check ALL background colors in the xterm rendering stack
12+
const bgInfo = await page.evaluate(() => {
13+
const term = window.app?.terminal;
14+
if (!term) return { error: 'no terminal' };
15+
16+
const el = term.element;
17+
if (!el) return { error: 'no element' };
18+
19+
const results = {};
20+
21+
// Walk up from xterm-screen to find who provides the background
22+
const screen = el.querySelector('.xterm-screen');
23+
const viewport = el.querySelector('.xterm-viewport');
24+
const rows = el.querySelector('.xterm-rows');
25+
26+
const elements = {
27+
'.xterm (term.element)': el,
28+
'.xterm-viewport': viewport,
29+
'.xterm-screen': screen,
30+
'.xterm-rows': rows,
31+
};
32+
33+
// Also check parent elements
34+
let parent = el.parentElement;
35+
let depth = 0;
36+
while (parent && depth < 5) {
37+
elements[`parent-${depth} (${parent.tagName}.${parent.className?.split(' ')[0] || ''})`] = parent;
38+
parent = parent.parentElement;
39+
depth++;
40+
}
41+
42+
for (const [name, elem] of Object.entries(elements)) {
43+
if (!elem) { results[name] = 'not found'; continue; }
44+
const cs = getComputedStyle(elem);
45+
results[name] = {
46+
background: cs.background?.slice(0, 80),
47+
backgroundColor: cs.backgroundColor,
48+
inlineStyle: elem.style.backgroundColor || elem.style.background || '(none)',
49+
};
50+
}
51+
52+
// Also check if xterm-viewport has inline style set by xterm.js
53+
if (viewport) {
54+
results['viewport-inline-full'] = viewport.style.cssText?.slice(0, 200);
55+
}
56+
57+
// Theme config
58+
results.theme = term.options?.theme;
59+
60+
// Overlay bg
61+
results.overlayBg = window.app?._localEchoOverlay?._font?.backgroundColor;
62+
63+
return results;
64+
});
65+
66+
console.log(JSON.stringify(bgInfo, null, 2));
67+
await browser.close();

test-bg-mismatch.mjs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { chromium } from 'playwright';
2+
3+
const browser = await chromium.launch({ headless: true });
4+
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
5+
6+
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
7+
await page.waitForTimeout(6000);
8+
await page.locator('.session-tab').first().click();
9+
await page.waitForTimeout(4000);
10+
11+
// Sample the ACTUAL canvas pixel color at an empty area
12+
const colorInfo = await page.evaluate(() => {
13+
const term = window.app?.terminal;
14+
if (!term) return { error: 'no terminal' };
15+
16+
// Find the WebGL canvas
17+
const screen = term.element?.querySelector('.xterm-screen');
18+
const canvases = screen?.querySelectorAll('canvas');
19+
20+
const results = {};
21+
22+
for (const canvas of canvases || []) {
23+
const ctx = canvas.getContext('2d') || canvas.getContext('webgl') || canvas.getContext('webgl2');
24+
const ctxType = ctx?.constructor?.name;
25+
26+
if (ctx && (ctxType === 'CanvasRenderingContext2D')) {
27+
// 2D context — can read pixels directly
28+
try {
29+
const pixel = ctx.getImageData(10, 10, 1, 1).data;
30+
results['2d'] = {
31+
r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3],
32+
hex: '#' + [pixel[0], pixel[1], pixel[2]].map(v => v.toString(16).padStart(2, '0')).join(''),
33+
};
34+
} catch (e) {
35+
results['2d'] = { error: e.message };
36+
}
37+
} else if (ctx) {
38+
// WebGL context — use readPixels
39+
try {
40+
const gl = ctx;
41+
const pixel = new Uint8Array(4);
42+
// Read from an empty area (bottom-left corner, row 0 col 0)
43+
gl.readPixels(10, canvas.height - 10, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
44+
results['webgl'] = {
45+
r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3],
46+
hex: '#' + [pixel[0], pixel[1], pixel[2]].map(v => v.toString(16).padStart(2, '0')).join(''),
47+
};
48+
} catch (e) {
49+
results['webgl'] = { error: e.message };
50+
}
51+
}
52+
}
53+
54+
// Also check what the terminal theme says
55+
results.theme = {
56+
background: term.options?.theme?.background,
57+
foreground: term.options?.theme?.foreground,
58+
};
59+
60+
// Check the overlay's configured background
61+
const overlay = window.app?._localEchoOverlay;
62+
results.overlayBg = overlay?._font?.backgroundColor;
63+
64+
// Check xterm-rows computed style
65+
const rows = term.element?.querySelector('.xterm-rows');
66+
if (rows) {
67+
results.rowsBg = getComputedStyle(rows).backgroundColor;
68+
}
69+
70+
// Check .xterm element bg
71+
const xtermEl = term.element;
72+
if (xtermEl) {
73+
results.xtermBg = getComputedStyle(xtermEl).backgroundColor;
74+
}
75+
76+
// Canvas count and types
77+
results.canvasCount = canvases?.length;
78+
results.canvasClasses = Array.from(canvases || []).map(c => c.className);
79+
80+
return results;
81+
});
82+
83+
console.log(JSON.stringify(colorInfo, null, 2));
84+
await browser.close();
85+
console.log('Done');

test-dims.mjs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { chromium } from 'playwright';
2+
3+
const browser = await chromium.launch({ headless: true });
4+
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
5+
6+
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
7+
await page.waitForTimeout(6000);
8+
9+
const firstTab = page.locator('.session-tab').first();
10+
await firstTab.click();
11+
await page.waitForTimeout(5000);
12+
13+
const info = await page.evaluate(() => {
14+
const term = window.app?.terminal;
15+
if (!term) return { error: 'no app.terminal' };
16+
17+
const core = term._core;
18+
const renderService = core?._renderService;
19+
const dims = renderService?.dimensions;
20+
21+
if (!dims) return { error: 'no dimensions', hasCore: !!core, hasRenderService: !!renderService };
22+
23+
return {
24+
css: {
25+
cell: dims.css?.cell,
26+
char: dims.css?.char,
27+
canvas: dims.css?.canvas,
28+
},
29+
device: {
30+
cell: dims.device?.cell,
31+
char: dims.device?.char,
32+
canvas: dims.device?.canvas,
33+
},
34+
topLevelKeys: Object.keys(dims),
35+
cssKeys: dims.css ? Object.keys(dims.css) : [],
36+
deviceKeys: dims.device ? Object.keys(dims.device) : [],
37+
cssAllDetail: dims.css ? Object.fromEntries(
38+
Object.entries(dims.css).map(([k, v]) => [k, v && typeof v === 'object' ? { ...v } : v])
39+
) : null,
40+
deviceAllDetail: dims.device ? Object.fromEntries(
41+
Object.entries(dims.device).map(([k, v]) => [k, v && typeof v === 'object' ? { ...v } : v])
42+
) : null,
43+
dpr: window.devicePixelRatio,
44+
};
45+
});
46+
47+
console.log(JSON.stringify(info, null, 2));
48+
await browser.close();

test-line-artifact.mjs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { chromium } from 'playwright';
2+
3+
const browser = await chromium.launch({ headless: true, deviceScaleFactor: 2 });
4+
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
5+
6+
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
7+
await page.waitForTimeout(6000);
8+
await page.locator('.session-tab').first().click();
9+
await page.waitForTimeout(4000);
10+
11+
await page.evaluate(() => {
12+
const settings = JSON.parse(localStorage.getItem('codeman-settings') || '{}');
13+
settings.localEchoEnabled = true;
14+
localStorage.setItem('codeman-settings', JSON.stringify(settings));
15+
if (window.app?.updateLocalEchoState) window.app.updateLocalEchoState();
16+
});
17+
await page.waitForTimeout(500);
18+
19+
// Force render and capture zoomed area of just the overlay
20+
const result = await page.evaluate(() => {
21+
const overlay = window.app?._localEchoOverlay;
22+
if (!overlay || !overlay._overlay) return { error: 'no overlay' };
23+
24+
overlay._lastPromptPos = { row: 5, col: 0 };
25+
overlay._pendingText = 'h';
26+
overlay._lastRenderKey = '';
27+
overlay._render();
28+
29+
const container = overlay._overlay;
30+
const lineDiv = container.querySelector('div');
31+
const charSpan = container.querySelector('div > span');
32+
33+
// Get precise rects
34+
const lineDivRect = lineDiv?.getBoundingClientRect();
35+
const charSpanRect = charSpan?.getBoundingClientRect();
36+
const screenRect = document.querySelector('.xterm-screen')?.getBoundingClientRect();
37+
38+
// Check what's right below the line div
39+
const belowY = (lineDivRect?.bottom || 0) + 1;
40+
const belowX = lineDivRect?.left || 0;
41+
const elBelow = document.elementFromPoint(belowX, belowY);
42+
43+
return {
44+
dpr: window.devicePixelRatio,
45+
lineDiv: {
46+
rect: lineDivRect,
47+
bg: lineDiv?.style.backgroundColor,
48+
height: lineDiv?.style.height,
49+
},
50+
charSpan: {
51+
rect: charSpanRect,
52+
transform: charSpan?.style.transform,
53+
// Check if span bottom exceeds lineDiv bottom
54+
extendsBelow: charSpanRect && lineDivRect ?
55+
(charSpanRect.bottom - lineDivRect.bottom).toFixed(2) : 'N/A',
56+
},
57+
elBelow: elBelow ? {
58+
tag: elBelow.tagName,
59+
class: elBelow.className,
60+
} : null,
61+
// Check the exact background color of the canvas
62+
canvasBg: document.querySelector('.xterm-screen')?.style.backgroundColor,
63+
themeBg: window.app?.terminal?.options?.theme?.background,
64+
overlayBg: lineDiv?.style.backgroundColor,
65+
};
66+
});
67+
console.log(JSON.stringify(result, null, 2));
68+
69+
// Take tight screenshot around the first char area
70+
const screenEl = page.locator('.xterm-screen').first();
71+
const screenBox = await screenEl.boundingBox();
72+
if (screenBox) {
73+
// Crop to just the overlay area (row 5, first few columns)
74+
const cellH = 19;
75+
const promptRow = 5;
76+
const cropY = screenBox.y + (promptRow - 1) * cellH;
77+
const cropH = cellH * 3;
78+
await page.screenshot({
79+
path: '/tmp/first-char-zoomed.png',
80+
clip: { x: screenBox.x, y: cropY, width: 200, height: cropH },
81+
});
82+
console.log('Zoomed screenshot saved');
83+
}
84+
85+
await page.evaluate(() => { window.app?._localEchoOverlay?.clear(); });
86+
await browser.close();
87+
console.log('Done');

0 commit comments

Comments
 (0)