diff --git a/CHANGELOG.md b/CHANGELOG.md index 4800e3e1..3498a78f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # aicodeman +## 0.3.8 + +### Patch Changes + +- Add tunnel status indicator with control panel — green pulsing dot in header when Cloudflare tunnel is active, dropdown with URL, remote clients, auth sessions, and start/stop/QR/revoke controls + ## 0.3.7 ### Patch Changes diff --git a/CLAUDE.md b/CLAUDE.md index 59a0256e..b98628ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,7 +52,7 @@ When user says "COM": 4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json` 5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web` -**Version**: 0.3.7 (must match `package.json`) +**Version**: 0.3.8 (must match `package.json`) ## Project Overview diff --git a/package.json b/package.json index d6ce316c..0d949045 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aicodeman", - "version": "0.3.7", + "version": "0.3.8", "description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/tools/remotion/compositions/ZerolagDemo.tsx b/tools/remotion/compositions/ZerolagDemo.tsx index 3d4a77a3..06a3dc9d 100644 --- a/tools/remotion/compositions/ZerolagDemo.tsx +++ b/tools/remotion/compositions/ZerolagDemo.tsx @@ -16,69 +16,54 @@ import { IOSKeyboard } from '../components/IOSKeyboard'; // ─── Scene timing (frames @ 30fps) ─── const TITLE_DUR = 60; const PHONES_DUR = 30; -const TYPING_DUR = 468; -const HOLD_DUR = 60; +const TYPING_DUR = 610; +const HOLD_DUR = 50; const OUTRO_DUR = 45; const TITLE_START = 0; const PHONES_START = TITLE_DUR; // 60 const TYPING_START = PHONES_START + PHONES_DUR; // 90 -const HOLD_START = TYPING_START + TYPING_DUR; // 558 -const OUTRO_START = HOLD_START + HOLD_DUR; // 618 +const HOLD_START = TYPING_START + TYPING_DUR; // 700 +const OUTRO_START = HOLD_START + HOLD_DUR; // 750 -export const ZEROLAG_TOTAL_FRAMES = OUTRO_START + OUTRO_DUR; // 663 +export const ZEROLAG_TOTAL_FRAMES = OUTRO_START + OUTRO_DUR; // 795 // ─── iPhone 17 Pro safe area ─── const SAFE_AREA_TOP = 59; // Below Dynamic Island const PHONE_SCALE = 1.12; // Scale up to fill more of the frame -// The Codeman screenshot starts content at y=0 (the session tab). -// On a real device it would sit below the safe area, so we offset it. -const SCREENSHOT_Y_OFFSET = SAFE_AREA_TOP; +// Claude Code header: tab bar + session info + prompt context from screenshot +const HEADER_H = 120; -// Terminal typing overlay position (relative to screenshot top) -// Session tab is ~44px, then terminal starts. Adding safe area offset: -const TERMINAL_TOP = SAFE_AREA_TOP + 52; +// Terminal typing overlay: aligned with the ❯ prompt position in the Claude Code screenshot +const TERMINAL_TOP = 185; const TERMINAL_LEFT = 14; -const TERMINAL_FONT = 22; // Large for video readability +const TERMINAL_FONT = 21; // Slightly smaller to fit toolbar below -// ─── Typing schedule (with typo + backspace correction) ─── -const CORRECT_TEXT = 'fix the auth bug in the login flow'; -const FRAME_GAP = 12; // ~400ms between keystrokes -const TYPO_INDEX = 28; // After "logi", type "m" instead of "n" +// Codeman toolbar from screenshot (bottom section showing /init, /clear, Run, etc.) +const TOOLBAR_H = 95; -// Remote connection lag: 600ms-1.2s+ per char (18-36+ frames) +// ─── Typing schedule ─── +const CORRECT_TEXT = + 'zerolag technology brings in a visual dom overlay to make typing instant, even if your codeman server is on the other side of the world'; +const FRAME_GAP = 4; // ~133ms per keystroke (~75 WPM) + +// Remote connection lag: 600ms–2.7s per char (18–80 frames @ 30fps). +// Periodic spikes simulate packet loss / retransmission bursts. +// TCP head-of-line blocking causes a single spike to freeze all subsequent chars. const LAGGY_DELAYS = [ - 24, 30, 36, 32, 26, 22, 34, 28, 38, 20, 30, 24, 32, 26, 36, 22, - 30, 24, 32, 28, 34, 26, 30, 22, 28, 36, 24, 30, 32, 26, 34, 28, 24, 30, - 26, 32, 28, 34, + 24, 30, 26, 32, 72, 28, 22, 34, 26, 30, 20, 28, 36, 24, 30, 22, 26, 34, 28, 20, 68, 30, 24, 32, + 26, 22, 28, 34, 30, 26, 32, 24, 80, 22, 30, 26, 28, 34, 24, 30, ]; -type KeyAction = { frame: number; action: 'type' | 'backspace'; char: string; lagDelay: number }; +type KeyAction = { frame: number; char: string; lagDelay: number }; const buildSchedule = (): KeyAction[] => { const actions: KeyAction[] = []; - let idx = 0; const lag = (i: number) => LAGGY_DELAYS[i % LAGGY_DELAYS.length]; - // Type correctly up to typo point: "fix the auth bug in the logi" - for (let i = 0; i < TYPO_INDEX; i++) { - actions.push({ frame: idx * FRAME_GAP, action: 'type', char: CORRECT_TEXT[i], lagDelay: lag(idx) }); - idx++; - } - - // Typo: type "m" instead of "n" - actions.push({ frame: idx * FRAME_GAP, action: 'type', char: 'm', lagDelay: lag(idx) }); - idx++; - - // Backspace to fix it - actions.push({ frame: idx * FRAME_GAP, action: 'backspace', char: '⌫', lagDelay: lag(idx) }); - idx++; - - // Type correct remaining: "n flow" - for (let i = TYPO_INDEX; i < CORRECT_TEXT.length; i++) { - actions.push({ frame: idx * FRAME_GAP, action: 'type', char: CORRECT_TEXT[i], lagDelay: lag(idx) }); - idx++; + for (let i = 0; i < CORRECT_TEXT.length; i++) { + actions.push({ frame: i * FRAME_GAP, char: CORRECT_TEXT[i], lagDelay: lag(i) }); } return actions; @@ -86,14 +71,16 @@ const buildSchedule = (): KeyAction[] => { const TYPING_SCHEDULE = buildSchedule(); -/** Replay actions in order up to current frame, computing the visible text buffer */ +/** + * Replay actions in order up to current frame, computing the visible text buffer. + * TCP-ordered: stops at first unresolved echo (head-of-line blocking). + */ const computeVisibleText = (frame: number, withLag: boolean): string => { let buffer = ''; for (const a of TYPING_SCHEDULE) { const threshold = withLag ? a.frame + a.lagDelay : a.frame; - if (frame < threshold) break; // TCP-ordered: stop at first unresolved - if (a.action === 'backspace') buffer = buffer.slice(0, -1); - else buffer += a.char; + if (frame < threshold) break; + buffer += a.char; } return buffer; }; @@ -128,8 +115,20 @@ const IOSStatusBar: React.FC = () => ( {/* WiFi */} - - + + {/* Battery */} @@ -145,9 +144,8 @@ const IOSStatusBar: React.FC = () => ( // ─── Typing overlay ─── const TypingOverlay: React.FC<{ typed: string; - overlayChars?: { char: string; confirmed: boolean }[]; cursorVisible: boolean; -}> = ({ typed, overlayChars, cursorVisible }) => { +}> = ({ typed, cursorVisible }) => { const frame = useCurrentFrame(); const cursorOpacity = cursorVisible ? (Math.floor(frame / 18) % 2 === 0 ? 0.85 : 0.5) : 0; const lineH = Math.round(TERMINAL_FONT * 1.4); @@ -167,13 +165,7 @@ const TypingOverlay: React.FC<{ }} > {'❯ '} - {overlayChars - ? overlayChars.map((oc, i) => ( - - {oc.char} - - )) - : {typed}} + {typed} = ({ typed, overlayChars, cursorVisible, activeKey, pressAge, showKeyboard = true, noAnimation }) => ( + showPromo?: boolean; +}> = ({ typed, cursorVisible, activeKey, pressAge, showKeyboard = true, noAnimation, showPromo }) => ( -
- {/* Real Codeman mobile screenshot, pushed down by safe area */} - - +
{/* iOS status bar in the safe area */} + {/* Claude Code header from screenshot (tabs + session info) */} +
+ + {/* Fade to terminal background */} +
+
+ + {/* Dark mask: hides screenshot text below Claude Code info (e.g. "Try edit...") */} +
+ {/* Smooth gradient blend from screenshot into dark terminal */} +
+
+
+ {/* Typing animation */} - + + + {/* Codeman toolbar from screenshot (/init, /clear, /compact, Run, Run Shell, voice) */} +
+ {/* Gradient blend at top */} +
+ +
+ + {/* Promo cards between text and toolbar (left phone only) */} + {showPromo && } {/* iOS keyboard at bottom */} {showKeyboard && ( @@ -230,22 +301,22 @@ const MobileCodeman: React.FC<{ ); -// ─── Label beneath phone ─── +// ─── Label above phone (prominent header) ─── const PhoneLabel: React.FC<{ title: string; detail: string; dotColor: string; detailColor: string; }> = ({ title, detail, dotColor, detailColor }) => ( -
+
); +// ─── Promo banners (inside left phone, between text and keyboard) ─── +const PromoBanners: React.FC = () => ( +
+
+ + + +
+
Codeman
+
github.com/Ark0N/Codeman
+
+
+
+ + + + +
+
xterm-zerolag-input
+
npmjs.com/package/xterm-zerolag-input
+
+
+
+); + // ─── Title scene ─── const TitleScene: React.FC = () => { const frame = useCurrentFrame(); @@ -357,12 +484,22 @@ const TypingDemo: React.FC = () => { }} >
- - + +
+ -
@@ -389,12 +526,22 @@ const PanelsEntrance: React.FC = () => { >
+ -
+ -
diff --git a/tools/remotion/public/mobile-claude.png b/tools/remotion/public/mobile-claude.png index 85bd7b63..ed4b152e 100644 Binary files a/tools/remotion/public/mobile-claude.png and b/tools/remotion/public/mobile-claude.png differ